diff --git a/_site/staticwebapp.config.json b/_site/staticwebapp.config.json deleted file mode 100644 index 56b596620..000000000 --- a/_site/staticwebapp.config.json +++ /dev/null @@ -1 +0,0 @@ -{"routes":[{"route":"/","redirect":"/en/","statusCode":301},{"route":"/index.html","redirect":"/en/","statusCode":301},{"route":"/en/references/release-notes","redirect":"/en/references/release-notes/3_26_0.html","statusCode":302},{"route":"/en/te3/other/release-notes","redirect":"/en/references/release-notes/3_26_0.html","statusCode":302},{"route":"/es/references/release-notes","redirect":"/es/references/release-notes/3_26_0.html","statusCode":302},{"route":"/es/te3/other/release-notes","redirect":"/es/references/release-notes/3_26_0.html","statusCode":302},{"route":"/zh/references/release-notes","redirect":"/zh/references/release-notes/3_26_0.html","statusCode":302},{"route":"/zh/te3/other/release-notes","redirect":"/zh/references/release-notes/3_26_0.html","statusCode":302},{"route":"/references/release-notes","redirect":"/en/references/release-notes/3_26_0.html","statusCode":302},{"route":"/Advanced-Scripting.html","redirect":"/en/how-tos/Advanced-Scripting.html","statusCode":301},{"route":"/Best-Practice-Analyzer.html","redirect":"/en/features/Best-Practice-Analyzer.html","statusCode":301},{"route":"/Command-line-Options.html","redirect":"/en/features/Command-line-Options.html","statusCode":301},{"route":"/Custom-Actions.html","redirect":"/en/tutorials/creating-macros.html","statusCode":301},{"route":"/FormatDax.html","redirect":"/en/references/FormatDax.html","statusCode":301},{"route":"/Importing-Tables.html","redirect":"/en/how-tos/Importing-Tables.html","statusCode":301},{"route":"/Power-BI-Desktop-Integration.html","redirect":"/en/getting-started/Power-BI-Desktop-Integration.html","statusCode":301},{"route":"/Useful-script-snippets.html","redirect":"/en/features/Useful-script-snippets.html","statusCode":301},{"route":"/Workspace-Database.html","redirect":"/en/tutorials/workspace-mode.html","statusCode":301},{"route":"/common/Datasets/direct-lake-dataset.html","redirect":"/en/features/Semantic-Model/direct-lake-sql-model.html","statusCode":301},{"route":"/eula","redirect":"/en/security/te3-eula.html","statusCode":301},{"route":"/onboarding/general-introduction.html","redirect":"/en/getting-started/general-introduction.html","statusCode":301},{"route":"/onboarding/index.html","redirect":"/en/getting-started/index.html","statusCode":301},{"route":"/onboarding/installation.html","redirect":"/en/getting-started/installation.html","statusCode":301},{"route":"/other/downloads.html","redirect":"/en/references/downloads.html","statusCode":301},{"route":"/privacy-policy.html","redirect":"/en/security/privacy-policy.html","statusCode":301},{"route":"/projects/te3","redirect":"/en/","statusCode":301},{"route":"/projects/te3/en/latest","redirect":"/en/","statusCode":301},{"route":"/projects/te3/en/latest/downloads.html","redirect":"/en/references/downloads.html","statusCode":301},{"route":"/projects/te3/en/latest/editions.html","redirect":"/en/getting-started/editions.html","statusCode":301},{"route":"/projects/te3/en/latest/getting-started.html","redirect":"/en/getting-started/getting-started.html","statusCode":301},{"route":"/projects/te3/en/latest/security-privacy.html","redirect":"/en/security/security-privacy.html","statusCode":301},{"route":"/roslyn","redirect":"/en/how-tos/Advanced-Scripting.html#compiling-with-roslyn","statusCode":301},{"route":"/te2/Advanced-Scripting.html","redirect":"/en/how-tos/Advanced-Scripting.html","statusCode":301},{"route":"/te2/Best-Practice-Analyzer.html","redirect":"/en/features/Best-Practice-Analyzer.html","statusCode":301},{"route":"/te2/Getting-Started.html","redirect":"/en/getting-started/Getting-Started-te2.html","statusCode":301},{"route":"/te2/Importing-Tables.html","redirect":"/en/how-tos/Importing-Tables.html","statusCode":301},{"route":"/te2/Power-BI-Desktop-Integration.html","redirect":"/en/getting-started/Power-BI-Desktop-Integration.html","statusCode":301},{"route":"/te2/Useful-script-snippets.html","redirect":"/en/features/Useful-script-snippets.html","statusCode":301},{"route":"/te3/downloads.html","redirect":"/en/references/downloads.html","statusCode":301},{"route":"/te3/editions.html","redirect":"/en/getting-started/editions.html","statusCode":301},{"route":"/te3/features/csharp-scripts.html","redirect":"/en/features/csharp-scripts.html","statusCode":301},{"route":"/te3/features/dax-debugger.html","redirect":"/en/features/dax-debugger.html","statusCode":301},{"route":"/te3/features/dax-editor.html","redirect":"/en/features/dax-editor.html","statusCode":301},{"route":"/te3/features/dax-scripts.html","redirect":"/en/features/dax-scripts.html","statusCode":301},{"route":"/te3/features/tmdl.html","redirect":"/en/features/tmdl.html","statusCode":301},{"route":"/te3/getting-started.html","redirect":"/en/getting-started/getting-started.html","statusCode":301},{"route":"/te3/index.html","redirect":"/en/troubleshooting/licensing-activation.html","statusCode":301},{"route":"/te3/logo.svg","redirect":"/en/logo.svg","statusCode":301},{"route":"/te3/other/downloads.html","redirect":"/en/references/downloads.html","statusCode":301},{"route":"/te3/other/release-history.html","redirect":"/en/references/release-history.html","statusCode":301},{"route":"/te3/tutorials/workspace-mode.html","redirect":"/en/tutorials/workspace-mode.html","statusCode":301},{"route":"/tmdl","redirect":"/en/features/tmdl.html","statusCode":301},{"route":"/tmuo","redirect":"/en/references/user-options.html","statusCode":301},{"route":"/user-options.html","redirect":"/en/references/user-options.html","statusCode":301},{"route":"/workspace","redirect":"/en/tutorials/workspace-mode.html","statusCode":301}],"responseOverrides":{"404":{"rewrite":"/404.html"}}} \ No newline at end of file diff --git a/build-docs.py b/build-docs.py index 30122c206..91920d985 100644 --- a/build-docs.py +++ b/build-docs.py @@ -18,6 +18,10 @@ --serve Build and serve locally (English only, for development) --skip-gen Skip running gen_redirects.py (use existing configs) --no-api-copy Skip copying API docs to localized sites + --skip-api Reuse existing content/api instead of regenerating API metadata + (~30-40% faster). LOCAL markdown iteration ONLY, with --serve/--lang; + never for testing, CI/CD, or release builds. + --permissive Don't fail the English build on DocFX warnings (local iteration) """ import argparse @@ -81,6 +85,62 @@ def run_command(cmd: list[str], description: str, check: bool = True, fail_on_wa return result.returncode +DOCFX: list[str] = [] # cache; populated on first ensure_docfx() + + +def _local_docfx_available() -> bool: + """True if a dotnet tool manifest in cwd or an ancestor declares docfx. + + Mirrors dotnet's manifest discovery (cwd upward, `.config/` or legacy path, + stop at an isRoot manifest). Filesystem-only — does not verify the tool is + restored; a missing restore surfaces as a clear `dotnet docfx` error at build time. + """ + for d in (Path.cwd(), *Path.cwd().parents): + manifest = next((m for m in (d / ".config" / "dotnet-tools.json", + d / "dotnet-tools.json") if m.is_file()), None) + if manifest is None: + continue + try: + data = json.loads(manifest.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError): + continue + if "docfx" in {k.lower() for k in data.get("tools", {})}: + return True + if data.get("isRoot"): + break # root manifest without docfx: dotnet stops searching here too + return False + + +def resolve_docfx() -> list[str]: + """Resolve how to invoke docfx: $DOCFX override, then a repo-pinned local tool + (`dotnet docfx`), then a global/PATH `docfx`. Raises if none is available.""" + override = os.environ.get("DOCFX") + if override: + return override.split() + if shutil.which("dotnet") and _local_docfx_available(): + return ["dotnet", "docfx"] + if shutil.which("docfx"): + return ["docfx"] + raise SystemExit( + "Error: docfx not found.\n" + " Local: dotnet tool install docfx (then `dotnet tool restore`)\n" + " Global: dotnet tool install -g docfx" + ) + + +def ensure_docfx() -> list[str]: + """Return the docfx invocation prefix, resolving (and caching) it on first use.""" + global DOCFX + if not DOCFX: + DOCFX = resolve_docfx() + return DOCFX + + +def run_docfx(args: list[str], description: str, **kwargs) -> int: + """Run docfx with the resolved invocation prefix (global/PATH or `dotnet docfx`).""" + return run_command([*ensure_docfx(), *args], description, **kwargs) + + def get_available_languages() -> list[str]: """Get list of available languages from metadata/languages.json or scan localizedContent/.""" manifest_path = Path("metadata/languages.json") @@ -151,7 +211,7 @@ def prepare_localized_content(lang: str, sync: bool = False) -> int: ) -def build_language(lang: str, sync: bool = False) -> int: +def build_language(lang: str, sync: bool = False, skip_api: bool = False, permissive: bool = False) -> int: """Build documentation for a specific language.""" config_path = f"localizedContent/{lang}/docfx.json" @@ -164,14 +224,20 @@ def build_language(lang: str, sync: bool = False) -> int: result = prepare_localized_content(lang, sync=sync) if result != 0: return result - + # Build the documentation — fail on DocFX warnings only for English (the # authored source). Localized content is Crowdin-managed and may carry - # translation warnings that must not block deployment. - return run_command( - ["docfx", config_path], + # translation warnings that must not block deployment. `permissive` lifts the + # English gate too, for local iteration where transient warnings are expected + # (warnings are still printed, just not fatal); full/CI builds leave it off. + # + # `docfx build` skips API metadata regeneration and reuses the existing + # content/api/*.yml (the _apiSource DLLs don't change between content edits), + # which is ~30-40% faster; bare `docfx` regenerates metadata then builds. + return run_docfx( + [*(["build"] if skip_api else []), config_path], f"Building {lang} documentation", - fail_on_warnings=(lang == "en") + fail_on_warnings=(lang == "en" and not permissive) ) @@ -279,7 +345,9 @@ def main() -> int: parser.add_argument("--list", action="store_true", help="List available languages") parser.add_argument("--serve", action="store_true", help="Build English and serve locally") parser.add_argument("--skip-gen", action="store_true", help="Skip gen_redirects.py") - parser.add_argument("--no-api-copy", action="store_true", help="Skip copying API docs") + parser.add_argument("--no-api-copy", action="store_true", help="Skip copying API docs to localized sites") + parser.add_argument("--skip-api", action="store_true", help="LOCAL markdown iteration only (requires --serve/--lang): reuse existing content/api, ~30-40%% faster. NEVER for testing/CI/CD/releases") + parser.add_argument("--permissive", action="store_true", help="Don't treat English DocFX warnings as build failures (for local iteration; keep full/CI builds strict)") parser.add_argument("--sync", action="store_true", help="Sync English fallback for missing/outdated translations (for local dev)") args = parser.parse_args() @@ -292,7 +360,39 @@ def main() -> int: suffix = " (default)" if lang == "en" else "" print(f" {lang}{suffix}") return 0 - + + # Resolve docfx now so a missing install exits before any build work happens. + ensure_docfx() + + # --skip-api is strictly a fast LOCAL iteration aid for editing markdown: it reuses + # the existing content/api/*.yml instead of regenerating API metadata. It must NEVER + # be used for full builds, testing, CI/CD, or releases (those must regenerate the API). + # Guard it conservatively: require an explicit single-target (--serve or --lang), + # forbid --all / the default all-languages build, and require API metadata to already + # exist so we never silently ship a site with missing or stale API docs. + if args.skip_api: + if args.all or not (args.serve or args.lang): + print( + "Error: --skip-api is for fast LOCAL iteration only and must target a single build.\n" + " Use it with --serve or --lang (e.g. `--serve --skip-api`, `--lang en --skip-api`).\n" + " Never use it for --all, testing, CI/CD, or release builds — omit it so API\n" + " metadata is regenerated.", + file=sys.stderr, + ) + return 1 + if not list(Path("content/api").glob("*.yml")): + print( + "Error: --skip-api reuses existing API metadata, but content/api/*.yml is missing.\n" + " Run a full build once (e.g. `python3 build-docs.py --lang en`) to generate it.", + file=sys.stderr, + ) + return 1 + print("\n" + "=" * 60) + print(" WARNING: --skip-api active — reusing existing content/api/*.yml.") + print(" Fast LOCAL markdown iteration ONLY; API docs may be stale.") + print(" NEVER use --skip-api for testing, CI/CD, or release builds.") + print("=" * 60) + # Run gen_redirects.py first (unless skipped) if not args.skip_gen: result = run_command( @@ -315,7 +415,7 @@ def main() -> int: if args.serve: # Build English only and serve - result = build_language("en", sync=True) + result = build_language("en", sync=True, skip_api=args.skip_api, permissive=args.permissive) if result != 0: return result @@ -329,8 +429,8 @@ def main() -> int: shutil.copy(manifest_src, manifest_dest) print("Copied languages.json to _site/en/") - return run_command( - ["docfx", "serve", "_site/en"], + return run_docfx( + ["serve", "_site/en"], "Serving documentation locally" ) @@ -354,7 +454,7 @@ def main() -> int: # Build all requested languages for lang in build_langs: - result = build_language(lang, sync=args.sync) + result = build_language(lang, sync=args.sync, skip_api=args.skip_api, permissive=args.permissive) if result != 0: return result diff --git a/build_scripts/README.md b/build_scripts/README.md new file mode 100644 index 000000000..bd0a01370 --- /dev/null +++ b/build_scripts/README.md @@ -0,0 +1,238 @@ +# Build tools + +This document covers new build tools: +- `te_script_runner.py`: standalone runner and module for other tools that need to execute C# scripts +- `csharp_doctest.py`: compiles and runs annotated `csharp` code blocks in markdown files +- `check_links.py`: dead link checker for built site + +Existing docfx and localization orchestration can be found in [../README.md](../README.md) + +## Prerequisites + +Required on PATH: + +- `python3` -- 3.10+ (the scripts use 3.10 syntax; validated on 3.14). +- `uv` -- provides `uvx`, for lint and type-check. +- `te` -- the Tabular Editor CLI, for the doc-validation scripts; you should use a build aligned with TE3 release for checking docs. +- `docfx` -- or `dotnet` with the pinned local docfx tool, to build the site. + +Run all scripts from the docs repo root; +paths like `_site` and `content/` resolve relative to the current directory. + +## Contributing and development notes + +Scripts have no build phase. +All Python build scripts are linted and type-checked: + +```shell +$ uvx ruff check --select F,B,SIM,I,UP +$ uvx mypy --strict +``` + +Scripts named herein are all built with a functional core, imperative shell style. +Build small components that deal in pure data, orchestrate these with command dispatch. +Expose useful functions in the command dispatch for testing and future ad-hoc use cases. + +## Doc validation + +### Code block validation and output generation + +`te_script_runner.py` and `csharp_doctest.py` contribute to validating code blocks in docs. +Both need `te` on PATH. + +Current state only validates semantic bridge docs. +The code block annotations can be used in any doc. + +#### `te_script_runner.py` -- generic runner + +Runs (or compile-checks) C# snippets against a throwaway, empty `.bim` model. +Output: +- stdout: the snippets' `Output()` only (te messages at level `output`), newline-joined +- stderr: te's own stderr stream (always passed through), plus the runner's diagnostics parsed from te's JSON -- `[compile-error]`, `[runtime-error]`, and `[]` for other non-output messages + +```shell +$ python3 build_scripts/te_script_runner.py run -e '' # execute (also -f , or stdin) +$ python3 build_scripts/te_script_runner.py check -e '' # compile only (te --dry-run) +$ python3 build_scripts/te_script_runner.py run -e '' -f -e '' # execute multiple scripts in order; also works for `check` +``` + +Operation: +1. Creates a new temporary directory +2. Inits a new empty model in that directory +3. Creates files for all scripts in temp directory (this is necessary to enforce strict ordering) +4. Executes all scripts in order with `te` binary; 1 execution of `te`, no `--save`, so all script results are transient +5. Cleans up temp directory + +Additional sub-commands for testing, validation, and ad-hoc use cases: + +- `python3 build_scripts/te_script_runner.py init`: set up the temp directory and model, returning the path; does not automatically clean up: that's your responsibility if you use this +- `python3 build_scripts/te_script_runner.py summarize`: parses and creates output based on `te`'s output + +Importable in other Python scripts: `run_snippets(snippets, dry_run=..., last_only=...) -> Result`. +`last_only` reports only the final snippet's `Output()`, rather than all snippets outputs. + +#### `csharp_doctest.py` -- doc orchestrator + +Checks annotated C# fences in a markdown file. +The annotation is invisible in rendered docfx output: + +``` + ```csharp {compile} + ```csharp {run id= setup= after= output=} +``` + +Commands (each validates first and bails on a malformed doc): + +```shell +$ python3 build_scripts/csharp_doctest.py validate # check annotation grammar + coverage counts, no CLI calls +$ python3 build_scripts/csharp_doctest.py compile # compile every {compile}/{run} block +$ python3 build_scripts/csharp_doctest.py compare # diff each {run} Output() against its fence +$ python3 build_scripts/csharp_doctest.py update # run {run} blocks, rewrite output blocks in place +``` + +Dealing with C# code blocks in a markdown doc: + +- code block without `csharp` in fence: skipped, but an annotation on such a block is a validation error +- code block with `csharp` and no annotation: skip +- `{compile}`: compile-check only (catches API drift; never executes) +- `{run ...}`: will execute the code block with `te` binary + - `id=`: unique name for this block in the document + - `setup=`: prepends a preamble (see `SETUP_SCRIPTS`, e.g. the sample Metric View); must be registered in the script before it can be used + - `output=`: if true, then this code block must be followed by literal `**Output**` on its own line, followed by a fenced code block; will check or update the output to what the script generates + - `after=`: replays earlier run blocks for their state only (not their output); for docs where many code blocks are provided and expected to be run in sequence + +The grammar for these `run` blocks is explicit: +every option must be provided in every block. + +Exit codes: +- `0`: all blocks in the doc pass +- `1`: a block failed (output mismatch, compile error, runtime error) +- `2`: malformed doc or bad usage (validate bails before any CLI calls). + +Output (note: the opposite split from `te_script_runner.py` -- here the whole report, errors included, is on stdout): +- stdout: the full report -- per-block verdicts, diffs, and compile/runtime error detail +- stderr: te's own stderr (passed through) and harness/operational errors (bad usage, unreadable file) + +Sweep all docs with annotated code blocks for valid annotations, bailing on first error: + +```shell +$ (set -eu; rg '```csharp \{' --glob content/**/*.md --files-with-matches | while read f; do python3 build_scripts/csharp_doctest.py validate $f; done) +``` + +Substitute `compile`, `compare`, or `update` for `validate` in above to run in the same way (bailing on first error). + +This orchestrator uses a thread pool with a thread per code block. +Each invocation of `te` as a sub-process takes ~1-2s, +so parallelizing nets a significant performance gain. + +#### Test fixtures (`test-fixtures/`) + +Small, self-contained inputs that exercise each code path: +a manual regression corpus you run by hand (there are no unit tests). +Run a command against a fixture and eyeball the result. + +Markdown fixtures drive `csharp_doctest.py`: +- `doc-valid.md`: one of every block kind (skip / `{compile}` / `{run}`); everything passes. +- `doc-mismatch.md`: a `{run}` whose `Output()` differs from its fence, so `compare` fails (and `update` would rewrite it; don't call update and commit, instead revert if you do update). +- `doc-compile-drift.md`: a `{run}` calling a nonexistent API, so `compile` fails. +- `err-*.md`: each holds exactly one grammar error (missing option, no output fence, annotation on a non-csharp fence, unknown `after=`, unknown annotation), so `validate` bails. + +`te-*.json` are canned `te --output-format json` outputs that drive `te_script_runner.py summarize` (its pure parser, no `te` needed): +the executed-run and `--dry-run` schemas, each in a success and a failure (compile / runtime) variant. + +```shell +$ python3 build_scripts/csharp_doctest.py validate test-fixtures/doc-valid.md # passes +$ python3 build_scripts/csharp_doctest.py compare test-fixtures/doc-mismatch.md # fails with a diff (nonzero exit) +$ python3 build_scripts/csharp_doctest.py compile test-fixtures/doc-compile-drift.md # fails on the drifted API +$ python3 build_scripts/csharp_doctest.py validate test-fixtures/err-unknown-after.md # bails with the grammar error +$ python3 build_scripts/te_script_runner.py summarize test-fixtures/te-runtime-error.json +``` + +### Link validation + +`check_links.py` validates all links (`href`/`src`) in the built `_site`. +It walks the generated HTML, +resolves each reference to a local file or an external URL, +visits every unique target once, and reports references to broken targets. + +Build the docs site first with `python3 build-docs.py --lang en` or `python3 build-docs.py --all`. + +```shell +$ python3 build_scripts/check_links.py validate # check _site: authored content, on-disk + external; requires built _site/ +$ python3 build_scripts/check_links.py validate stats # add per-host external-fetch diagnostics +$ python3 build_scripts/check_links.py validate local # on-disk checks only, skip external fetching +$ python3 build_scripts/check_links.py validate _site under=en # only check links from pages under _site/en +$ python3 build_scripts/check_links.py validate all # also include generated API and localized pages; noisy, likely unnecessary; requires build with `--all` +``` + +Broken link checks: +- local links (to something defined in this docs repo): the target file must exist + - detect old root links (e.g. ``) and resolve against the live docs site to check redirects; a dead one is an error, not a warning +- external links (to something not defined in this docs repo): must return a 2xx/3xx status; a bad status (e.g. 404) or a transport error (DNS, TLS/certificate, timeout, refused connection) fails +- fragments: ensure the `#anchor` is defined in the body of the target page (local file or fetched external page) +- text links: ensure the literal `:~:text=` string is in the body of the target page (local file or fetched external page) + +Internal failures are errors (nonzero exit): +own-site links, i.e. local files and root-absolute links (the latter checked over HTTP). +External failures are warnings (network issues may be transient, or the target blocks bots), +listed at the end as URLs to verify by hand, each with two counts: +how many docs reference it and how many total times it appears. +Fragment/text failures keep their `#anchor` / `:~:text=` in that list (the bare URL works; +the fragment is what broke); a wholly unreachable URL is listed bare. + +Exit codes: `1` if any internal (own-site) reference is broken, else `0`. External warnings never fail the run, so third-party link rot will not break CI. + +Options to modify output: + +- `local`: skip external URL fetching (on-disk checks only) +- `all`: include generated API and localized pages (default: authored `content/*.md` only) +- `stats`: print per-host external-fetch diagnostics +- `under=`: only check links from pages under `/` + +Every built HTML page under `_site` is walked (to index fragment anchors and collect links site-wide). +But by default only *failures on authored English content* are reported: +pages under `_site/en/` that map back to `content/*.md`, +excluding generated API reference (`en/api/`). +The other-language pages are near-duplicate translations, +so they and the generated API are hidden unless you pass `all` (which floods the report with those duplicates). + +`extract`, `resolve`, `enumerate`, and `fetch` expose the internal stages for testing. + +This script uses a rudimentary scheduler to avoid flooding a single host and getting a 429 storm. +We distribute work across a thread pool and the scheduler interleaves requests to different hosts. +There is a per-host maximum for in-flight requests, +and a per-host cooldown when we encounter 429s, +to avoid slamming a host that has already told us to back off. +On a 429 we honor `Retry-After` (otherwise exponential backoff: 1, 2, 4, ... seconds) +and also decrement that host's in-flight cap as ongoing backpressure; +after a per-URL retry limit we give up and record the last result. + +Reachability uses a `HEAD` request; +a body-downloading `GET` runs only when a fragment or `:~:text=` directive must be verified, +so it never pulls installers or images just to check a link. +A failed `HEAD` falls back to `GET` (some hosts reject `HEAD`), +and known HEAD-hostile hosts skip straight to `GET`. + +#### Reading the `stats` table + +`stats` prints one row per host, worst-first. Columns: + +- `total`: external URLs seen for the host +- `ok`: reachable, and any `#anchor` / `:~:text=` check passed; `total == ok` means all links to this domain were good +- `frag`: reachable (url+path), but a fragment or text check failed +- `bad`: unreachable, total (`= 401 + 403 + 404 + oth + net`) +- `reqs`: fetch attempts, including retries; `total == reqs` means everything succeeded on first fetch +- `429`: rate-limited responses seen +- `401` / `403` / `404`: per-host counts of those statuses +- `oth`: other failing HTTP (5xx, and 4xx that is not 401/403/404) +- `net`: non-HTTP failures (DNS, TLS, timeout, connection reset) +- `wait(s)`: total cooldown time applied to the host (rate limiting on our side for 429s) +- `fb`: HEAD requests that fell back to GET (candidates for the GET-only list) +- `cap`: ending in-flight cap (below the start value means it was throttled down) + +#### Interactive control (long runs) + +A full external run takes minutes; the terminal can query or stop it: + +- Status line (phase, counts, in-flight, cooling hosts) on **Ctrl-T** (macOS/BSD `SIGINFO`), **Ctrl-\\** (`SIGQUIT`, Linux/macOS), or **Ctrl-Break** (Windows `SIGBREAK`). +- **Ctrl-C** stops cleanly and prints a partial report over what was fetched; a second **Ctrl-C** force-quits. diff --git a/build_scripts/check_links.py b/build_scripts/check_links.py new file mode 100644 index 000000000..78ac8c717 --- /dev/null +++ b/build_scripts/check_links.py @@ -0,0 +1,944 @@ +#!/usr/bin/env python3 +""" +Standalone dead-link checker for the generated `_site`. + +Walks the built HTML, resolves every href/src to a local file or an external +URL, visits each unique target once, and reports references to broken targets +(missing files, missing fragments). Local failures are errors (nonzero exit); +external failures are warnings (network issues are often transient or the +target blocks bots), emitted as a copy-paste list of URLs to verify by hand. + +The functional core (extract, resolve, validate) is pure; the imperative shell +does filesystem, HTTP, and reporting. +""" + +import http.client +import os +import signal +import sys +import urllib.error +import urllib.request +from collections import Counter, defaultdict, deque +from collections.abc import Callable +from dataclasses import dataclass, field +from html.parser import HTMLParser +from pathlib import Path +from queue import Empty, Queue +from threading import Event, Thread +from time import monotonic +from types import FrameType +from typing import Any, NamedTuple +from urllib.parse import unquote, urldefrag, urlsplit + +import config_loader + +# Interactive control: a shared progress view that `main` reads for a Ctrl-T status line, and a stop flag it sets on +# Ctrl-C so the running command can wind down and still emit a partial report. Commands populate the view as they go. + +_STOP_POLL = 1.0 # max seconds the fetch scheduler blocks between stop-flag checks, so Ctrl-C stays responsive + +# Terminal keys that print a status line, by platform: Ctrl-T (BSD/macOS), Ctrl-\ (POSIX; overrides the quit/core +# dump), Ctrl-Break (Windows). We bind whichever the platform defines, so a status key exists everywhere. +_STATUS_SIGNALS = ("SIGINFO", "SIGQUIT", "SIGBREAK") + +_SignalHandler = Callable[[int, FrameType | None], None] + + +@dataclass +class _Progress: + """Live counters for the running command. `main` reads them for the status signal and sets `stop` on Ctrl-C; + commands advance the phase, bump the counters, and check `stop` to bail out early for a partial report.""" + + phase: str = "starting" + pages: int = 0 # built HTML pages scanned (enumerate phase) + links: int = 0 # link references seen while scanning + targets: int = 0 # unique targets discovered to check + urls_total: int = 0 # external URLs queued to fetch + urls_done: int = 0 # external URLs fetched so far + dispatched: int = 0 # URLs handed to the queue but not yet resolved (>= the few workers actively fetching) + workers: int = 0 # size of the fetch worker pool + cooling: dict[str, float] = field(default_factory=dict) # host -> monotonic deadline it is eligible again + started: float = 0.0 # monotonic start time, for elapsed + stop: Event = field(default_factory=Event) + + +def _status_line(progress: _Progress, now: float) -> str: + """A one-line snapshot of our own work for the Ctrl-T status signal (about the run, not OS resource stats).""" + p = progress + head = f"[check_links] phase={p.phase} elapsed={now - p.started:.0f}s" + if p.phase != "fetching external": + return f"{head} pages={p.pages} links={p.links} targets={p.targets}" + cooling = sorted( + ((host, deadline - now) for host, deadline in p.cooling.items() if deadline > now), + key=lambda hw: hw[1], + reverse=True, + ) + detail = "".join(f" {host}({wait:.1f}s)" for host, wait in cooling) # wide when many hosts cool at once; fine + return ( + f"{head} fetched={p.urls_done}/{p.urls_total} dispatched={p.dispatched} " + f"workers={p.workers} cooling={len(cooling)}{detail}" + ) + + +def _install_signal_handlers(on_stop: _SignalHandler, on_status: _SignalHandler) -> dict[int, Any]: + """Wire SIGINT (Ctrl-C, clean stop) and every available status signal (see `_STATUS_SIGNALS`), returning the + prior handlers for restoration. A no-op off the main thread, where handlers cannot be installed.""" + previous: dict[int, Any] = {} + try: + previous[signal.SIGINT] = signal.signal(signal.SIGINT, on_stop) + for name in _STATUS_SIGNALS: + signum = getattr(signal, name, None) + if signum is not None: + previous[signum] = signal.signal(signum, on_status) + except ValueError: + pass # not the main thread; interactive control is simply unavailable + return previous + + +def _restore_signal_handlers(previous: dict[int, Any]) -> None: + for signum, handler in previous.items(): + signal.signal(signum, handler) + + +def _normalize_text(text: str) -> str: + """Collapse whitespace and case-fold, so text-fragment queries match the page regardless of layout or case.""" + return " ".join(text.split()).casefold() + + +class _LinkAnchorParser(HTMLParser): + """Collects href/src references and fragment targets (`id`, and `name` on `a`), and optionally the page text.""" + + def __init__(self, collect_text: bool = False) -> None: + super().__init__(convert_charrefs=True) + self.links: list[tuple[str, int]] = [] # (href, 1-based line in the source HTML) + self.anchors: set[str] = set() + self._collect_text = collect_text + self._chunks: list[str] = [] + self._skip_depth = 0 # inside - + diff --git a/localizedContent/es/content/tutorials/udfs.md b/localizedContent/es/content/tutorials/udfs.md index 2c5d6d9b5..f65e47e02 100644 --- a/localizedContent/es/content/tutorials/udfs.md +++ b/localizedContent/es/content/tutorials/udfs.md @@ -2,7 +2,7 @@ uid: udfs title: Funciones DAX definidas por el usuario author: Daniel Otykier -updated: 2026-03-19 +updated: 2026-06-24 applies_to: products: - product: Tabular Editor 2 @@ -20,7 +20,7 @@ applies_to: # Funciones DAX definidas por el usuario -Las funciones DAX definidas por el usuario (UDFs) constituyen una nueva capacidad de los modelos semánticos, introducida en Power BI Desktop con la actualización de septiembre de 2025. +Las UDF de DAX (funciones DAX definidas por el usuario) son una característica de los modelos semánticos. La característica pasó a versión preliminar con la actualización de septiembre de 2025 de Power BI Desktop y está [disponible de forma general](https://community.fabric.microsoft.com/t5/Power-BI-Updates-Blog/DAX-User-Defined-Functions-Generally-Available/ba-p/5185738) desde la versión de junio de 2026 de Power BI. La característica te permite crear funciones DAX reutilizables que puedes invocar desde cualquier expresión DAX de tu modelo, incluso desde otras funciones. Esta potente característica te ayuda a mantener la coherencia, reducir la duplicación de código y crear expresiones DAX más fáciles de mantener. @@ -133,8 +133,31 @@ Además de especificar el modo de evaluación, también puedes restringir el tip Estas especificaciones de tipo son opcionales, pero si se indican, realizarán una conversión de tipo implícita en los argumentos que se pasen a la función y también afectarán a las sugerencias de autocompletado en Tabular Editor 3 al escribir código DAX que llame a la función. +Tabular Editor 3 valida los argumentos según los tipos de parámetro declarados. Si llama a una UDF con un argumento que no coincide con el tipo de su parámetro —por ejemplo, si pasa un valor escalar cuando se espera un parámetro `TABLEREF`—, el Analizador semántico genera un Report de advertencia o un error. + Consulta la [especificación de Microsoft para las UDF](https://learn.microsoft.com/en-us/dax/best-practices/dax-user-defined-functions) para ver la lista completa de restricciones disponibles. +### Parámetros opcionales con expresiones predeterminadas + +A partir de la versión 3.26.2, Tabular Editor 3 admite parámetros opcionales con expresiones predeterminadas. Añade `= expression` después del nombre del parámetro (y después de cualquier indicación de tipo o modo de evaluación) para que el parámetro sea opcional. Cuando quien llama omite el argumento, la expresión predeterminada proporciona el valor. + +```dax +FUNCTION AddTax = + ( + amount: NUMERIC, // Parámetro obligatorio + taxRate: NUMERIC = 0.1 // Parámetro opcional; el valor predeterminado es 10 % + ) + => amount * (1 + taxRate) +``` + +Si llamas a `AddTax(10)`, obtienes `11`, mientras que si llamas a `AddTax(10, 0.25)`, obtienes `12.5`. + +Los parámetros opcionales se rigen por algunas reglas: + +- Puedes dejar vacío un argumento para usar su valor predeterminado; por ejemplo, `MyFunc(1,,3)` omite el segundo argumento. El número mínimo de argumentos lo determina la posición del parámetro obligatorio situado más a la derecha. +- Una expresión predeterminada solo puede hacer referencia a nombres (columnas, tablas, medidas, funciones) visibles donde se define la función, y no puede hacer referencia a otro parámetro de la misma función. +- La comprobación de tipos con respecto a la indicación de tipo de un parámetro solo se aplica cuando se usa la expresión predeterminada; en cambio, un argumento pasado explícitamente se comprueba con respecto a esa indicación. + ## Uso de las UDF en tu modelo ### En expresiones de objetos @@ -311,15 +334,15 @@ Tabular Editor 3 detecta automáticamente cualquier comentario y lo muestra corr **La función no funciona tras el despliegue** -- Comprueba que tu entorno de destino admite UDFs (nivel de compatibilidad 1702 o superior). A fecha del dieciséis de septiembre de 2025, Power BI Service todavía no admite UDFs, ni tampoco Azure Analysis Services ni SQL Server Analysis Services. +- Comprueba que tu entorno de destino admite UDFs (nivel de compatibilidad 1702 o superior). El servicio Power BI admite las UDF a partir de la versión de junio de 2026. Azure Analysis Services y SQL Server Analysis Services no admiten las UDF. ## Limitaciones -- No todos los entornos de Power BI admiten UDFs (requiere compilaciones específicas) +- Las UDF requieren un nivel de compatibilidad 1702 o superior; Azure Analysis Services y SQL Server Analysis Services no las admiten - Las UDFs no pueden ser recursivas (llamarse a sí mismas) > [!NOTE] -> A partir de junio de 2026, las UDF están [disponibles de forma general](https://community.fabric.microsoft.com/t5/Power-BI-Updates-Blog/DAX-User-Defined-Functions-Generally-Available/ba-p/5185738). Como parte de esto, las UDF ahora admiten expresiones predeterminadas y parámetros opcionales. Sin embargo, Tabular Editor 3 muestra actualmente un mensaje de error incorrecto cuando se usa la sintaxis de la expresión predeterminada. Este problema se corregirá en nuestra próxima actualización de Tabular Editor 3. +> Con la [disponibilidad general](https://community.fabric.microsoft.com/t5/Power-BI-Updates-Blog/DAX-User-Defined-Functions-Generally-Available/ba-p/5185738) de las UDF en junio de 2026, las UDF admiten parámetros opcionales con expresiones predeterminadas. Tabular Editor 3 admite esta sintaxis desde la versión 3.26.2. Las versiones anteriores muestran un mensaje de error incorrecto cuando usas la sintaxis de expresión predeterminada. --- diff --git a/localizedContent/zh/content/features/ai-assistant.md b/localizedContent/zh/content/features/ai-assistant.md index 591cfe2ee..fd98941b0 100644 --- a/localizedContent/zh/content/features/ai-assistant.md +++ b/localizedContent/zh/content/features/ai-assistant.md @@ -2,7 +2,7 @@ uid: ai-assistant title: AI 助手 author: Morten Lønskov -updated: 2026-04-17 +updated: 2026-06-24 applies_to: products: - product: Tabular Editor 2 @@ -44,20 +44,20 @@ AI 助手采用自带密钥 BYOK 模式。 你只需从受支持的提供商中 ## 支持的提供程序 -在 **工具 > 偏好 > AI 助手 > AI 提供程序** 下配置 AI 提供程序。 从下拉列表中选择一个提供商——在你完成配置之前,默认是 **无(AI 已禁用)**——输入你的 API 密钥,并可按需替换默认模型。 对于 OpenAI 和 Anthropic,**模型名称** 字段是一个预先填入已知模型的组合框;你也可以输入自定义模型名称。 +在 **工具 > 偏好 > AI 助手 > AI 提供程序** 下配置 AI 提供程序。 从下拉列表中选择一个提供商——在你完成配置之前,默认是 **无(AI 已禁用)**——输入你的 API 密钥,并可按需替换默认模型。 对于 OpenAI 和 Anthropic,**模型名称**字段是一个预先填充了当前模型的下拉框——包括 Anthropic 的 `claude-fable-5` 和 `claude-opus-4-8`,以及 OpenAI 的 `gpt-5.5` 和 `gpt-5.5-pro`——这样你无需手动输入自定义 ID,也能直接选择较新的模型。 你也可以输入自定义模型名称。 -| 提供程序 | 默认模型 | 需要配置 | -| -------------- | ----------------- | ------------------------------ | -| OpenAI | gpt-4o | API 密钥。 可选:基础 URL、组织 ID 和项目 ID | -| Anthropic | claude-sonnet-4-6 | API 密钥。 可选:基础 URL | -| Azure OpenAI | 视部署而定 | API 密钥、端点 URL 和部署名称 | -| 自定义(兼容 OpenAI) | 由用户指定 | API 密钥和自定义端点 URL | +| 提供程序 | 默认模型 | 需要配置 | +| -------------- | ----------------------- | ------------------------------ | +| OpenAI | gpt-5.5 | API 密钥。 可选:基础 URL、组织 ID 和项目 ID | +| Anthropic | claude-sonnet-4-6 | API 密钥。 可选:基础 URL | +| Azure OpenAI | 视部署而定 | API 密钥、端点 URL 和部署名称 | +| 自定义(兼容 OpenAI) | 由用户指定 | API 密钥和自定义端点 URL | ![AI 助手提供程序偏好设置选择](~/content/assets/images/ai-assistant/ai-assistant-provider-preferences.png) ### OpenAI -将提供程序选择为 **OpenAI**,然后输入你的 API 密钥。 如果你的 OpenAI 账户使用组织 ID 和项目 ID,也可以选择填写这些信息。 默认模型是 **gpt-4o**,但你可以将其更改为你账户中可用的任意模型。 +将提供程序选择为 **OpenAI**,然后输入你的 API 密钥。 如果你的 OpenAI 账户使用组织 ID 和项目 ID,也可以选择填写这些信息。 默认模型为 **gpt-5.5**,但你可以将其更改为你账号下可用的任意模型。 ![AI 助手 OpenAI 配置](~/content/assets/images/ai-assistant/ai-assistant-openai-config.png) diff --git a/localizedContent/zh/content/features/table-groups.md b/localizedContent/zh/content/features/table-groups.md index 961a53ccf..47f3ca181 100644 --- a/localizedContent/zh/content/features/table-groups.md +++ b/localizedContent/zh/content/features/table-groups.md @@ -2,7 +2,7 @@ uid: table-groups title: 表格组 author: Daniel Otykier -updated: 2023-03-08 +updated: 2026-06-24 applies_to: products: - product: Tabular Editor 2 @@ -25,6 +25,8 @@ applies_to: 你可以通过两种方式创建表格组:在表格上右键并选择 **创建 > 表格组** 菜单选项;或者在选中一个或多个表格时,在 **属性视图** 中为表格组指定名称。 +你也可以在选中一个或多个表后,通过右键菜单中的 **移至组** 子菜单进行操作。 该子菜单会列出现有的表格组、用于根据所选表创建新组并打开组名编辑器的 **(新建...)** 项,以及用于取消表格组归属的 **(无)** 项。 + 你可以在 TOM Explorer 中通过拖放,将表格在不同表格组之间移动。 注意:与度量值、列和层次结构的显示文件夹不同,表格组不能嵌套。 在 TOM Explorer 中右键点击某个表格组,会显示与你选中该表格组内表格(s)时相同的上下文菜单选项。 diff --git a/localizedContent/zh/content/features/te-cli/includes/te-cli-preview-notice.md b/localizedContent/zh/content/features/te-cli/includes/te-cli-preview-notice.md index f77a5378b..df39c7c39 100644 --- a/localizedContent/zh/content/features/te-cli/includes/te-cli-preview-notice.md +++ b/localizedContent/zh/content/features/te-cli/includes/te-cli-preview-notice.md @@ -1,2 +1,2 @@ > [!IMPORTANT] -> Tabular Editor CLI 目前为 **有限公开预览版**。 可使用 Tabular Editor 账户进行评估;预览期间无需许可证。 在正式发布之前,命令、标志位和输出都可能会发生变化。 **该预览版本将在 2026-09-30 后停止运行。** 我们不建议在预览期间将该 CLI 用于生产环境的 CI/CD 流水线。 请查看我们的许可协议。 +> Tabular Editor CLI 目前为 **有限公开预览版**。 可使用 Tabular Editor 账户进行评估;预览期间无需许可证。 在正式发布之前,命令、标志位和输出都可能会发生变化。 **该预览版本将在 2026-09-30 后停止运行。** 我们不建议在预览期间将该 CLI 用于生产环境的 CI/CD 流水线。 diff --git a/localizedContent/zh/content/features/te-cli/te-cli-automation.md b/localizedContent/zh/content/features/te-cli/te-cli-automation.md index 0497af602..da2d6ea5f 100644 --- a/localizedContent/zh/content/features/te-cli/te-cli-automation.md +++ b/localizedContent/zh/content/features/te-cli/te-cli-automation.md @@ -28,11 +28,11 @@ Tabular Editor CLI 具备可组合性;每个命令都支持结构化输出, | `text`(默认) | 人工阅读 | 无论是连接到 TTY 还是通过管道传输,stdout 都输出纯文本。 | | `json` | 机器可读 | 始终向 stdout 输出有效的 JSON。 如果还需要在 stderr 上输出机器可读的错误信息,请使用 `--error-format json`。 | | `csv` | 表格结果(`query`、`bpa run`、`bpa rules`、`vertipaq`、`validate`、`test`、`refresh`、`profile list`、`session list`、`find`、`replace`、`get`、`ls`) | 采用 RFC 4180 转义。 | -| `tmsl`(别名 `bim`) | 整个对象的 TMSL/BIM 序列化 | `te get` 和 `te ls` 接受此格式。 | +| `tmsl`(别名 `bim`) | 整个对象的 TMSL/BIM 序列化 | 此格式被 `te get` 和 `te list` 支持。 | | `tmdl` | 整个对象的 TMDL 序列化 | 仅 `te get` 支持(单个对象)。 | ```bash -te ls --output-format json +te list --output-format json te query -q "EVALUATE VALUES('Date'[Year])" --output-format csv te bpa run --output-format json ``` @@ -52,11 +52,11 @@ te deploy ./model --non-interactive --force --ci github 每个 `te` 命令都会使用可预测的状态代码退出,因此调用方无需解析 stdout,就能根据成功或失败执行分支逻辑。 -| 退出代码 | 含义 | -| ---- | ---------------------------------------------------------------------------------------------- | -| `0` | 成功。 | -| `1` | 通用失败:参数无效、命令失败、验证错误、身份验证失败,或 BPA 门禁在严重级别 ≥ error 时未通过。 针对 `te diff`:发现差异(遵循 `diff`/`cmp` 的惯例)。 | -| `2` | 仅适用于 `te diff`:比较过程中出错,因此差异状态未知。 | +| 退出代码 | 含义 | +| ---- | ----------------------------------------------------------------------------------------------- | +| `0` | 成功。 | +| `1` | 通用失败:参数无效、命令失败、验证错误、身份验证失败,或 BPA 检查在严重级别 >= error 时未通过。 针对 `te diff`:发现差异(遵循 `diff`/`cmp` 的惯例)。 | +| `2` | 仅适用于 `te diff`:比较过程中出错,因此差异状态未知。 | 将退出代码与 `--ci ` 注释以及 `--trx ` 结合使用,可在 CI 中展示更丰富的失败信息——请参阅 @te-cli-cicd。 @@ -65,7 +65,7 @@ te deploy ./model --non-interactive --force --ci github 错误、警告和预览横幅会写入 **stderr**;结构化数据会写入 **stdout**。 这意味着你可以安全地通过管道传递 JSON,而不用担心其中混入进度指示或诊断信息: ```bash -te ls --output-format json | jq '.[] | .name' +te list --output-format json | jq '.[] | .name' te vertipaq --output-format json > stats.json ``` @@ -152,7 +152,7 @@ te deploy ./model ` ```bash # Count measures per table -te ls --type measure --output-format json \ +te list --type measure --output-format json \ | jq -r '.[] | .table' \ | sort | uniq -c | sort -rn ``` @@ -179,10 +179,10 @@ cat refresh.tmsl 下面这些小技巧,是在脚本或管道中组合 `te` 命令时经常会用到的: -- **幂等的创建与删除。** `te add Sales/Marker -t Measure -i "0" --if-not-exists --save` 用于创建度量值,`te rm Sales/OldMeasure --if-exists --save` 用于删除度量值;无论对象是否存在,两者都会以 `0` 退出——可在 CI 中安全地重复运行。 +- **幂等的创建与删除。** `te add Sales/Marker -t Measure -i "0" --if-not-exists --save` 和 `te remove Sales/OldMeasure --if-exists --save` 无论对象是否存在,都会以 `0` 退出——可在 CI 中安全地重复运行。 - **试运行查看差异。** `te replace` 默认会先试运行;只有在你对预览结果满意时才添加 `--save`。 - **输出供审查的 TMSL。** `te deploy ./model --xmla deploy.tmsl` 会生成部署脚本,而不会更改服务器——适合 DBA 审查或手动应用。 -- **仅输出路径。** `te ls --paths-only` 和 `te find --paths-only` 每行输出一个对象路径,非常适合通过管道传给 `xargs`、`te get` 或 `te set`。 模型级容器(`te ls Measures`、`te ls Columns`)与此配合良好,适合对整个模型进行一次全面扫描。 +- **仅输出路径。** `te list --paths-only` 和 `te find --paths-only` 每行输出一个对象路径,非常适合通过管道传给 `xargs`、`te get` 或 `te set`。 模型级容器(`te list Measures`、`te list Columns`)与此配合良好,可用于对整个模型进行全面扫描。 - **查询基准测试。** `te query --trace --cold --runs 5` 会在冷缓存下运行 DAX 查询,迭代五次,并捕获 FE/SE 跟踪事件。 - **CI 日志中的步骤耗时。** 长时间运行的命令(`te deploy`、`te refresh`、`te script`、`te validate`)会在 JSON 输出中包含 `durationMs` 字段——便于在管道摘要中展示各步骤耗时。 diff --git a/localizedContent/zh/content/features/te-cli/te-cli-cicd.md b/localizedContent/zh/content/features/te-cli/te-cli-cicd.md index f78df6d02..4152c73b6 100644 --- a/localizedContent/zh/content/features/te-cli/te-cli-cicd.md +++ b/localizedContent/zh/content/features/te-cli/te-cli-cicd.md @@ -167,7 +167,7 @@ steps: 默认情况下,`te deploy` 和 `te save` 会先运行 Best Practice Analyzer 作为预检门禁。 有三种行为值得提前确定: -- **强制执行**——默认行为。 如果 BPA 发现严重级别 ≥ error 的违规项,管道将失败。 如果你也希望警告导致失败,可在独立的 `te bpa run` 步骤中配合 `--fail-on warning` 使用。 +- **强制执行**——默认行为。 如果 BPA 发现严重级别 >= error 的违规项,流水线将失败。 如果你也希望警告导致失败,可在独立的 `te bpa run` 步骤中配合 `--fail-on warning` 使用。 - **自动修复**——`--fix-bpa` 会在内存中对部署产物应用 `fixExpression`。 不会修改源文件。 当模型是唯一可信来源,而你希望部署在无需开发者干预的情况下规范化样式时,这很有用。 - **绕过**——`--skip-bpa` 会为单个命令禁用该门禁。 适合紧急热修复;不建议作为默认做法。 diff --git a/localizedContent/zh/content/features/te-cli/te-cli-commands.md b/localizedContent/zh/content/features/te-cli/te-cli-commands.md index 38270a6fc..84294035f 100644 --- a/localizedContent/zh/content/features/te-cli/te-cli-commands.md +++ b/localizedContent/zh/content/features/te-cli/te-cli-commands.md @@ -31,8 +31,8 @@ te bpa run --help # Help for a command with subcommands CLI 中的对象定位在所有命令中都采用同一套语法。 以下参考中会出现两种路径形式: -- **``** - 解析为**恰好一个**对象或容器。 用于对单个目标执行操作的命令:`te get`、`te set`、`te add`、`te rm`、`te mv`、`te format -p`、`te deps`、`te macro run --on`。 -- **``** - 解析为**零个或多个**对象,并支持通配符。 用于对一组对象执行操作的命令:`te ls`、`te bpa run --path` 以及其他用于检查的命令。 +- **``** - 解析为**恰好一个**对象或容器。 用于对单个目标执行操作的命令:`te get`、`te set`、`te add`、`te remove`、`te move`、`te format -p`、`te deps`、`te macro run --on`。 +- **``** - 解析为**零个或多个**对象,并支持通配符。 用于对一组目标执行操作的命令:`te list`、`te bpa run --path` 以及其他检查类命令。 两种路径形式共用同一套语法规则;仅有两处不同: @@ -101,22 +101,42 @@ te get "Measures/Revenue/KPI" # KPI sub-object of a measure ### 筛选路径中的通配符 -筛选路径引入了一个通配符字符 `*`,用于匹配单个分段内任意长度的字符序列(贪婪匹配,且仅限单个分段)。 通配符是 `te ls` 和类似命令用来缩小结果范围的方式。 +筛选路径引入了一个通配符字符 `*`,用于匹配单个分段内任意长度的字符序列(贪婪匹配,且仅限单个分段)。 通配符用于缩小 `te list` 和类似命令的结果范围。 ```bash -te ls 'Sa*' # Tables whose name starts with Sa -te ls 'Sales/*Amount' # Children of Sales whose name ends with Amount -te ls '*/Amount' # An Amount column/measure across every table -te ls 'Roles/Re*/Members' # Members of every role matching Re* +te list 'Sa*' # Tables whose name starts with Sa +te list 'Sales/*Amount' # Children of Sales whose name ends with Amount +te list '*/Amount' # An Amount column/measure across every table +te list 'Roles/Re*/Members' # Members of every role matching Re* ``` -包含 **N 个分段** 的筛选路径会返回 **N 层深度** 的结果——通配符不会自动多展开一层,结果深度不会超过你输入的层级。 单分段快捷写法 `te ls Sales` 是个例外:未限定且不含通配符的表名会展开为该表的直接子项,以符合“让我看看 Sales 里有什么”的意图。 相比之下,`te ls Sa*` 只返回匹配的表,不会展开。 +包含 **N 个分段** 的筛选路径会返回 **N 层深度** 的结果——通配符不会自动多展开一层,结果深度不会超过你输入的层级。 单分段快捷写法 `te list Sales` 是个例外:未限定且不带通配符的表名会展开为该表的直接子对象,以符合“让我看看 Sales 里有什么”的意图。 相比之下,`te list Sa*` 只返回匹配的表,不会展开。 筛选路径中不支持 DAX 的方括号后缀;如需按字面匹配包含 `[` 和 `]` 的名称,请给名称加引号。 ### 错误和提示 -分段拼写错误时会给出一条与上下文相关的错误;如果 CLI 能猜到你的意图,还会附带“你是不是想输入……”的提示。 缺少父级的路径会在检查叶节点之前失败,因此信息会指向真正出错的分段。 空容器(例如,在没有层次结构的模型上运行 `te ls Hierarchies`)会给出简单的“这里没有内容”提示,而不是错误。 +分段拼写错误时会给出一条与上下文相关的错误;如果 CLI 能猜到你的意图,还会附带“你是不是想输入……”的提示。 缺少父级的路径会在检查叶节点之前失败,因此信息会指向真正出错的分段。 空容器(例如,在没有层次结构的模型上运行 `te list Hierarchies`)会给出简单的“这里没有内容”提示,而不是报错。 + +## 命令别名 + +大多数长格式命令也有对应的简短别名。 每行显示规范命令及其可用的等效短格式别名。 + +| 规范命令 | 别名形式(s) | +| ------------------------------- | --------------------------- | +| `te list` | `te ls` | +| `te remove` | `te rm` | +| `te move` | `te mv`, `te rename` | +| `te bpa rules list` | `te bpa rules ls` | +| `te bpa rules remove` | `te bpa rules rm` | +| `te config list` | `te config ls` | +| `te 宏 list` | `te 宏 ls` | +| `te 宏 remove` | `te 宏 rm` | +| `te incremental-refresh remove` | `te incremental-refresh rm` | +| `te profile list` | `te profile ls` | +| `te profile remove` | `te profile rm` | +| `te session list` | `te session ls` | +| `te test list` | `te test ls` | ## 全局选项 @@ -124,12 +144,12 @@ te ls 'Roles/Re*/Members' # Members of every role matching Re* | 选项 | 说明 | | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `-m, --model ` | 语义模型的路径(TMDL 文件夹、`.bim` 文件或 TE 文件夹)。 | +| `-m, --model ` | 语义模型的路径(TMDL 文件夹、`.bim` 文件、`Database.json` 文件夹或 `.SemanticModel` 文件夹)。 | | `-s, --server ` | Workspace 名称或终结点(例如 `MyWorkspace`、`powerbi://...`、`asazure://...`、`localhost`)。 | | `-d, --database ` | Workspace 上的语义模型名称。 | | `--local` | 连接到本地运行的 Power BI Desktop 实例(仅限 Windows)。 | | `--auth ` | 身份验证方法:`auto`、`interactive`、`spn`、`env`、`managed-identity`(默认值:`auto`)。 | -| `--output-format ` | 标准输出格式:`text` (默认)、`json`、`csv`、`tmsl` (别名 `bim`)、`tmdl`。 `csv` 会被输出表格数据的命令采用;`tmsl`/`tmdl` 仅在 `te get` 和 `te ls` 中用于整个对象的序列化。 命令会拒绝其不支持的格式。 | +| `--output-format ` | 标准输出格式:`text` (默认)、`json`、`csv`、`tmsl` (别名 `bim`)、`tmdl`。 输出表格数据的命令会识别 `csv`;`tmsl`/`tmdl` 仅由 `te get` 和 `te list` 用于整个对象的序列化。 命令会拒绝其不支持的格式。 | | `--error-format ` | 用于错误、警告和提示的 stderr 格式:`text`(默认)或 `json`。 其他值将回退为 `text`。 它独立于 `--output-format`,因此你可以将 JSON 格式的 stdout 与纯文本错误配合使用(反之亦然)。 | | `--recent [N]` | 使用最近使用过的模型。 未提供值 = 进入交互式选择器;`N` = 最近使用列表中的第 N 个(1 = 最近一次使用)。 | | `--non-interactive` | 禁用所有交互式提示。 如果缺少必需输入,将给出可操作的错误提示并失败退出。 | @@ -141,6 +161,12 @@ te ls 'Roles/Re*/Members' # Members of every role matching Re* 位置参数 `` → 全局选项 `--model` → `--server`/`--database`(远程)→ `te connect` 的当前活动连接 → `--recent`。 +> [!NOTE] +> **拼写错误的选项会被立即拒绝。** 如果你传入了当前命令无法识别的 `--flag`,CLI 会直接退出并给出可操作的错误信息,而不是悄悄把该标记当作位置参数吞掉。 这可以捕获 CI 脚本中把 `--force ` 误写成 `--forec` 之类的拼写错误。 + +> [!NOTE] +> **带点号的服务器名称。** `-s`/`--server` 会将带点号的名称(例如 `Sales.2026`)视为 Analysis Services 服务器主机名,而不是 Power BI Workspace。 当 CLI 需要这样判断时,会发出警告,并提示:如果你指的是 Power BI Workspace,请在末尾追加 `.Workspace`(例如 `Sales.2026.Workspace`),或使用完整的 `powerbi://` URL。 适用于 `te connect`、`te deploy`、`te refresh`、`te query`、`te vertipaq` 和 `te test run`。 + ## 模型 I/O ### load @@ -159,8 +185,8 @@ te load -s MyWorkspace -d MyModel # Remote workspace `te save` 接受: -- `-o, --output-path ` - 目标文件或文件夹。 **可选** - 若省略,`te save` 会写回源位置,保留原始格式。 -- `--serialization ` - `tmdl`, `bim`, `te-folder`, `pbip`, `database.json`。 默认会从已加载的模型中推断(BIM 源 → BIM;TMDL `SemanticModel/` → `definition/` 下的 TMDL)。 +- `-o, --output-path ` - 目标文件或文件夹。 **可选** - 若省略,`te save` 会写回源位置,保留原始格式。 文件扩展名也会用于推断格式:`.bim` 会写出单个 BIM 文件,`.json` 会写出 `Database.json` 文件夹,而不带扩展名的路径会写出 TMDL 文件夹。 +- `--serialization ` - `tmdl`、`bim`(别名 `tmsl`)、`database.json`、`pbip`。 省略时,格式会从 `-o` 路径的扩展名推断(如果完全省略 `-o`,则从已加载的模型推断)。 - `--force` - 跳过验证并覆盖现有输出。 某些拒绝情况(例如容器不明确、项目根目录中存在多个 `SemanticModel`)即使使用 `--force` 也会触发。 - `--skip-bpa` - 完全绕过 BPA 检查。 - `--fix-bpa` - 当规则定义了修复表达式时,自动修复 BPA 违规项。 @@ -180,10 +206,12 @@ te save -o ./out -s my-workspace -d my-model --skip-validation # Fast download ### open -在 Tabular Editor 3 桌面版中打开模型。 **仅限 Windows**(需要先安装 TE3)。 +在 Tabular Editor 3 桌面版中打开模型。 **仅限 Windows**(需要先安装 TE3)。 不带参数时,会启动 TE3 并打开一个空白的 Workspace。 ```bash -te open ./my-model +te open # Launch TE3 with a blank workspace +te open ./my-model # Open a TMDL folder in TE3 +te open ./model.bim # Open a BIM file in TE3 ``` ### init @@ -196,7 +224,7 @@ te open ./my-model - `--compatibility-mode ` - `PowerBI`(默认)或 `AnalysisServices`。 - `--compatibility-level `(别名 `--compat`)- 兼容级别。 当模式为 `PowerBI` 时,默认值为 `1702`;否则为 `1500`。 参见 @update-compatibility-level。 - `--name ` - 模型/数据库名称(默认:目录名称)。 -- `--serialization ` - `tmdl`(默认)、`bim`、`te-folder`、`pbip`。 +- `--serialization ` - `tmdl`(默认)、`bim`(别名 `tmsl`)、`database.json`、`pbip`。 - `--force` - 覆盖目标路径下任何现有文件或目录。 ```bash @@ -214,14 +242,18 @@ te init ./existing-dir --force # Overwrite non-empty `te set` 接受以下参数: -- `-q ` - 属性名称(例如 `expression`、`formatString`、`description`、`isHidden`)。 -- `-i ` - 值(使用 `-` 可从 stdin 读取)。 +- `-q ` - 属性名称(例如 `expression`、`formatString`、`description`、`isHidden`)。 **可重复** - 将每个 `-q` 与其后紧跟的 `-i` 配对,即可在一条命令中设置多个属性。 +- `-i ` - 值(使用 `-` 可从 stdin 读取)。 每个 `-q` 对应一个 `-i`。 +- `-t, --type ` - 用于在同一路径可能解析为多种对象类型时消除歧义(`度量值`、`Column`、`CalculatedColumn`、`Hierarchy`、`Calendar`、`分区`、`CalculationItem`)。 - `--save` / `--save-to ` - 保存更改。 +- `--serialization ` - 保存时覆盖序列化格式(`tmdl`、`bim`(别名 `tmsl`)、`database.json`)。 +- `--force` - 即使修改引入 DAX 验证错误,也会保存。 ```bash te set Sales/Amount -q expression -i "SUM(Sales[Amt])" --save te set "'Net Sales'[Sales Amount]" -q formatString -i "#,0" --save # DAX form with spaced names te set Sales -q isHidden -i true --save +te set Sales/Amount -q formatString -i "#,0" -q description -i "Net sales" --save # Multi-property ``` ### add @@ -231,7 +263,15 @@ te set Sales -q isHidden -i true --save `te add` 支持以下选项: - `-t, --type ` - 指定对象类型。 常用值:`Table`、`Measure`、`Column`、`CalculatedColumn`、`Hierarchy`、`Role`、`Perspective`、`Culture`、`CalculationGroup`、`CalculationItem`。 支持 Tab 自动补全;可通过运行 `te add --help` 获取完整列表。 +- `-i ` - 要赋给新对象的表达式或值(度量值/计算列使用 DAX,分区使用 M,等等)。 与 `-q` 搭配使用,可在同一条命令中为新对象设置其他属性。 +- `-q ` - 要在新对象上设置的其他属性(可重复;与 `-i` 搭配使用)。 +- `--file ` - 从文件读取 `-i` 的表达式,而不是直接写在命令里。 +- `--mode ` - 新表的存储模式:`import`(默认)、`directQuery`、`dual`、`directLake`。 - `--if-not-exists` - 如果对象已存在,则直接以 `0` 退出且不报错。 可用于幂等的 CI/CD 管道。 +- `--save` / `--save-to ` - 保存更改。 +- `--serialization ` - 保存时覆盖序列化格式(`tmdl`、`bim`(别名 `tmsl`)、`database.json`)。 +- `--source-type ` - 新表的初始分区源类型:`m`、`query` 或 `calculated`。 这会覆盖启发式检测结果。 `calculated` 仅在与 `-t CalculatedTable` 搭配使用时有效。 +- `--force` - 即使修改引入 DAX 验证错误,也会保存。 ```bash te add Sales/Revenue -t Measure -i "SUM(Sales[Amount])" --save @@ -244,32 +284,42 @@ te add Roles/Reader -t Role --save # New 对于数据绑定表,`te add` 还支持从 SQL、Lakehouse 或 Warehouse 源推断架构。 有关 `--source`、`--endpoint`、`--source-table`、`--columns` 等参数,可查看 `te add --help`。 -### rm +### 删除 -删除对象。 默认会检查依赖对象,以避免破坏现有引用。 +删除对象。 默认会检查依赖对象,以避免破坏现有引用。 (别名:`rm`。) -`te rm` 接受: +`te remove` 支持以下参数: -- `` — 位置参数:要删除的对象。 -- `--force` — 跳过依赖对象检查。 -- `--if-exists` — 如果对象不存在,则直接以 `0` 退出且不报错。 可用于幂等的 CI/CD 管道。 -- `--dry-run` — 预览删除操作而不实际执行。 -- `--save` — 将更改保存到已加载的模型。 +- `` - 位置参数:要删除的对象。 +- `-t, --type ` - 当路径匹配到表下的多个子对象时,用于消除歧义(例如同名的列和层次结构)。 +- `--force` - 跳过依赖对象检查。 +- `--if-exists` - 如果对象不存在,则直接以 `0` 退出且不报错。 可用于幂等的 CI/CD 管道。 +- `--dry-run` - 预览删除操作而不实际执行。 +- `--save` / `--save-to ` - 保存更改。 +- `--serialization ` - 保存时覆盖序列化格式(`tmdl`、`bim`(别名 `tmsl`)、`database.json`)。 ```bash -te rm Sales/Revenue --save -te rm "'Sales'[Revenue]" --save # DAX form -te rm Sales/Revenue --dry-run # Preview only -te rm Sales/OldMeasure --if-exists --save # Idempotent +te remove Sales/Revenue --save +te remove "'Sales'[Revenue]" --save # DAX form +te remove Sales/Revenue --dry-run # Preview only +te remove Sales/OldMeasure --if-exists --save # Idempotent ``` -### mv +### 移动 -移动或重命名模型对象。 源和目标都是 `` 参数。 +移动或重命名模型对象。 源和目标都是 `` 参数。 (别名:`mv`、`rename`。) + +`te move` 支持以下参数: + +- `-t, --type ` - 当源路径匹配到多种对象类型时,用于消除歧义(例如同名的列和层次结构)。 +- `--save` / `--save-to ` - 保存更改。 +- `--serialization ` - 保存时覆盖序列化格式(`tmdl`、`bim`(别名 `tmsl`)或 `database.json`)。 +- `--force` - 即使该变更会引入 DAX 验证错误,也仍会保存。 ```bash -te mv Sales/Revenue Finance/Revenue --save # Move measure to another table -te mv Sales/Revenue Sales/TotalRevenue --save # Rename measure +te move Sales/Revenue Finance/Revenue --save # Move measure to another table +te move Sales/Revenue Sales/TotalRevenue --save # Rename measure +te move Sales/Date Sales/CalendarDate -t Hierarchy --save # Disambiguate hierarchy from column ``` ### replace @@ -284,7 +334,7 @@ te mv Sales/Revenue Sales/TotalRevenue --save # Rename measure - `--dry-run` - 仅预览更改,不会实际应用。 默认行为。 - `--save` - 将变更保存回源位置。 与 `--revert` 和 `--stage` 互斥。 - `--save-to ` - 保存到不同的路径(意味着 `--save`)。 -- `--serialization ` - 模型序列化格式:`tmdl`、`bim`、`te-folder`。 +- `--serialization ` - 模型序列化: `tmdl`、`bim` (别名 `tmsl`)、`database.json`。 - `--force` - 即使替换引入 DAX 验证错误,也会保存。 `--in expressions` 会遍历所有包含表达式的属性: @@ -305,31 +355,31 @@ te replace "SUM" "SUMX" --regex --in expressions --save ## 检查 -### ls +### list -以类似文件系统的导航方式列出对象。 接受一个支持通配符的 `` 参数。 同时支持模型级容器(`Tables`, `Measures`, `Columns`, `Hierarchies`, `Relationships`, `Roles`, `Perspectives`, `Cultures`)以及表范围容器(`Sales/Measures`, `Sales/Columns`, …) 均受支持。 +以类似文件系统的导航方式列出对象。 接受一个支持通配符的 `` 参数。 支持模型级容器和表作用域容器——完整列表见上方的[容器关键字表](#containers-and-keywords)。 (别名:`ls`。) -`te ls` 支持: +`te list` 支持: - `--type ` - 限定为一种对象类型(`table`, `measure`, `column`, `hierarchy`, `partition`, `relationship`, `role`, `perspective`, `culture`)。 如果不提供 ``,这等同于输入匹配的容器关键字。 - `--paths-only` - 每行输出一个对象路径,适合通过管道传给 `xargs`、`te get` 或 `te set`。 - `--no-multiline` - 将多行单元格(通常是 DAX 或 M 表达式)折叠为单行并截断,让宽表中的各行仍便于浏览。 仅影响文本输出;JSON/CSV/TMSL 输出不受影响。 -- `--output-format tmsl`(别名 `bim`)- 将匹配的对象输出为 TMSL/BIM 脚本。 例如可用于 `te ls Tables --output-format bim > tables.json`。 `ls` 不支持 `--output-format tmdl`(TMDL 仅支持单对象输出——请使用 `te get`)。 +- `--output-format tmsl`(别名 `bim`)- 将匹配的对象输出为 TMSL/BIM 脚本。 适用于 `te list Tables --output-format bim > tables.json`。 `ls` 不支持 `--output-format tmdl`(TMDL 仅支持单对象输出——请使用 `te get`)。 ```bash -te ls # All tables in the model -te ls Sales # All children of Sales (columns + measures + hierarchies + partitions) -te ls Sales/Measures # Just Sales's measures -te ls 'Sales/*Amount' # Children of Sales whose name ends with Amount -te ls 'Sa*' # Tables whose name starts with Sa (no auto-expansion) -te ls '*/Amount' # An Amount column/measure across every table -te ls 'Roles/Re*/Members' # Members of every role matching Re* -te ls Sales/Geography/Levels # All levels of the Geography hierarchy -te ls "'Net Sales'/'Sales Amount'" # Quote names containing spaces -te ls Measures --paths-only # One Table/Measure per line for piping -te ls --type measure # Same as `te ls Measures` -te ls Measures --no-multiline # Wide table with column dividers, single-line DAX -te ls Tables --output-format bim > tables.json # All tables emitted as TMSL/BIM +te list # All tables in the model +te list Sales # All children of Sales (columns + measures + hierarchies + partitions) +te list Sales/Measures # Just Sales's measures +te list 'Sales/*Amount' # Children of Sales whose name ends with Amount +te list 'Sa*' # Tables whose name starts with Sa (no auto-expansion) +te list '*/Amount' # An Amount column/measure across every table +te list 'Roles/Re*/Members' # Members of every role matching Re* +te list Sales/Geography/Levels # All levels of the Geography hierarchy +te list "'Net Sales'/'Sales Amount'" # Quote names containing spaces +te list Measures --paths-only # One Table/Measure per line for piping +te list --type measure # Same as `te list Measures` +te list Measures --no-multiline # Wide table with column dividers, single-line DAX +te list Tables --output-format bim > tables.json # All tables emitted as TMSL/BIM ``` ### get @@ -343,7 +393,7 @@ te ls Tables --output-format bim > tables.json # All tables emitted as TMSL/BI - `--output-format tmsl`(别名 `bim`)- 将解析后的对象输出为 TMSL/BIM JSON。 - `--output-format tmdl` - 将解析后的对象输出为 TMDL(仅限命名对象)。 -`te get` 和 `te ls` 共用同一个描述符目录,因此无论输出为哪种格式,属性的呈现方式都一致:文本表格、JSON 和 CSV 显示的都是同一组属性;给模型新增属性后,也会在所有格式中自动可见。 +`te get` 和 `te list` 共用同一个描述符目录,因此无论输出为哪种格式,属性的呈现方式都一致:文本表格、JSON 和 CSV 显示的都是同一组属性;给模型新增属性后,也会在所有格式中自动可见。 ```bash te get Sales/Amount -q expression # Print DAX @@ -381,6 +431,12 @@ te find "CALCULATE" --in expressions --paths-only | xargs -I{} te get {} -q expr ```bash te diff ./model-v1 ./model-v2 te diff old.bim new.bim + +# Branch on exit code (POSIX sh): +te diff ./a ./b; case $? in 0) echo same;; 1) echo different;; *) echo error;; esac + +# Branch on exit code (PowerShell): +te diff ./a ./b; switch ($LASTEXITCODE) { 0 { 'same' } 1 { 'different' } default { 'error' } } ``` ### deps @@ -427,6 +483,9 @@ te validate --ci github --trx results.trx te validate --errors-only # Hide warnings and anti-pattern hints ``` +> [!NOTE] +> `te validate` 不支持 `--output-format csv`——CSV 会在一开始就被拒绝,并给出可操作的错误提示,而不是生成不完整的结果。 验证输出使用 `text` 或 `json`。 + ### bpa run 针对模型运行 Best Practice Analyzer 规则。 @@ -445,8 +504,8 @@ te validate --errors-only # Hide warnings and anti-pattern hints - `--fix` - 应用修复表达式,在可能的情况下自动修复违规项。 - `--save` - 应用修复后,将模型保存回原始位置。 - `--save-to ` - 应用修复后,将模型保存到其他路径。 -- `--serialization ` - 模型序列化格式:`tmdl`、`bim`、`te-folder`。 -- `--fail-on ` - 失败阈值:`error`(默认)或 `warning`。 当违规项达到该阈值时,将以退出代码 `1` 退出。 +- `--serialization ` - 模型序列化: `tmdl`、`bim` (别名 `tmsl`)、`database.json`。 +- `--fail-on ` - 失败阈值:`error`(默认)或 `warning`。 当违规项达到该阈值时,将以退出代码 `1` 退出。 无论 `--fail-on` 如何设置,规则加载或求值错误(表达式无效、规则文件无法读取)也会导致命令以非零状态退出。 - `--ci ` - 向 stderr 输出 CI 日志命令:`vsts`(Azure DevOps)、`github`(GitHub Actions)。 - `--trx ` - 将结果作为 VSTEST `.trx` 文件写入指定路径。 - `--no-multiline` - 将违规表中的多行单元格内容折叠为单行。 仅文本输出。 @@ -485,17 +544,17 @@ Rules loaded: 41 from 1 file(s) from bpa.rules config + built-in defaults + mode 子命令: -| 子命令 | 用途 | -| ------------------------------- | ---------------------------- | -| `add [model]` | 添加新的 BPA 规则。 | -| [`disable`](#bpa-rules-disable) | 为当前用户禁用一条内置 BPA 规则。 | -| [`enable`](#bpa-rules-enable) | 重新启用先前已禁用的内置 BPA 规则。 | -| `ignore [model]` | 将规则添加到模型的忽略列表。 | -| [`init`](#bpa-rules-init) | 在解析后的 PATH 下创建一个空的 BPA 规则文件。 | -| [`list`](#bpa-rules-list) | 列出来自所有来源的 BPA 规则及其状态。 | -| `rm [model]` | 删除一条 BPA 规则。 | -| `set [model]` | 更新 BPA 规则的属性。 | -| `unignore [model]` | 从模型的忽略列表中移除一条规则。 | +| 子命令 | 用途 | +| ----------------------------------- | ---------------------------- | +| `add [model]` | 添加新的 BPA 规则。 | +| [`disable`](#bpa-rules-disable) | 为当前用户禁用一条内置 BPA 规则。 | +| [`enable`](#bpa-rules-enable) | 重新启用先前已禁用的内置 BPA 规则。 | +| `ignore [model]` | 将规则添加到模型的忽略列表。 | +| [`init`](#bpa-rules-init) | 在解析后的 PATH 下创建一个空的 BPA 规则文件。 | +| [`list`](#bpa-rules-list)(别名 `ls`) | 列出来自所有来源的 BPA 规则及其状态。 | +| `remove [model]`(别名 `rm`) | 删除一条 BPA 规则。 | +| `set [model]` | 更新 BPA 规则的属性。 | +| `unignore [model]` | 从模型的忽略列表中移除一条规则。 | `te bpa rules` 的所有子命令都接受以下选项: @@ -503,7 +562,7 @@ Rules loaded: 41 from 1 file(s) from bpa.rules config + built-in defaults + mode - `--model-rules` - 操作嵌入在模型注释中的规则,而非文件中的规则。 > [!IMPORTANT] -> `te bpa rules set` 和 `te bpa rules rm` 会拒绝修改内置规则 ID。 如果尝试这样做,命令将以退出码 `1` 退出,并提示改用 `te bpa rules disable`。 要自定义内置规则的行为,先禁用该内置规则,再添加一个使用不同 ID 的自定义副本: +> `te bpa rules set` 和 `te bpa rules remove` 会拒绝修改内置规则 ID。 如果尝试这样做,命令将以退出码 `1` 退出,并提示改用 `te bpa rules disable`。 要自定义内置规则的行为,先禁用该内置规则,再添加一个使用不同 ID 的自定义副本: > > ```bash > te bpa rules disable TE3_BUILT_IN_DATE_TABLE_EXISTS @@ -512,7 +571,7 @@ Rules loaded: 41 from 1 file(s) from bpa.rules config + built-in defaults + mode #### bpa rules list -列出来自所有来源的规则(内置、用户、模型)。 +列出来自所有来源的规则(内置、用户、模型)。 (别名:`ls`。) `te bpa rules list` 接受以下选项: @@ -532,7 +591,7 @@ te bpa rules list --ignored #### bpa rules init -在配置的 PATH 处创建一个空的 BPA 规则文件(`[]`)。 在对尚不存在的路径执行 `te bpa rules set` / `te bpa rules rm` 之前,请先执行一次此命令。 +在配置的 PATH 处创建一个空的 BPA 规则文件(`[]`)。 在对尚不存在的路径执行 `te bpa rules set` / `te bpa rules remove` 之前,先运行一次这个命令。 `te bpa rules init` 接受以下选项: @@ -547,11 +606,44 @@ te bpa rules init --rules-file ./MyRules.json te bpa rules init --force ``` +#### bpa rules add / set / remove / ignore / unignore + +修改规则文件(`add`、`set`、`remove`(别名 `rm`))或模型内嵌的忽略列表(`ignore`、`unignore`)。 这三个修改型子命令都针对 `--rules-file ` 或 `--model-rules` 操作,并且会拒绝修改内置规则 ID。 + +- `te bpa rules add ` - 创建新规则。 将每个属性作为命名选项传入: + - `--name ` - 便于阅读的规则名称(必填)。 + - `--scope ` - 规则适用的对象类型(以逗号分隔):`度量值`、`Column`、`Table`、`Hierarchy`、`分区`、`Relationship`、`Role`、`Perspective`、`Culture` 等 (必填)。 + - `--expression ` - Dynamic LINQ 谓词。 对于违反规则的对象,返回 `true`(必需)。 + - `--category ` - 分组标签(例如 `Performance`、`Naming`、`DAX Expressions`)。 + - `--severity <1|2|3>` - `1`(信息)、`2`(警告,默认)、`3`(错误)。 + - `--description ` - 规则触发时向用户显示的说明。 + - `--fix-expression ` - `te bpa run --fix` 用于自动修复的 Dynamic LINQ 表达式。 +- `te bpa rules set ` - 更新现有规则的属性。 使用 `-q -i ` 参数对(可重复)。 属性名称:`name`、`expression`、`scope`、`category`、`severity`、`description`、`fixExpression`。 +- `te bpa rules remove ` - 删除规则。 +- `te bpa rules ignore ` - 将规则 ID 添加到模型的 `BestPracticeAnalyzer_IgnoreRules` 注解中。 +- `te bpa rules unignore ` - 从模型的忽略列表中移除规则 ID。 + +```bash +# Add a rule: measures that are not hidden and have no description +te bpa rules add MEASURE_NEEDS_DESCRIPTION \ + --name "Measures should have a description" \ + --scope Measure \ + --expression "not IsHidden and string.IsNullOrEmpty(Description)" \ + --severity 2 \ + --category Metadata + +# Update severity on an existing rule +te bpa rules set MEASURE_NEEDS_DESCRIPTION -q severity -i 3 + +# Remove the rule +te bpa rules remove MEASURE_NEEDS_DESCRIPTION +``` + #### bpa rules disable 禁用单个内置 BPA 规则。 该规则 ID 会添加到 CLI 配置中的 `bpa.disabledBuiltInRuleIds`。 后续的门禁执行(deploy、save、mutation)以及 `te bpa run` 都会跳过已禁用的规则。 -该命令是幂等的——对已禁用的规则再次运行 `disable` 会成功,但不会修改配置。 如果 `` 不是内置规则,命令会以退出代码 `1` 结束;可使用 `te bpa rules list` 查看有效的内置 ID。 +该命令是幂等的——对已禁用的规则再次运行 `disable` 也会成功,且不会修改配置。 如果 `` 不是内置规则,命令会以退出代码 `1` 结束;可使用 `te bpa rules list` 查看有效的内置 ID。 ```bash te bpa rules disable TE3_BUILT_IN_DATE_TABLE_EXISTS @@ -571,6 +663,7 @@ te bpa rules enable TE3_BUILT_IN_DATE_TABLE_EXISTS `te vertipaq` 支持: +- `` - 可选的位置参数:表名,用于将分析范围限定在单个表上。 - `--columns`, `--relationships`, `--partitions`, `--all`。 - `--detail` - 显示扩展列(数据/字典/层次结构大小明细、编码、分段)。 - `--fields ` - 要显示的字段,以逗号分隔(例如:`--fields name,card,size,%tbl,%db,bar`)。 可用字段因视图而异。 @@ -578,10 +671,12 @@ te bpa rules enable TE3_BUILT_IN_DATE_TABLE_EXISTS - `--import ` - 加载之前导出的 `.vpax` 文件并进行离线分析。 - `--obfuscate` - 对导出的 VPAX 中的名称和表达式进行混淆处理。 - `--top `、`--stats`、`--annotate`、`--save`。 +- `--auth ` - 连接到远程模型时,用于覆盖默认的身份验证方法。 ```bash -te vertipaq # Columns by size (default) -te vertipaq --all # Tables, columns, relationships, partitions +te vertipaq # Columns by size (default) +te vertipaq Sales # Stats limited to the Sales table +te vertipaq --all # Tables, columns, relationships, partitions te vertipaq --export stats.vpax te vertipaq --import stats.vpax # Analyze offline ``` @@ -616,7 +711,8 @@ te format --lang m --save # Format M `te query` 支持以下选项: -- `-q, --query ` - 内联查询。 +- `` - 位置参数:要执行的 DAX 查询。 等同于传入 `-q`。 选择你觉得更易读的写法即可;如果两者都提供,以显式的 `-q` 为准。 +- `-q, --query ` - 内联查询(即上述位置参数的命名参数形式)。 - `--file ` - 从文件读取查询。 - `--limit ` - 默认为 100。 - `-o, --output-file ` - 将结果写入文件(`.csv`、`.tsv`、`.json`、`.dax`)。 @@ -624,7 +720,8 @@ te format --lang m --save # Format M - `--no-validate` - 跳过执行前的 DAX 语义验证。 ```bash -te query -q "EVALUATE TOPN(5, 'Sales')" -s my-ws -d my-model +te query "EVALUATE TOPN(5, 'Sales')" -s my-ws -d my-model # Positional DAX +te query -q "EVALUATE TOPN(5, 'Sales')" -s my-ws -d my-model # Named-flag form te query --file query.dax --output-format json ``` @@ -681,14 +778,28 @@ echo "Info(Model.Name);" | te script -e - | 子命令 | 用途 | | ---------------------------------- | ------------------- | -| `list` | 列出宏。 | +| `list`(别名 `ls`) | 列出宏。 | | 宏:[`run `](#macro-run) | 运行宏。 | | `add ` | 添加宏。 | | `set ` | 更新宏属性。 | -| `rm ` | 删除宏。 | +| `remove `(别名 `rm`) | 删除宏。 | | `sort` | 排序并重新分配 ID。 | | 宏:[`init`](#macro-init) | 在解析得到的路径处创建一个空的宏文件。 | +#### 宏:add / set / remove + +修改宏文件 (`add`、`set`、`remove` (别名 `rm`))。 这三个命令都操作 `--macros `(或解析得到的宏文件)。 + +- `te macro add ` - 创建新宏。 使用 `-e ""`(内联)或 `-s `(脚本文件)提供脚本主体。 可选:`--tooltip `、`--contexts `(宏适用的上下文,例如 `Table,Measure`,即“表、度量值”)、`--enabled true|false`。 +- `te macro set ` - 更新宏属性。 使用成对的 `-q -i ` 参数(可重复)。 属性名称:`name`、`execute`、`enabled`、`tooltip`、`validContexts`。 +- `te macro remove ` - 删除宏。 + +```bash +te macro add MyMacro -e "Info(Selected.Measure.Name);" --tooltip "Print measure name" --contexts Measure +te macro set MyMacro -q tooltip -i "Updated tooltip" +te macro remove MyMacro +``` + #### macro init 在配置的路径下创建一个空的宏文件(`{\"Actions\":[]}`)。 当解析后的宏文件尚不存在时,只需运行一次该命令。 @@ -742,7 +853,7 @@ te macro run "Format DAX" --on "'Net Sales'[Sales Amount]" --save # DAX form w - `--bpa-rules ` - 可重复指定;仅针对本次部署覆盖 CLI 配置中的 `bpa.rules`。 除非 `bpa.builtInRules` 为 `false`,否则内置规则仍会生效。 - `--force` - 跳过交互式确认(CI 必需)。 - `--ci ` - `vsts` 或 `github`。 -- `--profile ` - 一次性使用已保存的 @te-cli-auth 配置文件。 +- `-p, --profile ` - 一次性使用已保存的 @te-cli-auth 配置文件。 ```bash te deploy ./model -s my-workspace -d my-model --force --ci github @@ -753,18 +864,21 @@ te deploy ./model --profile staging --force > [!IMPORTANT] > `te deploy` 会在执行前运行 Best Practice Analyzer 作为门控检查。 在交互模式下,会显示摘要和确认提示,且 **默认安全选项为 `n`**。 在 CI 中,传入 `--force` 可跳过该提示。 BPA 门控配置请参见 @te-cli-config。 +> [!NOTE] +> 当设置 `--output-format json` 时,`te deploy` 的 JSON 输出始终包含解析后的 `server` 和 `database`,即使它们是从活动连接或配置文件中解析得到的,而不是显式传入的。 管道可使用这些字段来确认部署目标,而无需重新解析命令行。 在使用 `--output-format json` 时,`te deploy` 和 `te format` 失败时也会以非零退出码退出,这与其文本模式下的行为一致——JSON 输出记录的是失败信息,而不是成功信号。 + ### refresh 在已部署的模型上触发数据刷新。 `te refresh` 支持: -- `--type ` - `full`、`dataonly`、`automatic`、`calculate`、`clearvalues`、`defragment`、`add`(默认值:`automatic`)。 +- `--type ` - `full`、`dataonly`(别名 `data-only`、`data`)、`automatic`(别名 `auto`)、`calculate`(别名 `calc`)、`clearvalues`(别名 `clear`)、`defragment`(别名 `defrag`)、`add`(默认值:`automatic`)。 - `--table ` - 刷新特定表(可为多个);可重复指定。 - `--partition ` - 刷新特定分区(可为多个)。 - `--apply-refresh-policy` - 应用增量刷新的刷新策略,以确定要刷新的分区。 - `--effective-date ` - 设置刷新策略使用的生效日期。 -- `--max-parallelism ` - 设置可并行刷新的最大分区数。 +- `--max-parallelism ` - 设置可并行刷新的最大分区数。 将刷新封装在 TMSL `sequence` 命令中。 - `--dry-run` - 输出 TMSL 脚本而不执行。 - `--no-progress`, `--trace [path]`。 @@ -782,7 +896,7 @@ te refresh --type full --dry-run > refresh.tmsl # Emit TMSL only te incremental-refresh show ``` -其他子命令(`set`、`rm`、`apply`)可通过 `te incremental-refresh --help` 查看说明。 +其他子命令(`set`、`remove`(别名 `rm`)、`apply`)可通过 `te incremental-refresh --help` 查看说明。 ## 测试 @@ -804,6 +918,8 @@ te test run --tag revenue ### test init / spec / use / list / snapshot / compare +`te test list` 也支持使用 `ls` 作为别名。 + 其他子命令可用于搭建测试脚手架、打印断言规范格式、切换当前活动套件、列出套件、捕获快照以及比较模型。 查看 `te test --help` 了解详情。 ```bash @@ -819,12 +935,14 @@ te test init --from-model --model ./my-model # Generate stubs from your measure 设置(或显示)当前终端会话的活动连接。 查看 @te-cli-auth。 ```bash -te connect # Show current active connection -te connect my-workspace my-model # Remote -te connect ./model # Local -te connect --local # Power BI Desktop (Windows) -te connect --profile prod # Activate a saved profile -te connect --clear # Clear the active connection (and any workspace mirror) +te connect # Show current active connection +te connect my-workspace my-model # Remote (positional) +te connect -s my-workspace -d my-model # Remote (named-flag form) +te connect ./model # Local +te connect --local # Power BI Desktop (Windows) +te connect --local my-report # Filter by report name (multiple PBI Desktop instances) +te connect --profile prod # Activate a saved profile +te connect --clear # Clear the active connection (and any workspace mirror) ``` #### 工作区模式(`-w` / `--workspace`) @@ -833,11 +951,11 @@ te connect --clear # Clear the active connection (and any worksp - `te connect -w ./src` - 主源为远程;`./src` 会接收初始 TMDL 导出,并在每次保存时保持镜像同步。 - `te connect ./src -w ` - 主源为本地;首次部署会将模型推送到 Workspace,后续保存会自动重新部署。 -- `--workspace-format ` - 在镜像到文件夹/文件时选择磁盘上的格式(例如,`-w ./model.bim` 会推断为 BIM)。 +- `--workspace-format ` - 将内容镜像到文件夹/文件时,选择磁盘上的格式:`tmdl`、`bim`(别名 `tmsl`)或 `database.json`。 若省略,则会根据 Workspace 目标路径推断格式(例如,`-w ./model.bim` 会推断为 BIM)。 - `--workspace-auth ` - 当主目标为本地时,为远程 Workspace 目标指定身份验证方式。 若设置了 `--auth`,则默认与其一致;否则默认为 `auto`。 - `--force` - 当目标已存在(例如文件夹非空或数据库已存在)时必须使用。 如果不使用它,`te connect` 会显示交互式 `y/n` 提示,并以 `n` 作为安全的默认选项。 -启用后,`te set --save`、`te rm --save`、`te script --save` 等都会透明地同时保存到本地和远程。 保存顺序始终是 **先本地,后远程**,因此即使推送到服务器失败,磁盘上的副本也会反映最新的用户更改。 使用 `te connect --clear` 清除镜像。 +启用后,`te set --save`、`te remove --save`、`te script --save` 等都会透明地进行双重保存。 保存顺序始终是 **先本地,后远程**,因此即使推送到服务器失败,磁盘上的副本也会反映最新的用户更改。 使用 `te connect --clear` 清除镜像。 ```bash te connect Finance "Revenue Model" -w ./revenue-model # Mirror remote → local TMDL @@ -850,25 +968,21 @@ te connect ./revenue-model -w Finance "Revenue Model" # Mirror local → remo ### profile list / show / set / remove -管理命名连接配置文件。 参见 @te-cli-auth。 +管理命名连接配置文件。 (`te profile list` 的别名:`ls`;`te profile remove` 的别名:`rm`。) 参见 @te-cli-auth。 ## 配置 -### config show / paths / init / set +### config list / paths / init / set -查看和管理 CLI 配置以及 TE3 PATH 覆盖设置。 参见 @te-cli-config。 +查看和管理 CLI 配置以及 TE3 PATH 覆盖设置。 (`te config list` 的别名是 `ls`。) 参见 @te-cli-config。 ```bash -te config show # Display all settings +te config list # Display all settings te config paths # Resolved TE3 file paths te config init # Create default config te config set autoFormat true ``` -### license - -`te license` 保留用于正式发布 GA 版,在此预览版本中不可用。 这个命令仍会被解析器识别——所以调用它的现有脚本不会在解析阶段报错——但所有子命令都会以状态 `1` 退出,并显示“此预览版本中不可用”的信息。 关于更全面的许可说明,请参见概述页面上的[预览说明](xref:te-cli#preview-notice)。 - ### migrate 说明旧版 Tabular Editor 2 CLI 参数如何映射到新 CLI 的参考指南。 在迁移基于 TE2 的管道时,可作为实时速查参考。 完整迁移指南参见 @te-cli-migrate。 @@ -885,10 +999,22 @@ te migrate --output-format json # Machine-readable mapping 使用具备模型感知能力的提示词启动引导式 REPL 会话。 参见 @te-cli-interactive。 +> [!TIP] +> 在终端中直接运行不带任何参数的 `te`,默认也会进入 REPL(就像运行 `te interactive` 一样)。 此行为由 `launchInteractiveMode` 配置项控制,参见 @te-cli-interactive#auto-launch-on-empty-invocation。 + +`te interactive` 接受以下选项: + +- `` - 可选位置参数:启动会话时加载本地模型、`.bim` 文件或 `.SemanticModel` 文件夹。 +- `--no-banner` - 启动时跳过欢迎横幅。 从脚本驱动 REPL 时很有用。 +- `--echo` - 在输出结果之前,将每条已执行命令回显到 stdout。 当通过 stdin 管道传入命令时很有帮助,这样日志会显示实际运行了什么。 +- `--batch` - 非交互式批处理模式:逐行从 stdin 读取命令,执行每条命令,并在 EOF 时退出。 当 stdin 被重定向时会自动启用。 +- `--no-batch` - 即使 stdin 被重定向,也强制使用交互式 TTY 模式(与 `--batch` 互斥)。 + ```bash te interactive # Connect later te interactive ./model # Start with a local model te interactive -s MyWorkspace -d MyModel # Start with a remote model +printf "list Measures\nexit\n" | te interactive ./model # Pipe commands via stdin ``` 引号和 DAX 风格的引用在会话内外的用法一致——有关 REPL 中支持括号感知的 argv 拆分的详细信息,请参见上文的[对象路径](#object-paths)一节以及 @te-cli-interactive。 @@ -899,12 +1025,12 @@ te interactive -s MyWorkspace -d MyModel # Start with a remote model 子命令: -| 子命令 | 用途 | -| ------- | ---------------------------------------- | -| `show` | 显示当前会话的详细信息(ID、文件路径、活动状态)。 未提供子命令时的默认行为。 | -| `list` | 列出所有会话文件。 | -| `clear` | 清除当前会话的活动状态。 | -| `prune` | 删除其 shell 进程已停止运行的会话文件。 | +| 子命令 | 用途 | +| --------------- | ---------------------------------------- | +| `show` | 显示当前会话的详细信息(ID、文件路径、活动状态)。 未提供子命令时的默认行为。 | +| `list`(别名 `ls`) | 列出所有会话文件。 | +| `clear` | 清除当前会话的活动状态。 | +| `prune` | 删除其 shell 进程已停止运行的会话文件。 | `te session prune` 支持以下选项: @@ -932,11 +1058,11 @@ te completion fish ## 退出代码 -| 退出代码 | 含义 | -| ---- | -------------------------------------------------------------------------- | -| `0` | 成功。 | -| `1` | 通用失败(参数无效、命令执行失败、校验错误、身份验证失败、BPA 门禁在严重级别 ≥ error 时未通过)。 用于 `te diff`:发现差异。 | -| `2` | 仅适用于 `te diff`:比较时发生错误,因此差异状态未知。 | +| 退出代码 | 含义 | +| ---- | ------------------------------------------------------------------------------ | +| `0` | 成功。 | +| `1` | 通用失败(参数无效、命令执行失败、验证错误、身份验证失败,或 BPA 关卡在严重性级别 >= error 时未通过)。 用于 `te diff`:发现差异。 | +| `2` | 仅适用于 `te diff`:比较时发生错误,因此差异状态未知。 | 如需在 CI 管道中进行更细致的控制,可将退出代码与 `--ci ` 注释以及 `--trx` 结果文件结合使用——参见 @te-cli-cicd。 diff --git a/localizedContent/zh/content/features/te-cli/te-cli-config.md b/localizedContent/zh/content/features/te-cli/te-cli-config.md index 29c8744da..06d0743a3 100644 --- a/localizedContent/zh/content/features/te-cli/te-cli-config.md +++ b/localizedContent/zh/content/features/te-cli/te-cli-config.md @@ -19,13 +19,13 @@ applies_to: Tabular Editor CLI 可从 JSON 文件读取可选配置。 配置控制三类内容: -- **文件路径** — CLI 在何处读取宏、BPA 规则和 (可选) TE3 Desktop 可执行文件,以及在何处写入查询日志。 -- **行为默认值** — BPA 门控、自动格式化、验证。 -- **已保存的连接配置文件** — 可在不同命名配置文件之间切换的列表。 +- **文件路径** — CLI 在何处读取宏、BPA 规则和(可选)TE3 Desktop 可执行文件,以及在何处写入查询日志。 +- **行为默认设置** — BPA 门禁、自动格式化、校验。 +- **已保存的连接配置文件** — 你可切换的已命名配置文件列表。 CLI 是自包含的 - 它不会从任何 Tabular Editor 3 桌面版安装路径读取或写入任何内容。 BPA 规则和宏文件必须通过此配置显式设置(或按需使用 `te bpa rules init` / `te macro init` 进行初始化)。 -大多数用户无需直接编辑配置文件;`te config show`、`te config set ` 和 `te profile set` 已涵盖常见操作。 +大多数用户无需直接编辑配置文件;`te config list`、`te config set ` 和 `te profile set` 已涵盖常见操作。 ## 配置文件位置 @@ -35,7 +35,7 @@ CLI 是自包含的 - 它不会从任何 Tabular Editor 3 桌面版安装路径 2. `~/.config/te/config.json`(在 Windows 上为 `%USERPROFILE%\.config\te\config.json`)。 3. 如果没有配置文件,CLI 将使用内置默认值。 -`TE_CONFIG` 在所有配置文件相关操作中都会被一致地遵循;`te config show`、`te config set`、`te config init` 和 `te config paths` 都会在解析后的 PATH 上进行读写。 这主要用于测试、脚本化安装以及按环境进行配置。 +所有配置文件相关操作都会一致地遵循 `TE_CONFIG`;`te config list`、`te config set`、`te config init` 和 `te config paths` 都会在解析后的路径上进行读写。 这主要用于测试、脚本化安装以及按环境进行配置。 要创建默认配置: @@ -47,15 +47,15 @@ te config init --force # Overwrite existing config ## 查看配置 ```bash -te config show # Display all settings -te config show --output-format json # Machine-readable +te config list # Display all settings +te config list --output-format json # Machine-readable te config paths # Show resolved macros and BPA rule paths ``` 使用 `te config paths` 查看 CLI 实际会为宏和 BPA 规则使用哪些文件。 这在调试缺失的数据文件时很有用。 输出会显示两行:`macros` (解析后的宏文件路径或 `[not set]`) 和 `bpa.rules` (由路径解析器解析出的第一个存在的 BPA 规则文件,或 `[not set]`)。 > [!NOTE] -> 在 `--output-format json` 模式下,`te config paths` 会显式输出值为 `null` 的字段 (例如 `{"macros": null, "bpa": {"rules": null}}`)。 报告解析结果本来就是此命令的全部用途,因此 `null` 表示“尝试过,但未解析到任何内容”,这是一个有意义的结果。 `te config show --output-format json` 默认会剔除值为 `null` 的字段,因此使用方应以容错方式解析。 +> 在 `--output-format json` 模式下,`te config paths` 会显式输出值为 `null` 的字段 (例如 `{"macros": null, "bpa": {"rules": null}}`)。 报告解析结果本来就是此命令的全部用途,因此 `null` 表示“尝试过,但未解析到任何内容”,这是一个有意义的结果。 `te config list --output-format json` 默认会去掉值为 null 的字段,因此使用方在解析时应采用宽松策略。 ## 设置值 @@ -79,7 +79,7 @@ te config set macros null # Clear a path override ```json { - "formatVersion": 1, + "formatVersion": 2, "macros": null, "autoFormat": false, "validateOnMutation": true, @@ -95,6 +95,7 @@ te config set macros null # Clear a path override }, "interactiveEditMode": "stage", + "launchInteractiveMode": "auto", "formatOptions": { "useSemicolons": false, @@ -142,18 +143,19 @@ CLI 不会自动检测 TE3 的任何安装位置——请显式配置这些项 所有与 BPA 相关的设置都位于 `bpa` 对象下,并可在 `te config set` 中使用点号分隔的键进行设置。 -| 键名 | 默认值 | 说明 | -| ---------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `autoFormat` | `false` | 在执行 `te add` / `te set` / `te mv` / `te macro run` 之后,对修改过的表达式运行 DAX Formatter。 默认使用内置格式化程序;可通过 `formatOptions.useSqlBiDaxFormatter` 改用 SQL BI Web 服务。 | -| `validateOnMutation` | `true` | 在执行更改命令(`add`、`set`、`mv`、`replace --save`、`macro run`)后,检查模型中的每个 `Table[Column]` 引用是否仍可解析。 可在部署前捕获因重命名或删除而引入的悬空引用。 | -| `bpa.onMutation` | `false` | 在每次更改命令(`set`、`add`、`mv`、`rm`、`macro run`)后,运行一次限定范围的 BPA 分析。 只检查受影响表中的对象,而不是整个模型——这对于迭代编辑时获得快速反馈很有用。 | -| `bpa.onDeploy` | `true` | 在执行 `te deploy` 之前运行 BPA 关卡检查。 如果有任一规则触发且严重级别 ≥ error,则中止部署。 可通过 `--skip-bpa` 在单次调用中跳过,或通过 `--fix-bpa` 自动修复。 | -| `bpa.onSave` | `true` | 在 `te save -o` 写入磁盘之前运行 BPA 关卡检查。 可通过 `--skip-bpa` 或 `--force` 在单次调用中跳过。 | -| `bpa.builtInRules` | `true` | 每次运行关卡检查时,都包含精选的内置 BPA 规则集。 设为 `false` 可完全忽略内置规则;此时关卡检查只运行通过 `bpa.rules` 配置的规则以及嵌入模型中的规则。 | -| `bpa.disabledBuiltInRuleIds` | `null` | 要从门禁中排除的各个内置规则的 ID。 可通过 `te bpa rules disable ` / `te bpa rules enable ` 修改——优先使用这些命令,而不是直接编辑该数组。 | -| `vertipaqOnRefresh` | `false` | 成功刷新后(`full`、`dataonly`、`automatic` 或 `add`),自动运行 VertiPaq 分析,以显示已刷新表的存储统计信息。 有助于立即发现意外的基数变化或内存回归。 | -| `interactiveEditMode` | `stage` | 在 `te interactive` 中对内存中变更的默认处理方式。 `stage` 会将变更保留在内存中,直到调用 `save`(最安全);`save` 会在每次产生变更的命令后写回源(对远程源请谨慎使用——每次 `set` 都会触发一次 XMLA 写入);`revert` 会在每条命令后丢弃变更,除非传入了 `--save` 或 `--stage`。 每个命令上的 `--save` / `--revert` / `--stage` 标志始终会覆盖此设置。 | -| `disableTelemetry` | `false` | 选择不参与匿名使用遥测数据收集。 CLI 会收集粗粒度的命令使用数据(命令名称、退出代码、持续时间),用于确定功能优先级。 CLI 绝不会收集模型内容、PATH 或查询文本。 | +| 键名 | 默认值 | 说明 | +| ---------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `autoFormat` | `false` | 在执行 `te add` / `te set` / `te move` / `te macro run` 之后,对修改过的表达式运行 DAX Formatter。 默认使用内置格式化程序;可通过 `formatOptions.useSqlBiDaxFormatter` 改用 SQL BI Web 服务。 | +| `validateOnMutation` | `true` | 在执行更改命令(`add`、`set`、`mv`、`replace --save`、`macro run`)后,检查模型中的每个 `Table[Column]` 引用是否仍可解析。 可在部署前捕获因重命名或删除而引入的悬空引用。 | +| `bpa.onMutation` | `false` | 在每次更改命令(`set`、`add`、`mv`、`rm`、`macro run`)后,运行一次限定范围的 BPA 分析。 只检查受影响表中的对象,而不是整个模型——这对于迭代编辑时获得快速反馈很有用。 | +| `bpa.onDeploy` | `true` | 在执行 `te deploy` 之前运行 BPA 关卡检查。 如果有任何规则以严重级别 >= error 触发,部署将中止。 可通过 `--skip-bpa` 在单次调用中跳过,或通过 `--fix-bpa` 自动修复。 | +| `bpa.onSave` | `true` | 在 `te save -o` 写入磁盘之前运行 BPA 关卡检查。 可通过 `--skip-bpa` 或 `--force` 在单次调用中跳过。 | +| `bpa.builtInRules` | `true` | 每次运行关卡检查时,都包含精选的内置 BPA 规则集。 设为 `false` 可完全忽略内置规则;此时关卡检查只运行通过 `bpa.rules` 配置的规则以及嵌入模型中的规则。 | +| `bpa.disabledBuiltInRuleIds` | `null` | 要从门禁中排除的各个内置规则的 ID。 可通过 `te bpa rules disable ` / `te bpa rules enable ` 修改——优先使用这些命令,而不是直接编辑该数组。 | +| `vertipaqOnRefresh` | `false` | 成功刷新后(`full`、`dataonly`、`automatic` 或 `add`),自动运行 VertiPaq 分析,以显示已刷新表的存储统计信息。 有助于立即发现意外的基数变化或内存回归。 | +| `interactiveEditMode` | `stage` | 在 `te interactive` 中对内存中变更的默认处理方式。 `stage` 会将变更保留在内存中,直到调用 `save`(最安全);`save` 会在每次产生变更的命令后写回源(对远程源请谨慎使用——每次 `set` 都会触发一次 XMLA 写入);`revert` 会在每条命令后丢弃变更,除非传入了 `--save` 或 `--stage`。 每个命令上的 `--save` / `--revert` / `--stage` 标志始终会覆盖此设置。 | +| `launchInteractiveMode` | `auto` | 控制在终端中不带任何参数运行 `te` 时,是否启动交互式 REPL。 `auto` (默认) 仅在三个流(stdin、stdout、stderr)都连接到 TTY 时才会启动 REPL,因此脚本和 CI 流水线会按常规方式解析,不会进入 REPL。 `always` 会在无论是否重定向的情况下都启动 REPL。 `never` 会完全禁用自动启动,恢复传统的空参数显示帮助行为。 全局 `--non-interactive` 标志会在单次调用中强制设为 `never`。 也可通过 `TE_INTERACTIVE` 环境变量为单次调用设置该值。 | +| `disableTelemetry` | `false` | 选择不参与匿名使用遥测数据收集。 CLI 会收集粗粒度的命令使用数据(命令名称、退出代码、持续时间),用于确定功能优先级。 CLI 绝不会收集模型内容、PATH 或查询文本。 | ```bash te config set bpa.rules "/etc/te/team.json,/etc/te/strict.json" @@ -200,11 +202,11 @@ BPA 闸门是一道安全防线,用于防止存在规则违规的模型被保 - `te deploy` 会触发闸门检查,除非传入 `--skip-bpa` 或 `bpa.onDeploy` 为 `false`。 - `te save` 会触发闸门检查,除非传入 `--skip-bpa`(或 `--force`)或 `bpa.onSave` 为 `false`。 -- `te add`、`te set`、`te mv`、`te macro run` 仅在 `bpa.onMutation` 为 `true` 时才会运行闸门。 +- `te add`、`te set`、`te move`、`te macro run` 仅在 `bpa.onMutation` 为 `true` 时才会运行门禁检查。 闸门检查会从 `bpa.rules` 加载 BPA 规则,并且默认还会加载内置规则集(由 `bpa.builtInRules` 控制)。 可通过 `bpa.disabledBuiltInRuleIds` 单独排除内置规则——可使用 `te bpa rules disable ` / `te bpa rules enable ` 管理。 -当闸门检查触发并发现严重性 ≥ `error` 的违规项时,命令会失败,退出代码为 `1`,并输出违规摘要。 处理选项: +当门禁检查触发并发现严重级别 >= `error` 的违规项时,命令会失败,退出代码为 `1`,并输出违规摘要。 处理选项: - `--fix-bpa` - 在内存中将规则的 `fixExpression` 应用于部署/保存产物;不会修改源文件。 - `--skip-bpa` - 仅对本次命令禁用闸门检查。 @@ -219,13 +221,13 @@ te bpa run ./model --fix --save # Apply fixes to the source ### 内置 BPA 规则 -CLI 随附一套权威的内置 BPA 规则集,并以 JSON 资源的形式嵌入其中。 内置规则是只读的:`te bpa rules set` 和 `te bpa rules rm` 拒绝修改内置 ID,并引导用户改用 `te bpa rules disable`。 如果想自定义内置规则的行为,可以把它复制到本地规则文件中,作为一个使用不同 ID 的新规则,然后再禁用内置规则。 +CLI 随附一套权威的内置 BPA 规则集,并以 JSON 资源的形式嵌入其中。 内置规则是只读的;`te bpa rules set` 和 `te bpa rules remove` 会拒绝修改内置 ID,并提示你改用 `te bpa rules disable`。 如果想自定义内置规则的行为,可以把它复制到本地规则文件中,作为一个使用不同 ID 的新规则,然后再禁用内置规则。 `bpa.builtInRules` 和 `bpa.disabledBuiltInRuleIds` 会一致地应用于部署/保存/变更的门控检查 **以及** 手动执行的 `te bpa run` 命令——通过 `te bpa rules disable` 禁用一次后,该规则将在所有场景中被排除。 ## 变更后行为 -运行会产生变更的命令(`te add`、`te set`、`te mv`、`te replace --save`、`te macro run`)时,CLI 会自动执行以下检查: +运行会产生变更的命令(`te add`、`te set`、`te move`、`te replace --save`、`te macro run`)时,CLI 会自动执行以下检查: 1. **TOM 错误**始终会被提示。 度量值、列、分区或计算项中的无效 DAX 或 M 始终会导致命令失败。 2. **架构验证** (`validateOnMutation`,默认值为 `true`) 会验证 DAX 中的 `Table[Column]` 引用是否仍可解析,并交叉检查元数据一致性。 @@ -238,18 +240,19 @@ CLI 随附一套权威的内置 BPA 规则集,并以 JSON 资源的形式嵌 使用以下 CLI 专用环境变量来设置路径、行为和诊断。 有关 Azure 身份验证变量(`AZURE_CLIENT_ID`、`AZURE_TENANT_ID`、`AZURE_CLIENT_CERTIFICATE_PATH` 等),见 @te-cli-auth。 -| 变量 | 用途 | -| ---------------- | ------------------------------------------------------------------------------------------------------------------------------- | -| `TE_CONFIG` | 替代配置文件的路径。 所有 `te config` 操作(`show`、`set`、`init`、`paths`)都会遵循该变量的设置。 | -| `TE_MACROS_PATH` | 覆盖宏文件路径(在解析顺序中排第二,见上文)。 由 `te macro` 命令读取。 | -| `TE_BPA_RULES` | 覆盖 `te bpa run` 和 `te bpa rules` 子命令使用的 BPA 规则文件/URL 列表。 | -| `TE_BPA_CONFIG` | 覆盖 deploy/save 门禁读取的 BPA 门禁配置 (`.te-bpa.json`) 的路径。 | -| `TE3_EXE_PATH` | Tabular Editor 3 桌面版二进制文件的路径。 此项 **仅** 用于 `te open`;在 Linux/macOS 上或不使用 `te open` 时,可安全留空。 会回退到 `PATH` 查找。 | -| `TE_DEBUG` | 设为 `1` 可全局启用调试日志(等同于 `--debug` 或配置中的 `debug: true`)。 | -| `NO_SPINNER` | 设为 `1` 或 `true` 可禁用动画进度指示器(可替代配置中的 `spinner: false`)。 | -| `CI` | 自动检测。 当设为 `1` 或 `true` 时,CLI 会禁用动画进度指示器,并切换为纯文本输出。 大多数 CI 运行器都会自动设置此项。 | -| `TE_SESSION` | 覆盖用于活动连接状态的按终端划分的会话 ID。 适用于在同一个 shell 中运行多个相互隔离的 CLI 会话,例如并行的 CI 矩阵作业。 使用 [`te session`](xref:te-cli-commands#session) 查看并管理会话。 | -| `TE_COMPAT` | 设为 `te2` 可强制启用 TE2 兼容模式;参见 @te-cli-migrate。 | +| 变量 | 用途 | +| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TE_CONFIG` | 替代配置文件的路径。 所有 `te config` 操作(`show`、`set`、`init`、`paths`)都会遵循该变量的设置。 | +| `TE_MACROS_PATH` | 覆盖宏文件路径(在解析顺序中排第二,见上文)。 由 `te macro` 命令读取。 | +| `TE_BPA_RULES` | 覆盖 `te bpa run` 和 `te bpa rules` 子命令使用的 BPA 规则文件/URL 列表。 | +| `TE_BPA_CONFIG` | 覆盖 deploy/save 门禁读取的 BPA 门禁配置 (`.te-bpa.json`) 的路径。 | +| `TE3_EXE_PATH` | Tabular Editor 3 桌面版二进制文件的路径。 此项 **仅** 用于 `te open`;在 Linux/macOS 上或不使用 `te open` 时,可安全留空。 会回退到 `PATH` 查找。 | +| `TE_DEBUG` | 设为 `1` 可全局启用调试日志(等同于 `--debug` 或配置中的 `debug: true`)。 | +| `NO_SPINNER` | 设为 `1` 或 `true` 可禁用动画进度指示器(可替代配置中的 `spinner: false`)。 | +| `CI` | 自动检测。 当设为 `1` 或 `true` 时,CLI 会禁用动画进度指示器,并切换为纯文本输出。 大多数 CI 运行器都会自动设置此项。 | +| `TE_SESSION` | 覆盖用于活动连接状态的按终端划分的会话 ID。 适用于在同一个 shell 中运行多个相互隔离的 CLI 会话,例如并行的 CI 矩阵作业。 使用 [`te session`](xref:te-cli-commands#session) 查看并管理会话。 | +| `TE_INTERACTIVE` | 为单次调用临时覆盖 `launchInteractiveMode` 设置。 接受 `auto`、`always` 或 `never`。 适合一次性脚本:既可以在不改动配置文件的情况下启用交互式 REPL(`TE_INTERACTIVE=always`),也可以强制使用经典的空参数显示帮助行为(`TE_INTERACTIVE=never`)。 | +| `TE_COMPAT` | 设为 `te2` 可强制启用 TE2 兼容模式;参见 @te-cli-migrate。 | ## 相关页面 diff --git a/localizedContent/zh/content/features/te-cli/te-cli-install.md b/localizedContent/zh/content/features/te-cli/te-cli-install.md index 896b9e6bc..4f7b7e06e 100644 --- a/localizedContent/zh/content/features/te-cli/te-cli-install.md +++ b/localizedContent/zh/content/features/te-cli/te-cli-install.md @@ -132,7 +132,7 @@ te config set hidePreviewNotice true ## Shell 自动补全 -CLI 提供适用于 **Bash**、**Zsh**、**PowerShell** 和 **Fish** 的 Tab 自动补全脚本。 选择与你使用的 shell 对应的代码块——每个代码块都会将自动补全设置为持久生效,在新的 shell 会话中也能使用。 +CLI 提供适用于 **Bash**、**Zsh**、**PowerShell** 和 **Fish** 的 Tab 自动补全脚本。 选择与你所用的 shell 相匹配的代码块——每个代码块都会为后续的新 shell 会话持久安装自动补全。 ### Bash(macOS/Linux) diff --git a/localizedContent/zh/content/features/te-cli/te-cli-interactive.md b/localizedContent/zh/content/features/te-cli/te-cli-interactive.md index 1510e5943..bdde16e8b 100644 --- a/localizedContent/zh/content/features/te-cli/te-cli-interactive.md +++ b/localizedContent/zh/content/features/te-cli/te-cli-interactive.md @@ -2,7 +2,7 @@ uid: te-cli-interactive title: 交互模式 author: Peer Grønnerup -updated: 2026-05-12 +updated: 2026-06-26 applies_to: products: - product: Tabular Editor 2 @@ -29,6 +29,13 @@ te interactive ./model # Start with a local model te interactive -s MyWorkspace -d MyModel # Start with a remote model ``` +`te interactive` 提供一些用于调整会话的标志: + +- `--no-banner` - 启动时跳过欢迎横幅。 +- `--echo` - 在输出结果之前,先将每条执行的命令回显到 stdout。 在用脚本驱动 REPL 时,便于记录日志。 +- `--batch` - 非交互式批处理模式:从 stdin 逐行读取命令,依次执行,并在遇到 EOF 时退出。 当 stdin 被重定向时会自动启用。 +- `--no-batch` - 即使 stdin 被重定向,也强制使用交互式 TTY 模式(与 `--batch` 互斥)。 + 会话会打印欢迎横幅,显示当前活动模型,并将你带到一个具备模型上下文的提示符下: ![Tabular Editor CLI 交互模式会话](~/content/assets/images/features/cli/cli-interactive-mode.png) @@ -84,6 +91,85 @@ ls 'Net Sales'/'Sales Amount' # Quoted segments with a slash separator 如果想在当前会话中为单个命令禁用提示,传入 `--non-interactive`。 +## 管道与重定向输入 + +交互模式也支持通过管道传入或重定向的 stdin,因此你可以用脚本驱动同一个 REPL,而不必手动逐条输入。 每一行输入都会作为一条命令执行,就像你在提示符处输入它一样;当输入耗尽时,会话将退出(或者在读到 `exit` 这一行时退出)。 + +```bash +printf "ls\nexit\n" | te interactive ./model # bash / git-bash +te interactive ./model < script.te # redirected file +``` + +```bat +(echo ls & echo exit) | te interactive .\model :: Windows cmd.exe +``` + +以 `#` 开头的行会被视为注释并跳过,因此你可以为脚本文件添加注释: + +``` +# script.te - inspect the model, then exit +ls tables +ls measures +exit +``` + +### 批处理模式和退出代码 + +当 stdin 通过管道传入时,`--batch` 是 **默认**:会话会在第一条失败的命令处停止,并以非零退出代码退出,因此可以安全地将管道运行用作构建或 CI 步骤。 使用 `--no-batch` 即可在某条命令失败后继续运行剩余各行。 运行成功时,进程退出代码为 `0`;在批处理模式下如果命令失败,则为非零。 + +```bash +# Default when piped: stop at the first failing command, exit non-zero +printf "bpa run --fail-on error\ndeploy --force\nexit\n" | te interactive ./model + +# Run every line regardless of failures +printf "bpa run --fail-on error\ndeploy --force\nexit\n" | te interactive ./model --no-batch +``` + +### 便于阅读的执行记录 + +`--echo` 会在对应输出之前将每一行输入写入 stdout,这在捕获管道运行的执行记录时很方便。 注释行不会被回显。 + +```bash +printf "ls tables\nexit\n" | te interactive ./model --echo +``` + +### 选项 + +| 选项 | 说明 | +| ------------- | ----------------------------------------- | +| `--no-banner` | 不显示欢迎横幅。 | +| `--echo` | 将每一行输入回显到 stdout(便于生成管道运行的执行记录)。 | +| `--batch` | 在第一个命令失败时即以非零状态码退出(当 stdin 通过管道输入时为默认行为)。 | +| `--no-batch` | 即使 stdin 通过管道传入,出错后也继续执行。 | + +### 欢迎横幅与预览提示 + +会话开始时可能会出现两条不同的信息——不要把它们混为一谈: + +- **欢迎横幅** 是在 [启动会话](#starting-a-session) 中介绍的交互式欢迎界面。 可使用 `--no-banner` 隐藏它。 当 stdin 通过管道传入时,本来就不会输出欢迎横幅,因此 `--no-banner` 只有在真正的交互式(TTY)会话中才会产生可见效果。 +- **预览到期通知**(`This is an early preview release ...`)则是另一条信息。 它始终写入 **stderr**,并且**不受** `--no-banner` 影响。 可使用 `te config set hidePreviewNotice true` 隐藏它。 + +## 无参数调用时自动启动 + +在终端中不带任何参数运行 `te`,会直接进入交互式 REPL,因此探索模型就像打开 shell 后输入 `te` 一样快。 当 stdin、stdout 或 stderr 被重定向时(如管道输出、CI 流水线或脚本中),CLI 会转入常规解析流程并改为输出帮助——因此,不带子命令调用 `te` 的 shell 脚本仍会保持原有行为。 + +此行为由 `launchInteractiveMode` 配置项控制,提供三个取值: + +| 值 | 效果 | +| ---------- | ----------------------------------------------------------------------------- | +| `auto`(默认) | 仅当三个流都连接到 TTY 时才启动 REPL。 否则回退到常规解析流程。 | +| `always` | 无论流是否被重定向,都启动 REPL。 适合始终需要交互式会话的情况。 | +| `never` | 从不自动启动 REPL。 单独运行 `te` 会打印帮助,与 0.6.0 之前的行为一致。 | + +可通过以下方式全局更改: + +```bash +te config set launchInteractiveMode never # keep the classic help-on-empty behavior +te config set launchInteractiveMode auto # restore the default +``` + +你也可以通过 `TE_INTERACTIVE` 环境变量仅对单次调用进行覆盖(取值相同),或在命令行中传入 `--non-interactive`——这两种方式都会在该次调用中强制设为 `never`,因此 `te --non-interactive` 会输出帮助信息,而不是启动 REPL。 + ## 何时使用交互模式与非交互模式 - **交互模式** 最适合探索、学习 CLI、针对单个模型执行一次性批量编辑,以及演示。 diff --git a/localizedContent/zh/content/features/te-cli/te-cli-limitations.md b/localizedContent/zh/content/features/te-cli/te-cli-limitations.md index c92afb796..eeae28785 100644 --- a/localizedContent/zh/content/features/te-cli/te-cli-limitations.md +++ b/localizedContent/zh/content/features/te-cli/te-cli-limitations.md @@ -41,12 +41,12 @@ CLI 会针对你在 Tabular Editor 2 和 3 中使用的同一个 `Model` 对象 ## Best Practice Analyzer -| 限制 | 说明 / 变通方法 | -| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **BPA 规则源必须是 HTTPS URL 或本地文件路径** | 只接受 `https://` URL 和不带协议的本地文件路径。 系统能识别 `http://`,但会在加载时故意拒绝,并给出清晰的错误信息——BPA 规则是可执行的规则表达式,通过未经过身份验证的通道获取会有被篡改的风险。 其他 URL 方案(`file://`、`ftp://`、…) 不受支持。 这同时适用于 `te bpa run --rules`,以及通过 [`te config set`](xref:te-cli-commands#config-show--paths--init--set) 配置的规则列表。 | -| **规则 URL 的验证在运行阶段进行,而不是在 `te config set` 时** | 像 `http://` 这样的拼写错误会被 `te config set` 接受,只有在 BPA 实际运行时才会暴露出来。 编辑已配置的规则源后,运行一次 `te bpa run`(或 `te validate`),以验证每个 URL 都能成功加载。 | -| **`--rules` 不会禁用内置规则** | 传入 `te bpa run --rules ` 时,提供的规则会在本次调用中替换 [`bpa.rules`](xref:te-cli-commands#config-show--paths--init--set) 和 `TE_BPA_RULES` 中的条目,但内置默认规则仍会一并加载。 若只想运行显式指定的规则文件,还需传入 `--no-defaults`。 | -| **没有可在单次调用中跳过 `bpa.rules` 配置的标志** | 配置了 `bpa.rules` 后,每次执行 `te bpa run` 都会在加载内置规则的同时加载这些规则。 目前没有可在单次运行中跳过已配置规则文件的标志。 变通方法:显式传入 `--rules `——该标志会在本次调用中完全替换 `bpa.rules` 和 `TE_BPA_RULES`。 | +| 限制 | 说明 / 变通方法 | +| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **BPA 规则源必须是 HTTPS URL 或本地文件路径** | 只接受 `https://` URL 和不带协议的本地文件路径。 系统能识别 `http://`,但会在加载时故意拒绝,并给出清晰的错误信息——BPA 规则是可执行的规则表达式,通过未经过身份验证的通道获取会有被篡改的风险。 其他 URL 方案(`file://`、`ftp://`、…) 不受支持。 这既适用于 `te bpa run --rules`,也适用于通过 [`te config set`](xref:te-cli-commands#config-list--paths--init--set) 配置的规则列表。 | +| **规则 URL 的验证在运行阶段进行,而不是在 `te config set` 时** | 像 `http://` 这样的拼写错误会被 `te config set` 接受,只有在 BPA 实际运行时才会暴露出来。 编辑已配置的规则源后,运行一次 `te bpa run`(或 `te validate`),以验证每个 URL 都能成功加载。 | +| **`--rules` 不会禁用内置规则** | 当传入 `te bpa run --rules ` 时,本次运行将使用提供的规则覆盖 [`bpa.rules`](xref:te-cli-commands#config-list--paths--init--set) 和 `TE_BPA_RULES` 中的条目,但仍会同时加载内置默认规则。 若只想运行显式指定的规则文件,还需传入 `--no-defaults`。 | +| **没有可在单次调用中跳过 `bpa.rules` 配置的标志** | 配置了 `bpa.rules` 后,每次执行 `te bpa run` 都会在加载内置规则的同时加载这些规则。 目前没有可在单次运行中跳过已配置规则文件的标志。 变通方法:显式传入 `--rules `——该标志会在本次调用中完全替换 `bpa.rules` 和 `TE_BPA_RULES`。 | ## 验证 @@ -56,9 +56,9 @@ CLI 会针对你在 Tabular Editor 2 和 3 中使用的同一个 `Model` 对象 ## 模型 I/O -| 限制 | 说明 / 变通方法 | -| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`--serialization` 不能将序列化格式与 PBIP 容器组合使用** | [`te save`](xref:te-cli-commands#save) 的 `--serialization` 选项将 `bim`、`tmdl`、`te-folder` 和 `pbip` 视为互斥选项,因此目前无法为采用 TMSL 序列化 (`.bim`) 的模型生成 PBIP 容器。 将 TMDL 保存到 PBIP 中,或将 `.bim` 保存在 PBIP 容器之外。 | +| 限制 | 说明 / 变通方法 | +| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`--serialization` 不能将序列化格式与 PBIP 容器组合使用** | 在 [`te save`](xref:te-cli-commands#save) 中,`--serialization` 选项会将 `bim`、`tmdl`、`Database.json` 和 `pbip` 视为互斥,因此目前无法将采用 TMSL 序列化(`.bim`)的模型打包生成 PBIP 容器。 将 TMDL 保存到 PBIP 中,或将 `.bim` 保存在 PBIP 容器之外。 | ## 身份验证 diff --git a/localizedContent/zh/content/features/te-cli/te-cli-migrate.md b/localizedContent/zh/content/features/te-cli/te-cli-migrate.md index 939e1e9e5..77506c23b 100644 --- a/localizedContent/zh/content/features/te-cli/te-cli-migrate.md +++ b/localizedContent/zh/content/features/te-cli/te-cli-migrate.md @@ -55,34 +55,34 @@ te migrate --output-format json # Machine-readable mapping 以下是最常用标志的简要汇总,并非完整列表。 运行 `te migrate` 查看完整列表。 -| TE2 标志 | 新 CLI 等效参数 | 说明 | -| ----------------------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------- | -| `file`(位置参数) | `te ` 或使用全局参数 `--model` | 在大多数命令中,这是第一个位置参数。 | -| `server`、`database` | `te connect ` 或 `te deploy ` | Server 参数不再是全局位置参数。 | -| `-L` / `-LOCAL` | `te connect --local` | 仅限 Windows。 | -| `-S` / `-SCRIPT` | `te script -S ` 或 `-e "code"` | 支持多个脚本、内联代码和 stdin。 注意:请使用大写 `-S`;小写 `-s` 是全局 `--server` 选项。 | -| `-A` / `-ANALYZE` | `te bpa run --rules ` | 支持 `--fail-on`、`--fix` 和多个规则文件。 | -| `-AX` / `-ANALYZEX` | `te bpa run --rules `(不带 `--model-rules`) | 现在默认会排除嵌入模型的规则。 | -| `-B` / `-BIM` | `te save -o --serialization bim` | | -| `-F` / `-FOLDER` | `te save -o --serialization te-folder` | 在 `-D` 之后,TE2 的 `-F` 表示 `-FULL`;参见 `--deploy-full`。 | -| `-TMDL` | `te save -o --serialization tmdl` | TMDL 是默认保存格式。 | -| `-D` / `-DEPLOY` | `te deploy ` | 这是一个独立的命令,使用具名选项。 | -| `-O` / `-OVERWRITE` | (默认)或使用 `--create-only` 选择不覆盖 | 在新的 CLI 中,覆盖是默认行为。 | -| `-C` / `-CONNECTIONS` | `te deploy --deploy-connections` | | -| `-P` / `-PARTITIONS` 分区 | `te deploy --deploy-partitions` 部署分区 | | -| `-Y` / `-SKIPPOLICY` | `te deploy --deploy-partitions --skip-refresh-policy` 部署分区并跳过刷新策略 | 需要 `--deploy-partitions`。 | -| `-SHARED` | `te deploy --deploy-shared-expressions` | 在 `-D` 之后,TE2 的 `-S` 表示 `-SHARED`。 | -| `-R` / `-ROLES` 角色 | `te deploy --deploy-roles` | | -| `-M` / `-MEMBERS` | `te deploy --deploy-role-members` | | -| `-FULL`(在 `-D` 之后) | `te deploy --deploy-full` | 等同于:覆盖 + 连接 + 分区 + 共享 + 角色 + 角色成员。 | -| `-X` / `-XMLA ` | `te deploy ... --xmla ` | 输出到 stdout 时使用 `-`。 | -| `-V` / `-VSTS` | 在 `validate`、`bpa run` 和 `deploy` 命令中使用 `--ci vsts` | 会向 stderr 输出 `##vso[...]` 注释。 | -| `-G` / `-GITHUB` | `--ci github` | 会输出 `::error::` / `::warning::` 注释。 | -| `-T` / `-TRX ` | 在 `validate`、`bpa run` 和 `test run` 命令中使用 `--trx ` | 用于 Azure DevOps 测试发布的 VSTEST `.trx` 文件。 | -| `-W` / `-WARN` | (默认) | 部署结果中始终会 Report 警告。 | -| `-E` / `-ERR` | (默认) | 出现 DAX 错误时,部署会返回非零退出代码。 | -| `-SC` / `-SCHEMACHECK` | _尚未实现。_ | TE2 架构检查会连接到真实的数据源。 这不同于 `te validate`(DAX 语义验证,不连接数据源)。 | -| `-L` / `-LOGIN `(位于 `-D` 之后) | `te auth login -u -p -t ` | 使用服务主体或基于环境变量的凭据。 登录状态会被缓存,因此后续命令会静默获取令牌——见 @te-cli-auth。 | +| TE2 标志 | 新 CLI 等效参数 | 说明 | +| ----------------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| `file`(位置参数) | `te ` 或使用全局参数 `--model` | 在大多数命令中,这是第一个位置参数。 | +| `server`、`database` | `te connect ` 或 `te deploy -s -d ` | `server` 不再是全局位置参数;`te deploy` 仅将 `` 作为位置参数,`server` 和 `database` 需通过命名标志指定。 | +| `-L` / `-LOCAL` | `te connect --local` | 仅限 Windows。 | +| `-S` / `-SCRIPT` | `te script -S ` 或 `-e "code"` | 支持多个脚本、内联代码和 stdin。 注意:请使用大写 `-S`;小写 `-s` 是全局 `--server` 选项。 | +| `-A` / `-ANALYZE` | `te bpa run --rules ` | 支持 `--fail-on`、`--fix` 和多个规则文件。 | +| `-AX` / `-ANALYZEX` | `te bpa run --rules `(不带 `--model-rules`) | 现在默认会排除嵌入模型的规则。 | +| `-B` / `-BIM` | `te save -o --serialization bim` | | +| `-F` / `-FOLDER` | `te save -o --serialization Database.json` | 在 `-D` 之后,TE2 的 `-F` 表示 `-FULL`;参见 `--deploy-full`。 | +| `-TMDL` | `te save -o --serialization tmdl` | TMDL 是默认保存格式。 | +| `-D` / `-DEPLOY` | `te deploy -s -d ` | 已拆分为带命名选项的独立命令;只有 `` 作为位置参数。 | +| `-O` / `-OVERWRITE` | (默认)或使用 `--create-only` 选择不覆盖 | 在新的 CLI 中,覆盖是默认行为。 | +| `-C` / `-CONNECTIONS` | `te deploy --deploy-connections` | | +| `-P` / `-PARTITIONS` 分区 | `te deploy --deploy-partitions` 部署分区 | | +| `-Y` / `-SKIPPOLICY` | `te deploy --deploy-partitions --skip-refresh-policy` 部署分区并跳过刷新策略 | 需要 `--deploy-partitions`。 | +| `-SHARED` | `te deploy --deploy-shared-expressions` | 在 `-D` 之后,TE2 的 `-S` 表示 `-SHARED`。 | +| `-R` / `-ROLES` 角色 | `te deploy --deploy-roles` | | +| `-M` / `-MEMBERS` | `te deploy --deploy-role-members` | | +| `-FULL`(在 `-D` 之后) | `te deploy --deploy-full` | 等同于:覆盖 + 连接 + 分区 + 共享 + 角色 + 角色成员。 | +| `-X` / `-XMLA ` | `te deploy ... --xmla ` | 输出到 stdout 时使用 `-`。 | +| `-V` / `-VSTS` | 在 `validate`、`bpa run` 和 `deploy` 命令中使用 `--ci vsts` | 会向 stderr 输出 `##vso[...]` 注释。 | +| `-G` / `-GITHUB` | `--ci github` | 会输出 `::error::` / `::warning::` 注释。 | +| `-T` / `-TRX ` | 在 `validate`、`bpa run` 和 `test run` 命令中使用 `--trx ` | 用于 Azure DevOps 测试发布的 VSTEST `.trx` 文件。 | +| `-W` / `-WARN` | (默认) | 部署结果中始终会 Report 警告。 | +| `-E` / `-ERR` | (默认) | 出现 DAX 错误时,部署会返回非零退出代码。 | +| `-SC` / `-SCHEMACHECK` | _尚未实现。_ | TE2 架构检查会连接到真实的数据源。 这不同于 `te validate`(DAX 语义验证,不连接数据源)。 | +| `-L` / `-LOGIN `(位于 `-D` 之后) | `te auth login -u -p -t ` | 使用服务主体或基于环境变量的凭据。 登录状态会被缓存,因此后续命令会静默获取令牌——见 @te-cli-auth。 | ## 迁移指南 diff --git a/localizedContent/zh/content/features/te-cli/te-cli.md b/localizedContent/zh/content/features/te-cli/te-cli.md index e7fbceae3..0de2d21d2 100644 --- a/localizedContent/zh/content/features/te-cli/te-cli.md +++ b/localizedContent/zh/content/features/te-cli/te-cli.md @@ -27,9 +27,9 @@ Tabular Editor CLI (`te`) 是适用于 Power BI 和 Analysis Services 语义模 每个命令都围绕三大设计支柱构建: -- **结构化输出** — 除默认的可读文本外,还支持 JSON、CSV、TMDL 和 TMSL。 -- **非交互模式** — 全局 `--non-interactive` 标志可禁用提示并快速失败。 -- **清晰的错误信息** — 写入 stderr,并使用可预测的退出代码。 +- **结构化输出** — 除默认的易读文本外,还可输出 JSON、CSV、TMDL 和 TMSL。 +- **非交互模式** — 全局 `--non-interactive` 标志会禁用交互提示,并在出错时快速失败。 +- **清晰的错误信息** — 写入 stderr,并返回可预测的退出码。 这三者结合起来,让同一个二进制文件能够很好地服务于三类截然不同的用户: @@ -49,31 +49,34 @@ CLI 将 50 多个命令划分为 10 个类别。 每个命令族都对应语义 | 命令族 | 功能 | 示例命令 | | ------------------------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [模型 I/O](xref:te-cli-commands#model-io) | 加载、保存、转换和初始化模型 | [`te load`](xref:te-cli-commands#load)、[`te save`](xref:te-cli-commands#save)、[`te init`](xref:te-cli-commands#init) | -| [模型编辑](xref:te-cli-commands#model-editing) | 获取/设置属性,添加/删除/移动对象 | [`te set`](xref:te-cli-commands#set)、[`te add`](xref:te-cli-commands#add)、[`te rm`](xref:te-cli-commands#rm)、[`te mv`](xref:te-cli-commands#mv) | -| [检视](xref:te-cli-commands#inspection) | 列出对象、搜索、比较差异、分析依赖关系 | [`te ls`](xref:te-cli-commands#ls)、[`te find`](xref:te-cli-commands#find)、[`te diff`](xref:te-cli-commands#diff)、[`te deps`](xref:te-cli-commands#deps) | +| [模型编辑](xref:te-cli-commands#model-editing) | 获取/设置属性,添加/删除/移动对象 | [`te set`](xref:te-cli-commands#set)、[`te add`](xref:te-cli-commands#add)、[`te remove`](xref:te-cli-commands#remove)、[`te move`](xref:te-cli-commands#move) | +| [检视](xref:te-cli-commands#inspection) | 列出对象、搜索、比较差异、分析依赖关系 | [`te list`](xref:te-cli-commands#list)、[`te find`](xref:te-cli-commands#find)、[`te diff`](xref:te-cli-commands#diff)、[`te deps`](xref:te-cli-commands#deps) | | [分析与质量](xref:te-cli-commands#analysis-and-quality) | 验证、运行 BPA、格式化 DAX、分析存储 | [`te validate`](xref:te-cli-commands#validate)、[`te bpa run`](xref:te-cli-commands#bpa-run)、[`te format`](xref:te-cli-commands#format)、[`te vertipaq`](xref:te-cli-commands#vertipaq) | | [执行](xref:te-cli-commands#execution) | 运行 DAX 查询、C# Script 和宏 | [`te query`](xref:te-cli-commands#query), [`te script`](xref:te-cli-commands#script), [`te 宏`](xref:te-cli-commands#macro) | | [部署与刷新](xref:te-cli-commands#deployment-and-refresh) | 部署到 Workspace、触发刷新、执行增量刷新 | [`te deploy`](xref:te-cli-commands#deploy)、[`te refresh`](xref:te-cli-commands#refresh)、[`te incremental-refresh`](xref:te-cli-commands#incremental-refresh) | | [测试](xref:te-cli-commands#testing) | 断言测试、快照、A/B 比较 | [`te test run`](xref:te-cli-commands#test-run) | | [连接与身份验证](xref:te-cli-commands#connection-and-authentication) | 连接到 Workspace,管理身份验证和配置文件 | [`te connect`](xref:te-cli-commands#connect), [`te auth`](xref:te-cli-commands#auth-login--status--logout), [`te profile`](xref:te-cli-commands#profile-list--show--set--remove) | -| [配置](xref:te-cli-commands#configuration) | 设置与许可 | [`te config`](xref:te-cli-commands#config-show--paths--init--set) | +| [配置](xref:te-cli-commands#configuration) | 设置与许可 | [`te config`](xref:te-cli-commands#config-list--paths--init--set) | | [Shell](xref:te-cli-commands#shell) | 交互模式、会话状态、Shell 自动补全 | [`te interactive`](xref:te-cli-commands#interactive), [`te session`](xref:te-cli-commands#session), [`te completion`](xref:te-cli-commands#completion) | +> [!TIP] +> 文档中使用规范的长形式动词(`list`、`remove`、`move`),但传统的短形式仍可作为别名使用(`ls`、`rm`、`mv`、`rename`)。 这既适用于顶层命令,也适用于 `te bpa rules`、`te macro`、`te config`、`te profile`、`te session` 和 `te test` 等命令组下的 `remove` / `list` 子命令。 完整映射请参见 @te-cli-commands#command-aliases。 + ## 开始使用 1. **注册或登录**:前往 [tabulareditor.com](https://tabulareditor.com/download-tabular-editor-cli) 注册 Tabular Editor 帐户或登录。 2. **下载并安装**:Windows、macOS 和 Linux 的说明见 @te-cli-install。 3. **进行身份验证**:运行 `te auth login`,即可连接到 Power BI 或 Fabric。 见 @te-cli-auth。 -4. **运行第一个命令**:`te --help` 会列出所有命令;`te --help` 会显示详细选项。 +4. **运行第一个命令**:`te --help` 会列出所有命令;`te --help` 会显示详细选项。 提示:在终端中单独运行 `te` 会进入交互式 REPL,这是探索模型的一种更友好的方式。 参见 @te-cli-interactive。 初次查看实时模型只需两条命令: ```bash te auth login -te ls -s MyWorkspace -d MyModel +te list -s MyWorkspace -d MyModel ``` -![Tabular Editor CLI te ls 示例输出](~/content/assets/images/features/cli/cli-command-ls.png) +![Tabular Editor CLI te list 命令示例输出](~/content/assets/images/features/cli/cli-command-ls.png) ## 预览提示 diff --git a/localizedContent/zh/content/features/views/tom-explorer-view.md b/localizedContent/zh/content/features/views/tom-explorer-view.md index b3133e8bd..bd77b6a4a 100644 --- a/localizedContent/zh/content/features/views/tom-explorer-view.md +++ b/localizedContent/zh/content/features/views/tom-explorer-view.md @@ -2,7 +2,7 @@ uid: tom-explorer-view title: TOM Explorer 视图 author: Morten Lønskov -updated: 2026-03-19 +updated: 2026-06-24 applies_to: products: - product: Tabular Editor 2 @@ -51,8 +51,8 @@ TOM Explorer 由两个主要区域组成:第一部分是 Data model 对象, - **创建**: 展开为子菜单,可在所选对象下创建新的度量值、列、层次结构、显示文件夹或计算项。 可用选项取决于所选的对象类型。 -- **移至组**: - 可将该表归入 TOM Explorer 中的表格组,便于浏览模型。 此选项仅适用于表。 +- **移至组**: + 展开为子菜单,用于将所选表整理到表格组中,方便在模型中导航。 该子菜单会列出现有的表格组、用于根据所选表创建新组并打开其名称编辑器的 **(New...)** 条目,以及用于移除表格组分配的 **(None)** 条目。 此选项仅适用于表。 - **设为不可见**: 将对象标记为在客户端工具中不可见。 该表仍是模型的一部分,但会对 Report 作者隐藏。 也可以使用快捷键 **Ctrl+I** 隐藏该对象。 diff --git a/localizedContent/zh/content/getting-started/github-flow.md b/localizedContent/zh/content/getting-started/github-flow.md new file mode 100644 index 000000000..978522983 --- /dev/null +++ b/localizedContent/zh/content/getting-started/github-flow.md @@ -0,0 +1,310 @@ +--- +uid: github-flow +title: GitHub Flow 与 Octopus Merge 合并模式 +author: Just Blindbæk +updated: 2026-07-03 +applies_to: + products: + - product: Tabular Editor 2 + full: true + - product: Tabular Editor 3 + editions: + - edition: Desktop + none: true + - edition: Business + full: true + - edition: Enterprise + full: true +--- + +# GitHub Flow 与 Octopus Merge 合并模式 + +本文介绍在[使用 Git 和保存到文件夹启用并行开发](xref:parallel-development)中推荐的日常 **GitHub Flow** 工作流,以及支持该流程的 **Octopus Merge** 模式:用于让共享测试环境持续与当前所有进行中的工作保持同步。 本文后半部分将逐步介绍一个实现这一点的完整参考管道——正如你将看到的,它涵盖的内容远不止合并步骤本身。 + +## GitHub Flow 的日常实践 + +GitHub Flow 的规则很简单——`main` 始终可部署,所有工作都在从 `main` 分出的短期分支上进行——但对语义模型团队来说,有几个细节值得明确说明。 + +**创建功能分支:** + +```cmd +git checkout main +git pull +git checkout -b feature/add-tax-calculation +``` + +**本地开发。** 开发者在 Tabular Editor 3 中进行开发。 开发者每次按下 **Ctrl+S** 时,都会发生两件事: + +- 模型元数据会以 [保存到文件夹 (Database.json) 格式](xref:parallel-development#what-is-save-to-folder) 保存到磁盘,可直接在 Git 中暂存并提交到功能分支。 +- 如果启用了 [工作区模式](xref:workspace-mode),模型会同时同步到共享开发 Workspace 中你个人的 Workspace 数据库——这样你既可以在 Tabular Editor 中进行实时测试,也可以让 Power BI Desktop [直接连接到 Workspace 数据库](xref:workspace-mode#advantages-of-workspace-mode) 进行 Report 端验证。 + +```cmd +git add . +git commit -m "Add tax calculation measure and supporting columns" +git push +``` + +> [!WARNING] +> 不要在托管 Workspace 数据库的 Fabric Workspace 上启用 Fabric Git 集成。 Tabular Editor 通过 XMLA endpoint 直接写入 Workspace 数据库,这些写入与 Git 分支没有任何关系——在同一个 Workspace 中启用 Git 集成,会对同一个数据库产生相互冲突、绕过流程的更改。 这一点也在[工作区模式文档](xref:workspace-mode)中有所说明。 + +**发起拉取请求。** 当开发者准备好进行更广泛的测试时,会创建一个以 `main` 为目标分支的拉取请求。 这也是 GitHub Flow 单独使用时给 BI 团队留下的一个问题:当多个开发者同时都提交了尚未关闭的拉取请求时,共享测试环境到底应该反映什么状态? 这正是 Octopus Merge 要回答的问题——见下文。 + +**批准并合并。** 一旦技术和业务审核人员都基于共享测试环境完成签核,功能分支就会合并到 `main` 并删除。 + +**部署到 UAT / 生产环境。** 要么每次合并到 `main` 都自动触发部署,要么累积若干次合并后按固定节奏部署(例如每周一次)。 两种方式都与 GitHub Flow 兼容——无论采用哪种方式,分支结构都相同,区别只在于发布触发方式。 + +## Octopus Merge:让测试环境保持实时更新 + +##### 注:命名消歧 + +“Octopus merge” 在 Git 生态中用来指代三个彼此相关但又不同的概念。 这里有必要明确我们指的是哪一种: + +1. **Git 原生的 octopus merge 策略**——当你运行 `git merge branch-a branch-b branch-c` 时,Git 会自动使用这种合并策略,将两个以上的分支头合并为一个合并提交,_前提是没有冲突_。 只要有任一分支与当前合并发生冲突,整个命令就会失败——Git 不会尝试在两个以上分支之间解决或隔离冲突。 这是 Git 的底层机制,不是工作流。 +2. **`lesfurets/git-octopus`**——一个现已归档的开源命令行工具,它将这种原生策略封装成了“持续合并”工作流:按命名模式匹配出一组分支,将它们合并,把结果推送到一个临时分支,并在每次推送时重复这一过程。 它还包含了逐个遍历分支的工具,用来精确找出是哪一个分支导致了冲突。 这个工具本身已不再维护,也不是我们建议直接采用的方案;但它开创的工作流,正是下文要介绍的模式。 +3. **本文所说的 Octopus Merge 模式**——一个自定义 CI/CD 管道,用于发现当前所有目标分支为 `main`、处于打开状态(非草稿)的拉取请求,使用 (1) 中 Git 原生的 octopus 策略将它们的源分支合并在一起,把结果推送到一个临时分支,并将该分支部署到共享测试环境。 这个模式与 (2) 的思路相同,但它被重新实现为由你自己掌控的管道脚本——例如 GitHub Actions 工作流,或调用 Azure DevOps REST API 的 Azure Pipelines 脚本——而不是一个独立的第三方工具。 + +本文提到“Octopus Merge”时,指的是 (3)。 注意,(3) _使用_ (1) 中的原生策略作为实际的合并机制——它增加的价值在于围绕这次合并的自动化和分支生命周期管理,而不是提供另一种合并方式。 + +简而言之,这个模式就是:**你的测试环境始终反映当前所有进行中工作的组合**——而不是只单独反映某一个功能。 每当开发者向任一打开的、非草稿的拉取请求推送代码时,管道都会从头重新构建这个组合分支,并重新部署它。 + +```mermaid +flowchart LR + main(["main"]) -.->|"deleted + recreated"| temp + prA["feature/A
(open PR)"] --> temp + prB["feature/B
(open PR)"] --> temp + prC["feature/C
(draft PR — excluded)"]:::draft + temp[["♻️ octopus/temp
deleted + recreated from main
on every run"]] --> test(["Test workspace"]) + + classDef draft stroke-dasharray: 5 5,opacity:0.55; +``` + +> [!NOTE] +> Tabular Editor 现已提供跨平台 CLI(`te`),目前处于有限公开预览阶段,专为 CI/CD 场景打造——支持非交互模式、原生 GitHub Actions/Azure DevOps 注解、VSTEST 输出,以及用于在管道中运行回归测试的 `te test run` 命令。 它与下文描述的这类管道天然契合,值得关注。 在撰写本文时,Tabular Editor 自身的文档仍建议不要在预览期间将其用于生产管道(文档说明该预览版本会于 2026-09-30 过期),因此本文中的参考实现改用已成熟的 `TabularEditor.exe` CLI。 有关这个新 CLI 当前具备的能力和示例,可以查看 [CI/CD 集成](xref:te-cli-cicd)。 + + + +## 参考实现 + +下面给出的是一个完整且可运行的管道,实现了 Octopus Merge——但需要先说明,它做的事情远不止合并这一步。 一次完整运行还会下载 Tabular Editor,并在把合并后的模型部署到共享测试工作区之前,按你的最佳实践规则和线上数据源架构对其进行验证。 Octopus Merge 是 5 个作业中的第 1 个;其余部分是一个通用的语义模型 CI/CD 管道,只是恰好以 Octopus Merge 的输出作为输入。 至于在该模型之上部署报表——这是另一个议题,且会因组织而异——文末会简要说明。 + +下面的示例分别展示了用于合并作业的 **Azure Pipelines**(调用 Azure DevOps REST API)和 **GitHub Actions**(调用 GitHub REST API),因为这两个平台的主要差异在于它们如何进行身份验证以及如何查询拉取请求——无论使用哪一种,底层的 Git 操作和 Tabular Editor CLI 调用都是相同的。 + +### 管道概览 + +一次完整运行由多个作业组成,每个作业都明确依赖于前面的作业: + +```mermaid +flowchart TD + merge["Octopus merge"] --> dl["Download Tabular Editor"] + dl --> bpa["BPA verification"] + dl --> schema["Schema validation"] + bpa --> deploy["Model deployment"] + schema --> deploy +``` + +把每个阶段作为独立作业运行,而不是写成一段很长的脚本——这样就能为每个关注点提供独立的通过/失败信号(合并冲突、BPA 违规、架构漂移、部署失败等),从而在运行失败时更快定位究竟哪里出了问题。 + +##### 注意:管道代理要求 + +由于 `TabularEditor.exe` 只能在 Windows 上运行,因此每个调用它的作业都需要基于 Windows 的代理/运行器,这包括 BPA 验证、架构验证和模型部署作业。 只要云托管的 Windows 代理能通过网络访问你的测试 Workspace 和数据源,就没问题;只有当这些端点无法从外网访问时(例如本地数据源),才需要自托管代理。 Octopus 合并作业本身没有这个限制,因为它只需要 Git。 + +### 触发管道 + +该管道不会由常规的 Git 推送触发器触发。 由于它需要合并当前 _所有_ 打开的拉取请求——而不只是发生变更的那一个——因此通常不配置自动分支触发器,而是通过以下两种方式之一调用: + +- **通过拉取请求管道或分支策略**,这样每当创建指向 `main` 的拉取请求时,或向任何存在打开拉取请求的分支推送新提交时,它都会运行。 +- **按计划运行**(例如每隔几分钟一次),如果你的 CI/CD 平台不便直接配置“在任意打开的拉取请求分支更新时运行”,这会是一个更简单的替代方案。 + +两种方式的效果相同:只要向任何存在打开拉取请求的分支推送,组合测试环境就会重新构建。 + +### 作业 1:Octopus 合并 + +这个作业负责发现当前所有打开的拉取请求,将它们合并在一起,并将结果发布到一个临时分支。 + +**具体步骤如下:** + +1. **进行身份验证并查询拉取请求。** 该作业会调用源代码管理平台的 REST API,获取所有以 `main` 为目标的打开拉取请求,并使用具有列出拉取请求权限的令牌进行身份验证(包括草稿拉取请求——过滤会在下一步进行,而不是在 API 层完成)。 +2. **筛选出非草稿拉取请求。** 草稿拉取请求会被排除——这样开发者就可以推送尚在开发中的提交,而不会将其纳入共享测试构建。 只有当 PR 被标记为“准备好评审”时,才会参与合并。 +3. **全新克隆 repository。** 该作业不会复用之前的检出结果,而是在每次运行时都从头克隆 repository,并使用管道自身的访问令牌进行身份验证。 这可确保合并始终从干净、已知的状态开始。 +4. **删除并重新创建临时分支。** 临时输出分支(例如 `octopus/temp`)的远程和本地副本如果存在,都会被强制删除,然后再从 `main` 全新创建。 这个分支绝不会在不同运行之间快进或复用——每次都会从头重建。 +5. **用一条命令合并所有符合条件的拉取请求分支。** 向 `git merge` 传入两个以上的分支时,会自动调用 Git 的原生 octopus 合并策略——这正是这种模式使用上文所述底层 Git 机制的地方。 +6. **推送结果**,如果合并成功。 + +**Azure Pipelines**,调用 Azure DevOps REST API: + +```yaml +- task: PowerShell@2 + displayName: Git octopus merge + inputs: + targetType: 'inline' + script: | + $prs = Invoke-RestMethod -Uri "https://dev.azure.com/$(Org)/$(Project)/_apis/git/repositories/$(Repo)/pullrequests?api-version=7.0" ` + -Headers @{ Authorization = "Bearer $(System.AccessToken)" } + $branches = $prs.value | Where-Object { $_.isDraft -eq $false -and $_.targetRefName -eq "refs/heads/main" } | + ForEach-Object { $_.sourceRefName -replace 'refs/heads', 'origin' } + + git clone $(Build.Repository.Uri) repo --quiet + cd repo + git checkout main --quiet + git push origin --delete octopus/temp --quiet 2>$null + git checkout -b octopus/temp --quiet + if ($branches.Count -gt 0) { + git merge --quiet $branches + } + git push --set-upstream origin octopus/temp --quiet +``` + +**GitHub Actions**,通过 `gh` CLI 调用 GitHub REST API: + +```yaml +- name: Git octopus merge + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + branches=$(gh pr list --base main --state open --json isDraft,headRefName \ + --jq '.[] | select(.isDraft == false) | .headRefName') + + git clone "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" repo --quiet + cd repo + git checkout main --quiet + git push origin --delete octopus/temp --quiet || true + git checkout -b octopus/temp --quiet + if [ -n "$branches" ]; then + git merge --quiet $(echo "$branches" | sed 's/^/origin\//') + fi + git push --set-upstream origin octopus/temp --quiet +``` + +两个版本做的都是同一件事:列出所有面向 `main` 的开放且非草稿 PR,将它们解析为分支引用,然后把它们合并到一个重新创建的 `octopus/temp` 分支中。 + +**处理合并失败:** + +如果合并失败——最可能的原因是两个或多个开放的拉取请求之间发生冲突——不要只是记录错误然后停止。 更稳妥的实现方式应当先重置工作目录,并在这个临时分支上推送一个**空占位提交**,然后再让流水线运行失败: + +``` +git reset --hard --quiet +git checkout main --quiet +git branch -D octopus/temp --quiet +git checkout -b octopus/temp --quiet +git config user.email "octopus-merge@users.noreply.github.com" +git config user.name "Octopus Merge" +git commit --allow-empty -m "init" --quiet +git push origin octopus/temp --quiet +``` + +这一步很重要,因为下游作业(BPA 验证、架构验证、部署)可能依赖这个临时分支以某种明确定义的状态存在。 如果没有这一步,合并失败可能会导致该分支缺失,或处于合并到一半的状态,从而让后续作业出现令人困惑的连带故障,而不是在合并步骤中报出一个明确的错误。 + +##### 注意——如何诊断是哪个分支引发了冲突 + +这种模式的直接实现方式不会自动识别到底是哪一个拉取请求导致了合并冲突——它只会 Report:合并失败。 与已归档的 `lesfurets/git-octopus` 工具相比,这是一个真实存在的限制;后者内置了逐个遍历分支以定位问题来源的工具。 在实践中,大多数团队都会手动处理:先暂时撤下你怀疑有问题的拉取请求(改回草稿状态,或直接关闭),然后反复运行流水线,直到合并再次成功,以缩小范围并找出是哪个分支导致了问题。 如果这种反复试错的过程成了团队的瓶颈,那就值得在你自己的流水线中加入一个自动逐个排查的步骤。 + +### 作业 2:下载 Tabular Editor + +由于后续作业需要调用 Tabular Editor CLI,而又不能假定构建代理已预装它,因此会有一个单独的作业在每次运行开始时下载一份 Tabular Editor 便携版: + +- 直接获取最新发布版本(例如从 Tabular Editor 的 GitHub Releases)。 +- 将其解压,并删除下载的压缩包。 +- 让解压得到的 `TabularEditor.exe` 可供同一代理/运行器上的后续作业使用。 + +每次运行都重新下载最新版本,可以让流水线自动保持最新,而不必跟踪并更新锁定的版本号——不过,如果你的团队更看重确定性和可复现的构建,那么将版本锁定到某个特定发布版,并有计划地进行更新,也值得作为替代方案考虑。 + +### 作业 3:BPA 验证 + +这个作业会对合并产出的每个语义模型运行 Tabular Editor 的 [Best Practices Analyzer](xref:best-practice-analyzer),并根据团队统一的质量规则进行验证。 + +如果你的 repository 中包含多个语义模型——这在服务多个业务领域的 BI 团队中很常见——那么每个模型通常都会位于各自的子文件夹中,而这个作业会依次遍历它们: + +``` +TabularEditor.exe "" -A "" -V +``` + +- `-A` 会让 Tabular Editor 使用指定的 BPA 规则文件进行检查。 +- `-V` 会验证模型,并生成 Report 以输出结果。 + +> [!NOTE] +> 先决定好:BPA 违规是要让流水线 **失败**,还是只给出 **警告**。 当规则集仍在调优时,先从警告模式开始确实很诱人;但如果长期保持这样,违规项可能会在从未阻止部署的情况下悄然累积。 应将仅警告的 BPA 步骤视为过渡状态,后续要“升级”退出,而不是永久配置。 + +### 作业 4:架构验证 + +这个作业会将每个模型的预期架构与其真实在线的数据源进行比较——例如,它能在列被重命名或缺失导致测试环境刷新失败之前发现问题。 + +``` +TabularEditor.exe "" -S ".cs" -SC -V -W +``` + +- `-S` 会运行一个 C# Script 来设置模型的数据源连接字符串——通常从管道环境变量或机密中读取,从而无需将真实连接信息提交到源代码管理中。 +- `-SC` 会执行架构检查,将模型的元数据与实时数据源进行比较。 +- `-V -W` 用于验证结果并控制警告的处理方式。 + +如果你的模型依赖于数据库对象,而这些对象本身也是作为管道的一部分部署的——例如从源代码管理发布的 SQL 视图——要确保该部署步骤在架构验证之前运行,这样检查针对的就是所有内容都部署到测试环境后模型实际会看到的那些对象。 如果这两个作业是彼此独立编写的,这种顺序依赖关系就很容易被忽略。 + +> [!NOTE] +> 部署上游数据对象(SQL 视图及其他数据库工件)的具体机制因组织的数据平台而异,也不属于 Octopus Merge 模式本身的一部分。 对这一模式来说,唯一重要的是:架构验证必须在你的数据源处于测试环境预期状态之后进行——至于由什么来让它达到这个状态,由你决定。 + +### 作业 5:模型部署 + +当 BPA 验证和架构验证都成功后,此作业会将合并后的模型部署到共享测试 Workspace,使用 Tabular Editor 的“保存到文件夹”(`Database.json`)格式,并通过 XMLA endpoint 直接部署: + +``` +TabularEditor.exe "\database.json" -D "Provider=MSOLAP;Data Source=;User ID=app:@;Password=;LocaleIdentifier=1033" "" -O -P -R -W -V -E +``` + +有几点值得特别说明: + +- 身份验证通过 **服务主体**(Azure AD 应用注册)完成,而不是用户账户——这更适合无人值守的管道,也避免了必须在管道机密中保存真实用户凭据。 +- 传递给 Tabular Editor 的模型名称通常与文件夹名称一致,因此包含多个模型的 repository 会将每个模型部署到名称对应的 Dataset。 +- `-O -P -R -W -V -E` 标志分别涵盖覆盖、处理、角色、警告、验证和错误处理——如果你需要根据自己的设置调整其中任何一项,请参阅 [Tabular Editor CLI 参考](xref:command-line-options) 获取完整的标志列表。 + +> [!NOTE] +> 业务评审人员在共享测试环境中签署确认时,验证的是 Report,而不是原始的 XMLA 连接——实际上,在他们签署之前,仍需要有步骤部署 Power BI Report 并将其绑定到刚部署的测试模型上(并可选择更新任何已发布的 Power BI Apps)。 每次运行是重新部署所有 Report,还是只部署受本次更改影响的 Report,这类决策因组织而异,本文不作展开——该部分管道请参阅 @powerbi-cicd。 + +### 完整工作流图 + +```mermaid +flowchart TB + subgraph dev["Developer"] + direction LR + d1["Create feature branch"] --> d2["Local commits"] --> d3["Open PR
(ready for review)"] + end + + subgraph ci["CI Pipeline"] + direction LR + c1["Octopus merge"] -->|"success"| c2["Download Tabular Editor"] + c1 -.->|"conflict"| c1f["Empty placeholder commit"] + c2 --> c3["BPA verification"] + c2 --> c4["Schema validation"] + c3 --> c5["Model deployment"] + c4 --> c5 + end + + subgraph review["Review"] + direction LR + r1["Technical sign-off"] --> r2["Business sign-off"] + end + + subgraph release["Release"] + direction LR + rel1["Merge to main"] --> rel2["Deploy to UAT / production"] + end + + d3 --> c1 + c5 --> r1 + r2 --> rel1 + c1f -.->|"developer resolves conflict, pushes fix"| d2 + r1 -.->|"changes requested, developer pushes fix"| d2 +``` + +## 关键原则 + +- `main` 始终处于可部署状态;功能分支生命周期短,且彼此独立。 +- 一次性分支会在每次运行时删除,并基于 `main` 重新创建——绝不会 fast-forward,也不会复用。 +- 合并失败后,一次性分支应保持在明确的状态中(即使是空的),而不是处于缺失或半合并状态。 +- 每个验证阶段(BPA、架构)都应是管道中独立的作业,拥有各自的通过/失败信号,而不是揉进一个脚本里。 +- 组织特定的步骤(例如部署 SQL 视图)应与通用模式清晰区分开,无论在管道代码中还是在内部文档里都应如此——这样即使需要将该模式应用到其他项目,它也仍然便于移植。 + +## 后续步骤 + +- [借助 Git 和“保存到文件夹”实现并行开发](xref:parallel-development)——本管道所支持的分支策略。 +- [CI/CD 集成](xref:te-cli-cicd)——新版 Tabular Editor CLI 的 CI/CD 模式,目前处于有限公开预览阶段。 +- @powerbi-cicd +- @as-cicd diff --git a/localizedContent/zh/content/getting-started/parallel-development.md b/localizedContent/zh/content/getting-started/parallel-development.md index 36d00368e..89c7d7a66 100644 --- a/localizedContent/zh/content/getting-started/parallel-development.md +++ b/localizedContent/zh/content/getting-started/parallel-development.md @@ -2,7 +2,7 @@ uid: parallel-development title: 通过 Git 和“保存到文件夹”实现并行开发 author: Daniel Otykier -updated: 2026-06-11 +updated: 2026-07-03 applies_to: products: - product: Tabular Editor 2 @@ -127,26 +127,66 @@ Git 是一个免费开源的分布式版本控制系统,旨在以高速高效 分支策略会决定日常开发工作流的具体方式;很多时候,分支还会与团队采用的项目管理方法直接对应。 例如,在 Azure DevOps 中使用[敏捷过程](https://docs.microsoft.com/en-us/azure/devops/boards/work-items/guidance/agile-process-workflow?view=azure-devops)时,你的待办项通常由 **史诗**、**功能**、**用户故事**、**任务** 和 **缺陷** 组成。 -在敏捷术语中,**用户故事** 是一项可交付、可测试的工作成果。 一个用户故事可能由多个 **任务** 组成,这些是更小的工作单元,通常由开发人员在用户故事交付之前完成。 在理想情况下,所有用户故事都已拆分为易于管理的任务,每个任务只需几个小时即可完成,整个用户故事累计也不超过几天。 这样,用户故事就非常适合作为所谓的主题分支,开发人员可以针对用户故事中的每个任务进行一次或多次提交。 当所有任务完成后,你就可以把用户故事交付给客户。这时,你会把主题分支合并到交付分支(例如“Test”分支),并将代码部署到测试环境。 +在敏捷术语中,**用户故事** 是一项可交付、可测试的工作成果。 一个用户故事可能由多个**任务**组成——这些是开发人员在交付用户故事之前需要完成的较小工作项。 理想情况下,所有用户故事都会被拆分为可管理的任务,每个任务只需几小时即可完成,整个用户故事合计也不超过几天。 因此,用户故事非常适合使用短生命周期的功能分支:开发人员可以针对每个任务进行一次或多次提交,然后再合并分支并将代码部署到测试环境。 -选择合适的分支策略取决于很多因素。 通常,Microsoft 推荐采用 [Trunk-based Development](https://docs.microsoft.com/en-us/azure/devops/repos/git/git-branching-guidance?view=azure-devops)([视频](https://youtu.be/t_4lLR6F_yk?t=232))策略,以敏捷方式持续交付小步增量。 其核心思路是:每新增一个功能或修复一个缺陷,都从“Main”分支拉出一个分支(见下图)。 通过从功能分支向 Main 发起拉取请求来强制执行代码评审流程;并利用 Azure DevOps 的分支策略功能,我们可以配置规则,要求代码在拉取请求完成前必须成功通过构建且无错误。 +合适的分支策略取决于许多不同因素:团队规模、发布节奏、监管约束、你维护的语义模型数量,以及现有 CI/CD 设置的成熟度。 本文介绍三种策略: -![主干开发](~/content/assets/images/trunk-based-development.png) +- **[GitHub Flow + Octopus Merge](#github-flow--octopus-merge)**——我们为大多数语义模型团队推荐的方法,也是本文重点介绍的方案。 +- **[GitFlow](#gitflow-branching-and-deployment-environments)**——这是一个可行的替代方案,尤其适合有正式且不频繁的发布周期,或有监管签核要求的团队。 +- **[纯主干开发](#trunk-based-development)**——这是最简单的方法。即使大多数 BI 团队最终会需要 GitHub Flow 提供的额外结构,也值得先将其作为基线来理解。 + +> [!NOTE] +> Tabular Editor 不依赖任何分支策略。 无论你选择下面哪种策略,“保存到文件夹”和“工作区模式 Workspace”的工作方式都完全相同——本文的建议基于我们在企业项目中看到的成功模式,而不是工具本身施加的限制。 + +## GitHub Flow + Octopus Merge + +对于使用 Tabular Editor 和 Power BI 构建语义模型的团队,我们建议采用 **[GitHub Flow](https://docs.github.com/en/get-started/using-github/github-flow)**,并结合 **Octopus Merge** 模式来进行持续集成测试。 + +GitHub Flow 是一种轻量级分支模型,只有一条硬性规则:**`main` 始终可部署。** 所有工作都在从 `main` 创建的短生命周期功能分支上进行;没有人直接向 `main` 提交;在审查和自动检查通过后,通过拉取请求将分支合并回 `main`。 与 GitFlow 不同,它没有 `develop` 分支,也不会为每个环境单独设分支——环境推进(dev → test → UAT → production)由部署管道处理,而不是依赖长期存在的分支。 + +```mermaid +gitGraph + commit id: "初始提交" + branch "feature/add-tax-calculation" + commit id: "新增度量值" + commit id: "新增列" + checkout main + merge "feature/add-tax-calculation" id: "PR 已合并:税额计算" + branch "feature/fix-rls" + commit id: "修复角色" + checkout main + merge "feature/fix-rls" id: "PR 已合并:修复 RLS" + branch "feature/new-report-page" + commit id: "进行中" + checkout main + commit id: "热修复" + merge "feature/new-report-page" id: "PR 已合并:新 Report 页面" +``` -### 主干开发 +`main` 始终保持为单一主线,并且随时可部署;短生命周期的功能分支从它分出,再通过拉取请求直接合并回来。 对比本页后面的 GitFlow 图示,后者有五条并行的长期分支线。 -不过,出于多种原因,这种策略在商业智能开发团队中可能并不现实: +仅靠 GitHub Flow 本身,还无法回答一个 BI 团队特有的问题:当多个开发人员各自都有未合并的拉取请求时,共享测试环境在任意时刻反映的到底是什么? **Octopus Merge** 给出了答案:CI 管道会持续将当前所有尚未合并的拉取请求合并到一个临时分支中,并将结果部署到共享测试环境——这样业务用户始终验证的是所有进行中工作的组合结果,而不是彼此隔离的单个功能。 有关这一模式如何工作以及如何构建,请参阅 [GitHub Flow and the Octopus Merge pattern](xref:github-flow)。 -- 新功能往往需要业务用户进行较长时间的测试与验证,可能需要数周才能完成。 因此,你很可能需要一个面向用户的测试环境。 -- BI 解决方案通常采用多层架构,一般包括带 ETL 的 Data Warehouse 层、主数据管理层、语义层以及 Report。 这些层之间存在依赖关系,进一步增加了测试和部署的复杂度。 -- BI 团队可能需要负责开发和维护多个不同的语义模型,分别服务于不同的业务领域(销售、库存、物流、财务、人力资源等),其成熟度各不相同,开发节奏也不一致。 -- BI 解决方案最重要的部分是数据! 作为 BI 开发人员,你无法像传统软件开发那样:从源代码管理中签出代码,按下 F5,然后在几分钟的编译时间内就让完整解决方案跑起来。 你的解决方案离不开数据,而这些数据必须经过多层的加载、ETL 或处理,才能交付给最终用户。 把数据纳入 DevOps 工作流,可能会把构建和部署时间从几分钟拉长到几小时,甚至几天。 在某些场景下,由于资源或成本限制,甚至可能根本做不到。 +这种组合尤其适合语义模型开发,原因包括: -毫无疑问,BI 团队会受益于一种分支策略:支持在完整 BI 解决方案的任意层上并行开发,并且可以把已准备好测试的功能自由组合起来。 但尤其是由于上面最后一个要点,我们需要认真考虑该如何处理数据。 例如,我们给某个维度新增一个属性时,是否希望在构建和部署流水线中自动加载该维度? 如果加载这个维度只需要几分钟,那可能没问题;但如果我们是在一个数十亿行的事实表里新增一列呢? 此外,如果开发人员在并行开发新功能,每位开发者是否都应该拥有自己的开发数据库?否则,如何避免他们在共享数据库中相互干扰? +- **心智模型更简单。** 只需要理解两种分支概念,而不是 GitFlow 的五种,因此上手成本更低,尤其适合既有 Report 作者和业务分析师,也有模型开发人员的团队。 +- **`main` 始终可部署。** 如果你需要快速发布一个紧急修复——比如出错的度量值,或与安全相关的 RLS 变更——你不必再去判断多个长期存在的分支中,哪一个当前反映的是生产环境。 +- **环境推进由管道负责,而不是由分支结构承担。** 新增一个环境,只需修改管道;不需要再新增一个永久分支,让每位开发人员都得记住要合并进去。 +- **短生命周期分支可减少合并冲突**——这对 Octopus Merge 很重要,因为它会将所有当前开放的分支一并合并,用于集成测试。 每个分支存续时间越短,发生冲突的范围就越小。 +- **比 GitFlow 的版本化发布列车模型更适合数据产品的持续交付**,因为语义模型往往是逐步演进的,而不是按一个个独立版本发布。 -上述问题没有简单答案——尤其是当你要考虑 BI 解决方案的所有层级,以及全球各地 BI 团队不同的组织形态和偏好的工作流程时。 另外,当我们深入到实际的构建、部署和测试自动化时,会主要聚焦在 Analysis Services。 从 DevOps 的透视来看,ETL 和数据库层也有各自的挑战,但不在本文讨论范围内。 不过在继续之前,我们先看看另一种分支策略,以及它可能如何应用到 BI 工作流中。 +这并不意味着 GitFlow 就错了——想了解它在哪些情况下仍然适用,可以看看下文的 [GitFlow 分支与部署环境](#gitflow-branching-and-deployment-environments)。 -### GitFlow 分支与部署环境 +### 关键原则 + +- `main` 始终处于可部署状态。 +- 功能分支生命周期短,且彼此独立。 +- 测试环境始终反映当前所有进行中工作的组合——而不只是某一个孤立的功能。 具体怎么做,可以看看 [GitHub Flow 与 Octopus Merge 模式](xref:github-flow)。 +- 任何用于 Tabular Editor Workspace 数据库的 Workspace 都不应启用 Fabric Git 集成——Tabular Editor 会通过 XMLA endpoint 直接写入 Workspace 数据库,而这些写入与 Git 分支毫无关系。 这一点在适用于 Workspace 的 [工作区模式文档](xref:workspace-mode) 中也有专门说明。 + +## GitFlow 分支与部署环境 + +对于确实需要 GitFlow 所提供结构的团队来说,GitFlow 仍然是一个稳妥的选择——例如:需要正式的版本化发布、与特定分支绑定的合规/监管签核关口,或发布频率较低(如每月或每季度一次),在这类流程中,长期存在的 `develop` 分支和发布分支与流程能够自然对应。 如果这正符合你的团队情况,那么下面的方法非常值得采用。 下面介绍的策略基于 [Vincent Driessen 的 GitFlow](https://nvie.com/posts/a-successful-git-branching-model/)。 @@ -159,34 +199,59 @@ Git 是一个免费开源的分布式版本控制系统,旨在以高速高效 - 一套或多套 **UAT** 环境,用于你和业务用户测试并验证新功能。 部署直接从包含待测代码的 feature 分支发起。 如果你希望并行测试多个新功能,就需要多个测试环境。 只要稍作协调,并仔细考虑各个 BI 层级之间的依赖关系,通常一个测试环境就足够了。 - 一个或多个 **沙盒** 环境,你和团队可以在其中开发新功能,而不会影响上述任何环境。 和测试环境一样,通常只需要一个共享的沙盒环境就足够了。 -我们要强调:这些考量并不存在真正“放之四海皆准”的方案。 也许你并不是在云端构建解决方案,因此无法利用可扩展性或灵活性,在数秒或数分钟内快速创建新资源。 又或者你的数据量非常大,受限于资源/成本/时间,复制多套环境并不现实。 在继续之前,也请先问问自己:你是否真的需要支持并行开发与测试。 对于只有少数利益相关方的小团队,这种情况并不常见。在这种情况下,你依然可以从 CI/CD 中受益,但 GitFlow 分支策略可能有些“杀鸡用牛刀”。 +我们要强调:这些考量并不存在真正“放之四海皆准”的方案。 也许你并不是在云端构建解决方案,因此无法利用可扩展性或灵活性,在数秒或数分钟内快速创建新资源。 又或者你的数据量非常大,受限于资源/成本/时间,复制多套环境并不现实。 + +即使你确实需要支持并行开发,你也可能会发现:多个开发者通常可以轻松共享同一个开发或沙盒环境,而不会遇到太多麻烦。 不过,针对表格模型,我们仍建议开发者使用各自独立的 [Workspace 数据库](xref:workspace-mode),以免互相干扰。 + +> [!NOTE] +> 如果你考虑采用 GitFlow,主要是因为需要一个共享、始终反映进行中工作的测试环境,那么不妨考虑 [GitHub Flow + Octopus Merge](#github-flow--octopus-merge) 是否能以更低的分支管理开销实现同样的结果。 GitFlow 的 `develop`/金丝雀分支和 Octopus Merge 的临时测试分支,都是用不同方式来解决类似的问题。 + +## 主干开发 + +主干开发是最简单的分支模型:开发者要么直接向 `main` 提交小而频繁的更改,要么通过生命周期极短的功能分支提交,并在数小时内合并回去。 通常,Microsoft 推荐采用 [主干开发](https://docs.microsoft.com/en-us/azure/devops/repos/git/git-branching-guidance?view=azure-devops)([视频](https://youtu.be/t_4lLR6F_yk?t=232))策略,用于以敏捷方式持续交付小幅增量。 + +![主干开发](~/content/assets/images/trunk-based-development.png) -即使你确实需要支持并行开发,你也可能会发现:多个开发者通常可以轻松共享同一个开发或沙盒环境,而不会遇到太多麻烦。 不过,针对表格模型,我们仍建议开发者使用各自独立的 [Workspace 数据库](xref:workspace-mode),以免相互“踩脚”。 +但在最纯粹的形式下,主干开发对于 BI 团队可能会带来一些实际阻力: + +- 新功能通常需要业务用户进行较长时间的测试与验证,可能持续数周——因此,你需要一个不是 `main` 本身的地方来验证进行中的工作。 +- BI 解决方案通常是多层的(Warehouse/ETL、主数据管理、语义层、Report),而层与层之间的依赖关系会让测试和部署更复杂。 +- 一个 BI 团队可能需要维护多个处于不同成熟阶段、演进节奏各异的语义模型。 +- 要让变更可供测试,不只是代码,数据也必须完成加载、ETL 和处理。 如果每次构建都包含完整的数据刷新,管道运行时间可能会从几分钟暴涨到几小时;而对于非常大的事实表,这种做法甚至根本不可行。 + +**上文所述的 GitHub Flow + Octopus Merge,最好理解为对主干开发的一种改进,它直接解决了这些顾虑**——而不是对主干开发的背离。 它保留了主干开发的核心简洁性(一个长期存在的分支、短生命周期的功能分支、没有发布列车),同时补上了 BI 团队所缺失、但又真正需要的一环:一个共享测试环境,由管道而非长期分支来填充,并且始终反映所有进行中工作的当前合并状态。 如果你正在本页的三种策略中做选择,而你的团队喜欢主干开发的简洁性、但又遇到了上述限制,我们通常会推荐 GitHub Flow + Octopus Merge。 ## 常见工作流 -假设你已经建好了 Git repository,并且与分支策略对齐,把表格模型“源代码”加入该 repository 其实很简单:用 Tabular Editor 将元数据保存到本地 repository 的新分支上。 然后,将新文件暂存并提交,把分支推送到远程 repository,并发起拉取请求,以便将你的分支合并到主分支。 +假设你已经建好了 Git repository,并且与分支策略对齐,把表格模型“源代码”加入该 repository 其实很简单:用 Tabular Editor 将元数据保存到本地 repository 的新分支上。 然后,暂存并提交新文件,将你的分支推送到远程 repository,并创建一个拉取请求,把你的分支合并到 main 分支。 + +无论你选择上述哪种策略,具体命令都完全相同——不同之处在于打开拉取请求_之后_会发生什么(GitHub Flow 的情况请参阅 [GitHub Flow 与 Octopus Merge 模式](xref:github-flow),GitFlow 则请参考你的发布/金丝雀流程)。 总的来说,工作流如下: -具体工作流取决于你的分支策略,以及你的 Git repository 是怎么配置的。 一般来说,工作流大致如下: +1. 开始着手新功能之前,先在 Git 中创建一个新的功能分支: -1. 在开始开发新功能之前,在 Git 中创建一个新的功能分支。 在基于主干的开发场景中,你需要使用以下 Git 命令来检出主分支、拉取最新代码,并以此为基础创建功能分支: ```cmd git checkout main git pull - git checkout -b "feature\AddTaxCalculation" + git checkout -b feature/add-tax-calculation ``` + 2. 在 Tabular Editor 中从本地 Git repository 打开你的模型元数据。 理想情况下,使用 [Workspace 数据库](xref:workspace-mode),以便更轻松地测试和调试 DAX 代码。 + 3. 使用 Tabular Editor 对模型进行必要的更改。 及时保存更改(CTRL+S)。 每次保存后就定期把代码更改提交到 Git,避免丢失工作,并保留所有更改的完整历史记录: + ```cmd git add . git commit -m "Description of what was changed and why since last commit" git push ``` + 4. 如果你没有使用 Workspace 数据库,请使用 Tabular Editor 的 **Model > Deploy...** 选项部署到沙盒/开发环境,以便测试对模型元数据所做的更改。 + 5. 完成后,当所有代码都已提交并推送到远程 repository 时,你需要提交一个拉取请求,以便将你的代码集成到主分支。 如果遇到合并冲突,你得在本地解决。比如可以使用 Visual Studio Team Explorer,或者直接用文本编辑器打开 .json 文件来解决冲突(Git 会插入冲突标记,用于指示代码中哪些部分存在冲突)。 -6. 解决所有冲突后,可能还需要进行代码审查,并根据分支策略执行自动化构建/测试等流程,才能完成拉取请求。 不过,这取决于你的分支策略和整体设置。 -在接下来的文章中,我们会介绍如何使用 Azure DevOps 配置 Git 分支策略、搭建自动化构建与部署管道等更多细节。 在其他自动化构建和 Git 托管环境中也可以使用类似技术,例如 TeamCity、GitHub 等。 +6. 在所有冲突解决后,在完成拉取请求之前,通常还需要经过代码审查和自动化构建/测试——如果你采用的是上面的 GitHub Flow 方法,还包括 Octopus Merge 的测试部署——等流程。 + +在接下来的文章中,我们会进一步介绍如何使用 Azure DevOps 和 GitHub Actions 配置 Git 分支策略、搭建自动化构建与部署管道等细节。 在其他自动化构建和 Git 托管环境中也可以采用类似技术,例如 TeamCity、GitLab 等。 ## 后续步骤 diff --git a/localizedContent/zh/content/getting-started/refresh-preview-query.md b/localizedContent/zh/content/getting-started/refresh-preview-query.md index 7d02befd1..e73e4383d 100644 --- a/localizedContent/zh/content/getting-started/refresh-preview-query.md +++ b/localizedContent/zh/content/getting-started/refresh-preview-query.md @@ -2,7 +2,7 @@ uid: refresh-preview-query title: 刷新、预览与查询数据 author: Daniel Otykier -updated: 2026-01-08 +updated: 2026-06-24 applies_to: products: - product: Tabular Editor 2 @@ -84,7 +84,15 @@ Tabular Editor 3 支持对不同对象类型执行刷新操作。 支持的刷 ![预览数据](~/content/assets/images/preview-data-big.png) -你可以打开多个此类表格预览,并在用户界面中按你的习惯随意排列。 此外,你还可以对单个列进行排序或筛选。 可预览的行数实际上没有限制。 Tabular Editor 只是在模型上执行一条 [`TOPNSKIP`](https://dax.guide/topnskip) DAX 查询,返回少量记录,用于填充当前视图。 +你可以打开多个此类表格预览,并在用户界面中按你的习惯随意排列。 此外,你还可以对单个列进行排序或筛选。 Tabular Editor 会针对模型执行 DAX 查询,只返回足以填充当前视图的一小部分记录,然后在你滚动时逐页加载更多行。 + +分页的工作方式取决于存储模式和引擎: + +- 在支持 [`WINDOW`](https://dax.guide/window) 函数的引擎中,对于 Import 表,分页会基于表的主键使用 `WINDOW`,因此你可以滚动浏览整张表。 +- 如果引擎不支持 `WINDOW`,或者表没有主键,预览将显示前几行,并提示无法滚动。 +- DirectQuery 表只显示前几行——无法分页——并会显示一条信息。 + +预览元数据会在当前会话中缓存,因此重新打开预览时不会再次查询服务器。 点击 **刷新** 以重新读取元数据,例如当模型已在 Tabular Editor 之外完成处理后。 如果一个或多个计算列处于无效状态,这些列会显示文本 _(需要计算)_。 你可以通过右键单击该列并选择 **重新计算表格...** 选项来重新计算表格。 diff --git a/localizedContent/zh/content/references/downloads.md b/localizedContent/zh/content/references/downloads.md index 8ad847ace..1229d97d6 100644 --- a/localizedContent/zh/content/references/downloads.md +++ b/localizedContent/zh/content/references/downloads.md @@ -2,7 +2,7 @@ uid: downloads title: 全部下载 author: Daniel Otykier -updated: 2026-04-17 +updated: 2026-07-10 --- # Tabular Editor 3 下载 @@ -11,14 +11,14 @@ updated: 2026-04-17 ## 最新版本 -Tabular Editor 3.26.1 **.NET 8** 下载: +Tabular Editor 3.26.3 **.NET 8** 下载: -- 下载 [Tabular Editor 3.26.1(64 位)](https://cdn.tabulareditor.com/files/TabularEditor.3.26.1.Installer.x64.Net8.exe) _(推荐)_ -- 下载 [Tabular Editor 3.26.1 (ARM64)](https://cdn.tabulareditor.com/files/TabularEditor.3.26.1.Installer.ARM64.Net8.exe) -- 便携版:[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.1.x64.Net8.zip), [ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.1.ARM64.Net8.zip) -- MSI 版本:[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.1.x64.Net8.msi), [ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.1.ARM64.Net8.msi) +- 下载 [Tabular Editor 3.26.3 (64 位)](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.Installer.x64.Net8.exe) _(推荐)_ +- 下载 [Tabular Editor 3.26.3 (ARM64)](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.Installer.ARM64.Net8.exe) +- 便携版:[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.x64.Net8.zip),[ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.ARM64.Net8.zip) +- MSI 版:[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.x64.Net8.msi),[ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.ARM64.Net8.msi) -阅读 [最新发布说明](release-notes/3_26_1.md)。 +查看[最新发布说明](release-notes/3_26_3.md)。 ## 安装说明 @@ -30,6 +30,14 @@ Tabular Editor 3.26.1 **.NET 8** 下载: ## 更新历史 +- 2026-07-10 **Tabular Editor 3.26.3** (_[发布说明](release-notes/3_26_3.md)_) + - .NET 8 安装程序(.exe):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.Installer.x64.Net8.exe),[ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.Installer.ARM64.Net8.exe) + - .NET 8 便携版(.zip):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.x64.Net8.zip),[ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.ARM64.Net8.zip) + - .NET 8 安装程序(.msi):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.x64.Net8.msi),[ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.ARM64.Net8.msi) +- 2026-06-25 **Tabular Editor 3.26.2**(_[发布说明](release-notes/3_26_2.md)_) + - .NET 8 安装程序(.exe):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.Installer.x64.Net8.exe),[ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.Installer.ARM64.Net8.exe) + - .NET 8 便携版(.zip):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.x64.Net8.zip),[ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.ARM64.Net8.zip) + - .NET 8 安装程序(.msi):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.x64.Net8.msi),[ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.ARM64.Net8.msi) - 2026-04-17 **Tabular Editor 3.26.1** (_[发布说明](release-notes/3_26_1.md)_) - .NET 8 安装程序(.exe):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.1.Installer.x64.Net8.exe), [ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.1.Installer.ARM64.Net8.exe) - .NET 8 便携版(.zip):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.1.x64.Net8.zip), [ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.1.ARM64.Net8.zip) @@ -62,16 +70,5 @@ Tabular Editor 3.26.1 **.NET 8** 下载: - .NET 8 安装程序(.exe):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.22.1.Installer.x64.Net8.exe),[x86](https://cdn.tabulareditor.com/files/TabularEditor.3.22.1.Installer.x86.Net8.exe) - .NET 8 便携版(.zip):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.22.1.x64.Net8.zip),[x86](https://cdn.tabulareditor.com/files/TabularEditor.3.22.1.x86.Net8.zip) - .NET 8 安装程序(.msi):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.22.1.x64.Net8.msi),[x86](https://cdn.tabulareditor.com/files/TabularEditor.3.22.1.x86.Net8.msi) -- 2025-04-25 **Tabular Editor 3.21.0** (_[发布说明](release-notes/3_21_0.md)_) - - .NET 8 安装程序(.exe):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.21.0.Installer.x64.Net8.exe),[x86](https://cdn.tabulareditor.com/files/TabularEditor.3.21.0.Installer.x86.Net8.exe) - - .NET 8 便携版(.zip):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.21.0.x64.Net8.zip),[x86](https://cdn.tabulareditor.com/files/TabularEditor.3.21.0.x86.Net8.zip) - - .NET 8 安装程序(.msi):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.21.0.x64.Net8.msi),[x86](https://cdn.tabulareditor.com/files/TabularEditor.3.21.0.x86.Net8.msi) -- 2025-04-11 **Tabular Editor 3.20.1** (_[发布说明](release-notes/3_20_1.md)_) - - .NET 8 安装程序(.exe):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.20.1.Installer.x64.Net8.exe),[x86](https://cdn.tabulareditor.com/files/TabularEditor.3.20.1.Installer.x86.Net8.exe) - - .NET 8 便携版(.zip):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.20.1.x64.Net8.zip),[x86](https://cdn.tabulareditor.com/files/TabularEditor.3.20.1.x86.Net8.zip) - - .NET 8 安装程序(.msi):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.20.1.x64.Net8.msi),[x86](https://cdn.tabulareditor.com/files/TabularEditor.3.20.1.x86.Net8.msi) - - .NET 6 安装程序(.exe):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.20.1.Installer.x64.exe),[x86](https://cdn.tabulareditor.com/files/TabularEditor.3.20.1.Installer.x86.exe) - - .NET 6 便携版(.zip):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.20.1.x64.zip),[x86](https://cdn.tabulareditor.com/files/TabularEditor.3.20.1.x86.zip) - - .NET 6 安装程序(.msi):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.20.1.x64.msi),[x86](https://cdn.tabulareditor.com/files/TabularEditor.3.20.1.x86.msi) 如需更早版本,请参阅[完整发布历史](release-history.md)。 \ No newline at end of file diff --git a/localizedContent/zh/content/references/release-history.md b/localizedContent/zh/content/references/release-history.md index 09f1bd67b..890e0d892 100644 --- a/localizedContent/zh/content/references/release-history.md +++ b/localizedContent/zh/content/references/release-history.md @@ -5,6 +5,14 @@ title: 完整发布历史 # 完整发布历史 +- 2026-07-10 **Tabular Editor 3.26.3** (_[发布说明](release-notes/3_26_3.md)_) + - .NET 8 安装程序(.exe):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.Installer.x64.Net8.exe),[ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.Installer.ARM64.Net8.exe) + - .NET 8 便携版(.zip):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.x64.Net8.zip),[ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.ARM64.Net8.zip) + - .NET 8 安装程序(.msi):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.x64.Net8.msi),[ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.ARM64.Net8.msi) +- 2026-06-25 **Tabular Editor 3.26.2**(_[发行说明](release-notes/3_26_2.md)_) + - .NET 8 安装程序(.exe):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.Installer.x64.Net8.exe),[ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.Installer.ARM64.Net8.exe) + - .NET 8 便携版(.zip):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.x64.Net8.zip),[ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.ARM64.Net8.zip) + - .NET 8 安装程序(.msi):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.x64.Net8.msi),[ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.ARM64.Net8.msi) - 2026-04-17 **Tabular Editor 3.26.1** (_[发布说明](release-notes/3_26_1.md)_) - .NET 8 安装程序(.exe):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.1.Installer.x64.Net8.exe), [ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.1.Installer.ARM64.Net8.exe) - .NET 8 便携版(.zip):[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.1.x64.Net8.zip), [ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.1.ARM64.Net8.zip) diff --git a/localizedContent/zh/content/references/release-notes/3_26_2.md b/localizedContent/zh/content/references/release-notes/3_26_2.md new file mode 100644 index 000000000..035c5a0d5 --- /dev/null +++ b/localizedContent/zh/content/references/release-notes/3_26_2.md @@ -0,0 +1,120 @@ +--- +uid: release-3-26-2 +--- + +# Tabular Editor 3.26.2 + +# [**下载**](#tab/downloads) + +Tabular Editor 3.26.2 下载: + +- 下载 [Tabular Editor 3.26.2 (x64)](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.Installer.x64.Net8.exe) _(推荐)_ +- 下载 [Tabular Editor 3.26.2 (ARM64)](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.Installer.ARM64.Net8.exe) +- 便携版:[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.x64.Net8.zip), [ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.ARM64.Net8.zip) +- MSI 版:[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.x64.Net8.msi), [ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.ARM64.Net8.msi) + +_如果您之前未使用过 Tabular Editor 3,则可获得 30 天试用期,安装后即可激活。 你也可以 [购买许可证](https://tabulareditor.com/licensing)。_ + +# [**SHA-256 校验和**](#tab/checksums) + +| 文件 | .NET 运行时 | 平台 | SHA-256 | +| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ----- | ------------------------------------------------------------------ | +| [TabularEditor.3.26.2.Installer.x64.Net8.exe](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.Installer.x64.Net8.exe) | .NET 8 | x64 | `CDCEEF571902E58E3368F5A60BBF6926C310B34B2CC4ABA2F3D28B9A5C1C98AC` | +| [TabularEditor.3.26.2.x64.Net8.msi](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.x64.Net8.msi) | .NET 8 | x64 | `D3C0FA8184855C57D16D78CD9D2A44A7622E3C67CD07CFDC7B6D0EA59FB30E09` | +| [TabularEditor.3.26.2.x64.Net8.zip](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.x64.Net8.zip) | .NET 8 | x64 | `B4A2B836E6DA359E798D7546934F1A477C2F4C74D653995D77D2F182A57A8725` | +| [TabularEditor.3.26.2.Installer.ARM64.Net8.exe](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.Installer.ARM64.Net8.exe) | .NET 8 | ARM64 | `7CC1E25D51EA665787500DC460BB533F457E26FF7AE5A4B9F44480F980F1998F` | +| [TabularEditor.3.26.2.ARM64.Net8.msi](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.ARM64.Net8.msi) | .NET 8 | ARM64 | `A8A6E324D38E80140B171B5D7F84C5EBADD6AFE2810529504F332091ECF7E682` | +| [TabularEditor.3.26.2.ARM64.Net8.zip](https://cdn.tabulareditor.com/files/TabularEditor.3.26.2.ARM64.Net8.zip) | .NET 8 | ARM64 | `B265579D384A6586B7599CBC0A54032C5F477D3A485FC1F773F79D4B31D128C6` | + +*** + +查看我们的[发布博客](https://tabulareditor.com/blog/tabular-editor-3-june-2026-release),简要了解本次发布中最重要的更新。 + +## 3.26.2 版本改进 + +- 已在 TOM Explorer 中为表添加 **移到组** 右键子菜单。 该子菜单会列出现有表格组、一个 **(New...)** 条目(使用所选表创建新组并打开名称编辑器),以及一个 **(None)** 条目(移除表格组分配)。 + +- 已用最新版本更新 AI Assistant 偏好设置中的模型预设,用户无需输入自定义模型 ID 即可选择当前模型。 已添加 Anthropic 的 `claude-fable-5` 和 `claude-opus-4-8`,以及 OpenAI 的 `gpt-5.5`/`gpt-5.5-pro` 和其他当前的 GPT-5 变体。 已移除弃用条目(例如较旧的 Codex 变体),默认 OpenAI 模型现为 `gpt-5.5`。 + +- **Semantic Analyzer 改进** + - 新增对 DAX UDF 中带默认表达式的可选参数的支持。 + - 当调用 UDF 时传入的参数与其声明类型不匹配(例如向 `TABLEREF` 参数传递标量值等),Semantic Analyzer 现在会发出合适的警告 / 错误信息 + - Semantic Analyzer 现在会在 `IGNORE` 未直接用作 `SUMMARIZECOLUMNS` 的表达式参数、而被用于其他位置时报告错误,与引擎行为一致。 + - Semantic Analyzer 现已支持 Power BI Desktop 2026 年四月版为 `NAMEOF` 函数引入的新可选 component 参数和 escaping 参数。 有效的 component 关键字为 `TABLE`、`COLUMN`、`MEASURE`、`CALENDAR`、`FULL`、`SELF`、`PARENT`;有效的 escaping 关键字为 `ESCAPED`、`UNESCAPED`、`MINIMALLYESCAPED`。 返回类型保持不变(标量字符串)。 例如,`EVALUATE { NAMEOF([MyMeasure], TABLE, MINIMALLYESCAPED) }` 会返回完全限定度量值引用中的表名部分。

+ +- **Semantic Bridge 改进** + - 现在,导入 Databricks Metric View 时会将该视图的元数据一并带入语义模型。 + - `comment` 会成为模型、维度和度量值的描述。 + - `display_name` 会成为 TOM 模型中面向用户显示的名称;如果未定义 `display_name`,则回退到 `name`。 + - 度量值和维度的 `format` 会转换为 TOM 的 FormatString,并根据该格式推断生成的列或度量值的 DataType(货币、百分比、日期等)。 + 对于没有明确 TOM 对应项的格式规范,会回退到合理的默认值,并显示警告。 + - SQL 到 DAX 的翻译已改进:行计数 (`COUNT(*)`)、`MEASURE(name)` 引用、二元运算、字面量(包括 `NULL` -> `BLANK()`)、将 `/` 映射为可安全处理空白值的 `DIVIDE`,以及括号优先级现在都能更准确地转换。 + - 不在已识别集合内的表达式仍会回退为原样 SQL,并附带警告。 + - 翻译后的 DAX 仅为尽力而为的结果——部署前先审核并验证。 + - 已添加对带嵌套联接的雪花架构的支持。 + - YAML 反序列化现同时接受顶层的 `fields:` 和 `dimensions:`:Databricks v1.1 规范将它们视为别名。 + 往返转换会保留源 YAML 中使用的关键字。 在 TE3 中创建的视图(没有源 YAML)在 v1.1 及更早版本中默认使用 `dimensions:`:`fields:` 别名是在 v1.1 规范发布后才引入的,因此我们保持与面向原始 1.1 规范的工具兼容。 + 当 v1.1+ 视图使用 `dimensions:` 时,会显示反序列化警告(规范在 v1.1+ 中将 `fields:` 标记为首选);同样,当 v1.1 之前的视图使用 `fields:` 时,也会显示对应警告。 + - C# 脚本界面将 `Dimension` 重命名为 `Field`(`view.Fields`, `view.AddField`, `Dimension` -> `Field`, `MakeValidationRuleForDimension` -> `MakeValidationRuleForField`, `ctx.DimensionNames` -> `ctx.FieldNames`)。 + - 旧名称仍作为已弃用的转发别名保留,依然可以编译和运行,但会显示弃用警告,因此现有脚本以及诸如 `Model.Dimensions["x"]` 这样的已存储路径仍然可用。 + - 窗口度量值(包括 v1.1 的 `offset:` 字段)在进行 YAML 反序列化/序列化往返时会被保留。 对于规范标记为实验性的功能,我们暂不进行转换;YAML 往返会保留它们,以免丢失。 + - 根据更新后的 v1.1 规范,联接现已支持两个新属性: + - 支持 `rely:` 映射 + - `cardinality:` 字段可往返保留两种规范值(`many_to_one`、`one_to_many`)以及省略该字段的形式。 + 遇到未知值时,会回退为 `many_to_one` 并发出警告。 + - 声明为 `cardinality: one_to_many` 的联接仍会导入,但会有意跳过与源的关系——v1.1 规范将这类联接视为独立的事实源,并在源粒度上分别聚合。 + 仍会创建联接表,使其出现在生成的 TOM 模型中,但不会定义任何关系。 + 每个受影响的联接都会显示一条警告,说明这一差异。 + +## 3.26.2 中的缺陷修复 + +- 修复了 AI 聊天在 Anthropic Claude Opus 4.7+ 和 Fable 5 上因不再支持 temperature 采样参数而报 `BadRequest` 错误(“temperature is deprecated for this model”)的问题。 这套重试逻辑现在除了此前已处理的情况外,也能识别该提供商的这类措辞,并在不显式指定 temperature 的情况下透明地重新发送请求。 + +- 在 `NAMEOF` 和 `TABLEOF` 函数的自动完成建议中,现在也会提供日历,与表、列和度量值一起显示。 这也包括尚未配置时间单位列的日历(例如刚添加、还未配置的日历),它们此前会被遗漏。 现在,输入带引号的前缀(例如 `'My Cal`)时,也会像表一样,把建议范围缩小到匹配的日历。 + +- 表格 **Data Preview** 中的排序和分页现在在各种存储模式下都更加可靠: + +- 按没有属性层次结构的列排序时,不会再返回顺序错误的行。 在可能的情况下,分页现在会使用 `WINDOW` 函数(适用于支持该函数的引擎,以及具有主键的表);否则,预览将显示前几行,并提示已禁用滚动。 + +- 属性层次结构的可用性现在直接从服务器读取,因此即使模型在 Tabular Editor 之外被处理后,信息仍然准确。 + +- DirectQuery 表现在会正确显示前几行(DirectQuery 不支持分页),并附带一条信息。 + +- 预览元数据会在当前会话中缓存,并且仅在你单击 **刷新** 时更新,从而避免多余的服务器往返(包括重复的行计数查询)。 + +- 当无法根据模型解析所属表时,DAX 格式化程序不再从 `DEFINE MEASURE` 块中移除表限定符。 此前,这可能会把有效的 `MEASURE 'Sales'[Amount] = ...` 变成无效的 `MEASURE [Amount] = ...`,从而导致语句无效。 + +- 当将筛选器移除函数(`ALL`、`ALLSELECTED`、`REMOVEFILTERS` 或 `ALLCROSSFILTERED`)用作 DAX 用户定义函数的顶层主体表达式时,语义分析器不再误报错误,例如 `FUNCTION F = () => REMOVEFILTERS('Sales')`。 此类 UDF 在作为 `CALCULATE` 的 FILTER 参数调用时是有效的。 无效用法仍会在调用处被报告;关系修饰符(`CROSSFILTER` / `USERELATIONSHIP`)以及 `KEEPFILTERS` 作为 UDF 函数体时仍然无效,与引擎行为一致。 + +- 在重命名被 NAMEOF 函数调用引用的表时,公式修复现在会正确更新该表引用。 + +- 当将 `TABLEOF(...)` 作为表引用参数传递给 `ALL`、`ALLEXCEPT`、`ALLSELECTED`、`ALLNOBLANKROW`、`ALLCROSSFILTERED` 或 `REMOVEFILTERS` 时,语义分析器不再误报错误。 + +- **更新表架构** 操作现在在对混合对象类型执行或在表格组上执行时,行为都能正确。 + +--- + +## 从 Tabular Editor 2.x 迁移过来? + +观看 [此视频](https://youtu.be/O4ATwdzCvWc),快速了解 Tabular Editor 3 的主要功能。 另外,也请务必查看我们的[上手指南](https://docs.tabulareditor.com/onboarding/index.html)。 + +**Tabular Editor 3 主要功能概览:** + +- 完全可自定义的 IDE,支持多显示器、Hi-DPI 和主题切换 +- 全新的强大 DAX 代码编辑器,具备自动完成、语法检查、代码折叠等众多功能 +- \*工作区模式,可将更改保存到磁盘,并同时将模型元数据同步到 Analysis Services +- \*通过无限滚动预览表数据,创建 PivotGrid,或编写 DAX 查询来浏览模型或测试计算逻辑 +- \*设置数据定时刷新 +- 同时更新 Provider 和 Structured数据源的表架构(没错,连 M 查询也支持!) +- 创建 Data model 图 +- 创建 DAX 脚本,使您能够在单个文档中编辑多个度量值或其他计算对象 +- 录制 C# Script 并保存为宏(以前称为“自定义操作”) +- VertiPaq分析器集成 +- DAX调试器 +- 集成 DAX优化器 +- 代码操作:轻松重构你的 DAX 代码。 + +\*=仅在连接到 Analysis Services 或 Power BI 实例时可用 + +--- + diff --git a/localizedContent/zh/content/references/release-notes/3_26_3.md b/localizedContent/zh/content/references/release-notes/3_26_3.md new file mode 100644 index 000000000..851c190e5 --- /dev/null +++ b/localizedContent/zh/content/references/release-notes/3_26_3.md @@ -0,0 +1,65 @@ +--- +uid: release-3-26-3 +--- + +# Tabular Editor 3.26.3 + +# [**下载**](#tab/downloads) + +Tabular Editor 3.26.3 下载链接: + +- 下载 [Tabular Editor 3.26.3 (x64)](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.Installer.x64.Net8.exe) _(推荐)_ +- 下载 [Tabular Editor 3.26.3 (ARM64)](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.Installer.ARM64.Net8.exe) +- 便携版本:[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.x64.Net8.zip), [ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.ARM64.Net8.zip) +- MSI 版:[x64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.x64.Net8.msi), [ARM64](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.ARM64.Net8.msi) + +_如果您之前未使用过 Tabular Editor 3,则可获得 30 天试用期,安装后即可激活。 你也可以 [购买许可证](https://tabulareditor.com/licensing)._ + +# [**SHA-256 校验和**](#tab/checksums) + +| 文件 | .NET 运行时 | 平台 | SHA-256 | +| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ----- | ------------------------------------------------------------------ | +| [TabularEditor.3.26.3.Installer.x64.Net8.exe](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.Installer.x64.Net8.exe) | .NET 8 | x64 | `2413B042E935F010EA03E1EE7D01B6F0CDD076B10F29F1CD5651BECE1F4ADEEC` | +| [TabularEditor.3.26.3.x64.Net8.msi](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.x64.Net8.msi) | .NET 8 | x64 | `AE7DBAE6A0432EFDB75765F6C1B3CE91495018F058A24BE67EB2BC7DF39BD915` | +| [TabularEditor.3.26.3.x64.Net8.zip](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.x64.Net8.zip) | .NET 8 | x64 | `AAEB730DADA50A424F55D0AC41961E23D09F3913A72DB240319D78271BF7DAE2` | +| [TabularEditor.3.26.3.Installer.ARM64.Net8.exe](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.Installer.ARM64.Net8.exe) | .NET 8 | ARM64 | `8563D579F32F776A8BF4BA75D17C11B1094E1550B24DD448A2404D63860E8F78` | +| [TabularEditor.3.26.3.ARM64.Net8.msi](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.ARM64.Net8.msi) | .NET 8 | ARM64 | `FC9F0B2D7B8F75BFDCBFB534CF8450E44014E706D1FB125544317A0C4683D4F2` | +| [TabularEditor.3.26.3.ARM64.Net8.zip](https://cdn.tabulareditor.com/files/TabularEditor.3.26.3.ARM64.Net8.zip) | .NET 8 | ARM64 | `37312FB9D663FA59AB07D3AA45F88B21B8FF812B8F185AF8DCFCB4D9A8583AA7` | + +*** + +## 3.26.3 缺陷修复 + +- 修复了一个问题:如果“文件 > 还原”失败(例如,当磁盘上的 TMDL 包含无法解析的引用时),未保存更改跟踪可能会在该会话的剩余时间内一直被禁用。 此前,后续编辑将不再显示表示未保存更改的星号,而“还原”或“关闭”可能会在没有任何警告的情况下直接丢弃未保存的更改。 现在,即使还原失败,更改跟踪也会保持启用。 +- 在以文件夹形式序列化的(TMDL)模型中,成功执行“文件 > 还原”后,模型不再显示为有未保存的更改。 此前,标题栏中的星号以及“从源刷新元数据?”确认提示会在此后的每次还原时持续出现,直到模型被保存。 +- 修复了 DAX 自动完成功能在当前参数上下文中建议无效的用户定义函数(UDF),并遗漏有效函数的问题。 UDF 现在会按推断出的返回类型进行筛选:在需要标量的位置,只显示返回标量、单列表格,或返回未类型化输入参数的 UDF;在需要表的位置,只显示返回表(或透传)的 UDF;此外,筛选参数(例如 CALCULATE / CALCULATETABLE 的参数)现在也会提示 UDF,而此前这类提示完全不会出现。 定义中包含语义错误的 UDF(例如需要行语境,或错误使用 MATCHBY 的 UDF)不再会出现在建议列表中,因为它们无法被合法调用。 +- DAX 格式化程序不再将 DAX 函数默认参数值中的 `=` 替换为 `:`。 此前,格式化诸如 `(p = [m]) => 1` 这样的函数时,会生成无效的 `(p: [m]) => 1`,因为格式化程序会把参数名后的任何内容都当作 `: type` 类型注解。 现在,类型注解、默认值以及两者的组合 (`p: INT64 = 1`) 都能正确格式化。 +- 修复了一个问题:在上传混淆模型的新版本后,将 DAX优化器问题标记为“已忽略”会失效,导致已忽略的问题重新显示为未解决。 现在,已忽略的问题会在混淆模型的不同版本之间保留。 +- 修复了一个间歇性崩溃(“Collection was modified”):在上传模型或其新版本后,DAX优化器视图可能会立即发生此问题。 现在,会在进度旋转指示器仍在显示时刷新视图,而不是在窗口重绘过程中在后台刷新。 + +--- + +## 从 Tabular Editor 2.x 迁移过来? + +观看 [此视频](https://youtu.be/O4ATwdzCvWc),快速了解 Tabular Editor 3 的主要功能。 此外,别忘了查看我们的[入门指南](https://docs.tabulareditor.com/onboarding/index.html)。 + +**Tabular Editor 3 主要功能概览:** + +- 可完全自定义的 IDE,支持多显示器、Hi-DPI 和主题 +- 全新且强大的 DAX 代码编辑器,支持自动补全、语法检查、代码折叠等更多功能 +- \*工作区模式:可让你将更改保存到磁盘,同时将模型元数据同步到 Analysis Services +- \*通过无限滚动预览表数据,创建 PivotGrid,或编写 DAX 查询来浏览模型或测试计算逻辑 +- \*安排数据刷新 +- 同时更新 Provider 数据源和 Structured数据源上的表架构(是的,甚至包括 M 查询!) +- 创建 Data model 示意图 +- 创建 DAX 脚本,让你在一个文档中批量编辑多个度量值或其他计算对象 +- 录制 C# Script 并保存为宏(以前称为“自定义操作”) +- 集成 VertiPaq分析器 +- DAX调试器 +- 集成 DAX优化器 +- 代码操作:轻松重构你的 DAX 代码。 + +\*=仅在连接到 Analysis Services 或 Power BI 实例时可用 + +--- + diff --git a/localizedContent/zh/content/references/whats-new.md b/localizedContent/zh/content/references/whats-new.md index e483558a1..7a49ec21c 100644 --- a/localizedContent/zh/content/references/whats-new.md +++ b/localizedContent/zh/content/references/whats-new.md @@ -2,7 +2,7 @@ uid: whats-new title: 更新内容 author: Morten Lønskov -updated: 2026-03-19 +updated: 2026-06-24 --- - + diff --git a/localizedContent/zh/content/tutorials/udfs.md b/localizedContent/zh/content/tutorials/udfs.md index 485656c98..43f000bfe 100644 --- a/localizedContent/zh/content/tutorials/udfs.md +++ b/localizedContent/zh/content/tutorials/udfs.md @@ -2,7 +2,7 @@ uid: udfs title: DAX 用户自定义函数 author: Daniel Otykier -updated: 2026-03-19 +updated: 2026-06-24 applies_to: products: - product: Tabular Editor 2 @@ -20,7 +20,7 @@ applies_to: # DAX 用户自定义函数 -DAX 用户自定义函数 (UDFs) 是语义模型的一项新功能,在 Power BI Desktop 的 2025 年九月更新中引入。 +DAX 用户自定义函数 (UDF) 是语义模型提供的一项能力。 该功能在 Power BI Desktop 2025 年九月更新中进入预览阶段,并自 Power BI 2026 年六月版本起[正式发布](https://community.fabric.microsoft.com/t5/Power-BI-Updates-Blog/DAX-User-Defined-Functions-Generally-Available/ba-p/5185738)。 该功能让你能够创建可复用的 DAX 函数,并可在模型中的任何 DAX 表达式里调用,甚至在其他函数中也能调用。 这个强大的功能可帮助你保持一致性、减少代码重复,并创建更易维护的 DAX 表达式。 @@ -133,8 +133,31 @@ ROW( 这些类型说明是可选的,但一旦指定,它们会对传入函数的参数执行隐式类型转换;同时,也会影响在 Tabular Editor 3 中编写调用该函数的 DAX 代码时的自动完成建议。 +Tabular Editor 3 会根据已声明的参数类型验证实参。 如果你调用 UDF 时传入的实参与其参数类型不匹配——例如,在需要 `TABLEREF` 参数的位置传入标量值——语义分析器会 Report 警告或错误。 + 可用约束的完整列表,请参阅 [Microsoft 的 UDF 规范](https://learn.microsoft.com/en-us/dax/best-practices/dax-user-defined-functions)。 +### 带默认表达式的可选参数 + +从 3.26.2 版本开始,Tabular Editor 3 支持带默认表达式的可选参数。 在参数名称后(以及任何类型或求值模式提示之后)追加 `= expression`,即可将该参数设为可选。 调用方省略该实参时,将由默认表达式提供其值。 + +```dax +FUNCTION AddTax = + ( + amount: NUMERIC, // 必需参数 + taxRate: NUMERIC = 0.1 // 可选参数,默认为 10% + ) + => amount * (1 + taxRate) +``` + +调用 `AddTax(10)` 会返回 `11`,而 `AddTax(10, 0.25)` 会返回 `12.5`。 + +可选参数遵循以下规则: + +- 调用方可以将实参留空,以回退到默认值,例如 `MyFunc(1,,3)` 省略了第二个实参。 最少参数个数由最右侧必需参数的位置决定。 +- 默认表达式只能引用在函数定义位置可见的名称(列、表、度量值、函数),并且不能引用同一函数的其他参数。 +- 只有在使用默认表达式时,才会按参数的类型提示进行类型检查;如果显式传入实参,则改为检查该实参是否符合该类型提示。 + ## 在模型中使用 UDF ### 在对象表达式中 @@ -311,15 +334,15 @@ Tabular Editor 3 会自动识别所有注释,并在自动完成建议和工具 **部署后函数无法正常工作** -- 确认目标环境支持 UDF(兼容级别 1702+)。 截至 2025 年九月十六日,Power BI 服务尚不支持 UDF,Azure Analysis Services 和 SQL Server Analysis Services 也同样不支持。 +- 确认目标环境支持 UDF(兼容级别 1702+)。 Power BI 服务自 2026 年六月版本起支持 UDF。 Azure Analysis Services 和 SQL Server Analysis Services 不支持 UDF。 ## 局限性 -- 并非所有 Power BI 环境都支持 UDF(需要特定构建版本) +- UDF 需要 1702 或更高的兼容级别;Azure Analysis Services 和 SQL Server Analysis Services 不支持 UDF - UDF 不能递归(调用自身) > [!NOTE] -> 截至 2026 年六月,UDF 已 [正式可用](https://community.fabric.microsoft.com/t5/Power-BI-Updates-Blog/DAX-User-Defined-Functions-Generally-Available/ba-p/5185738)。 作为其中的一部分,UDF 现已支持默认表达式和可选参数。 但是,在使用默认表达式语法时,Tabular Editor 3 目前会误报一条错误信息。 此问题将在 Tabular Editor 3 的下一次更新中修复。 +> 随着 UDF 于 2026 年六月[正式发布](https://community.fabric.microsoft.com/t5/Power-BI-Updates-Blog/DAX-User-Defined-Functions-Generally-Available/ba-p/5185738),UDF 支持带默认表达式的可选参数。 Tabular Editor 3 自 3.26.2 版本起支持此语法。 较旧版本在使用默认表达式语法时会显示误报的错误信息。 ---