Compare commits
74 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6671155ec7 | ||
|
|
61aa3f4994 | ||
|
|
68c6611e30 | ||
|
|
890eafb883 | ||
|
|
d1bb583274 | ||
|
|
04447e5a15 | ||
|
|
2f1ad3b06e | ||
|
|
d9851295e8 | ||
|
|
f3ab0aaa76 | ||
|
|
a81cf9cd5b | ||
|
|
3c6261dc9b | ||
|
|
6642cf12d2 | ||
|
|
c6bc1b9338 | ||
|
|
cb71ba1cf7 | ||
|
|
ff96cc3f3b | ||
|
|
b723a134a7 | ||
|
|
f7ca7d87e7 | ||
|
|
dcb48fde7c | ||
|
|
e677b018cf | ||
|
|
849d309228 | ||
|
|
93c4275848 | ||
|
|
895e90d710 | ||
|
|
63f4738a2e | ||
|
|
acf82b006a | ||
|
|
c1d8176a6c | ||
|
|
2dee744bef | ||
|
|
700c489988 | ||
|
|
b83e710fcf | ||
|
|
756a7c98e3 | ||
|
|
a1f806849e | ||
|
|
5f40a4281c | ||
|
|
62706771b2 | ||
|
|
8b7bef9e6d | ||
|
|
5819c41ddd | ||
|
|
62680dd060 | ||
|
|
81abf49f37 | ||
|
|
148956e91b | ||
|
|
50e28a52c4 | ||
|
|
fd3b33bcc0 | ||
|
|
f3db52c13d | ||
|
|
2cb60d3ed3 | ||
|
|
cbaad119ea | ||
|
|
65ffbb1859 | ||
|
|
8390a228e3 | ||
|
|
d10e227d7f | ||
|
|
98c9a23df3 | ||
|
|
2d730f72f6 | ||
|
|
05627e320b | ||
|
|
b900b9c19a | ||
|
|
beba2af69e | ||
|
|
be79eb94f3 | ||
|
|
3f1f74f0f8 | ||
|
|
ccdc9c618f | ||
|
|
652428632a | ||
|
|
fd574a46cd | ||
|
|
8ae92a679d | ||
|
|
26d6fd645b | ||
|
|
45303c68f2 | ||
|
|
39516bb840 | ||
|
|
f4130b45f4 | ||
|
|
2636bd5aae | ||
|
|
32aad78e5d | ||
|
|
9cfab6329d | ||
|
|
b102d83af2 | ||
|
|
2e85c4f15e | ||
|
|
136f4d77f7 | ||
|
|
a788175862 | ||
|
|
91293bdfd0 | ||
|
|
06b86f488f | ||
|
|
7a2797c839 | ||
|
|
6afc7d7a24 | ||
|
|
3a54f2d84c | ||
|
|
22f75b1699 | ||
|
|
5acb3f33e3 |
3
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
assets/auto-sync-ai*.png filter=lfs diff=lfs merge=lfs -text
|
||||
assets/release-vibe-bi.png filter=lfs diff=lfs merge=lfs -text
|
||||
marketing/images/*.png filter=lfs diff=lfs merge=lfs -text
|
||||
37
.github/workflows/downloads-chart.yml
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
name: Update downloads chart
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Daily at 06:15 UTC (after pypistats has processed the prior day)
|
||||
- cron: "15 6 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
regenerate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Regenerate cumulative downloads chart
|
||||
run: python scripts/generate_downloads_chart.py
|
||||
|
||||
- name: Commit and push updated chart
|
||||
run: |
|
||||
if git diff --quiet assets/downloads-chart.svg; then
|
||||
echo "No changes to downloads chart."
|
||||
exit 0
|
||||
fi
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add assets/downloads-chart.svg
|
||||
git commit -m "chore: refresh downloads chart"
|
||||
git push
|
||||
34
.gitignore
vendored
|
|
@ -32,3 +32,37 @@ Thumbs.db
|
|||
|
||||
# pbi-cli specific
|
||||
~/.pbi-cli/
|
||||
|
||||
# Extracted wheel/sdist build directories
|
||||
pbi_cli_tool-*/
|
||||
|
||||
# Demo and scratch Power BI project files
|
||||
demo.Report/
|
||||
demo.SemanticModel/
|
||||
demo.pbip
|
||||
pbir_desktop_test/
|
||||
"pbib examples/"
|
||||
|
||||
# Marketing assets (local only, not for GitHub)
|
||||
marketing/
|
||||
|
||||
# Local-only documents (private notes, call prep, etc.)
|
||||
docs/
|
||||
|
||||
# Assets not referenced in README
|
||||
assets/*.png
|
||||
assets/Claude_AI_symbol.svg
|
||||
assets/New_Power_BI_Logo.svg
|
||||
assets/commands.svg
|
||||
assets/cta-start.svg
|
||||
assets/dax-skill.svg
|
||||
assets/deploy-secure.svg
|
||||
assets/docs-diagnostics.svg
|
||||
assets/dual-layer.svg
|
||||
assets/feature-grid.svg
|
||||
assets/header.svg
|
||||
assets/how-it-works.svg
|
||||
assets/modeling-skill.svg
|
||||
assets/release-vibe-bi.svg
|
||||
assets/token-cost.svg
|
||||
assets/workflow.svg
|
||||
|
|
|
|||
31
CHANGELOG.md
|
|
@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [3.10.6] - 2026-04-07
|
||||
|
||||
### Fixed
|
||||
- `visual bind` no longer writes the legacy `Commands` block (SemanticQueryDataShapeCommand) to `visual.json`. PBIR 2.7.0 uses `additionalProperties: false` on the query object, so the `Commands` field is a hard schema violation. Only `queryState` projections are now written.
|
||||
- `pbi report validate` and the full PBIR validator no longer flag a missing `layoutOptimization` field as an error. The real Microsoft schema does not list it as required; the previous check was against a stale internal schema.
|
||||
- `pbi report set-background` now always writes `transparency: 0` alongside the color. Power BI Desktop defaults a missing `transparency` property to 100 (fully invisible), making the color silently unrendered. The new `--transparency` flag (0-100, default 0) lets callers override for semi-transparent backgrounds.
|
||||
|
||||
### Added
|
||||
- `--no-sync` flag on `report`, `visual`, `filters`, and `bookmarks` command groups. Suppresses the per-command Desktop auto-sync for scripted multi-step builds. Use `pbi report reload` for a single explicit sync at the end of the script.
|
||||
|
||||
## [3.10.5] - 2026-04-06
|
||||
|
||||
### Fixed
|
||||
- ASCII banner: replaced incorrect hand-crafted art with the official design from `assets/banner.svg`. The `I` in PBI is now correctly narrow, and a small `███╗/╚══╝` block serves as the visible `-` separator between PBI and CLI.
|
||||
|
||||
## [3.10.4] - 2026-04-06
|
||||
|
||||
### Added
|
||||
- ASCII banner displayed in terminal when `pbi` is invoked with no subcommand. Renders in Power BI yellow on color terminals, plain text fallback on legacy terminals without Unicode support. Skipped when `--json` flag is used.
|
||||
|
||||
## [3.10.3] - 2026-04-05
|
||||
|
||||
### Changed
|
||||
- Claude Code integration is now fully opt-in. `pbi connect` no longer writes to `~/.claude/`. Run `pbi-cli skills install` explicitly to register skills with Claude Code. See [SECURITY.md](SECURITY.md) for full details.
|
||||
- `pbi-cli` is now a dedicated management entry point (`pbi-cli skills install/uninstall/list`). Skills subcommands have been removed from the `pbi` entry point.
|
||||
|
||||
### Fixed
|
||||
- DLL licensing: Microsoft Analysis Services client library DLLs bundled in `src/pbi_cli/dlls/` are now correctly attributed under the Microsoft Software License Terms, not sublicensed under MIT. Full EULA text in `THIRD_PARTY_LICENSES.md`.
|
||||
- `pyproject.toml` updated to PEP 639 dual SPDX expression (`MIT AND LicenseRef-Microsoft-AS-Client-Libraries`) with `license-files` declaration.
|
||||
- README.md and README.pypi.md updated to reflect the correct 3-step setup flow: `pipx install` → `pbi-cli skills install` → `pbi connect`.
|
||||
|
||||
## [3.10.0] - 2026-04-02
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ tests/ # Mirrors src/ structure
|
|||
|
||||
1. Create `src/pbi_cli/skills/your-skill/SKILL.md`
|
||||
2. Follow the frontmatter format from existing skills
|
||||
3. Test with `pbi skills list` and `pbi skills install`
|
||||
3. Test with `pbi-cli skills list` and `pbi-cli skills install`
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
|
|
|
|||
7
Claude_AI_symbol.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated by Pixelmator Pro 3.6.17 -->
|
||||
<svg width="1200" height="1200" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="g314">
|
||||
<path id="path147" fill="#d97757" stroke="none" d="M 233.959793 800.214905 L 468.644287 668.536987 L 472.590637 657.100647 L 468.644287 650.738403 L 457.208069 650.738403 L 417.986633 648.322144 L 283.892639 644.69812 L 167.597321 639.865845 L 54.926208 633.825623 L 26.577238 627.785339 L 3.3e-05 592.751709 L 2.73832 575.27533 L 26.577238 559.248352 L 60.724873 562.228149 L 136.187973 567.382629 L 249.422867 575.194763 L 331.570496 580.026978 L 453.261841 592.671082 L 472.590637 592.671082 L 475.328857 584.859009 L 468.724915 580.026978 L 463.570557 575.194763 L 346.389313 495.785217 L 219.543671 411.865906 L 153.100723 363.543762 L 117.181267 339.060425 L 99.060455 316.107361 L 91.248367 266.01355 L 123.865784 230.093994 L 167.677887 233.073853 L 178.872513 236.053772 L 223.248367 270.201477 L 318.040283 343.570496 L 441.825592 434.738342 L 459.946411 449.798706 L 467.194672 444.64447 L 468.080597 441.020203 L 459.946411 427.409485 L 392.617493 305.718323 L 320.778564 181.932983 L 288.80542 130.630859 L 280.348999 99.865845 C 277.369171 87.221436 275.194641 76.590698 275.194641 63.624268 L 312.322174 13.20813 L 332.8591 6.604126 L 382.389313 13.20813 L 403.248352 31.328979 L 434.013519 101.71814 L 483.865753 212.537048 L 561.181274 363.221497 L 583.812134 407.919434 L 595.892639 449.315491 L 600.40271 461.959839 L 608.214783 461.959839 L 608.214783 454.711609 L 614.577271 369.825623 L 626.335632 265.61084 L 637.771851 131.516846 L 641.718201 93.745117 L 660.402832 48.483276 L 697.530334 24.000122 L 726.52356 37.852417 L 750.362549 72 L 747.060486 94.067139 L 732.886047 186.201416 L 705.100708 330.52356 L 686.979919 427.167847 L 697.530334 427.167847 L 709.61084 415.087341 L 758.496704 350.174561 L 840.644348 247.490051 L 876.885925 206.738342 L 919.167847 161.71814 L 946.308838 140.29541 L 997.61084 140.29541 L 1035.38269 196.429626 L 1018.469849 254.416199 L 965.637634 321.422852 L 921.825562 378.201538 L 859.006714 462.765259 L 819.785278 530.41626 L 823.409424 535.812073 L 832.75177 534.92627 L 974.657776 504.724915 L 1051.328979 490.872559 L 1142.818848 475.167786 L 1184.214844 494.496582 L 1188.724854 514.147644 L 1172.456421 554.335693 L 1074.604126 578.496765 L 959.838989 601.449829 L 788.939636 641.879272 L 786.845764 643.409485 L 789.261841 646.389343 L 866.255127 653.637634 L 899.194702 655.409424 L 979.812134 655.409424 L 1129.932861 666.604187 L 1169.154419 692.537109 L 1192.671265 724.268677 L 1188.724854 748.429688 L 1128.322144 779.194641 L 1046.818848 759.865845 L 856.590759 714.604126 L 791.355774 698.335754 L 782.335693 698.335754 L 782.335693 703.731567 L 836.69812 756.885986 L 936.322205 846.845581 L 1061.073975 962.81897 L 1067.436279 991.490112 L 1051.409424 1014.120911 L 1034.496704 1011.704712 L 924.885986 929.234924 L 882.604126 892.107544 L 786.845764 811.48999 L 780.483276 811.48999 L 780.483276 819.946289 L 802.550415 852.241699 L 919.087341 1027.409424 L 925.127625 1081.127686 L 916.671204 1098.604126 L 886.469849 1109.154419 L 853.288696 1103.114136 L 785.073914 1007.355835 L 714.684631 899.516785 L 657.906067 802.872498 L 650.979858 806.81897 L 617.476624 1167.704834 L 601.771851 1186.147705 L 565.530212 1200 L 535.328857 1177.046997 L 519.302124 1139.919556 L 535.328857 1066.550537 L 554.657776 970.792053 L 570.362488 894.68457 L 584.536926 800.134277 L 592.993347 768.724976 L 592.429626 766.630859 L 585.503479 767.516968 L 514.22821 865.369263 L 405.825531 1011.865906 L 320.053711 1103.677979 L 299.516815 1111.812256 L 263.919525 1093.369263 L 267.221497 1060.429688 L 287.114136 1031.114136 L 405.825531 880.107361 L 477.422913 786.52356 L 523.651062 732.483276 L 523.328918 724.671265 L 520.590698 724.671265 L 205.288605 929.395935 L 149.154434 936.644409 L 124.993355 914.01355 L 127.973183 876.885986 L 139.409409 864.80542 L 234.201385 799.570435 L 233.879227 799.8927 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4 KiB |
38
NOTICE
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
pbi-cli
|
||||
Copyright (c) 2026 pbi-cli contributors
|
||||
|
||||
This product is licensed under the MIT License. See the LICENSE file for
|
||||
the full text of that license.
|
||||
|
||||
This product bundles the following third-party software inside the
|
||||
`pbi-cli-tool` PyPI distribution. These components are NOT covered by the
|
||||
MIT License and retain their original license terms. See
|
||||
THIRD_PARTY_LICENSES.md for the full text of each license.
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
Microsoft Analysis Services Client Libraries
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
Licensed under the Microsoft Software License Terms - Microsoft Analysis
|
||||
Management Objects (AMO) and the Microsoft Software License Terms -
|
||||
Microsoft Analysis Services - ADOMD.NET. The full text of both licenses is
|
||||
reproduced in THIRD_PARTY_LICENSES.md at the root of this distribution.
|
||||
|
||||
Bundled binaries (src/pbi_cli/dlls/):
|
||||
|
||||
- Microsoft.AnalysisServices.dll
|
||||
- Microsoft.AnalysisServices.Core.dll
|
||||
- Microsoft.AnalysisServices.Tabular.dll
|
||||
- Microsoft.AnalysisServices.Tabular.Json.dll
|
||||
- Microsoft.AnalysisServices.AdomdClient.dll
|
||||
|
||||
These binaries are redistributed unmodified from the official Microsoft
|
||||
NuGet packages:
|
||||
|
||||
- Microsoft.AnalysisServices.NetCore.retail.amd64
|
||||
- Microsoft.AnalysisServices.AdomdClient.retail.amd64
|
||||
|
||||
By installing `pbi-cli-tool` you agree to the Microsoft license terms
|
||||
applicable to these bundled binaries in addition to the MIT License that
|
||||
applies to the rest of the package.
|
||||
36
New_Power_BI_Logo.svg
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="630px" height="630px" viewBox="0 0 630 630" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
|
||||
<title>PBI Logo</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
|
||||
<stop stop-color="#EBBB14" offset="0%"></stop>
|
||||
<stop stop-color="#B25400" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-2">
|
||||
<stop stop-color="#F9E583" offset="0%"></stop>
|
||||
<stop stop-color="#DE9800" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<path d="M346,604 L346,630 L320,630 L153,630 C138.640597,630 127,618.359403 127,604 L127,183 C127,168.640597 138.640597,157 153,157 L320,157 C334.359403,157 346,168.640597 346,183 L346,604 Z" id="path-3"></path>
|
||||
<filter x="-9.1%" y="-6.3%" width="136.5%" height="116.9%" filterUnits="objectBoundingBox" id="filter-4">
|
||||
<feOffset dx="20" dy="10" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="10" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0530211976 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
|
||||
</filter>
|
||||
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-5">
|
||||
<stop stop-color="#F9E68B" offset="0%"></stop>
|
||||
<stop stop-color="#F3CD32" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="PBI-Logo" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group" transform="translate(77.500000, 0.000000)">
|
||||
<rect id="Rectangle" fill="url(#linearGradient-1)" x="256" y="0" width="219" height="630" rx="26"></rect>
|
||||
<g id="Combined-Shape">
|
||||
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-3"></use>
|
||||
<use fill="url(#linearGradient-2)" fill-rule="evenodd" xlink:href="#path-3"></use>
|
||||
</g>
|
||||
<path d="M219,604 L219,630 L193,630 L26,630 C11.6405965,630 1.75851975e-15,618.359403 0,604 L0,341 C-1.75851975e-15,326.640597 11.6405965,315 26,315 L193,315 C207.359403,315 219,326.640597 219,341 L219,604 Z" id="Combined-Shape" fill="url(#linearGradient-5)"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
161
README.md
|
|
@ -1,5 +1,5 @@
|
|||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/banner.svg" alt="pbi-cli — Vibe Modeling" width="850"/>
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/banner.svg" alt="pbi-cli" width="850"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
|
@ -18,7 +18,8 @@
|
|||
<p align="center">
|
||||
<a href="#why-pbi-cli">Why pbi-cli</a> •
|
||||
<a href="#get-started">Get Started</a> •
|
||||
<a href="#just-ask-claude">Just Ask Claude</a> •
|
||||
<a href="#semantic-model-layer">Modeling</a> •
|
||||
<a href="#report-layer">Reporting</a> •
|
||||
<a href="#skills">Skills</a> •
|
||||
<a href="#all-commands">All Commands</a> •
|
||||
<a href="#contributing">Contributing</a>
|
||||
|
|
@ -32,16 +33,33 @@
|
|||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/before-after.svg" alt="Why pbi-cli" width="850"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/layers.svg" alt="Dual-Layer Architecture" width="850"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/stats.svg" alt="pbi-cli at a Glance" width="850"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/downloads-chart.svg" alt="pbi-cli cumulative downloads from PyPI" width="850"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<sub>Cumulative downloads, refreshed daily from <a href="https://pypistats.org/packages/pbi-cli-tool">pypistats.org</a> via GitHub Actions.</sub>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## Get Started
|
||||
|
||||
```bash
|
||||
pipx install pbi-cli-tool # 1. Install (handles PATH automatically)
|
||||
pbi connect # 2. Auto-detects Power BI Desktop and installs skills
|
||||
pbi-cli skills install # 2. Register Claude Code skills (one-time setup)
|
||||
pbi connect # 3. Connect to Power BI Desktop
|
||||
```
|
||||
|
||||
Open Power BI Desktop with a `.pbix` file, run `pbi connect`, and start asking Claude.
|
||||
Open Power BI Desktop with a `.pbix` file, run the three commands above, and start asking Claude.
|
||||
|
||||
> **Requires:** Windows with Python 3.10+ and Power BI Desktop running.
|
||||
|
||||
|
|
@ -77,7 +95,9 @@ Add the printed path to your system PATH, then restart your terminal. We recomme
|
|||
|
||||
---
|
||||
|
||||
## Just Ask Claude
|
||||
## Semantic Model Layer
|
||||
|
||||
Ask Claude to work with your Power BI semantic model. Requires `pbi connect`.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/chat-demo.svg" alt="Just Ask Claude" width="850"/>
|
||||
|
|
@ -115,9 +135,72 @@ Add the printed path to your system PATH, then restart your terminal. We recomme
|
|||
|
||||
---
|
||||
|
||||
## Report Layer
|
||||
|
||||
Ask Claude to build and manage your Power BI reports. No connection needed -- works directly on PBIR files.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/chat-demo-report.svg" alt="Ask Claude to build reports" width="850"/>
|
||||
</p>
|
||||
|
||||
### Build a report in 6 steps
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/report-workflow.svg" alt="Report workflow" width="850"/>
|
||||
</p>
|
||||
|
||||
### Visuals, pages, themes, filters
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/report-layer.svg" alt="Report layer capabilities" width="850"/>
|
||||
</p>
|
||||
|
||||
### 32 visual types
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/visual-types.svg" alt="32 Visual Types" width="850"/>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/architecture-flow.svg" alt="Architecture" width="850"/>
|
||||
</p>
|
||||
|
||||
**Two layers, one CLI:**
|
||||
|
||||
- **Semantic Model layer** -- Direct in-process .NET interop from Python to Power BI Desktop via TOM/ADOMD. No MCP server, no external binaries, sub-second execution.
|
||||
- **Report layer** -- Reads and writes PBIR (Enhanced Report Format) JSON files directly. No connection needed. Works with `.pbip` projects.
|
||||
|
||||
### Desktop Auto-Sync
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/auto-sync.svg" alt="Desktop Auto-Sync" width="850"/>
|
||||
</p>
|
||||
|
||||
<details>
|
||||
<summary><b>Configuration details</b></summary>
|
||||
|
||||
All config lives in `~/.pbi-cli/`:
|
||||
|
||||
```
|
||||
~/.pbi-cli/
|
||||
config.json # Default connection preference
|
||||
connections.json # Named connections
|
||||
repl_history # REPL command history
|
||||
```
|
||||
|
||||
Bundled DLLs ship inside the Python package (`pbi_cli/dlls/`).
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Skills
|
||||
|
||||
After running `pbi connect`, Claude Code discovers **12 Power BI skills** automatically. Each skill teaches Claude a different area. You don't need to memorize commands.
|
||||
After running `pbi-cli skills install`, Claude Code discovers **12 Power BI skills**. Each skill teaches Claude a different area. You don't need to memorize commands.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/skills-hub.svg" alt="12 Skills" width="850"/>
|
||||
|
|
@ -147,35 +230,6 @@ After running `pbi connect`, Claude Code discovers **12 Power BI skills** automa
|
|||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/architecture-flow.svg" alt="Architecture" width="850"/>
|
||||
</p>
|
||||
|
||||
**Two layers, one CLI:**
|
||||
|
||||
- **Semantic Model layer** -- Direct in-process .NET interop from Python to Power BI Desktop via TOM/ADOMD. No MCP server, no external binaries, sub-second execution.
|
||||
- **Report layer** -- Reads and writes PBIR (Enhanced Report Format) JSON files directly. No connection needed. Works with `.pbip` projects.
|
||||
|
||||
<details>
|
||||
<summary><b>Configuration details</b></summary>
|
||||
|
||||
All config lives in `~/.pbi-cli/`:
|
||||
|
||||
```
|
||||
~/.pbi-cli/
|
||||
config.json # Default connection preference
|
||||
connections.json # Named connections
|
||||
repl_history # REPL command history
|
||||
```
|
||||
|
||||
Bundled DLLs ship inside the Python package (`pbi_cli/dlls/`).
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## All Commands
|
||||
|
||||
27 command groups covering both the semantic model and the report layer.
|
||||
|
|
@ -209,26 +263,6 @@ Run `pbi <command> --help` for full options.
|
|||
|
||||
---
|
||||
|
||||
## Supported Visual Types (32)
|
||||
|
||||
pbi-cli supports creating and binding data to 32 Power BI visual types:
|
||||
|
||||
**Charts:** bar, line, column, area, ribbon, waterfall, stacked bar, clustered bar, clustered column, scatter, funnel, combo, donut/pie, treemap
|
||||
|
||||
**Cards/KPIs:** card (legacy), cardVisual (modern), cardNew, multi-row card, KPI, gauge
|
||||
|
||||
**Tables:** table, matrix (pivot table)
|
||||
|
||||
**Slicers:** slicer, text slicer, list slicer, advanced slicer (tile/image)
|
||||
|
||||
**Maps:** Azure Map
|
||||
|
||||
**Decorative:** action button, image, shape, textbox, page navigator
|
||||
|
||||
Use friendly aliases: `pbi visual add --page p1 --type bar` instead of `--type barChart`.
|
||||
|
||||
---
|
||||
|
||||
## REPL Mode
|
||||
|
||||
For interactive work, the REPL keeps a persistent connection:
|
||||
|
|
@ -264,6 +298,21 @@ pytest -m "not e2e" # Run tests (488 tests)
|
|||
|
||||
---
|
||||
|
||||
## Bundled third-party software
|
||||
|
||||
`pbi-cli-tool` ships with Microsoft Analysis Services client library
|
||||
assemblies (`Microsoft.AnalysisServices.*.dll`) under `src/pbi_cli/dlls/`.
|
||||
These binaries are **not** covered by pbi-cli's MIT license. They are
|
||||
redistributed unmodified under the Microsoft Software License Terms for
|
||||
Microsoft Analysis Management Objects (AMO) and Microsoft Analysis
|
||||
Services - ADOMD.NET. Full terms are in
|
||||
[THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md) and the companion
|
||||
[NOTICE](NOTICE) file. By installing `pbi-cli-tool` you agree to those
|
||||
terms in addition to the MIT License that applies to the rest of the
|
||||
package.
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please open an issue first to discuss what you'd like to change.
|
||||
|
|
@ -281,5 +330,5 @@ Contributions are welcome! Please open an issue first to discuss what you'd like
|
|||
</p>
|
||||
|
||||
<p align="center">
|
||||
<sub>MIT License</sub>
|
||||
<sub>MIT License — bundled Microsoft DLLs are licensed separately, see <a href="THIRD_PARTY_LICENSES.md">THIRD_PARTY_LICENSES.md</a></sub>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -39,10 +39,11 @@ Install and set up pbi-cli from https://github.com/MinaSaad1/pbi-cli.git
|
|||
|
||||
```bash
|
||||
pipx install pbi-cli-tool # 1. Install (handles PATH automatically)
|
||||
pbi connect # 2. Auto-detects Power BI Desktop and installs skills
|
||||
pbi-cli skills install # 2. Register Claude Code skills (one-time setup)
|
||||
pbi connect # 3. Connect to Power BI Desktop
|
||||
```
|
||||
|
||||
That's it. Open Power BI Desktop with a `.pbix` file, run `pbi connect`, and everything is set up automatically. Open Claude Code and start asking.
|
||||
Open Power BI Desktop with a `.pbix` file, run the three commands above, and start asking Claude.
|
||||
|
||||
> **Requires:** Windows with Python 3.10+ and Power BI Desktop running.
|
||||
|
||||
|
|
@ -70,7 +71,7 @@ Add the printed path to your system PATH, then restart your terminal. We recomme
|
|||
|
||||
## Skills
|
||||
|
||||
After running `pbi connect`, Claude Code discovers **12 Power BI skills**. Each skill teaches Claude a different area. You don't need to memorize commands.
|
||||
After running `pbi-cli skills install`, Claude Code discovers **12 Power BI skills**. Each skill teaches Claude a different area. You don't need to memorize commands.
|
||||
|
||||
### Semantic Model (require `pbi connect`)
|
||||
|
||||
|
|
@ -172,6 +173,21 @@ pytest -m "not e2e" # Run tests (488 tests)
|
|||
|
||||
---
|
||||
|
||||
## Bundled third-party software
|
||||
|
||||
`pbi-cli-tool` ships with Microsoft Analysis Services client library
|
||||
assemblies (`Microsoft.AnalysisServices.*.dll`) inside the PyPI wheel
|
||||
under `src/pbi_cli/dlls/`. These binaries are **not** covered by
|
||||
pbi-cli's MIT license. They are redistributed unmodified under the
|
||||
Microsoft Software License Terms for Microsoft Analysis Management
|
||||
Objects (AMO) and Microsoft Analysis Services - ADOMD.NET. Full terms
|
||||
are in [THIRD_PARTY_LICENSES.md](https://github.com/MinaSaad1/pbi-cli/blob/master/THIRD_PARTY_LICENSES.md)
|
||||
and the companion [NOTICE](https://github.com/MinaSaad1/pbi-cli/blob/master/NOTICE)
|
||||
file. By installing `pbi-cli-tool` you agree to those terms in addition
|
||||
to the MIT License that applies to the rest of the package.
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please open an issue first to discuss what you'd like to change.
|
||||
|
|
@ -189,5 +205,5 @@ Contributions are welcome! Please open an issue first to discuss what you'd like
|
|||
</p>
|
||||
|
||||
<p align="center">
|
||||
<sub>MIT License</sub>
|
||||
<sub>MIT License — bundled Microsoft DLLs are licensed separately, see <a href="https://github.com/MinaSaad1/pbi-cli/blob/master/THIRD_PARTY_LICENSES.md">THIRD_PARTY_LICENSES.md</a></sub>
|
||||
</p>
|
||||
|
|
|
|||
121
SECURITY.md
|
|
@ -2,18 +2,24 @@
|
|||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
|---------|-----------|
|
||||
| 1.x | Yes |
|
||||
| < 1.0 | No |
|
||||
Only the latest 3.x release line receives security fixes. Users on older
|
||||
major versions should upgrade.
|
||||
|
||||
| Version | Supported |
|
||||
|----------|------------------|
|
||||
| 3.10.x | Yes |
|
||||
| 3.9.x | Critical fixes |
|
||||
| < 3.9 | No |
|
||||
| 2.x | No |
|
||||
| 1.x | No |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you discover a security vulnerability in pbi-cli, please report it responsibly.
|
||||
If you discover a security vulnerability in pbi-cli, please report it
|
||||
responsibly. **Do not open a public issue.**
|
||||
|
||||
**Do not open a public issue.**
|
||||
|
||||
Instead, email **security@pbi-cli.dev** or use [GitHub private vulnerability reporting](https://github.com/MinaSaad1/pbi-cli/security/advisories/new).
|
||||
Use [GitHub private vulnerability reporting](https://github.com/MinaSaad1/pbi-cli/security/advisories/new)
|
||||
to submit the report.
|
||||
|
||||
Please include:
|
||||
|
||||
|
|
@ -24,15 +30,96 @@ Please include:
|
|||
|
||||
## Response Timeline
|
||||
|
||||
- **Acknowledgment**: Within 48 hours
|
||||
- **Initial assessment**: Within 1 week
|
||||
- **Fix release**: As soon as possible, depending on severity
|
||||
- **Acknowledgment**: within 48 hours
|
||||
- **Initial assessment**: within 1 week
|
||||
- **Fix release**: as soon as possible, severity-dependent
|
||||
|
||||
## Security Considerations
|
||||
## Architecture and Trust Boundaries
|
||||
|
||||
pbi-cli connects to local Power BI instances via MCP (Model Context Protocol) over stdio. Key security notes:
|
||||
pbi-cli connects to a locally running Power BI Desktop instance via direct
|
||||
in-process .NET interop. **There is no MCP server, no subprocess, and no
|
||||
network listener.** Earlier 1.x releases used a separate
|
||||
`PBIDesktopMCPServer.exe` subprocess; that architecture was removed in 2.x
|
||||
and this document reflects the current 3.x behavior.
|
||||
|
||||
- **No network exposure**: The MCP server binary communicates over local stdio pipes, not network sockets
|
||||
- **No credentials stored**: Connection details are saved locally but passwords and tokens are never persisted
|
||||
- **Local binary execution**: pbi-cli launches `PBIDesktopMCPServer.exe` as a subprocess; ensure the binary is from a trusted source
|
||||
- **Config file permissions**: Connection store at `~/.pbi-cli/connections.json` should have user-only read/write permissions
|
||||
- **Direct in-process .NET interop.** pbi-cli loads bundled Microsoft
|
||||
Analysis Services Tabular Object Model (TOM) and ADOMD.NET client
|
||||
assemblies into the Python process via `pythonnet` / `clr-loader`. See
|
||||
`src/pbi_cli/core/dotnet_loader.py` and `src/pbi_cli/core/tom_backend.py`.
|
||||
The bundled DLLs live in `src/pbi_cli/dlls/` and are Microsoft binaries
|
||||
redistributed under the Microsoft Software License Terms for Microsoft
|
||||
Analysis Management Objects (AMO) and Microsoft Analysis Services -
|
||||
ADOMD.NET. See [`THIRD_PARTY_LICENSES.md`](THIRD_PARTY_LICENSES.md) and
|
||||
[`NOTICE`](NOTICE) for details.
|
||||
- **Local-only connection.** pbi-cli connects to Power BI Desktop's
|
||||
embedded Analysis Services instance over a loopback TCP port that
|
||||
Desktop binds at launch. pbi-cli does not open any network sockets for
|
||||
inbound traffic and does not communicate over the public network.
|
||||
- **No credentials persisted.** Connection metadata (port, workspace
|
||||
path, model name) is stored at `~/.pbi-cli/connections.json`. No
|
||||
tokens, passwords, or session credentials are written to disk. The
|
||||
file should have user-only read/write permissions.
|
||||
- **Report-layer commands are fully offline.** `pbi report`, `pbi visual`,
|
||||
`pbi filters`, `pbi bookmarks`, `pbi format`, and `pbi theme` operate on
|
||||
PBIR JSON files on disk. They do not require `pbi connect` and do not
|
||||
touch the Power BI Desktop process.
|
||||
|
||||
## Global Configuration Modifications
|
||||
|
||||
pbi-cli integrates with Claude Code by writing to the user's global Claude
|
||||
configuration directory. Users should be aware of exactly which files are
|
||||
modified and when. This section is authoritative; if behavior diverges from
|
||||
the description below, please file a security advisory.
|
||||
|
||||
### Files written
|
||||
|
||||
| Path | Written by | Contents |
|
||||
|--------------------------------|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `~/.claude/CLAUDE.md` | `pbi-cli skills install` | Appends a block wrapped in `<!-- pbi-cli:start -->` / `<!-- pbi-cli:end -->` markers that lists the 12 bundled skills and their trigger conditions. Source: `src/pbi_cli/core/claude_integration.py`. |
|
||||
| `~/.claude/skills/power-bi-*/` | `pbi-cli skills install` | Copies the 12 bundled `SKILL.md` files from `src/pbi_cli/skills/` so Claude Code discovers them. Source: `src/pbi_cli/commands/skills_cmd.py`. |
|
||||
|
||||
No other paths under `~/.claude/` are read or modified. pbi-cli does not
|
||||
access user conversation history, project memory files, unrelated skills,
|
||||
or any other Claude Code state.
|
||||
|
||||
### When it happens
|
||||
|
||||
- **`pbi-cli skills install`** — the explicit install command (3.10.3+).
|
||||
Before writing anything, the command displays the exact paths it will
|
||||
modify and requires the user to confirm. Passing `--yes` / `-y` skips
|
||||
the prompt for non-interactive use.
|
||||
- **`pbi-cli skills uninstall`** — removes the skill files and, when
|
||||
removing all skills, cleans up the `CLAUDE.md` block between its
|
||||
marker comments.
|
||||
- **`pbi connect`** — connects to Power BI Desktop only. It does **not**
|
||||
write to `~/.claude/`. On a successful connection it checks whether
|
||||
skills are installed and, if not, prints a one-line tip:
|
||||
`Run 'pbi-cli skills install' to register pbi-cli skills with Claude Code.`
|
||||
|
||||
### Opt-out
|
||||
|
||||
Claude Code integration is fully opt-in as of 3.10.3. Simply do not run
|
||||
`pbi-cli skills install` and `~/.claude/` is never touched.
|
||||
|
||||
To remove a previously installed integration, run:
|
||||
|
||||
```
|
||||
pbi-cli skills uninstall
|
||||
```
|
||||
|
||||
### Why this matters
|
||||
|
||||
`~/.claude/CLAUDE.md` is Claude Code's global instruction file and is
|
||||
loaded into every Claude Code session across every project on the
|
||||
machine. Modifying it affects Claude's behavior everywhere, not just for
|
||||
Power BI work. The pbi-cli block is bounded by comment markers so it can
|
||||
be removed cleanly, but users on multi-tenant or sensitive machines
|
||||
should be aware of this before running `pbi-cli skills install`.
|
||||
|
||||
## Bundled Binaries
|
||||
|
||||
`pbi-cli-tool` ships Microsoft Analysis Services client library DLLs
|
||||
inside the PyPI wheel under `src/pbi_cli/dlls/`. These are unmodified
|
||||
Microsoft binaries redistributed under Microsoft's own license terms,
|
||||
**not** under MIT. See [`THIRD_PARTY_LICENSES.md`](THIRD_PARTY_LICENSES.md)
|
||||
and [`NOTICE`](NOTICE) at the repo root for full terms and attribution.
|
||||
|
|
|
|||
329
THIRD_PARTY_LICENSES.md
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
# Third-Party Licenses
|
||||
|
||||
pbi-cli itself is licensed under the MIT License (see [LICENSE](LICENSE)). This
|
||||
file lists third-party software bundled with the `pbi-cli-tool` PyPI
|
||||
distribution and the license terms under which each component is
|
||||
redistributed.
|
||||
|
||||
---
|
||||
|
||||
## Microsoft Analysis Services Client Libraries
|
||||
|
||||
The following assemblies shipped under `src/pbi_cli/dlls/` inside the PyPI
|
||||
wheel are **not** covered by the MIT License. They are unmodified Microsoft
|
||||
Corporation binaries redistributed under the *Microsoft Software License
|
||||
Terms — Microsoft Analysis Management Objects (AMO)* and the materially
|
||||
identical *Microsoft Software License Terms — Microsoft Analysis Services –
|
||||
ADOMD.NET*:
|
||||
|
||||
| File | Governing License | Source NuGet Package |
|
||||
|--------------------------------------------------|-------------------------------|----------------------------------------------------------------|
|
||||
| `Microsoft.AnalysisServices.dll` | Microsoft AMO License | `Microsoft.AnalysisServices.NetCore.retail.amd64` |
|
||||
| `Microsoft.AnalysisServices.Core.dll` | Microsoft AMO License | `Microsoft.AnalysisServices.NetCore.retail.amd64` |
|
||||
| `Microsoft.AnalysisServices.Tabular.dll` | Microsoft AMO License | `Microsoft.AnalysisServices.NetCore.retail.amd64` |
|
||||
| `Microsoft.AnalysisServices.Tabular.Json.dll` | Microsoft AMO License | `Microsoft.AnalysisServices.NetCore.retail.amd64` |
|
||||
| `Microsoft.AnalysisServices.AdomdClient.dll` | Microsoft ADOMD.NET License | `Microsoft.AnalysisServices.AdomdClient.retail.amd64` |
|
||||
|
||||
These files are the object code form of code listed on Microsoft's REDIST
|
||||
list and are therefore distributable under the "DISTRIBUTABLE CODE" section
|
||||
of the governing license. pbi-cli does not modify, reverse engineer, decompile,
|
||||
or recompile them.
|
||||
|
||||
**Copyright © Microsoft Corporation. All rights reserved.**
|
||||
|
||||
By installing `pbi-cli-tool` you agree to the Microsoft license terms
|
||||
reproduced below in addition to the MIT License that applies to the rest of
|
||||
the package. If you do not agree to these terms, do not install or use
|
||||
`pbi-cli-tool`.
|
||||
|
||||
### Canonical sources
|
||||
|
||||
The original license documents are published by Microsoft at:
|
||||
|
||||
- AMO license: <https://go.microsoft.com/fwlink/?linkid=852989> (distributed as
|
||||
`AMO_Eula.zip`, English text in `Eula/EulaText_1033.rtf`)
|
||||
- ADOMD.NET license: <https://go.microsoft.com/fwlink/?linkid=852895>
|
||||
|
||||
The text below was extracted from the English (locale 1033) RTF file published
|
||||
by Microsoft. The ADOMD.NET license is materially identical to the AMO license
|
||||
reproduced here; the only difference is the product name line ("MICROSOFT
|
||||
ANALYSIS SERVICES – ADOMD.NET" instead of "MICROSOFT ANALYSIS MANAGEMENT
|
||||
OBJECTS (AMO)"). Both licenses apply to their respective bundled binaries as
|
||||
listed in the table above.
|
||||
|
||||
---
|
||||
|
||||
## MICROSOFT SOFTWARE LICENSE TERMS
|
||||
|
||||
### MICROSOFT ANALYSIS MANAGEMENT OBJECTS (AMO)
|
||||
|
||||
IF YOU LIVE IN (OR ARE A BUSINESS WITH A PRINCIPAL PLACE OF BUSINESS IN) THE
|
||||
UNITED STATES, PLEASE READ THE "BINDING ARBITRATION AND CLASS ACTION WAIVER"
|
||||
SECTION BELOW. IT AFFECTS HOW DISPUTES ARE RESOLVED.
|
||||
|
||||
These license terms are an agreement between you and Microsoft Corporation
|
||||
(or one of its affiliates). They apply to the software named above and any
|
||||
Microsoft services or software updates (except to the extent such services or
|
||||
updates are accompanied by new or additional terms, in which case those
|
||||
different terms apply prospectively and do not alter your or Microsoft's
|
||||
rights relating to pre-updated software or services). IF YOU COMPLY WITH
|
||||
THESE LICENSE TERMS, YOU HAVE THE RIGHTS BELOW. BY USING THE SOFTWARE, YOU
|
||||
ACCEPT THESE TERMS.
|
||||
|
||||
1. **INSTALLATION AND USE RIGHTS.**
|
||||
|
||||
a. **General.** You may install and use any number of copies of the
|
||||
software.
|
||||
|
||||
b. **Third Party Software.** The software may include third party
|
||||
applications that Microsoft, not the third party, licenses to you under
|
||||
this agreement. Any included notices for third party applications are
|
||||
for your information only.
|
||||
|
||||
c. **Competitive Benchmarking.** If you are a direct competitor, and you
|
||||
access or use the software for purposes of competitive benchmarking,
|
||||
analysis, or intelligence gathering, you waive as against Microsoft,
|
||||
its subsidiaries, and its affiliated companies (including prospectively)
|
||||
any competitive use, access, and benchmarking test restrictions in the
|
||||
terms governing your software to the extent your terms of use are, or
|
||||
purport to be, more restrictive than Microsoft's terms. If you do not
|
||||
waive any such purported restrictions in the terms governing your
|
||||
software, you are not allowed to access or use this software, and will
|
||||
not do so.
|
||||
|
||||
2. **DISTRIBUTABLE CODE.** The software may contain code you are permitted to
|
||||
distribute (i.e. make available for third parties) in applications you
|
||||
develop, as described in this Section.
|
||||
|
||||
a. **Distribution Rights.** The code and test files described below are
|
||||
distributable if included with the software.
|
||||
|
||||
i. **REDIST.TXT Files.** You may copy and distribute the object code
|
||||
form of code listed on the REDIST list in the software, if any, or
|
||||
listed at Redist.txt;
|
||||
|
||||
ii. **Image Library.** You may copy and distribute images, graphics,
|
||||
and animations in the Image Library as described in the software
|
||||
documentation; and
|
||||
|
||||
iii. **Third Party Distribution.** You may permit distributors of your
|
||||
applications to copy and distribute any of this distributable code
|
||||
you elect to distribute with your applications.
|
||||
|
||||
b. **Distribution Requirements.** For any code you distribute, you must:
|
||||
|
||||
i. add significant primary functionality to it in your applications;
|
||||
|
||||
ii. require distributors and external end users to agree to terms that
|
||||
protect it and Microsoft at least as much as this agreement; and
|
||||
|
||||
iii. indemnify, defend, and hold harmless Microsoft from any claims,
|
||||
including attorneys' fees, related to the distribution or use of
|
||||
your applications, except to the extent that any claim is based
|
||||
solely on the unmodified distributable code.
|
||||
|
||||
c. **Distribution Restrictions.** You may not:
|
||||
|
||||
i. use Microsoft's trademarks or trade dress in your application in any
|
||||
way that suggests your application comes from or is endorsed by
|
||||
Microsoft; or
|
||||
|
||||
ii. modify or distribute the source code of any distributable code so
|
||||
that any part of it becomes subject to any license that requires
|
||||
that the distributable code, any other part of the software, or any
|
||||
of Microsoft's other intellectual property be disclosed or
|
||||
distributed in source code form, or that others have the right to
|
||||
modify it.
|
||||
|
||||
3. **DATA COLLECTION.** The software may collect information about you and
|
||||
your use of the software and send that to Microsoft. Microsoft may use
|
||||
this information to provide services and improve Microsoft's products and
|
||||
services. Your opt-out rights, if any, are described in the product
|
||||
documentation. Some features in the software may enable collection of data
|
||||
from users of your applications that access or use the software. If you
|
||||
use these features to enable data collection in your applications, you
|
||||
must comply with applicable law, including getting any required user
|
||||
consent, and maintain a prominent privacy policy that accurately informs
|
||||
users about how you use, collect, and share their data. You can learn more
|
||||
about Microsoft's data collection and use in the product documentation and
|
||||
the Microsoft Privacy Statement at
|
||||
<https://go.microsoft.com/fwlink/?LinkId=521839>. You agree to comply with
|
||||
all applicable provisions of the Microsoft Privacy Statement.
|
||||
|
||||
4. **SCOPE OF LICENSE.** The software is licensed, not sold. Microsoft
|
||||
reserves all other rights. Unless applicable law gives you more rights
|
||||
despite this limitation, you will not (and have no right to):
|
||||
|
||||
a. work around any technical limitations in the software that only allow
|
||||
you to use it in certain ways;
|
||||
|
||||
b. reverse engineer, decompile or disassemble the software;
|
||||
|
||||
c. remove, minimize, block, or modify any notices of Microsoft or its
|
||||
suppliers in the software;
|
||||
|
||||
d. use the software in any way that is against the law or to create or
|
||||
propagate malware; or
|
||||
|
||||
e. share, publish, distribute, or lend the software (except for any
|
||||
distributable code, subject to the terms above), provide the software
|
||||
as a stand-alone hosted solution for others to use, or transfer the
|
||||
software or this agreement to any third party.
|
||||
|
||||
5. **EXPORT RESTRICTIONS.** You must comply with all domestic and
|
||||
international export laws and regulations that apply to the software,
|
||||
which include restrictions on destinations, end users, and end use. For
|
||||
further information on export restrictions, visit <http://aka.ms/exporting>.
|
||||
|
||||
6. **SUPPORT SERVICES.** Microsoft is not obligated under this agreement to
|
||||
provide any support services for the software. Any support provided is
|
||||
"as is", "with all faults", and without warranty of any kind.
|
||||
|
||||
7. **UPDATES.** The software may periodically check for updates, and download
|
||||
and install them for you. You may obtain updates only from Microsoft or
|
||||
authorized sources. Microsoft may need to update your system to provide
|
||||
you with updates. You agree to receive these automatic updates without any
|
||||
additional notice. Updates may not include or support all existing
|
||||
software features, services, or peripheral devices.
|
||||
|
||||
8. **BINDING ARBITRATION AND CLASS ACTION WAIVER.** This Section applies if
|
||||
you live in (or, if a business, your principal place of business is in)
|
||||
the United States. If you and Microsoft have a dispute, you and Microsoft
|
||||
agree to try for 60 days to resolve it informally. If you and Microsoft
|
||||
can't, you and Microsoft agree to binding individual arbitration before
|
||||
the American Arbitration Association under the Federal Arbitration Act
|
||||
("FAA"), and not to sue in court in front of a judge or jury. Instead, a
|
||||
neutral arbitrator will decide. Class action lawsuits, class-wide
|
||||
arbitrations, private attorney-general actions, and any other proceeding
|
||||
where someone acts in a representative capacity are not allowed; nor is
|
||||
combining individual proceedings without the consent of all parties. The
|
||||
complete Arbitration Agreement contains more terms and is at
|
||||
<http://aka.ms/arb-agreement-1>. You and Microsoft agree to these terms.
|
||||
|
||||
9. **ENTIRE AGREEMENT.** This agreement, and any other terms Microsoft may
|
||||
provide for supplements, updates, or third-party applications, is the
|
||||
entire agreement for the software.
|
||||
|
||||
10. **APPLICABLE LAW AND PLACE TO RESOLVE DISPUTES.** If you acquired the
|
||||
software in the United States or Canada, the laws of the state or
|
||||
province where you live (or, if a business, where your principal place of
|
||||
business is located) govern the interpretation of this agreement, claims
|
||||
for its breach, and all other claims (including consumer protection,
|
||||
unfair competition, and tort claims), regardless of conflict of laws
|
||||
principles, except that the FAA governs everything related to arbitration.
|
||||
If you acquired the software in any other country, its laws apply, except
|
||||
that the FAA governs everything related to arbitration. If U.S. federal
|
||||
jurisdiction exists, you and Microsoft consent to exclusive jurisdiction
|
||||
and venue in the federal court in King County, Washington for all
|
||||
disputes heard in court (excluding arbitration). If not, you and
|
||||
Microsoft consent to exclusive jurisdiction and venue in the Superior
|
||||
Court of King County, Washington for all disputes heard in court
|
||||
(excluding arbitration).
|
||||
|
||||
11. **CONSUMER RIGHTS; REGIONAL VARIATIONS.** This agreement describes
|
||||
certain legal rights. You may have other rights, including consumer
|
||||
rights, under the laws of your state, province, or country. Separate and
|
||||
apart from your relationship with Microsoft, you may also have rights
|
||||
with respect to the party from which you acquired the software. This
|
||||
agreement does not change those other rights if the laws of your state,
|
||||
province, or country do not permit it to do so. For example, if you
|
||||
acquired the software in one of the below regions, or mandatory country
|
||||
law applies, then the following provisions apply to you:
|
||||
|
||||
a. **Australia.** You have statutory guarantees under the Australian
|
||||
Consumer Law and nothing in this agreement is intended to affect those
|
||||
rights.
|
||||
|
||||
b. **Canada.** If you acquired this software in Canada, you may stop
|
||||
receiving updates by turning off the automatic update feature,
|
||||
disconnecting your device from the Internet (if and when you
|
||||
re-connect to the Internet, however, the software will resume checking
|
||||
for and installing updates), or uninstalling the software. The product
|
||||
documentation, if any, may also specify how to turn off updates for
|
||||
your specific device or software.
|
||||
|
||||
c. **Germany and Austria.**
|
||||
|
||||
i. **Warranty.** The properly licensed software will perform
|
||||
substantially as described in any Microsoft materials that
|
||||
accompany the software. However, Microsoft gives no contractual
|
||||
guarantee in relation to the licensed software.
|
||||
|
||||
ii. **Limitation of Liability.** In case of intentional conduct, gross
|
||||
negligence, claims based on the Product Liability Act, as well as,
|
||||
in case of death or personal or physical injury, Microsoft is
|
||||
liable according to the statutory law.
|
||||
|
||||
Subject to the foregoing clause ii., Microsoft will only be liable for
|
||||
slight negligence if Microsoft is in breach of such material
|
||||
contractual obligations, the fulfillment of which facilitate the due
|
||||
performance of this agreement, the breach of which would endanger the
|
||||
purpose of this agreement and the compliance with which a party may
|
||||
constantly trust in (so-called "cardinal obligations"). In other cases
|
||||
of slight negligence, Microsoft will not be liable for slight
|
||||
negligence.
|
||||
|
||||
12. **DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED "AS IS." YOU BEAR THE
|
||||
RISK OF USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES, OR
|
||||
CONDITIONS. TO THE EXTENT PERMITTED UNDER APPLICABLE LAWS, MICROSOFT
|
||||
EXCLUDES ALL IMPLIED WARRANTIES, INCLUDING MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE, AND NON-INFRINGEMENT.**
|
||||
|
||||
13. **LIMITATION ON AND EXCLUSION OF DAMAGES. IF YOU HAVE ANY BASIS FOR
|
||||
RECOVERING DAMAGES DESPITE THE PRECEDING DISCLAIMER OF WARRANTY, YOU CAN
|
||||
RECOVER FROM MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO U.S.
|
||||
$5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL,
|
||||
LOST PROFITS, SPECIAL, INDIRECT OR INCIDENTAL DAMAGES.**
|
||||
|
||||
This limitation applies to (a) anything related to the software,
|
||||
services, content (including code) on third party Internet sites, or
|
||||
third party applications; and (b) claims for breach of contract, warranty,
|
||||
guarantee, or condition; strict liability, negligence, or other tort; or
|
||||
any other claim; in each case to the extent permitted by applicable law.
|
||||
|
||||
It also applies even if Microsoft knew or should have known about the
|
||||
possibility of the damages. The above limitation or exclusion may not
|
||||
apply to you because your state, province, or country may not allow the
|
||||
exclusion or limitation of incidental, consequential, or other damages.
|
||||
|
||||
---
|
||||
|
||||
*Please note: As this software is distributed in Canada, some of the clauses
|
||||
in this agreement are provided below in French.*
|
||||
|
||||
*Remarque: Ce logiciel étant distribué au Canada, certaines des clauses dans
|
||||
ce contrat sont fournies ci-dessous en français.*
|
||||
|
||||
**EXONÉRATION DE GARANTIE.** Le logiciel visé par une licence est offert
|
||||
« tel quel ». Toute utilisation de ce logiciel est à votre seule risque et
|
||||
péril. Microsoft n'accorde aucune autre garantie expresse. Vous pouvez
|
||||
bénéficier de droits additionnels en vertu du droit local sur la protection
|
||||
des consommateurs, que ce contrat ne peut modifier. La ou elles sont
|
||||
permises par le droit locale, les garanties implicites de qualité marchande,
|
||||
d'adéquation à un usage particulier et d'absence de contrefaçon sont exclues.
|
||||
|
||||
**LIMITATION DES DOMMAGES-INTÉRÊTS ET EXCLUSION DE RESPONSABILITÉ POUR LES
|
||||
DOMMAGES.** Vous pouvez obtenir de Microsoft et de ses fournisseurs une
|
||||
indemnisation en cas de dommages directs uniquement à hauteur de 5,00 $ US.
|
||||
Vous ne pouvez prétendre à aucune indemnisation pour les autres dommages, y
|
||||
compris les dommages spéciaux, indirects ou accessoires et pertes de
|
||||
bénéfices.
|
||||
|
||||
Cette limitation concerne:
|
||||
|
||||
- tout ce qui est relié au logiciel, aux services ou au contenu (y compris le
|
||||
code) figurant sur des sites Internet tiers ou dans des programmes tiers; et
|
||||
- les réclamations au titre de violation de contrat ou de garantie, ou au
|
||||
titre de responsabilité stricte, de négligence ou d'une autre faute dans la
|
||||
limite autorisée par la loi en vigueur.
|
||||
|
||||
Elle s'applique également, même si Microsoft connaissait ou devrait connaître
|
||||
l'éventualité d'un tel dommage. Si votre pays n'autorise pas l'exclusion ou
|
||||
la limitation de responsabilité pour les dommages indirects, accessoires ou
|
||||
de quelque nature que ce soit, il se peut que la limitation ou l'exclusion
|
||||
ci-dessus ne s'appliquera pas à votre égard.
|
||||
|
||||
**EFFET JURIDIQUE.** Le présent contrat décrit certains droits juridiques.
|
||||
Vous pourriez avoir d'autres droits prévus par les lois de votre pays. Le
|
||||
présent contrat ne modifie pas les droits que vous confèrent les lois de
|
||||
votre pays si celles-ci ne le permettent pas.
|
||||
|
|
@ -1,51 +1,80 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="150" viewBox="0 0 850 150">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="230" viewBox="0 0 850 230">
|
||||
<rect width="100%" height="100%" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="425" y="28" font-family="'Segoe UI', Arial, sans-serif" font-size="18" fill="#F2C811" text-anchor="middle" font-weight="bold">Architecture</text>
|
||||
<text x="425" y="28" font-family="'Segoe UI', Arial, sans-serif" font-size="18" fill="#F2C811" text-anchor="middle" font-weight="bold">Architecture: Two Layers, One CLI</text>
|
||||
|
||||
<!-- Box 1: Claude Code -->
|
||||
<rect x="15" y="50" width="130" height="75" rx="8" fill="#161b22" stroke="#d97757" stroke-width="2"/>
|
||||
<text x="80" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#d97757" text-anchor="middle" font-weight="bold">Claude Code</text>
|
||||
<text x="80" y="104" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">AI Agent</text>
|
||||
<rect x="15" y="65" width="110" height="130" rx="8" fill="#161b22" stroke="#d97757" stroke-width="2"/>
|
||||
<text x="70" y="105" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#d97757" text-anchor="middle" font-weight="bold">Claude</text>
|
||||
<text x="70" y="120" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#d97757" text-anchor="middle" font-weight="bold">Code</text>
|
||||
<text x="70" y="142" font-family="'Courier New', Courier, monospace" font-size="13" fill="#8b949e" text-anchor="middle">AI Agent</text>
|
||||
<text x="70" y="157" font-family="'Courier New', Courier, monospace" font-size="13" fill="#8b949e" text-anchor="middle">12 Skills</text>
|
||||
|
||||
<!-- Arrow 1 > 2 -->
|
||||
<line x1="149" y1="88" x2="185" y2="88" stroke="#F2C811" stroke-width="2" stroke-dasharray="4,3" stroke-opacity="0.5"/>
|
||||
<polygon points="189,88 182,83 182,93" fill="#F2C811" fill-opacity="0.5"/>
|
||||
<text x="169" y="78" font-family="'Segoe UI', Arial, sans-serif" font-size="8" fill="#8b949e" text-anchor="middle">CLI</text>
|
||||
<!-- Arrow Claude > pbi-cli -->
|
||||
<line x1="129" y1="130" x2="172" y2="130" stroke="#F2C811" stroke-width="2" stroke-dasharray="4,3" stroke-opacity="0.4"/>
|
||||
<polygon points="176,130 169,125 169,135" fill="#F2C811" fill-opacity="0.5"/>
|
||||
|
||||
<!-- Box 2: pbi-cli -->
|
||||
<rect x="193" y="50" width="130" height="75" rx="8" fill="#161b22" stroke="#58a6ff" stroke-width="2"/>
|
||||
<text x="258" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#58a6ff" text-anchor="middle" font-weight="bold">pbi-cli</text>
|
||||
<text x="258" y="104" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">Python + Click</text>
|
||||
<!-- Box 2: pbi-cli (taller, showing two paths) -->
|
||||
<rect x="180" y="65" width="120" height="130" rx="8" fill="#161b22" stroke="#F2C811" stroke-width="2"/>
|
||||
<text x="240" y="95" font-family="'Segoe UI', Arial, sans-serif" font-size="15" fill="#F2C811" text-anchor="middle" font-weight="bold">pbi-cli</text>
|
||||
<line x1="195" y1="105" x2="285" y2="105" stroke="#F2C811" stroke-width="1" stroke-opacity="0.15"/>
|
||||
<text x="240" y="125" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#58a6ff" text-anchor="middle">TOM Backend</text>
|
||||
<text x="240" y="140" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e" text-anchor="middle">(.NET interop)</text>
|
||||
<line x1="195" y1="148" x2="285" y2="148" stroke="#F2C811" stroke-width="1" stroke-opacity="0.1"/>
|
||||
<text x="240" y="167" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0" text-anchor="middle">PBIR Backend</text>
|
||||
<text x="240" y="182" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e" text-anchor="middle">(JSON files)</text>
|
||||
|
||||
<!-- Arrow 2 > 3 (wider gap for "in-process" label) -->
|
||||
<line x1="327" y1="88" x2="393" y2="88" stroke="#F2C811" stroke-width="2" stroke-dasharray="4,3" stroke-opacity="0.5"/>
|
||||
<polygon points="397,88 390,83 390,93" fill="#F2C811" fill-opacity="0.5"/>
|
||||
<text x="362" y="78" font-family="'Segoe UI', Arial, sans-serif" font-size="9" fill="#8b949e" text-anchor="middle">in-process</text>
|
||||
<!-- ============= TOP PATH: Modeling Layer ============= -->
|
||||
|
||||
<!-- Box 3: pythonnet -->
|
||||
<rect x="401" y="50" width="120" height="75" rx="8" fill="#161b22" stroke="#7b61ff" stroke-width="2"/>
|
||||
<text x="461" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#7b61ff" text-anchor="middle" font-weight="bold">pythonnet</text>
|
||||
<text x="461" y="104" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">CLR Bridge</text>
|
||||
<!-- Arrow pbi-cli > pythonnet (top) -->
|
||||
<line x1="304" y1="105" x2="362" y2="105" stroke="#58a6ff" stroke-width="2" stroke-dasharray="4,3" stroke-opacity="0.4"/>
|
||||
<polygon points="366,105 359,100 359,110" fill="#58a6ff" fill-opacity="0.5"/>
|
||||
<text x="335" y="96" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">in-process</text>
|
||||
|
||||
<!-- Arrow 3 > 4 -->
|
||||
<line x1="525" y1="88" x2="561" y2="88" stroke="#F2C811" stroke-width="2" stroke-dasharray="4,3" stroke-opacity="0.5"/>
|
||||
<polygon points="565,88 558,83 558,93" fill="#F2C811" fill-opacity="0.5"/>
|
||||
<text x="545" y="78" font-family="'Segoe UI', Arial, sans-serif" font-size="8" fill="#8b949e" text-anchor="middle">.NET calls</text>
|
||||
<!-- Box 3a: pythonnet -->
|
||||
<rect x="370" y="73" width="100" height="55" rx="8" fill="#161b22" stroke="#7b61ff" stroke-width="1.5"/>
|
||||
<text x="420" y="98" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#7b61ff" text-anchor="middle" font-weight="bold">pythonnet</text>
|
||||
<text x="420" y="114" font-family="'Courier New', Courier, monospace" font-size="13" fill="#8b949e" text-anchor="middle">CLR Bridge</text>
|
||||
|
||||
<!-- Box 4: .NET TOM -->
|
||||
<rect x="569" y="50" width="130" height="75" rx="8" fill="#161b22" stroke="#F2C811" stroke-width="2"/>
|
||||
<text x="634" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#F2C811" text-anchor="middle" font-weight="bold">.NET TOM</text>
|
||||
<text x="634" y="104" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">ADOMD.NET</text>
|
||||
<!-- Arrow pythonnet > .NET TOM -->
|
||||
<line x1="474" y1="100" x2="512" y2="100" stroke="#58a6ff" stroke-width="2" stroke-dasharray="4,3" stroke-opacity="0.4"/>
|
||||
<polygon points="516,100 509,95 509,105" fill="#58a6ff" fill-opacity="0.5"/>
|
||||
|
||||
<!-- Arrow 4 > 5 -->
|
||||
<line x1="703" y1="88" x2="727" y2="88" stroke="#F2C811" stroke-width="2" stroke-dasharray="4,3" stroke-opacity="0.5"/>
|
||||
<polygon points="731,88 724,83 724,93" fill="#F2C811" fill-opacity="0.5"/>
|
||||
<text x="717" y="78" font-family="'Segoe UI', Arial, sans-serif" font-size="8" fill="#8b949e" text-anchor="middle">XMLA</text>
|
||||
<!-- Box 4a: .NET TOM -->
|
||||
<rect x="520" y="73" width="110" height="55" rx="8" fill="#161b22" stroke="#58a6ff" stroke-width="1.5"/>
|
||||
<text x="575" y="98" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#58a6ff" text-anchor="middle" font-weight="bold">.NET TOM</text>
|
||||
<text x="575" y="114" font-family="'Courier New', Courier, monospace" font-size="13" fill="#8b949e" text-anchor="middle">ADOMD.NET</text>
|
||||
|
||||
<!-- Box 5: Power BI -->
|
||||
<rect x="735" y="50" width="100" height="75" rx="8" fill="#161b22" stroke="#F2C811" stroke-width="2"/>
|
||||
<text x="785" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#F2C811" text-anchor="middle" font-weight="bold">Power BI</text>
|
||||
<text x="785" y="104" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">msmdsrv.exe</text>
|
||||
<!-- Arrow .NET TOM > Power BI (top) -->
|
||||
<line x1="634" y1="100" x2="700" y2="100" stroke="#58a6ff" stroke-width="2" stroke-dasharray="4,3" stroke-opacity="0.4"/>
|
||||
<polygon points="704,100 697,95 697,105" fill="#58a6ff" fill-opacity="0.5"/>
|
||||
<text x="670" y="91" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">XMLA</text>
|
||||
|
||||
<!-- ============= BOTTOM PATH: Report Layer ============= -->
|
||||
|
||||
<!-- Arrow pbi-cli > PBIR Files (bottom) -->
|
||||
<line x1="304" y1="170" x2="520" y2="170" stroke="#06d6a0" stroke-width="2" stroke-dasharray="4,3" stroke-opacity="0.4"/>
|
||||
<polygon points="524,170 517,165 517,175" fill="#06d6a0" fill-opacity="0.5"/>
|
||||
<text x="412" y="161" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">read/write JSON</text>
|
||||
|
||||
<!-- Box 3b: PBIR Files -->
|
||||
<rect x="528" y="143" width="100" height="55" rx="8" fill="#161b22" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<text x="578" y="168" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0" text-anchor="middle" font-weight="bold">PBIR Files</text>
|
||||
<text x="578" y="184" font-family="'Courier New', Courier, monospace" font-size="13" fill="#8b949e" text-anchor="middle">.Report/</text>
|
||||
|
||||
<!-- Arrow PBIR > Power BI (bottom, with auto-sync label) -->
|
||||
<line x1="632" y1="170" x2="700" y2="170" stroke="#06d6a0" stroke-width="2" stroke-dasharray="4,3" stroke-opacity="0.4"/>
|
||||
<polygon points="704,170 697,165 697,175" fill="#06d6a0" fill-opacity="0.5"/>
|
||||
<text x="670" y="161" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">auto-sync</text>
|
||||
|
||||
<!-- Box 5: Power BI Desktop (spans both paths) -->
|
||||
<rect x="708" y="65" width="128" height="140" rx="8" fill="#161b22" stroke="#F2C811" stroke-width="2"/>
|
||||
<text x="772" y="112" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#F2C811" text-anchor="middle" font-weight="bold">Power BI</text>
|
||||
<text x="772" y="130" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#F2C811" text-anchor="middle" font-weight="bold">Desktop</text>
|
||||
<text x="772" y="155" font-family="'Courier New', Courier, monospace" font-size="13" fill="#8b949e" text-anchor="middle">Semantic Model</text>
|
||||
<text x="772" y="170" font-family="'Courier New', Courier, monospace" font-size="13" fill="#8b949e" text-anchor="middle">+ Report</text>
|
||||
|
||||
<!-- Footer -->
|
||||
<text x="425" y="218" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">No MCP server, no external binaries. Sub-second execution.</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 6.4 KiB |
118
assets/auto-sync.svg
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="380" viewBox="0 0 850 380">
|
||||
<defs>
|
||||
<linearGradient id="sync-glow" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#06d6a0" stop-opacity="0"/>
|
||||
<stop offset="50%" stop-color="#06d6a0" stop-opacity="0.15"/>
|
||||
<stop offset="100%" stop-color="#06d6a0" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="425" y="30" font-family="'Segoe UI', Arial, sans-serif" font-size="20" fill="#06d6a0" text-anchor="middle" font-weight="bold">Desktop Auto-Sync</text>
|
||||
<text x="425" y="52" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">Report changes appear in Desktop automatically. Your modeling work is preserved.</text>
|
||||
|
||||
<!-- ============ 4 Steps: equal width, centered ============ -->
|
||||
<!-- Card width=160, arrow gap=30, total = 4*160 + 3*30 = 730, start x = (850-730)/2 = 60 -->
|
||||
|
||||
<!-- Step 1: CLI Writes -->
|
||||
<rect x="60" y="75" width="160" height="165" rx="10" fill="#161b22" stroke="#58a6ff" stroke-width="1.5"/>
|
||||
<!-- Step badge -->
|
||||
<circle cx="140" cy="92" r="14" fill="#58a6ff"/>
|
||||
<text x="140" y="98" font-family="'Segoe UI', Arial, sans-serif" font-size="15" fill="#0d1117" text-anchor="middle" font-weight="bold">1</text>
|
||||
<text x="140" y="126" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#58a6ff" text-anchor="middle" font-weight="bold">CLI Writes</text>
|
||||
<!-- File icon -->
|
||||
<g transform="translate(110, 140)">
|
||||
<rect x="0" y="0" width="32" height="40" rx="3" fill="none" stroke="#58a6ff" stroke-width="1.5"/>
|
||||
<line x1="7" y1="11" x2="25" y2="11" stroke="#58a6ff" stroke-width="1" stroke-opacity="0.4"/>
|
||||
<line x1="7" y1="19" x2="25" y2="19" stroke="#58a6ff" stroke-width="1" stroke-opacity="0.4"/>
|
||||
<line x1="7" y1="27" x2="18" y2="27" stroke="#58a6ff" stroke-width="1" stroke-opacity="0.4"/>
|
||||
<!-- Pencil -->
|
||||
<line x1="26" y1="30" x2="38" y2="18" stroke="#06d6a0" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<circle cx="38" cy="18" r="2.5" fill="#06d6a0"/>
|
||||
</g>
|
||||
<text x="140" y="200" font-family="'Courier New', Courier, monospace" font-size="11" fill="#8b949e" text-anchor="middle">page.json</text>
|
||||
<text x="140" y="215" font-family="'Courier New', Courier, monospace" font-size="11" fill="#8b949e" text-anchor="middle">visual.json</text>
|
||||
<text x="140" y="230" font-family="'Courier New', Courier, monospace" font-size="11" fill="#8b949e" text-anchor="middle">filters</text>
|
||||
|
||||
<!-- Arrow 1 > 2 -->
|
||||
<line x1="225" y1="155" x2="248" y2="155" stroke="#F2C811" stroke-width="2.5"/>
|
||||
<polygon points="253,155 245,149 245,161" fill="#F2C811"/>
|
||||
|
||||
<!-- Step 2: Snapshot -->
|
||||
<rect x="258" y="75" width="160" height="165" rx="10" fill="#161b22" stroke="#F2C811" stroke-width="1.5"/>
|
||||
<circle cx="338" cy="92" r="14" fill="#F2C811"/>
|
||||
<text x="338" y="98" font-family="'Segoe UI', Arial, sans-serif" font-size="15" fill="#0d1117" text-anchor="middle" font-weight="bold">2</text>
|
||||
<text x="338" y="126" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#F2C811" text-anchor="middle" font-weight="bold">Snapshot</text>
|
||||
<!-- Camera icon -->
|
||||
<g transform="translate(308, 140)">
|
||||
<rect x="0" y="10" width="50" height="34" rx="5" fill="none" stroke="#F2C811" stroke-width="1.5"/>
|
||||
<circle cx="25" cy="27" r="11" fill="none" stroke="#F2C811" stroke-width="1.5"/>
|
||||
<circle cx="25" cy="27" r="5" fill="#F2C811" opacity="0.3"/>
|
||||
<rect x="16" y="4" width="18" height="10" rx="3" fill="none" stroke="#F2C811" stroke-width="1.2"/>
|
||||
</g>
|
||||
<text x="338" y="205" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e" text-anchor="middle">Captures your</text>
|
||||
<text x="338" y="221" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#F2C811" text-anchor="middle" font-weight="600">PBIR changes</text>
|
||||
|
||||
<!-- Arrow 2 > 3 -->
|
||||
<line x1="423" y1="155" x2="446" y2="155" stroke="#F2C811" stroke-width="2.5"/>
|
||||
<polygon points="451,155 443,149 443,161" fill="#F2C811"/>
|
||||
|
||||
<!-- Step 3: Desktop Saves -->
|
||||
<rect x="456" y="75" width="160" height="165" rx="10" fill="#161b22" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<circle cx="536" cy="92" r="14" fill="#06d6a0"/>
|
||||
<text x="536" y="98" font-family="'Segoe UI', Arial, sans-serif" font-size="15" fill="#0d1117" text-anchor="middle" font-weight="bold">3</text>
|
||||
<text x="536" y="126" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#06d6a0" text-anchor="middle" font-weight="bold">Desktop Saves</text>
|
||||
<!-- Save icon with checkmark -->
|
||||
<g transform="translate(508, 140)">
|
||||
<rect x="0" y="0" width="44" height="44" rx="5" fill="none" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<rect x="10" y="0" width="24" height="16" rx="3" fill="none" stroke="#06d6a0" stroke-width="1" stroke-opacity="0.5"/>
|
||||
<rect x="7" y="26" width="30" height="16" rx="3" fill="#06d6a0" opacity="0.12"/>
|
||||
<!-- Large checkmark -->
|
||||
<polyline points="14,36 20,42 32,28" fill="none" stroke="#06d6a0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<text x="536" y="205" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e" text-anchor="middle">Preserves your</text>
|
||||
<text x="536" y="221" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0" text-anchor="middle" font-weight="600">modeling work</text>
|
||||
|
||||
<!-- Arrow 3 > 4 -->
|
||||
<line x1="621" y1="155" x2="644" y2="155" stroke="#F2C811" stroke-width="2.5"/>
|
||||
<polygon points="649,155 641,149 641,161" fill="#F2C811"/>
|
||||
|
||||
<!-- Step 4: Re-apply + Reopen -->
|
||||
<rect x="654" y="75" width="160" height="165" rx="10" fill="#161b22" stroke="#d97757" stroke-width="1.5"/>
|
||||
<circle cx="734" cy="92" r="14" fill="#d97757"/>
|
||||
<text x="734" y="98" font-family="'Segoe UI', Arial, sans-serif" font-size="15" fill="#0d1117" text-anchor="middle" font-weight="bold">4</text>
|
||||
<text x="734" y="126" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#d97757" text-anchor="middle" font-weight="bold">Reopen</text>
|
||||
<!-- Refresh icon: simple two-tone circle with PBI logo -->
|
||||
<g transform="translate(710, 142)">
|
||||
<circle cx="24" cy="24" r="22" fill="none" stroke="#d97757" stroke-width="2.5" stroke-dasharray="69 69" stroke-dashoffset="0"/>
|
||||
<circle cx="24" cy="24" r="22" fill="none" stroke="#06d6a0" stroke-width="2.5" stroke-dasharray="69 69" stroke-dashoffset="69"/>
|
||||
|
||||
<!-- Center PBI mini icon -->
|
||||
<rect x="13" y="13" width="7" height="20" rx="2" fill="#F2C811" opacity="0.6"/>
|
||||
<rect x="21" y="18" width="7" height="15" rx="2" fill="#F2C811" opacity="0.8"/>
|
||||
<rect x="29" y="22" width="7" height="11" rx="2" fill="#F2C811"/>
|
||||
</g>
|
||||
<text x="734" y="205" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e" text-anchor="middle">PBIR restored,</text>
|
||||
<text x="734" y="221" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#d97757" text-anchor="middle" font-weight="600">Desktop reopens</text>
|
||||
|
||||
<!-- ============ Bottom comparison ============ -->
|
||||
<rect x="60" y="258" width="730" height="1" fill="url(#sync-glow)"/>
|
||||
|
||||
<!-- Without -->
|
||||
<rect x="60" y="275" width="350" height="85" rx="8" fill="#161b22" stroke="#ff6b6b" stroke-width="1" stroke-opacity="0.5"/>
|
||||
<text x="235" y="300" font-family="'Segoe UI', Arial, sans-serif" font-size="15" fill="#ff6b6b" text-anchor="middle" font-weight="bold">Without Auto-Sync</text>
|
||||
<text x="90" y="324" font-family="'Segoe UI', Arial, sans-serif" font-size="15" fill="#ff6b6b">✗</text>
|
||||
<text x="110" y="324" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Close Desktop manually every time</text>
|
||||
<text x="90" y="346" font-family="'Segoe UI', Arial, sans-serif" font-size="15" fill="#ff6b6b">✗</text>
|
||||
<text x="110" y="346" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Risk losing unsaved modeling work</text>
|
||||
|
||||
<!-- With -->
|
||||
<rect x="440" y="275" width="350" height="85" rx="8" fill="#161b22" stroke="#06d6a0" stroke-width="1" stroke-opacity="0.5"/>
|
||||
<text x="615" y="300" font-family="'Segoe UI', Arial, sans-serif" font-size="15" fill="#06d6a0" text-anchor="middle" font-weight="bold">With Auto-Sync</text>
|
||||
<text x="470" y="324" font-family="'Segoe UI', Arial, sans-serif" font-size="15" fill="#06d6a0">✓</text>
|
||||
<text x="490" y="324" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#e6edf3">Desktop reopens automatically</text>
|
||||
<text x="470" y="346" font-family="'Segoe UI', Arial, sans-serif" font-size="15" fill="#06d6a0">✓</text>
|
||||
<text x="490" y="346" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#e6edf3">All work preserved -- both layers</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.6 KiB |
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<!-- Title -->
|
||||
<text x="425" y="32" font-family="'Segoe UI', Arial, sans-serif" font-size="18" fill="#F2C811" text-anchor="middle" font-weight="bold">Undo Anything. Instantly.</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Snapshot your model before risky changes, restore when things go wrong</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">Snapshot your model before risky changes, restore when things go wrong</text>
|
||||
|
||||
<!-- Chat container -->
|
||||
<rect x="40" y="65" width="770" height="420" rx="8" fill="#161b22" stroke="#30363d" stroke-width="1"/>
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<circle cx="62" cy="80" r="5" fill="#ff5f57"/>
|
||||
<circle cx="78" cy="80" r="5" fill="#febc2e"/>
|
||||
<circle cx="94" cy="80" r="5" fill="#28c840"/>
|
||||
<text x="116" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#d97757" font-weight="bold">Claude Code</text>
|
||||
<text x="116" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#d97757" font-weight="bold">Claude Code</text>
|
||||
|
||||
<!-- User message 1 -->
|
||||
<rect x="280" y="106" width="510" height="28" rx="12" fill="#1a3a5c" stroke="#264d73" stroke-width="1"/>
|
||||
|
|
@ -22,14 +22,14 @@
|
|||
|
||||
<!-- Claude response 1 -->
|
||||
<rect x="60" y="146" width="520" height="62" rx="12" fill="#1c1c1c" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="166" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="166" font-family="'Courier New', Courier, monospace" font-size="10" fill="#58a6ff">pbi database export-tmdl ./snapshots/before-restructure/</text>
|
||||
<text x="78" y="166" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="166" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff">pbi database export-tmdl ./snapshots/before-restructure/</text>
|
||||
<text x="78" y="186" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">✓ Snapshot saved: 12 tables, 34 measures, 11 relationships</text>
|
||||
<text x="78" y="202" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Go ahead and make your changes. You can always roll back.</text>
|
||||
<text x="78" y="202" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">Go ahead and make your changes. You can always roll back.</text>
|
||||
|
||||
<!-- Time indicator -->
|
||||
<rect x="340" y="222" width="170" height="22" rx="11" fill="#21262d"/>
|
||||
<text x="425" y="237" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">30 minutes later...</text>
|
||||
<text x="425" y="237" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e" text-anchor="middle">30 minutes later...</text>
|
||||
|
||||
<!-- User message 2 -->
|
||||
<rect x="310" y="256" width="480" height="44" rx="12" fill="#1a3a5c" stroke="#264d73" stroke-width="1"/>
|
||||
|
|
@ -38,11 +38,11 @@
|
|||
|
||||
<!-- Claude response 2 -->
|
||||
<rect x="60" y="312" width="540" height="80" rx="12" fill="#1c1c1c" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="332" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="332" font-family="'Courier New', Courier, monospace" font-size="10" fill="#58a6ff">pbi database import-tmdl ./snapshots/before-restructure/</text>
|
||||
<text x="78" y="332" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="332" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff">pbi database import-tmdl ./snapshots/before-restructure/</text>
|
||||
<text x="78" y="352" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">✓ Model restored to pre-restructure state</text>
|
||||
<text x="78" y="370" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#c9d1d9">All 12 tables, 34 measures, and 11 relationships are back.</text>
|
||||
<text x="78" y="386" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Your measures should be working again. Refresh Power BI to see the changes.</text>
|
||||
<text x="78" y="370" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#c9d1d9">All 12 tables, 34 measures, and 11 relationships are back.</text>
|
||||
<text x="78" y="386" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">Your measures should be working again. Refresh Power BI to see the changes.</text>
|
||||
|
||||
<!-- User message 3 -->
|
||||
<rect x="560" y="404" width="230" height="28" rx="12" fill="#1a3a5c" stroke="#264d73" stroke-width="1"/>
|
||||
|
|
@ -50,8 +50,8 @@
|
|||
|
||||
<!-- Claude response 3 -->
|
||||
<rect x="60" y="440" width="300" height="32" rx="12" fill="#1c1c1c" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="461" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#c9d1d9">That's what snapshots are for. Want to save a new one?</text>
|
||||
<text x="78" y="461" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#c9d1d9">That's what snapshots are for. Want to save a new one?</text>
|
||||
|
||||
<!-- Footer -->
|
||||
<text x="425" y="510" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Never lose work again. Snapshot before, restore after.</text>
|
||||
<text x="425" y="510" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">Never lose work again. Snapshot before, restore after.</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
|
@ -1,4 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="265" viewBox="0 0 850 265">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="720" height="265" viewBox="40 0 720 265">
|
||||
<defs>
|
||||
<linearGradient id="pbi-bar1" x1="50%" y1="0%" x2="50%" y2="100%">
|
||||
<stop offset="0%" stop-color="#EBBB14"/>
|
||||
|
|
@ -14,76 +14,83 @@
|
|||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="100%" height="100%" fill="#0d1117" rx="8"/>
|
||||
<rect x="40" y="0" width="720" height="265" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- "VIBE MODELING" block art (shadow layer) -->
|
||||
<!-- "VIBE BI" block art (shadow) -->
|
||||
<text font-family="'Courier New', Courier, monospace" font-size="9" fill="#7A6508" xml:space="preserve">
|
||||
<tspan x="130" y="22">██╗ ██╗ ██╗ ██████╗ ███████╗ ███╗ ███╗ ██████╗ ██████╗ ███████╗ ██╗ ██╗ ███╗ ██╗ ██████╗ </tspan>
|
||||
<tspan x="130" y="33">██║ ██║ ██║ ██╔══██╗ ██╔════╝ ████╗ ████║ ██╔═══██╗ ██╔══██╗ ██╔════╝ ██║ ██║ ████╗ ██║ ██╔════╝ </tspan>
|
||||
<tspan x="130" y="44">██║ ██║ ██║ ██████╔╝ █████╗ ██╔████╔██║ ██║ ██║ ██║ ██║ █████╗ ██║ ██║ ██╔██╗ ██║ ██║ ███╗</tspan>
|
||||
<tspan x="130" y="55">╚██╗ ██╔╝ ██║ ██╔══██╗ ██╔══╝ ██║╚██╔╝██║ ██║ ██║ ██║ ██║ ██╔══╝ ██║ ██║ ██║╚██╗██║ ██║ ██║</tspan>
|
||||
<tspan x="130" y="66"> ╚████╔╝ ██║ ██████╔╝ ███████╗ ██║ ╚═╝ ██║ ╚██████╔╝ ██████╔╝ ███████╗ ███████╗ ██║ ██║ ╚████║ ╚██████╔╝</tspan>
|
||||
<tspan x="130" y="77"> ╚═══╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═════╝ </tspan>
|
||||
<tspan x="271" y="22">██╗ ██╗ ██╗ ██████╗ ███████╗ ██████╗ ██╗</tspan>
|
||||
<tspan x="271" y="33">██║ ██║ ██║ ██╔══██╗ ██╔════╝ ██╔══██╗ ██║</tspan>
|
||||
<tspan x="271" y="44">██║ ██║ ██║ ██████╔╝ █████╗ ██████╔╝ ██║</tspan>
|
||||
<tspan x="271" y="55">╚██╗ ██╔╝ ██║ ██╔══██╗ ██╔══╝ ██╔══██╗ ██║</tspan>
|
||||
<tspan x="271" y="66"> ╚████╔╝ ██║ ██████╔╝ ███████╗ ██████╔╝ ██║</tspan>
|
||||
<tspan x="271" y="77"> ╚═══╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝</tspan>
|
||||
</text>
|
||||
|
||||
<!-- "VIBE MODELING" block art (main layer) -->
|
||||
<!-- "VIBE BI" block art (main) -->
|
||||
<text font-family="'Courier New', Courier, monospace" font-size="9" fill="#F2C811" xml:space="preserve">
|
||||
<tspan x="129" y="21">██╗ ██╗ ██╗ ██████╗ ███████╗ ███╗ ███╗ ██████╗ ██████╗ ███████╗ ██╗ ██╗ ███╗ ██╗ ██████╗ </tspan>
|
||||
<tspan x="129" y="32">██║ ██║ ██║ ██╔══██╗ ██╔════╝ ████╗ ████║ ██╔═══██╗ ██╔══██╗ ██╔════╝ ██║ ██║ ████╗ ██║ ██╔════╝ </tspan>
|
||||
<tspan x="129" y="43">██║ ██║ ██║ ██████╔╝ █████╗ ██╔████╔██║ ██║ ██║ ██║ ██║ █████╗ ██║ ██║ ██╔██╗ ██║ ██║ ███╗</tspan>
|
||||
<tspan x="129" y="54">╚██╗ ██╔╝ ██║ ██╔══██╗ ██╔══╝ ██║╚██╔╝██║ ██║ ██║ ██║ ██║ ██╔══╝ ██║ ██║ ██║╚██╗██║ ██║ ██║</tspan>
|
||||
<tspan x="129" y="65"> ╚████╔╝ ██║ ██████╔╝ ███████╗ ██║ ╚═╝ ██║ ╚██████╔╝ ██████╔╝ ███████╗ ███████╗ ██║ ██║ ╚████║ ╚██████╔╝</tspan>
|
||||
<tspan x="129" y="76"> ╚═══╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═════╝ </tspan>
|
||||
<tspan x="270" y="21">██╗ ██╗ ██╗ ██████╗ ███████╗ ██████╗ ██╗</tspan>
|
||||
<tspan x="270" y="32">██║ ██║ ██║ ██╔══██╗ ██╔════╝ ██╔══██╗ ██║</tspan>
|
||||
<tspan x="270" y="43">██║ ██║ ██║ ██████╔╝ █████╗ ██████╔╝ ██║</tspan>
|
||||
<tspan x="270" y="54">╚██╗ ██╔╝ ██║ ██╔══██╗ ██╔══╝ ██╔══██╗ ██║</tspan>
|
||||
<tspan x="270" y="65"> ╚████╔╝ ██║ ██████╔╝ ███████╗ ██████╔╝ ██║</tspan>
|
||||
<tspan x="270" y="76"> ╚═══╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Separator -->
|
||||
<line x1="60" y1="95" x2="790" y2="95" stroke="#F2C811" stroke-opacity="0.15" stroke-width="1"/>
|
||||
<line x1="50" y1="87" x2="750" y2="87" stroke="#F2C811" stroke-opacity="0.15" stroke-width="1"/>
|
||||
|
||||
<!-- Layout: Claude(130) ~ arrow ~ PBI-CLI(425) ~ arrow ~ PowerBI(720) -->
|
||||
<!-- Tagline -->
|
||||
<text x="400" y="106" font-family="'Segoe UI', Arial, sans-serif" font-size="15" fill="#e6edf3" text-anchor="middle" font-weight="600">The First CLI for Both Power BI Modeling and Reporting</text>
|
||||
|
||||
<!-- Claude AI logo (centered at x=130, y=168), scale=0.075 -> 90x90 -->
|
||||
<g transform="translate(85, 123) scale(0.075)">
|
||||
<!-- Layout: Claude ~ arrow ~ PBI-CLI block art ~ arrow ~ PowerBI -->
|
||||
|
||||
<!-- Claude AI icon (centered at x=100) -->
|
||||
<g transform="translate(75, 125) scale(0.04)">
|
||||
<path fill="#d97757" d="M 233.959793 800.214905 L 468.644287 668.536987 L 472.590637 657.100647 L 468.644287 650.738403 L 457.208069 650.738403 L 417.986633 648.322144 L 283.892639 644.69812 L 167.597321 639.865845 L 54.926208 633.825623 L 26.577238 627.785339 L 3.3e-05 592.751709 L 2.73832 575.27533 L 26.577238 559.248352 L 60.724873 562.228149 L 136.187973 567.382629 L 249.422867 575.194763 L 331.570496 580.026978 L 453.261841 592.671082 L 472.590637 592.671082 L 475.328857 584.859009 L 468.724915 580.026978 L 463.570557 575.194763 L 346.389313 495.785217 L 219.543671 411.865906 L 153.100723 363.543762 L 117.181267 339.060425 L 99.060455 316.107361 L 91.248367 266.01355 L 123.865784 230.093994 L 167.677887 233.073853 L 178.872513 236.053772 L 223.248367 270.201477 L 318.040283 343.570496 L 441.825592 434.738342 L 459.946411 449.798706 L 467.194672 444.64447 L 468.080597 441.020203 L 459.946411 427.409485 L 392.617493 305.718323 L 320.778564 181.932983 L 288.80542 130.630859 L 280.348999 99.865845 C 277.369171 87.221436 275.194641 76.590698 275.194641 63.624268 L 312.322174 13.20813 L 332.8591 6.604126 L 382.389313 13.20813 L 403.248352 31.328979 L 434.013519 101.71814 L 483.865753 212.537048 L 561.181274 363.221497 L 583.812134 407.919434 L 595.892639 449.315491 L 600.40271 461.959839 L 608.214783 461.959839 L 608.214783 454.711609 L 614.577271 369.825623 L 626.335632 265.61084 L 637.771851 131.516846 L 641.718201 93.745117 L 660.402832 48.483276 L 697.530334 24.000122 L 726.52356 37.852417 L 750.362549 72 L 747.060486 94.067139 L 732.886047 186.201416 L 705.100708 330.52356 L 686.979919 427.167847 L 697.530334 427.167847 L 709.61084 415.087341 L 758.496704 350.174561 L 840.644348 247.490051 L 876.885925 206.738342 L 919.167847 161.71814 L 946.308838 140.29541 L 997.61084 140.29541 L 1035.38269 196.429626 L 1018.469849 254.416199 L 965.637634 321.422852 L 921.825562 378.201538 L 859.006714 462.765259 L 819.785278 530.41626 L 823.409424 535.812073 L 832.75177 534.92627 L 974.657776 504.724915 L 1051.328979 490.872559 L 1142.818848 475.167786 L 1184.214844 494.496582 L 1188.724854 514.147644 L 1172.456421 554.335693 L 1074.604126 578.496765 L 959.838989 601.449829 L 788.939636 641.879272 L 786.845764 643.409485 L 789.261841 646.389343 L 866.255127 653.637634 L 899.194702 655.409424 L 979.812134 655.409424 L 1129.932861 666.604187 L 1169.154419 692.537109 L 1192.671265 724.268677 L 1188.724854 748.429688 L 1128.322144 779.194641 L 1046.818848 759.865845 L 856.590759 714.604126 L 791.355774 698.335754 L 782.335693 698.335754 L 782.335693 703.731567 L 836.69812 756.885986 L 936.322205 846.845581 L 1061.073975 962.81897 L 1067.436279 991.490112 L 1051.409424 1014.120911 L 1034.496704 1011.704712 L 924.885986 929.234924 L 882.604126 892.107544 L 786.845764 811.48999 L 780.483276 811.48999 L 780.483276 819.946289 L 802.550415 852.241699 L 919.087341 1027.409424 L 925.127625 1081.127686 L 916.671204 1098.604126 L 886.469849 1109.154419 L 853.288696 1103.114136 L 785.073914 1007.355835 L 714.684631 899.516785 L 657.906067 802.872498 L 650.979858 806.81897 L 617.476624 1167.704834 L 601.771851 1186.147705 L 565.530212 1200 L 535.328857 1177.046997 L 519.302124 1139.919556 L 535.328857 1066.550537 L 554.657776 970.792053 L 570.362488 894.68457 L 584.536926 800.134277 L 592.993347 768.724976 L 592.429626 766.630859 L 585.503479 767.516968 L 514.22821 865.369263 L 405.825531 1011.865906 L 320.053711 1103.677979 L 299.516815 1111.812256 L 263.919525 1093.369263 L 267.221497 1060.429688 L 287.114136 1031.114136 L 405.825531 880.107361 L 477.422913 786.52356 L 523.651062 732.483276 L 523.328918 724.671265 L 520.590698 724.671265 L 205.288605 929.395935 L 149.154434 936.644409 L 124.993355 914.01355 L 127.973183 876.885986 L 139.409409 864.80542 L 234.201385 799.570435 L 233.879227 799.8927 Z"/>
|
||||
</g>
|
||||
<text x="130" y="222" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#d97757" text-anchor="middle" font-weight="600">Claude Code</text>
|
||||
<text x="100" y="192" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#d97757" text-anchor="middle" font-weight="600">Claude Code</text>
|
||||
|
||||
<!-- Left arrow -->
|
||||
<line x1="190" y1="175" x2="288" y2="175" stroke="#F2C811" stroke-width="2" stroke-dasharray="6,4" stroke-opacity="0.5"/>
|
||||
<polygon points="292,175 284,170 284,180" fill="#F2C811" fill-opacity="0.5"/>
|
||||
<line x1="148" y1="155" x2="253" y2="155" stroke="#F2C811" stroke-width="2" stroke-dasharray="6,4" stroke-opacity="0.5"/>
|
||||
<polygon points="257,155 249,150 249,160" fill="#F2C811" fill-opacity="0.5"/>
|
||||
|
||||
<!-- PBI-CLI block art (font-size 8, ~240px wide, centered at x=425) -->
|
||||
<!-- shadow -->
|
||||
<!-- PBI-CLI block art as the center element (shadow) -->
|
||||
<text font-family="'Courier New', Courier, monospace" font-size="8" fill="#7A6508" xml:space="preserve">
|
||||
<tspan x="306" y="143">██████╗ ██████╗ ██╗ ██████╗ ██╗ ██╗</tspan>
|
||||
<tspan x="306" y="155">██╔══██╗ ██╔══██╗ ██║ ██╔════╝ ██║ ██║</tspan>
|
||||
<tspan x="306" y="167">██████╔╝ ██████╔╝ ██║ ███╗ ██║ ██║ ██║</tspan>
|
||||
<tspan x="306" y="179">██╔═══╝ ██╔══██╗ ██║ ╚══╝ ██║ ██║ ██║</tspan>
|
||||
<tspan x="306" y="191">██║ ██████╔╝ ██║ ╚██████╗ ███████╗ ██║</tspan>
|
||||
<tspan x="306" y="203">╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝</tspan>
|
||||
<tspan x="276" y="133">██████╗ ██████╗ ██╗ ██████╗ ██╗ ██╗</tspan>
|
||||
<tspan x="276" y="145">██╔══██╗ ██╔══██╗ ██║ ██╔════╝ ██║ ██║</tspan>
|
||||
<tspan x="276" y="157">██████╔╝ ██████╔╝ ██║ ███╗ ██║ ██║ ██║</tspan>
|
||||
<tspan x="276" y="169">██╔═══╝ ██╔══██╗ ██║ ╚══╝ ██║ ██║ ██║</tspan>
|
||||
<tspan x="276" y="181">██║ ██████╔╝ ██║ ╚██████╗ ███████╗ ██║</tspan>
|
||||
<tspan x="276" y="193">╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝</tspan>
|
||||
</text>
|
||||
<!-- main -->
|
||||
<!-- PBI-CLI block art as the center element (main) -->
|
||||
<text font-family="'Courier New', Courier, monospace" font-size="8" fill="#F2C811" xml:space="preserve">
|
||||
<tspan x="305" y="142">██████╗ ██████╗ ██╗ ██████╗ ██╗ ██╗</tspan>
|
||||
<tspan x="305" y="154">██╔══██╗ ██╔══██╗ ██║ ██╔════╝ ██║ ██║</tspan>
|
||||
<tspan x="305" y="166">██████╔╝ ██████╔╝ ██║ ███╗ ██║ ██║ ██║</tspan>
|
||||
<tspan x="305" y="178">██╔═══╝ ██╔══██╗ ██║ ╚══╝ ██║ ██║ ██║</tspan>
|
||||
<tspan x="305" y="190">██║ ██████╔╝ ██║ ╚██████╗ ███████╗ ██║</tspan>
|
||||
<tspan x="305" y="202">╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝</tspan>
|
||||
<tspan x="275" y="132">██████╗ ██████╗ ██╗ ██████╗ ██╗ ██╗</tspan>
|
||||
<tspan x="275" y="144">██╔══██╗ ██╔══██╗ ██║ ██╔════╝ ██║ ██║</tspan>
|
||||
<tspan x="275" y="156">██████╔╝ ██████╔╝ ██║ ███╗ ██║ ██║ ██║</tspan>
|
||||
<tspan x="275" y="168">██╔═══╝ ██╔══██╗ ██║ ╚══╝ ██║ ██║ ██║</tspan>
|
||||
<tspan x="275" y="180">██║ ██████╔╝ ██║ ╚██████╗ ███████╗ ██║</tspan>
|
||||
<tspan x="275" y="192">╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Right arrow -->
|
||||
<line x1="558" y1="175" x2="656" y2="175" stroke="#F2C811" stroke-width="2" stroke-dasharray="6,4" stroke-opacity="0.5"/>
|
||||
<polygon points="660,175 652,170 652,180" fill="#F2C811" fill-opacity="0.5"/>
|
||||
<line x1="533" y1="155" x2="640" y2="155" stroke="#F2C811" stroke-width="2" stroke-dasharray="6,4" stroke-opacity="0.5"/>
|
||||
<polygon points="644,155 636,150 636,160" fill="#F2C811" fill-opacity="0.5"/>
|
||||
|
||||
<!-- Power BI logo (3-bar chart, centered at x=720) -->
|
||||
<g transform="translate(685, 125)">
|
||||
<rect x="40" y="0" width="30" height="90" rx="5" fill="url(#pbi-bar1)"/>
|
||||
<rect x="20" y="24" width="30" height="66" rx="5" fill="url(#pbi-bar2)"/>
|
||||
<rect x="0" y="46" width="30" height="44" rx="5" fill="url(#pbi-bar3)"/>
|
||||
<!-- Power BI logo (centered at x=700) -->
|
||||
<g transform="translate(668, 120)">
|
||||
<rect x="40" y="0" width="24" height="70" rx="4" fill="url(#pbi-bar1)"/>
|
||||
<rect x="20" y="18" width="24" height="52" rx="4" fill="url(#pbi-bar2)"/>
|
||||
<rect x="0" y="34" width="24" height="36" rx="4" fill="url(#pbi-bar3)"/>
|
||||
</g>
|
||||
<text x="720" y="236" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#F2C811" text-anchor="middle" font-weight="600">Power BI</text>
|
||||
<text x="700" y="208" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#F2C811" text-anchor="middle" font-weight="600">Power BI</text>
|
||||
|
||||
<!-- Modeling + Reporting pills below PBI-CLI -->
|
||||
<rect x="283" y="205" width="100" height="22" rx="11" fill="#58a6ff" fill-opacity="0.1" stroke="#58a6ff" stroke-width="1"/>
|
||||
<text x="333" y="220" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#58a6ff" text-anchor="middle" font-weight="600">Modeling</text>
|
||||
<text x="401" y="220" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">+</text>
|
||||
<rect x="413" y="205" width="100" height="22" rx="11" fill="#06d6a0" fill-opacity="0.1" stroke="#06d6a0" stroke-width="1"/>
|
||||
<text x="463" y="220" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#06d6a0" text-anchor="middle" font-weight="600">Reporting</text>
|
||||
|
||||
<!-- Install command -->
|
||||
<text x="425" y="252" font-family="'Courier New', Courier, monospace" font-size="14" fill="#58a6ff" text-anchor="middle" font-weight="bold">pipx install pbi-cli-tool</text>
|
||||
<text x="400" y="252" font-family="'Courier New', Courier, monospace" font-size="14" fill="#58a6ff" text-anchor="middle" font-weight="bold">pipx install pbi-cli-tool</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
|
|
@ -1,68 +1,111 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="380" viewBox="0 0 850 380">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="345" viewBox="0 0 850 345" font-family="'Segoe UI', system-ui, sans-serif">
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="100%" height="100%" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="425" y="35" font-family="'Segoe UI', Arial, sans-serif" font-size="18" fill="#F2C811" text-anchor="middle" font-weight="bold">Why pbi-cli?</text>
|
||||
<text x="425" y="36" font-size="19" fill="#F2C811" font-weight="bold" text-anchor="middle">Why pbi-cli?</text>
|
||||
|
||||
<!-- Left Panel: WITHOUT -->
|
||||
<rect x="20" y="55" width="395" height="305" rx="8" fill="#161b22" stroke="#ff6b6b" stroke-width="2"/>
|
||||
<text x="217" y="85" font-family="'Segoe UI', Arial, sans-serif" font-size="15" fill="#ff6b6b" text-anchor="middle" font-weight="bold">WITHOUT pbi-cli</text>
|
||||
<!-- LEFT panel -->
|
||||
<rect x="15" y="50" width="385" height="285" rx="10" fill="#160a0a" stroke="#ff4444" stroke-width="1.5"/>
|
||||
|
||||
<!-- Pain point 1 -->
|
||||
<text x="50" y="120" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#ff6b6b">✗</text>
|
||||
<text x="70" y="120" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">Manual GUI clicking for every change</text>
|
||||
<!-- LEFT panel header (top-rounded only via path) -->
|
||||
<path d="M 25,50 L 390,50 A 10,10 0 0,1 400,60 L 400,94 L 15,94 L 15,60 A 10,10 0 0,1 25,50 Z" fill="#ff4444" fill-opacity="0.15"/>
|
||||
<text x="207" y="77" font-size="15" fill="#ff6b6b" font-weight="bold" text-anchor="middle">✗ WITHOUT pbi-cli</text>
|
||||
|
||||
<!-- Pain point 2 -->
|
||||
<text x="50" y="155" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#ff6b6b">✗</text>
|
||||
<text x="70" y="155" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">No version control for models</text>
|
||||
<!-- RIGHT panel -->
|
||||
<rect x="450" y="50" width="385" height="285" rx="10" fill="#0a1612" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
|
||||
<!-- Pain point 3 -->
|
||||
<text x="50" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#ff6b6b">✗</text>
|
||||
<text x="70" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">Copy-paste DAX from documentation</text>
|
||||
<!-- RIGHT panel header (top-rounded only via path) -->
|
||||
<path d="M 460,50 L 825,50 A 10,10 0 0,1 835,60 L 835,94 L 450,94 L 450,60 A 10,10 0 0,1 460,50 Z" fill="#06d6a0" fill-opacity="0.12"/>
|
||||
<text x="642" y="77" font-size="15" fill="#06d6a0" font-weight="bold" text-anchor="middle">✓ WITH pbi-cli</text>
|
||||
|
||||
<!-- Pain point 4 -->
|
||||
<text x="50" y="225" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#ff6b6b">✗</text>
|
||||
<text x="70" y="225" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">MCP tools cost 4,000+ tokens each</text>
|
||||
<!-- LEFT token metric card -->
|
||||
<rect x="35" y="102" width="345" height="76" rx="8" fill="#ff4444" fill-opacity="0.07" stroke="#ff4444" stroke-opacity="0.25" stroke-width="1"/>
|
||||
<text x="207" y="149" font-size="44" fill="#ff4444" font-weight="bold" text-anchor="middle">4,000+</text>
|
||||
<text x="207" y="168" font-size="10" fill="#8b949e" text-anchor="middle" letter-spacing="2">TOKENS PER OPERATION</text>
|
||||
|
||||
<!-- Pain point 5 -->
|
||||
<text x="50" y="260" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#ff6b6b">✗</text>
|
||||
<text x="70" y="260" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">No scripting or CI/CD integration</text>
|
||||
<!-- RIGHT token metric card -->
|
||||
<rect x="470" y="102" width="345" height="76" rx="8" fill="#06d6a0" fill-opacity="0.07" stroke="#06d6a0" stroke-opacity="0.25" stroke-width="1"/>
|
||||
<text x="642" y="149" font-size="44" fill="#06d6a0" font-weight="bold" text-anchor="middle">~30</text>
|
||||
<text x="642" y="168" font-size="10" fill="#8b949e" text-anchor="middle" letter-spacing="2">TOKENS PER OPERATION</text>
|
||||
|
||||
<!-- Pain point 6 -->
|
||||
<text x="50" y="295" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#ff6b6b">✗</text>
|
||||
<text x="70" y="295" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">Context switching between tools</text>
|
||||
<!-- LEFT token comparison bar (full width = expensive) -->
|
||||
<rect x="35" y="186" width="345" height="7" rx="3.5" fill="#ff4444" fill-opacity="0.6"/>
|
||||
|
||||
<!-- Left footer -->
|
||||
<text x="217" y="340" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#ff6b6b" text-anchor="middle" opacity="0.6">Slow, manual, error-prone</text>
|
||||
<!-- RIGHT token comparison bars -->
|
||||
<rect x="470" y="186" width="345" height="7" rx="3.5" fill="#06d6a0" fill-opacity="0.1"/>
|
||||
<rect x="470" y="186" width="26" height="7" rx="3.5" fill="#06d6a0" fill-opacity="0.8"/>
|
||||
|
||||
<!-- Right Panel: WITH -->
|
||||
<rect x="435" y="55" width="395" height="305" rx="8" fill="#161b22" stroke="#06d6a0" stroke-width="2"/>
|
||||
<text x="632" y="85" font-family="'Segoe UI', Arial, sans-serif" font-size="15" fill="#06d6a0" text-anchor="middle" font-weight="bold">WITH pbi-cli</text>
|
||||
<!-- ======================== -->
|
||||
<!-- LEFT feature rows -->
|
||||
<!-- ======================== -->
|
||||
|
||||
<!-- Solution 1 -->
|
||||
<text x="465" y="120" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#06d6a0">✓</text>
|
||||
<text x="485" y="120" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#c9d1d9">CLI commands from any terminal</text>
|
||||
<!-- Row 1 -->
|
||||
<rect x="35" y="202" width="345" height="40" rx="6" fill="#ff4444" fill-opacity="0.05"/>
|
||||
<g transform="translate(52,211)">
|
||||
<rect x="0" y="0" width="24" height="20" rx="2" fill="none" stroke="#ff6b6b" stroke-width="1.5"/>
|
||||
<line x1="0" y1="7" x2="24" y2="7" stroke="#ff6b6b" stroke-width="1.5"/>
|
||||
<circle cx="4" cy="3.5" r="1.5" fill="#ff6b6b" opacity="0.7"/>
|
||||
<circle cx="9" cy="3.5" r="1.5" fill="#ff6b6b" opacity="0.7"/>
|
||||
<circle cx="14" cy="3.5" r="1.5" fill="#ff6b6b" opacity="0.7"/>
|
||||
</g>
|
||||
<text x="90" y="218" font-size="12" fill="#c9d1d9" font-weight="bold">GUI-only workflow</text>
|
||||
<text x="90" y="234" font-size="10.5" fill="#8b949e">Click through every change manually</text>
|
||||
|
||||
<!-- Solution 2 -->
|
||||
<text x="465" y="155" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#06d6a0">✓</text>
|
||||
<text x="485" y="155" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#c9d1d9">TMDL export for full Git support</text>
|
||||
<!-- Row 2 -->
|
||||
<rect x="35" y="247" width="345" height="40" rx="6" fill="#ff4444" fill-opacity="0.05"/>
|
||||
<g transform="translate(52,256)">
|
||||
<circle cx="12" cy="11" r="11" fill="none" stroke="#ff6b6b" stroke-width="1.5"/>
|
||||
<line x1="3.2" y1="3.2" x2="20.8" y2="18.8" stroke="#ff6b6b" stroke-width="1.5"/>
|
||||
</g>
|
||||
<text x="90" y="263" font-size="12" fill="#c9d1d9" font-weight="bold">No version control</text>
|
||||
<text x="90" y="279" font-size="10.5" fill="#8b949e">Reports and models untrackable</text>
|
||||
|
||||
<!-- Solution 3 -->
|
||||
<text x="465" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#06d6a0">✓</text>
|
||||
<text x="485" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#c9d1d9">Claude writes and validates DAX</text>
|
||||
<!-- Row 3 -->
|
||||
<rect x="35" y="292" width="345" height="40" rx="6" fill="#ff4444" fill-opacity="0.05"/>
|
||||
<g transform="translate(52,301)">
|
||||
<circle cx="12" cy="11" r="11" fill="none" stroke="#ff6b6b" stroke-width="1.5"/>
|
||||
<line x1="3.2" y1="3.2" x2="20.8" y2="18.8" stroke="#ff6b6b" stroke-width="1.5"/>
|
||||
</g>
|
||||
<text x="90" y="308" font-size="12" fill="#c9d1d9" font-weight="bold">No AI automation</text>
|
||||
<text x="90" y="324" font-size="10.5" fill="#8b949e">Cannot script or automate workflows</text>
|
||||
|
||||
<!-- Solution 4 -->
|
||||
<text x="465" y="225" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#06d6a0">✓</text>
|
||||
<text x="485" y="225" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#c9d1d9">CLI commands cost ~30 tokens each</text>
|
||||
<!-- ======================== -->
|
||||
<!-- RIGHT feature rows -->
|
||||
<!-- ======================== -->
|
||||
|
||||
<!-- Solution 5 -->
|
||||
<text x="465" y="260" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#06d6a0">✓</text>
|
||||
<text x="485" y="260" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#c9d1d9">Script everything, automate pipelines</text>
|
||||
<!-- Row 1 -->
|
||||
<rect x="470" y="202" width="345" height="40" rx="6" fill="#06d6a0" fill-opacity="0.05"/>
|
||||
<g transform="translate(487,211)">
|
||||
<rect x="0" y="0" width="28" height="20" rx="3" fill="#06d6a0" fill-opacity="0.1" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<text x="4" y="14" font-family="'Courier New', monospace" font-size="9" fill="#06d6a0">$ _</text>
|
||||
</g>
|
||||
<text x="528" y="218" font-size="12" fill="#c9d1d9" font-weight="bold">CLI-native commands</text>
|
||||
<text x="528" y="234" font-size="10.5" fill="#8b949e">Automate from any terminal or script</text>
|
||||
|
||||
<!-- Solution 6 -->
|
||||
<text x="465" y="295" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#06d6a0">✓</text>
|
||||
<text x="485" y="295" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#c9d1d9">One tool, one workflow, one context</text>
|
||||
<!-- Row 2 -->
|
||||
<rect x="470" y="247" width="345" height="40" rx="6" fill="#06d6a0" fill-opacity="0.05"/>
|
||||
<g transform="translate(487,253)">
|
||||
<circle cx="5" cy="5" r="4" fill="none" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<circle cx="5" cy="23" r="4" fill="none" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<circle cx="19" cy="10" r="4" fill="none" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<line x1="5" y1="9" x2="5" y2="19" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<path d="M 5,9 C 5,12 19,10 19,10" fill="none" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
</g>
|
||||
<text x="528" y="263" font-size="12" fill="#c9d1d9" font-weight="bold">Full Git support</text>
|
||||
<text x="528" y="279" font-size="10.5" fill="#8b949e">TMDL + PBIR diff, branch, review</text>
|
||||
|
||||
<!-- Row 3 -->
|
||||
<rect x="470" y="292" width="345" height="40" rx="6" fill="#06d6a0" fill-opacity="0.05"/>
|
||||
<g transform="translate(487,300)">
|
||||
<path d="M 12,0 L 14,10 L 24,12 L 14,14 L 12,24 L 10,14 L 0,12 L 10,10 Z" fill="#06d6a0" fill-opacity="0.85"/>
|
||||
</g>
|
||||
<text x="528" y="308" font-size="12" fill="#c9d1d9" font-weight="bold">Claude AI builds everything</text>
|
||||
<text x="528" y="324" font-size="10.5" fill="#8b949e">Models, measures, and report layouts</text>
|
||||
|
||||
<!-- VS badge (drawn last so it sits on top of both panels) -->
|
||||
<circle cx="425" cy="215" r="28" fill="#0d1117" stroke="#F2C811" stroke-width="2"/>
|
||||
<text x="425" y="221" font-size="15" fill="#F2C811" font-weight="bold" text-anchor="middle">VS</text>
|
||||
|
||||
<!-- Right footer -->
|
||||
<text x="632" y="340" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#06d6a0" text-anchor="middle" opacity="0.6">Fast, scriptable, AI-native</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 6.4 KiB |
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<!-- Title -->
|
||||
<text x="425" y="32" font-family="'Segoe UI', Arial, sans-serif" font-size="18" fill="#F2C811" text-anchor="middle" font-weight="bold">One Prompt, Five Measures</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Stop clicking through the GUI for every single measure</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">Stop clicking through the GUI for every single measure</text>
|
||||
|
||||
<!-- Chat container -->
|
||||
<rect x="40" y="65" width="770" height="380" rx="8" fill="#161b22" stroke="#30363d" stroke-width="1"/>
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<circle cx="62" cy="80" r="5" fill="#ff5f57"/>
|
||||
<circle cx="78" cy="80" r="5" fill="#febc2e"/>
|
||||
<circle cx="94" cy="80" r="5" fill="#28c840"/>
|
||||
<text x="116" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#d97757" font-weight="bold">Claude Code</text>
|
||||
<text x="116" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#d97757" font-weight="bold">Claude Code</text>
|
||||
|
||||
<!-- User message -->
|
||||
<rect x="220" y="106" width="570" height="44" rx="12" fill="#1a3a5c" stroke="#264d73" stroke-width="1"/>
|
||||
|
|
@ -24,47 +24,47 @@
|
|||
<!-- Claude response -->
|
||||
<rect x="60" y="162" width="620" height="268" rx="12" fill="#1c1c1c" stroke="#30363d" stroke-width="1"/>
|
||||
|
||||
<text x="78" y="182" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e">Creating 5 measures in Sales table...</text>
|
||||
<text x="78" y="182" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Creating 5 measures in Sales table...</text>
|
||||
|
||||
<!-- Measure 1 -->
|
||||
<text x="78" y="206" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">✓</text>
|
||||
<text x="95" y="206" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#c9d1d9" font-weight="bold">YTD Revenue</text>
|
||||
<text x="200" y="206" font-family="'Courier New', Courier, monospace" font-size="10" fill="#d97757">TOTALYTD ( SUM ( Sales[Amount] ), Calendar[Date] )</text>
|
||||
<text x="95" y="206" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#c9d1d9" font-weight="bold">YTD Revenue</text>
|
||||
<text x="200" y="206" font-family="'Courier New', Courier, monospace" font-size="12" fill="#d97757">TOTALYTD ( SUM ( Sales[Amount] ), Calendar[Date] )</text>
|
||||
|
||||
<!-- Measure 2 -->
|
||||
<text x="78" y="232" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">✓</text>
|
||||
<text x="95" y="232" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#c9d1d9" font-weight="bold">QTD Revenue</text>
|
||||
<text x="200" y="232" font-family="'Courier New', Courier, monospace" font-size="10" fill="#d97757">TOTALQTD ( SUM ( Sales[Amount] ), Calendar[Date] )</text>
|
||||
<text x="95" y="232" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#c9d1d9" font-weight="bold">QTD Revenue</text>
|
||||
<text x="200" y="232" font-family="'Courier New', Courier, monospace" font-size="12" fill="#d97757">TOTALQTD ( SUM ( Sales[Amount] ), Calendar[Date] )</text>
|
||||
|
||||
<!-- Measure 3 -->
|
||||
<text x="78" y="258" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">✓</text>
|
||||
<text x="95" y="258" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#c9d1d9" font-weight="bold">MTD Revenue</text>
|
||||
<text x="200" y="258" font-family="'Courier New', Courier, monospace" font-size="10" fill="#d97757">TOTALMTD ( SUM ( Sales[Amount] ), Calendar[Date] )</text>
|
||||
<text x="95" y="258" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#c9d1d9" font-weight="bold">MTD Revenue</text>
|
||||
<text x="200" y="258" font-family="'Courier New', Courier, monospace" font-size="12" fill="#d97757">TOTALMTD ( SUM ( Sales[Amount] ), Calendar[Date] )</text>
|
||||
|
||||
<!-- Measure 4 -->
|
||||
<text x="78" y="284" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">✓</text>
|
||||
<text x="95" y="284" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#c9d1d9" font-weight="bold">PY Revenue</text>
|
||||
<text x="200" y="284" font-family="'Courier New', Courier, monospace" font-size="10" fill="#d97757">CALCULATE ( SUM ( Sales[Amount] ), SAMEPERIODLASTYEAR (... ) )</text>
|
||||
<text x="95" y="284" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#c9d1d9" font-weight="bold">PY Revenue</text>
|
||||
<text x="200" y="284" font-family="'Courier New', Courier, monospace" font-size="12" fill="#d97757">CALCULATE ( SUM ( Sales[Amount] ), SAMEPERIODLASTYEAR (... ) )</text>
|
||||
|
||||
<!-- Measure 5 -->
|
||||
<text x="78" y="310" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">✓</text>
|
||||
<text x="95" y="310" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#c9d1d9" font-weight="bold">YoY Growth %</text>
|
||||
<text x="200" y="310" font-family="'Courier New', Courier, monospace" font-size="10" fill="#d97757">DIVIDE ( [YTD Revenue] - [PY Revenue], [PY Revenue] )</text>
|
||||
<text x="95" y="310" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#c9d1d9" font-weight="bold">YoY Growth %</text>
|
||||
<text x="200" y="310" font-family="'Courier New', Courier, monospace" font-size="12" fill="#d97757">DIVIDE ( [YTD Revenue] - [PY Revenue], [PY Revenue] )</text>
|
||||
|
||||
<!-- Summary line -->
|
||||
<line x1="78" y1="325" x2="650" y2="325" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="345" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#F2C811" font-weight="bold">5 measures created in 3.2 seconds</text>
|
||||
<text x="78" y="363" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">All formatted, described, and assigned to Sales table display folder</text>
|
||||
<text x="78" y="363" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">All formatted, described, and assigned to Sales table display folder</text>
|
||||
|
||||
<!-- Comparison callout -->
|
||||
<rect x="78" y="378" width="200" height="38" rx="6" fill="#161b22" stroke="#ff6b6b" stroke-opacity="0.4"/>
|
||||
<text x="178" y="397" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#ff6b6b" text-anchor="middle">GUI: ~10 min, 50+ clicks</text>
|
||||
<text x="178" y="411" font-family="'Segoe UI', Arial, sans-serif" font-size="9" fill="#8b949e" text-anchor="middle">per measure, repeated 5x</text>
|
||||
<text x="178" y="397" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#ff6b6b" text-anchor="middle">GUI: ~10 min, 50+ clicks</text>
|
||||
<text x="178" y="411" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">per measure, repeated 5x</text>
|
||||
|
||||
<rect x="298" y="378" width="200" height="38" rx="6" fill="#161b22" stroke="#06d6a0" stroke-opacity="0.4"/>
|
||||
<text x="398" y="397" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#06d6a0" text-anchor="middle">pbi-cli: 3 seconds, 1 prompt</text>
|
||||
<text x="398" y="411" font-family="'Segoe UI', Arial, sans-serif" font-size="9" fill="#8b949e" text-anchor="middle">all five at once</text>
|
||||
<text x="398" y="397" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0" text-anchor="middle">pbi-cli: 3 seconds, 1 prompt</text>
|
||||
<text x="398" y="411" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">all five at once</text>
|
||||
|
||||
<!-- Footer -->
|
||||
<text x="425" y="470" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Batch operations that would take hours in the GUI, done in seconds</text>
|
||||
<text x="425" y="470" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">Batch operations that would take hours in the GUI, done in seconds</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
59
assets/chat-demo-report.svg
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="520" viewBox="0 0 850 520">
|
||||
<rect width="100%" height="100%" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="425" y="32" font-family="'Segoe UI', Arial, sans-serif" font-size="18" fill="#06d6a0" text-anchor="middle" font-weight="bold">Just Ask Claude -- Report Layer</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">Describe the report you want, Claude builds it</text>
|
||||
|
||||
<!-- Chat container -->
|
||||
<rect x="40" y="65" width="770" height="420" rx="8" fill="#161b22" stroke="#30363d" stroke-width="1"/>
|
||||
|
||||
<!-- Header bar -->
|
||||
<rect x="40" y="65" width="770" height="30" rx="8" fill="#21262d"/>
|
||||
<rect x="40" y="87" width="770" height="8" fill="#21262d"/>
|
||||
<!-- Traffic lights -->
|
||||
<circle cx="62" cy="80" r="5" fill="#ff5f57"/>
|
||||
<circle cx="78" cy="80" r="5" fill="#febc2e"/>
|
||||
<circle cx="94" cy="80" r="5" fill="#28c840"/>
|
||||
<text x="116" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#d97757" font-weight="bold">Claude Code</text>
|
||||
<text x="205" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">pbi-cli report session</text>
|
||||
|
||||
<!-- User message 1 -->
|
||||
<rect x="240" y="110" width="550" height="48" rx="12" fill="#1a3a5c" stroke="#264d73" stroke-width="1"/>
|
||||
<text x="260" y="131" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#e2e8f0">Add a bar chart showing revenue by region to the overview</text>
|
||||
<text x="260" y="148" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#e2e8f0">page, and apply our corporate brand theme</text>
|
||||
|
||||
<!-- Claude response 1: creates visual -->
|
||||
<rect x="60" y="170" width="520" height="50" rx="12" fill="#1c1c1c" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="189" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="189" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff">pbi visual add --page overview --type bar --name revenue_chart</text>
|
||||
<text x="78" y="210" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">✓ Created barChart visual on page overview</text>
|
||||
|
||||
<!-- Claude response 2: binds data -->
|
||||
<rect x="60" y="230" width="580" height="50" rx="12" fill="#1c1c1c" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="249" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="249" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff">pbi visual bind revenue_chart --category "Geo[Region]" --value "Sales[Revenue]"</text>
|
||||
<text x="78" y="270" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">✓ Bound Category=Geo[Region], Y=Sales[Revenue]</text>
|
||||
|
||||
<!-- Claude response 3: applies theme -->
|
||||
<rect x="60" y="290" width="460" height="50" rx="12" fill="#1c1c1c" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="309" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="309" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff">pbi report set-theme --file corporate-brand.json</text>
|
||||
<text x="78" y="330" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">✓ Theme applied: Corporate Brand</text>
|
||||
|
||||
<!-- Desktop sync message -->
|
||||
<rect x="60" y="350" width="400" height="28" rx="8" fill="#06d6a0" fill-opacity="0.06" stroke="#06d6a0" stroke-width="1" stroke-opacity="0.3"/>
|
||||
<text x="78" y="369" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">↻ Desktop synced: Power BI reopened with changes</text>
|
||||
|
||||
<!-- User message 2 -->
|
||||
<rect x="380" y="390" width="410" height="32" rx="12" fill="#1a3a5c" stroke="#264d73" stroke-width="1"/>
|
||||
<text x="400" y="411" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#e2e8f0">Now filter it to show only the top 10 regions</text>
|
||||
|
||||
<!-- Claude response 4: adds filter -->
|
||||
<rect x="60" y="432" width="540" height="32" rx="12" fill="#1c1c1c" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="453" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="453" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff">pbi filters add-topn --page overview --n 10 --order-by Revenue</text>
|
||||
|
||||
<!-- Footer -->
|
||||
<text x="425" y="508" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">No drag-and-drop. No manual binding. Just describe what you want on the page.</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<!-- Title -->
|
||||
<text x="425" y="32" font-family="'Segoe UI', Arial, sans-serif" font-size="18" fill="#F2C811" text-anchor="middle" font-weight="bold">Just Ask Claude</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Plain English in, Power BI changes out</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">Plain English in, Power BI changes out</text>
|
||||
|
||||
<!-- Chat container -->
|
||||
<rect x="40" y="65" width="770" height="420" rx="8" fill="#161b22" stroke="#30363d" stroke-width="1"/>
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
<circle cx="62" cy="80" r="5" fill="#ff5f57"/>
|
||||
<circle cx="78" cy="80" r="5" fill="#febc2e"/>
|
||||
<circle cx="94" cy="80" r="5" fill="#28c840"/>
|
||||
<text x="116" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#d97757" font-weight="bold">Claude Code</text>
|
||||
<text x="205" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">pbi-cli session</text>
|
||||
<text x="116" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#d97757" font-weight="bold">Claude Code</text>
|
||||
<text x="205" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">pbi-cli session</text>
|
||||
|
||||
<!-- User message 1 -->
|
||||
<rect x="350" y="110" width="440" height="32" rx="12" fill="#1a3a5c" stroke="#264d73" stroke-width="1"/>
|
||||
|
|
@ -24,12 +24,12 @@
|
|||
|
||||
<!-- Claude response 1 -->
|
||||
<rect x="60" y="152" width="480" height="68" rx="12" fill="#1c1c1c" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="171" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="171" font-family="'Courier New', Courier, monospace" font-size="11" fill="#58a6ff">pbi connect</text>
|
||||
<text x="225" y="171" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e">+</text>
|
||||
<text x="237" y="171" font-family="'Courier New', Courier, monospace" font-size="11" fill="#58a6ff">pbi model stats</text>
|
||||
<text x="78" y="171" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="171" font-family="'Courier New', Courier, monospace" font-size="13" fill="#58a6ff">pbi connect</text>
|
||||
<text x="225" y="171" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">+</text>
|
||||
<text x="237" y="171" font-family="'Courier New', Courier, monospace" font-size="13" fill="#58a6ff">pbi model stats</text>
|
||||
<text x="78" y="192" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">✓ Connected to Adventure Works</text>
|
||||
<text x="78" y="210" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#c9d1d9">Found 12 tables, 34 measures, 87 columns, 11 relationships</text>
|
||||
<text x="78" y="210" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#c9d1d9">Found 12 tables, 34 measures, 87 columns, 11 relationships</text>
|
||||
|
||||
<!-- User message 2 -->
|
||||
<rect x="280" y="232" width="510" height="32" rx="12" fill="#1a3a5c" stroke="#264d73" stroke-width="1"/>
|
||||
|
|
@ -37,10 +37,10 @@
|
|||
|
||||
<!-- Claude response 2 -->
|
||||
<rect x="60" y="274" width="530" height="68" rx="12" fill="#1c1c1c" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="293" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="293" font-family="'Courier New', Courier, monospace" font-size="10" fill="#58a6ff">pbi measure create "YTD Revenue" -t Sales</text>
|
||||
<text x="78" y="293" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="293" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff">pbi measure create "YTD Revenue" -t Sales</text>
|
||||
<text x="78" y="312" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">✓ Created measure YTD Revenue in Sales</text>
|
||||
<text x="78" y="330" font-family="'Courier New', Courier, monospace" font-size="10" fill="#d97757">TOTALYTD ( SUM ( Sales[Amount] ), Calendar[Date] )</text>
|
||||
<text x="78" y="330" font-family="'Courier New', Courier, monospace" font-size="12" fill="#d97757">TOTALYTD ( SUM ( Sales[Amount] ), Calendar[Date] )</text>
|
||||
|
||||
<!-- User message 3 -->
|
||||
<rect x="430" y="354" width="360" height="32" rx="12" fill="#1a3a5c" stroke="#264d73" stroke-width="1"/>
|
||||
|
|
@ -48,14 +48,14 @@
|
|||
|
||||
<!-- Claude response 3 -->
|
||||
<rect x="60" y="396" width="400" height="50" rx="12" fill="#1c1c1c" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="415" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="415" font-family="'Courier New', Courier, monospace" font-size="10" fill="#58a6ff">pbi dax execute "EVALUATE ROW(..."</text>
|
||||
<text x="78" y="415" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="415" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff">pbi dax execute "EVALUATE ROW(..."</text>
|
||||
<text x="78" y="436" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">YTD Revenue: $1,245,890</text>
|
||||
|
||||
<!-- Input bar at bottom -->
|
||||
<rect x="60" y="454" width="730" height="26" rx="13" fill="#0d1117" stroke="#484f58" stroke-width="1"/>
|
||||
<text x="82" y="472" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#484f58">Ask Claude to modify your Power BI model...</text>
|
||||
<text x="82" y="472" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#484f58">Ask Claude to modify your Power BI model...</text>
|
||||
|
||||
<!-- Footer -->
|
||||
<text x="425" y="508" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">No DAX syntax to memorize. No GUI menus to navigate. Just describe what you need.</text>
|
||||
<text x="425" y="508" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">No DAX syntax to memorize. No GUI menus to navigate. Just describe what you need.</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<!-- Title -->
|
||||
<text x="425" y="32" font-family="'Segoe UI', Arial, sans-serif" font-size="18" fill="#F2C811" text-anchor="middle" font-weight="bold">Debug DAX Like Never Before</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Describe the problem in plain English, Claude finds and fixes it</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">Describe the problem in plain English, Claude finds and fixes it</text>
|
||||
|
||||
<!-- Chat container -->
|
||||
<rect x="40" y="65" width="770" height="420" rx="8" fill="#161b22" stroke="#30363d" stroke-width="1"/>
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<circle cx="62" cy="80" r="5" fill="#ff5f57"/>
|
||||
<circle cx="78" cy="80" r="5" fill="#febc2e"/>
|
||||
<circle cx="94" cy="80" r="5" fill="#28c840"/>
|
||||
<text x="116" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#d97757" font-weight="bold">Claude Code</text>
|
||||
<text x="116" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#d97757" font-weight="bold">Claude Code</text>
|
||||
|
||||
<!-- User message 1 -->
|
||||
<rect x="280" y="106" width="510" height="44" rx="12" fill="#1a3a5c" stroke="#264d73" stroke-width="1"/>
|
||||
|
|
@ -23,29 +23,29 @@
|
|||
|
||||
<!-- Claude response 1: Investigation -->
|
||||
<rect x="60" y="162" width="620" height="120" rx="12" fill="#1c1c1c" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="182" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#c9d1d9" font-weight="bold">Investigating...</text>
|
||||
<text x="78" y="182" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#c9d1d9" font-weight="bold">Investigating...</text>
|
||||
|
||||
<text x="78" y="202" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e">1.</text>
|
||||
<text x="92" y="202" font-family="'Courier New', Courier, monospace" font-size="10" fill="#58a6ff">pbi measure list -t Sales</text>
|
||||
<text x="310" y="202" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#06d6a0">Found YTD Revenue</text>
|
||||
<text x="78" y="202" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">1.</text>
|
||||
<text x="92" y="202" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff">pbi measure list -t Sales</text>
|
||||
<text x="310" y="202" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">Found YTD Revenue</text>
|
||||
|
||||
<text x="78" y="222" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e">2.</text>
|
||||
<text x="92" y="222" font-family="'Courier New', Courier, monospace" font-size="10" fill="#58a6ff">pbi measure get "YTD Revenue"</text>
|
||||
<text x="330" y="222" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#06d6a0">DAX syntax OK</text>
|
||||
<text x="78" y="222" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">2.</text>
|
||||
<text x="92" y="222" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff">pbi measure get "YTD Revenue"</text>
|
||||
<text x="330" y="222" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">DAX syntax OK</text>
|
||||
|
||||
<text x="78" y="242" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e">3.</text>
|
||||
<text x="92" y="242" font-family="'Courier New', Courier, monospace" font-size="10" fill="#58a6ff">pbi table list</text>
|
||||
<text x="220" y="242" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#febc2e">⚠ Calendar table exists but...</text>
|
||||
<text x="78" y="242" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">3.</text>
|
||||
<text x="92" y="242" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff">pbi table list</text>
|
||||
<text x="220" y="242" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#febc2e">⚠ Calendar table exists but...</text>
|
||||
|
||||
<text x="78" y="262" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e">4.</text>
|
||||
<text x="92" y="262" font-family="'Courier New', Courier, monospace" font-size="10" fill="#58a6ff">pbi calendar check</text>
|
||||
<text x="245" y="262" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#ff6b6b">✖ Calendar NOT marked as date table</text>
|
||||
<text x="78" y="262" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">4.</text>
|
||||
<text x="92" y="262" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff">pbi calendar check</text>
|
||||
<text x="245" y="262" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#ff6b6b">✖ Calendar NOT marked as date table</text>
|
||||
|
||||
<!-- Claude diagnosis -->
|
||||
<rect x="60" y="292" width="620" height="68" rx="12" fill="#1c1c1c" stroke="#ff6b6b" stroke-width="1" stroke-opacity="0.4"/>
|
||||
<text x="78" y="312" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#ff6b6b" font-weight="bold">Root cause found</text>
|
||||
<text x="78" y="330" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#c9d1d9">Your Calendar table lost its "Mark as Date Table" setting. TOTALYTD needs</text>
|
||||
<text x="78" y="346" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#c9d1d9">this to work. This can happen after a refresh or schema change.</text>
|
||||
<text x="78" y="330" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#c9d1d9">Your Calendar table lost its "Mark as Date Table" setting. TOTALYTD needs</text>
|
||||
<text x="78" y="346" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#c9d1d9">this to work. This can happen after a refresh or schema change.</text>
|
||||
|
||||
<!-- User message 2 -->
|
||||
<rect x="570" y="372" width="220" height="28" rx="12" fill="#1a3a5c" stroke="#264d73" stroke-width="1"/>
|
||||
|
|
@ -53,11 +53,11 @@
|
|||
|
||||
<!-- Claude response 2: Fix -->
|
||||
<rect x="60" y="410" width="540" height="62" rx="12" fill="#1c1c1c" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="430" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="430" font-family="'Courier New', Courier, monospace" font-size="10" fill="#58a6ff">pbi calendar mark-as-date-table -t Calendar -c Date</text>
|
||||
<text x="78" y="430" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="430" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff">pbi calendar mark-as-date-table -t Calendar -c Date</text>
|
||||
<text x="78" y="450" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">✓ Calendar marked as date table. YTD Revenue should work now.</text>
|
||||
<text x="78" y="466" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Verified: YTD Revenue = $1,245,890 (no longer blank)</text>
|
||||
<text x="78" y="466" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">Verified: YTD Revenue = $1,245,890 (no longer blank)</text>
|
||||
|
||||
<!-- Footer -->
|
||||
<text x="425" y="510" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Stop guessing why your DAX is broken. Let Claude trace the problem.</text>
|
||||
<text x="425" y="510" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">Stop guessing why your DAX is broken. Let Claude trace the problem.</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
39
assets/downloads-chart.svg
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="340" viewBox="0 0 850 340">
|
||||
<defs>
|
||||
<linearGradient id="areaFill" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#58a6ff" stop-opacity="0.45"/>
|
||||
<stop offset="100%" stop-color="#58a6ff" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="100%" height="100%" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="425" y="33" font-family="'Segoe UI', Arial, sans-serif" font-size="17" fill="#F2C811" text-anchor="middle" font-weight="bold">pbi-cli Downloads Over Time</text>
|
||||
<text x="425" y="52" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Cumulative installs from PyPI • mirrors excluded • source: pypistats.org</text>
|
||||
|
||||
<!-- Gridlines & y-labels -->
|
||||
<line x1="70" y1="280.0" x2="810" y2="280.0" stroke="#21262d" stroke-width="1" stroke-dasharray="2,3"/><line x1="70" y1="239.6" x2="810" y2="239.6" stroke="#21262d" stroke-width="1" stroke-dasharray="2,3"/><line x1="70" y1="199.2" x2="810" y2="199.2" stroke="#21262d" stroke-width="1" stroke-dasharray="2,3"/><line x1="70" y1="158.8" x2="810" y2="158.8" stroke="#21262d" stroke-width="1" stroke-dasharray="2,3"/><line x1="70" y1="118.4" x2="810" y2="118.4" stroke="#21262d" stroke-width="1" stroke-dasharray="2,3"/><line x1="70" y1="78.0" x2="810" y2="78.0" stroke="#21262d" stroke-width="1" stroke-dasharray="2,3"/>
|
||||
<text x="62" y="284.0" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="end">0</text><text x="62" y="243.6" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="end">1,000</text><text x="62" y="203.2" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="end">2,000</text><text x="62" y="162.8" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="end">3,000</text><text x="62" y="122.4" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="end">4,000</text><text x="62" y="82.0" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="end">5,000</text>
|
||||
|
||||
<!-- Area fill -->
|
||||
<path d="M 70.00,280 L 70.00,255.48 L 99.60,244.77 L 129.20,243.48 L 158.80,243.24 L 188.40,241.90 L 218.00,241.22 L 247.60,239.92 L 277.20,236.81 L 306.80,230.35 L 336.40,226.75 L 366.00,215.04 L 395.60,206.84 L 425.20,182.51 L 454.80,172.66 L 484.40,166.76 L 514.00,160.94 L 543.60,159.45 L 573.20,155.89 L 602.80,150.28 L 632.40,141.15 L 662.00,137.87 L 691.60,133.11 L 721.20,128.38 L 750.80,126.80 L 780.40,125.03 L 810.00,119.05 L 810.00,280 Z" fill="url(#areaFill)"/>
|
||||
|
||||
<!-- Line -->
|
||||
<path d="M 70.00,255.48 L 99.60,244.77 L 129.20,243.48 L 158.80,243.24 L 188.40,241.90 L 218.00,241.22 L 247.60,239.92 L 277.20,236.81 L 306.80,230.35 L 336.40,226.75 L 366.00,215.04 L 395.60,206.84 L 425.20,182.51 L 454.80,172.66 L 484.40,166.76 L 514.00,160.94 L 543.60,159.45 L 573.20,155.89 L 602.80,150.28 L 632.40,141.15 L 662.00,137.87 L 691.60,133.11 L 721.20,128.38 L 750.80,126.80 L 780.40,125.03 L 810.00,119.05" fill="none" stroke="#58a6ff" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
|
||||
<!-- Data points -->
|
||||
<circle cx="70.00" cy="255.48" r="2.5" fill="#58a6ff"/><circle cx="99.60" cy="244.77" r="2.5" fill="#58a6ff"/><circle cx="129.20" cy="243.48" r="2.5" fill="#58a6ff"/><circle cx="158.80" cy="243.24" r="2.5" fill="#58a6ff"/><circle cx="188.40" cy="241.90" r="2.5" fill="#58a6ff"/><circle cx="218.00" cy="241.22" r="2.5" fill="#58a6ff"/><circle cx="247.60" cy="239.92" r="2.5" fill="#58a6ff"/><circle cx="277.20" cy="236.81" r="2.5" fill="#58a6ff"/><circle cx="306.80" cy="230.35" r="2.5" fill="#58a6ff"/><circle cx="336.40" cy="226.75" r="2.5" fill="#58a6ff"/><circle cx="366.00" cy="215.04" r="2.5" fill="#58a6ff"/><circle cx="395.60" cy="206.84" r="2.5" fill="#58a6ff"/><circle cx="425.20" cy="182.51" r="2.5" fill="#58a6ff"/><circle cx="454.80" cy="172.66" r="2.5" fill="#58a6ff"/><circle cx="484.40" cy="166.76" r="2.5" fill="#58a6ff"/><circle cx="514.00" cy="160.94" r="2.5" fill="#58a6ff"/><circle cx="543.60" cy="159.45" r="2.5" fill="#58a6ff"/><circle cx="573.20" cy="155.89" r="2.5" fill="#58a6ff"/><circle cx="602.80" cy="150.28" r="2.5" fill="#58a6ff"/><circle cx="632.40" cy="141.15" r="2.5" fill="#58a6ff"/><circle cx="662.00" cy="137.87" r="2.5" fill="#58a6ff"/><circle cx="691.60" cy="133.11" r="2.5" fill="#58a6ff"/><circle cx="721.20" cy="128.38" r="2.5" fill="#58a6ff"/><circle cx="750.80" cy="126.80" r="2.5" fill="#58a6ff"/><circle cx="780.40" cy="125.03" r="2.5" fill="#58a6ff"/><circle cx="810.00" cy="119.05" r="4" fill="#F2C811"/>
|
||||
|
||||
<!-- Last-value callout -->
|
||||
<rect x="720.0" y="92.0" width="82" height="24" rx="4" fill="#0d1a2a" stroke="#F2C811" stroke-width="1"/>
|
||||
<text x="761.0" y="108.0" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#F2C811" text-anchor="middle" font-weight="bold">3,984 total</text>
|
||||
|
||||
<!-- X-axis labels -->
|
||||
<text x="70.0" y="298" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Mar 26</text><text x="247.6" y="298" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Apr 01</text><text x="454.8" y="298" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Apr 08</text><text x="632.4" y="298" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Apr 14</text><text x="810.0" y="298" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Apr 20</text>
|
||||
|
||||
<!-- Footer: date range -->
|
||||
<text x="70" y="328" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Mar 26, 2026 → Apr 20, 2026</text>
|
||||
<text x="810" y="328" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="end">26 days of data</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
|
|
@ -1,113 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="520" viewBox="0 0 850 520">
|
||||
<rect width="100%" height="100%" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="425" y="35" font-family="'Segoe UI', Arial, sans-serif" font-size="18" fill="#F2C811" text-anchor="middle" font-weight="bold">22 Command Groups</text>
|
||||
<text x="425" y="52" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Covering the full Tabular Object Model</text>
|
||||
|
||||
<!-- Row 1 -->
|
||||
<rect x="22" y="68" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="32" y="90" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">connect</text>
|
||||
<text x="32" y="118" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Link to Power BI Desktop</text>
|
||||
|
||||
<rect x="227" y="68" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="237" y="90" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">dax</text>
|
||||
<text x="237" y="118" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Execute and validate DAX queries</text>
|
||||
|
||||
<rect x="432" y="68" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="442" y="90" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">measure</text>
|
||||
<text x="442" y="118" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Create, update, delete measures</text>
|
||||
|
||||
<rect x="637" y="68" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="647" y="90" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">table</text>
|
||||
<text x="647" y="118" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Manage tables and schemas</text>
|
||||
|
||||
<!-- Row 2 -->
|
||||
<rect x="22" y="140" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="32" y="162" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">column</text>
|
||||
<text x="32" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Add, rename, hide columns</text>
|
||||
|
||||
<rect x="227" y="140" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="237" y="162" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">relationship</text>
|
||||
<text x="237" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Define table joins and cardinality</text>
|
||||
|
||||
<rect x="432" y="140" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="442" y="162" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">model</text>
|
||||
<text x="442" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Model metadata and statistics</text>
|
||||
|
||||
<rect x="637" y="140" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="647" y="162" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">database</text>
|
||||
<text x="647" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">TMDL/TMSL import and export</text>
|
||||
|
||||
<!-- Row 3 -->
|
||||
<rect x="22" y="212" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="32" y="234" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">security-role</text>
|
||||
<text x="32" y="262" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Row-level security roles</text>
|
||||
|
||||
<rect x="227" y="212" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="237" y="234" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">calc-group</text>
|
||||
<text x="237" y="262" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Calculation groups and items</text>
|
||||
|
||||
<rect x="432" y="212" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="442" y="234" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">partition</text>
|
||||
<text x="442" y="262" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Table partitions and refresh</text>
|
||||
|
||||
<rect x="637" y="212" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="647" y="234" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">perspective</text>
|
||||
<text x="647" y="262" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">User perspectives and views</text>
|
||||
|
||||
<!-- Row 4 -->
|
||||
<rect x="22" y="284" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="32" y="306" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">hierarchy</text>
|
||||
<text x="32" y="334" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Column hierarchies and levels</text>
|
||||
|
||||
<rect x="227" y="284" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="237" y="306" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">expression</text>
|
||||
<text x="237" y="334" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Named M/Power Query sources</text>
|
||||
|
||||
<rect x="432" y="284" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="442" y="306" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">calendar</text>
|
||||
<text x="442" y="334" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Date table configuration</text>
|
||||
|
||||
<rect x="637" y="284" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="647" y="306" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">trace</text>
|
||||
<text x="647" y="334" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Query tracing and profiling</text>
|
||||
|
||||
<!-- Row 5 -->
|
||||
<rect x="22" y="356" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="32" y="378" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">transaction</text>
|
||||
<text x="32" y="406" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Atomic multi-step changes</text>
|
||||
|
||||
<rect x="227" y="356" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="237" y="378" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">advanced</text>
|
||||
<text x="237" y="406" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Culture and locale settings</text>
|
||||
|
||||
<rect x="432" y="356" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="442" y="378" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">repl</text>
|
||||
<text x="442" y="406" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Interactive DAX shell</text>
|
||||
|
||||
<rect x="637" y="356" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="647" y="378" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">skills</text>
|
||||
<text x="647" y="406" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Claude Code AI skill packs</text>
|
||||
|
||||
<!-- Row 6 -->
|
||||
<rect x="22" y="428" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="32" y="450" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">disconnect</text>
|
||||
<text x="32" y="478" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Close active connection</text>
|
||||
|
||||
<rect x="227" y="428" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="237" y="450" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">connections</text>
|
||||
<text x="237" y="478" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Manage saved sessions</text>
|
||||
|
||||
<rect x="432" y="428" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.3"/>
|
||||
<text x="442" y="450" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#F2C811" font-weight="bold">setup</text>
|
||||
<text x="442" y="478" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Environment and DLL check</text>
|
||||
|
||||
<!-- Summary cell -->
|
||||
<rect x="637" y="428" width="195" height="62" rx="6" fill="#161b22" stroke="#F2C811" stroke-opacity="0.8"/>
|
||||
<text x="734" y="456" font-family="'Segoe UI', Arial, sans-serif" font-size="15" fill="#F2C811" text-anchor="middle" font-weight="bold">100+ Subcommands</text>
|
||||
<text x="734" y="478" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Full TOM coverage</text>
|
||||
|
||||
<!-- Footer -->
|
||||
<text x="425" y="510" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff" text-anchor="middle" font-weight="bold">pbi <group> <command> [options]</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 9.7 KiB |
|
|
@ -1,19 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="475" height="135" viewBox="0 0 475 135">
|
||||
<rect width="100%" height="100%" fill="#0d1117" rx="8"/>
|
||||
<text font-family="'Courier New', Courier, monospace" font-size="14.5" fill="#7A6508" xml:space="preserve">
|
||||
<tspan x="21" y="29">██████╗ ██████╗ ██╗ ██████╗ ██╗ ██╗</tspan>
|
||||
<tspan x="21" y="47">██╔══██╗ ██╔══██╗ ██║ ██╔════╝ ██║ ██║</tspan>
|
||||
<tspan x="21" y="65">██████╔╝ ██████╔╝ ██║ ███╗ ██║ ██║ ██║</tspan>
|
||||
<tspan x="21" y="83">██╔═══╝ ██╔══██╗ ██║ ╚══╝ ██║ ██║ ██║</tspan>
|
||||
<tspan x="21" y="101">██║ ██████╔╝ ██║ ╚██████╗ ███████╗ ██║</tspan>
|
||||
<tspan x="21" y="119">╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝</tspan>
|
||||
</text>
|
||||
<text font-family="'Courier New', Courier, monospace" font-size="14.5" fill="#F2C811" xml:space="preserve">
|
||||
<tspan x="20" y="28">██████╗ ██████╗ ██╗ ██████╗ ██╗ ██╗</tspan>
|
||||
<tspan x="20" y="46">██╔══██╗ ██╔══██╗ ██║ ██╔════╝ ██║ ██║</tspan>
|
||||
<tspan x="20" y="64">██████╔╝ ██████╔╝ ██║ ███╗ ██║ ██║ ██║</tspan>
|
||||
<tspan x="20" y="82">██╔═══╝ ██╔══██╗ ██║ ╚══╝ ██║ ██║ ██║</tspan>
|
||||
<tspan x="20" y="100">██║ ██████╔╝ ██║ ╚██████╗ ███████╗ ██║</tspan>
|
||||
<tspan x="20" y="118">╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
159
assets/layers.svg
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="440" viewBox="0 0 850 440">
|
||||
<rect width="100%" height="100%" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="425" y="32" font-family="'Segoe UI', Arial, sans-serif" font-size="16" fill="#F2C811" text-anchor="middle" font-weight="bold">Dual-Layer Architecture</text>
|
||||
|
||||
<!-- ===== MODELING BAND y=48-188 ===== -->
|
||||
<rect x="15" y="48" width="820" height="140" rx="10" fill="#58a6ff" fill-opacity="0.05" stroke="#58a6ff" stroke-width="1.5"/>
|
||||
<rect x="15" y="48" width="6" height="140" rx="3" fill="#58a6ff" fill-opacity="0.85"/>
|
||||
|
||||
<!-- Database cylinder icon (centered at x=85, top of band) -->
|
||||
<g transform="translate(59, 58)">
|
||||
<ellipse cx="26" cy="10" rx="24" ry="10" fill="#58a6ff" fill-opacity="0.18" stroke="#58a6ff" stroke-width="2"/>
|
||||
<line x1="2" y1="10" x2="2" y2="46" stroke="#58a6ff" stroke-width="2"/>
|
||||
<line x1="50" y1="10" x2="50" y2="46" stroke="#58a6ff" stroke-width="2"/>
|
||||
<ellipse cx="26" cy="46" rx="24" ry="10" fill="#58a6ff" fill-opacity="0.1" stroke="#58a6ff" stroke-width="2"/>
|
||||
<path d="M2,22 Q26,32 50,22" fill="none" stroke="#58a6ff" stroke-width="1" opacity="0.35"/>
|
||||
<path d="M2,33 Q26,43 50,33" fill="none" stroke="#58a6ff" stroke-width="1" opacity="0.35"/>
|
||||
<circle cx="14" cy="26" r="2" fill="#58a6ff" fill-opacity="0.5"/>
|
||||
<circle cx="26" cy="28" r="2" fill="#58a6ff" fill-opacity="0.4"/>
|
||||
<circle cx="38" cy="26" r="2" fill="#58a6ff" fill-opacity="0.5"/>
|
||||
<circle cx="20" cy="38" r="2" fill="#58a6ff" fill-opacity="0.3"/>
|
||||
<circle cx="32" cy="38" r="2" fill="#58a6ff" fill-opacity="0.3"/>
|
||||
</g>
|
||||
|
||||
<!-- MODELING label BELOW the cylinder icon -->
|
||||
<text x="85" y="138" font-family="'Segoe UI', Arial, sans-serif" font-size="16" fill="#58a6ff" font-weight="bold" text-anchor="middle">MODELING</text>
|
||||
|
||||
<line x1="155" y1="54" x2="155" y2="182" stroke="#58a6ff" stroke-opacity="0.2" stroke-width="1"/>
|
||||
|
||||
<!-- ===== MODELING TILE 1: Export / Import ===== -->
|
||||
<rect x="168" y="60" width="195" height="120" rx="8" fill="#58a6ff" fill-opacity="0.08" stroke="#58a6ff" stroke-opacity="0.3" stroke-width="1"/>
|
||||
<g transform="translate(240, 68)">
|
||||
<line x1="14" y1="46" x2="14" y2="8" stroke="#58a6ff" stroke-width="3" stroke-linecap="round"/>
|
||||
<polygon points="14,3 8,14 20,14" fill="#58a6ff"/>
|
||||
<line x1="32" y1="8" x2="32" y2="46" stroke="#58a6ff" stroke-width="3" stroke-linecap="round"/>
|
||||
<polygon points="32,52 26,40 38,40" fill="#58a6ff"/>
|
||||
<rect x="0" y="58" width="46" height="14" rx="7" fill="#58a6ff" fill-opacity="0.2" stroke="#58a6ff" stroke-width="0.8"/>
|
||||
<text x="23" y="68" font-family="'Segoe UI', Arial, sans-serif" font-size="9" fill="#a5d6ff" text-anchor="middle" font-weight="600">TMDL</text>
|
||||
</g>
|
||||
<text x="265" y="158" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#a5d6ff" text-anchor="middle" font-weight="600">Export / Import</text>
|
||||
|
||||
<!-- ===== MODELING TILE 2: DAX Engine ===== -->
|
||||
<rect x="373" y="60" width="195" height="120" rx="8" fill="#58a6ff" fill-opacity="0.08" stroke="#58a6ff" stroke-opacity="0.3" stroke-width="1"/>
|
||||
<g transform="translate(446, 68)">
|
||||
<circle cx="24" cy="24" r="24" fill="#F2C811" fill-opacity="0.08" stroke="#F2C811" stroke-width="2"/>
|
||||
<circle cx="24" cy="24" r="16" fill="#F2C811" fill-opacity="0.06"/>
|
||||
<polygon points="18,13 18,35 38,24" fill="#F2C811" fill-opacity="0.95"/>
|
||||
<rect x="5" y="54" width="38" height="14" rx="7" fill="#F2C811" fill-opacity="0.2" stroke="#F2C811" stroke-width="0.8"/>
|
||||
<text x="24" y="64" font-family="'Segoe UI', Arial, sans-serif" font-size="9" fill="#F2C811" text-anchor="middle" font-weight="600">DAX</text>
|
||||
</g>
|
||||
<text x="470" y="158" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#a5d6ff" text-anchor="middle" font-weight="600">DAX Engine</text>
|
||||
|
||||
<!-- ===== MODELING TILE 3: Schema Control ===== -->
|
||||
<rect x="578" y="60" width="195" height="120" rx="8" fill="#58a6ff" fill-opacity="0.08" stroke="#58a6ff" stroke-opacity="0.3" stroke-width="1"/>
|
||||
<g transform="translate(647, 68)">
|
||||
<rect x="0" y="0" width="48" height="40" rx="3" fill="none" stroke="#58a6ff" stroke-width="2"/>
|
||||
<line x1="0" y1="12" x2="48" y2="12" stroke="#58a6ff" stroke-width="1.5" opacity="0.7"/>
|
||||
<line x1="16" y1="0" x2="16" y2="40" stroke="#58a6ff" stroke-width="1.5" opacity="0.5"/>
|
||||
<rect x="2" y="2" width="12" height="8" rx="1" fill="#58a6ff" fill-opacity="0.4"/>
|
||||
<rect x="18" y="2" width="28" height="8" rx="1" fill="#58a6ff" fill-opacity="0.4"/>
|
||||
<rect x="2" y="14" width="12" height="7" rx="1" fill="#58a6ff" fill-opacity="0.3"/>
|
||||
<rect x="18" y="14" width="28" height="7" rx="1" fill="#58a6ff" fill-opacity="0.3"/>
|
||||
<rect x="2" y="23" width="12" height="7" rx="1" fill="#58a6ff" fill-opacity="0.2"/>
|
||||
<rect x="18" y="23" width="28" height="7" rx="1" fill="#58a6ff" fill-opacity="0.2"/>
|
||||
<rect x="2" y="32" width="12" height="6" rx="1" fill="#58a6ff" fill-opacity="0.1"/>
|
||||
<rect x="18" y="32" width="28" height="6" rx="1" fill="#58a6ff" fill-opacity="0.1"/>
|
||||
<rect x="0" y="46" width="48" height="14" rx="7" fill="#58a6ff" fill-opacity="0.2" stroke="#58a6ff" stroke-width="0.8"/>
|
||||
<text x="24" y="56" font-family="'Segoe UI', Arial, sans-serif" font-size="9" fill="#a5d6ff" text-anchor="middle" font-weight="600">SCHEMA</text>
|
||||
</g>
|
||||
<text x="675" y="158" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#a5d6ff" text-anchor="middle" font-weight="600">Schema Control</text>
|
||||
|
||||
<!-- ===== CONNECTOR: PBI-CLI Block Art ===== -->
|
||||
<line x1="15" y1="232" x2="310" y2="232" stroke="#F2C811" stroke-opacity="0.2" stroke-width="1" stroke-dasharray="4,4"/>
|
||||
<line x1="540" y1="232" x2="835" y2="232" stroke="#F2C811" stroke-opacity="0.2" stroke-width="1" stroke-dasharray="4,4"/>
|
||||
|
||||
<!-- PBI-CLI block art (shadow) -->
|
||||
<text font-family="'Courier New', Courier, monospace" font-size="7" fill="#7A6508" xml:space="preserve">
|
||||
<tspan x="321" y="210">██████╗ ██████╗ ██╗ ██████╗ ██╗ ██╗</tspan>
|
||||
<tspan x="321" y="219">██╔══██╗ ██╔══██╗ ██║ ██╔════╝ ██║ ██║</tspan>
|
||||
<tspan x="321" y="228">██████╔╝ ██████╔╝ ██║ ███╗ ██║ ██║ ██║</tspan>
|
||||
<tspan x="321" y="237">██╔═══╝ ██╔══██╗ ██║ ╚══╝ ██║ ██║ ██║</tspan>
|
||||
<tspan x="321" y="246">██║ ██████╔╝ ██║ ╚██████╗ ███████╗ ██║</tspan>
|
||||
<tspan x="321" y="255">╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝</tspan>
|
||||
</text>
|
||||
<!-- PBI-CLI block art (main) -->
|
||||
<text font-family="'Courier New', Courier, monospace" font-size="7" fill="#F2C811" xml:space="preserve">
|
||||
<tspan x="320" y="209">██████╗ ██████╗ ██╗ ██████╗ ██╗ ██╗</tspan>
|
||||
<tspan x="320" y="218">██╔══██╗ ██╔══██╗ ██║ ██╔════╝ ██║ ██║</tspan>
|
||||
<tspan x="320" y="227">██████╔╝ ██████╔╝ ██║ ███╗ ██║ ██║ ██║</tspan>
|
||||
<tspan x="320" y="236">██╔═══╝ ██╔══██╗ ██║ ╚══╝ ██║ ██║ ██║</tspan>
|
||||
<tspan x="320" y="245">██║ ██████╔╝ ██║ ╚██████╗ ███████╗ ██║</tspan>
|
||||
<tspan x="320" y="254">╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝</tspan>
|
||||
</text>
|
||||
|
||||
<!-- ===== REPORTING BAND y=278-418 ===== -->
|
||||
<rect x="15" y="278" width="820" height="140" rx="10" fill="#06d6a0" fill-opacity="0.05" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<rect x="15" y="278" width="6" height="140" rx="3" fill="#06d6a0" fill-opacity="0.85"/>
|
||||
|
||||
<!-- Bar chart icon (centered at x=85, top of band) -->
|
||||
<g transform="translate(56, 288)">
|
||||
<rect x="0" y="44" width="12" height="20" rx="2" fill="none" stroke="#06d6a0" stroke-width="2"/>
|
||||
<rect x="15" y="30" width="12" height="34" rx="2" fill="#06d6a0" fill-opacity="0.25" stroke="#06d6a0" stroke-width="2"/>
|
||||
<rect x="30" y="14" width="12" height="50" rx="2" fill="#06d6a0" fill-opacity="0.45" stroke="#06d6a0" stroke-width="2"/>
|
||||
<rect x="45" y="24" width="12" height="40" rx="2" fill="#06d6a0" fill-opacity="0.35" stroke="#06d6a0" stroke-width="2"/>
|
||||
<line x1="0" y1="64" x2="57" y2="64" stroke="#06d6a0" stroke-width="1.5" opacity="0.5"/>
|
||||
<polyline points="6,42 21,28 36,12 51,22" fill="none" stroke="#7ee787" stroke-width="1.5" stroke-dasharray="3,2" opacity="0.6"/>
|
||||
</g>
|
||||
|
||||
<!-- REPORTING label BELOW the bar chart icon -->
|
||||
<text x="85" y="372" font-family="'Segoe UI', Arial, sans-serif" font-size="16" fill="#06d6a0" font-weight="bold" text-anchor="middle">REPORTING</text>
|
||||
|
||||
<line x1="155" y1="284" x2="155" y2="412" stroke="#06d6a0" stroke-opacity="0.2" stroke-width="1"/>
|
||||
|
||||
<!-- ===== REPORTING TILE 1: Visual Builder ===== -->
|
||||
<rect x="168" y="290" width="195" height="120" rx="8" fill="#06d6a0" fill-opacity="0.08" stroke="#06d6a0" stroke-opacity="0.3" stroke-width="1"/>
|
||||
<g transform="translate(228, 298)">
|
||||
<rect x="0" y="0" width="60" height="44" rx="4" fill="none" stroke="#06d6a0" stroke-width="2"/>
|
||||
<rect x="5" y="24" width="10" height="18" rx="1" fill="#06d6a0" fill-opacity="0.45"/>
|
||||
<rect x="18" y="16" width="10" height="26" rx="1" fill="#06d6a0" fill-opacity="0.6"/>
|
||||
<rect x="31" y="8" width="10" height="34" rx="1" fill="#06d6a0" fill-opacity="0.8"/>
|
||||
<rect x="44" y="12" width="10" height="30" rx="1" fill="#06d6a0" fill-opacity="0.7"/>
|
||||
<circle cx="54" cy="8" r="9" fill="#06d6a0" fill-opacity="0.2" stroke="#06d6a0" stroke-width="1"/>
|
||||
<text x="54" y="12" font-family="'Segoe UI', Arial, sans-serif" font-size="8" fill="#06d6a0" text-anchor="middle" font-weight="bold">32</text>
|
||||
<rect x="0" y="50" width="60" height="14" rx="7" fill="#06d6a0" fill-opacity="0.2" stroke="#06d6a0" stroke-width="0.8"/>
|
||||
<text x="30" y="60" font-family="'Segoe UI', Arial, sans-serif" font-size="9" fill="#7ee787" text-anchor="middle" font-weight="600">VISUALS</text>
|
||||
</g>
|
||||
<text x="265" y="388" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#7ee787" text-anchor="middle" font-weight="600">Visual Builder</text>
|
||||
|
||||
<!-- ===== REPORTING TILE 2: Data Binding ===== -->
|
||||
<rect x="373" y="290" width="195" height="120" rx="8" fill="#06d6a0" fill-opacity="0.08" stroke="#06d6a0" stroke-opacity="0.3" stroke-width="1"/>
|
||||
<g transform="translate(430, 298)">
|
||||
<circle cx="15" cy="22" r="15" fill="none" stroke="#06d6a0" stroke-width="2"/>
|
||||
<circle cx="55" cy="22" r="15" fill="none" stroke="#06d6a0" stroke-width="2"/>
|
||||
<rect x="24" y="16" width="12" height="12" rx="3" fill="#06d6a0" fill-opacity="0.4" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<rect x="34" y="16" width="12" height="12" rx="3" fill="#06d6a0" fill-opacity="0.4" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<circle cx="15" cy="22" r="5" fill="#06d6a0" fill-opacity="0.5"/>
|
||||
<circle cx="55" cy="22" r="5" fill="#06d6a0" fill-opacity="0.5"/>
|
||||
<rect x="10" y="44" width="50" height="14" rx="7" fill="#06d6a0" fill-opacity="0.2" stroke="#06d6a0" stroke-width="0.8"/>
|
||||
<text x="35" y="54" font-family="'Segoe UI', Arial, sans-serif" font-size="9" fill="#7ee787" text-anchor="middle" font-weight="600">BIND</text>
|
||||
</g>
|
||||
<text x="470" y="388" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#7ee787" text-anchor="middle" font-weight="600">Data Binding</text>
|
||||
|
||||
<!-- ===== REPORTING TILE 3: Theme Engine ===== -->
|
||||
<rect x="578" y="290" width="195" height="120" rx="8" fill="#06d6a0" fill-opacity="0.08" stroke="#06d6a0" stroke-opacity="0.3" stroke-width="1"/>
|
||||
<g transform="translate(647, 298)">
|
||||
<circle cx="26" cy="22" r="22" fill="none" stroke="#06d6a0" stroke-width="2"/>
|
||||
<circle cx="26" cy="28" r="7" fill="#0d1117" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<circle cx="12" cy="9" r="6" fill="#06d6a0" fill-opacity="0.9"/>
|
||||
<circle cx="26" cy="3" r="6" fill="#F2C811" fill-opacity="0.9"/>
|
||||
<circle cx="40" cy="9" r="6" fill="#58a6ff" fill-opacity="0.9"/>
|
||||
<circle cx="44" cy="22" r="6" fill="#c084fc" fill-opacity="0.9"/>
|
||||
<circle cx="8" cy="22" r="6" fill="#d97757" fill-opacity="0.9"/>
|
||||
<rect x="3" y="50" width="46" height="14" rx="7" fill="#06d6a0" fill-opacity="0.2" stroke="#06d6a0" stroke-width="0.8"/>
|
||||
<text x="26" y="60" font-family="'Segoe UI', Arial, sans-serif" font-size="9" fill="#7ee787" text-anchor="middle" font-weight="600">THEME</text>
|
||||
</g>
|
||||
<text x="675" y="388" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#7ee787" text-anchor="middle" font-weight="600">Theme Engine</text>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<!-- Title -->
|
||||
<text x="425" y="32" font-family="'Segoe UI', Arial, sans-serif" font-size="18" fill="#F2C811" text-anchor="middle" font-weight="bold">Model Health Check</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Ask Claude to audit your semantic model in seconds</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">Ask Claude to audit your semantic model in seconds</text>
|
||||
|
||||
<!-- Chat container -->
|
||||
<rect x="40" y="65" width="770" height="340" rx="8" fill="#161b22" stroke="#30363d" stroke-width="1"/>
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<circle cx="62" cy="80" r="5" fill="#ff5f57"/>
|
||||
<circle cx="78" cy="80" r="5" fill="#febc2e"/>
|
||||
<circle cx="94" cy="80" r="5" fill="#28c840"/>
|
||||
<text x="116" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#d97757" font-weight="bold">Claude Code</text>
|
||||
<text x="116" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#d97757" font-weight="bold">Claude Code</text>
|
||||
|
||||
<!-- User message -->
|
||||
<rect x="380" y="106" width="410" height="28" rx="12" fill="#1a3a5c" stroke="#264d73" stroke-width="1"/>
|
||||
|
|
@ -24,56 +24,56 @@
|
|||
<rect x="60" y="146" width="640" height="250" rx="12" fill="#1c1c1c" stroke="#30363d" stroke-width="1"/>
|
||||
|
||||
<text x="78" y="168" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#c9d1d9" font-weight="bold">Model Audit Report</text>
|
||||
<text x="250" y="168" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Adventure Works (12 tables, 34 measures)</text>
|
||||
<text x="250" y="168" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">Adventure Works (12 tables, 34 measures)</text>
|
||||
|
||||
<line x1="78" y1="178" x2="680" y2="178" stroke="#30363d" stroke-width="1"/>
|
||||
|
||||
<!-- Finding 1: Warning -->
|
||||
<text x="78" y="198" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#febc2e">⚠</text>
|
||||
<text x="98" y="198" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#febc2e" font-weight="bold">3 unused measures</text>
|
||||
<text x="240" y="198" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Test_Calc, Old_Revenue, Copy_of_Sales not referenced anywhere</text>
|
||||
<text x="98" y="198" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#febc2e" font-weight="bold">3 unused measures</text>
|
||||
<text x="240" y="198" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">Test_Calc, Old_Revenue, Copy_of_Sales not referenced anywhere</text>
|
||||
|
||||
<!-- Finding 2: Warning -->
|
||||
<text x="78" y="224" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#febc2e">⚠</text>
|
||||
<text x="98" y="224" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#febc2e" font-weight="bold">Wide table detected</text>
|
||||
<text x="250" y="224" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Product has 42 columns, consider splitting or hiding unused</text>
|
||||
<text x="98" y="224" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#febc2e" font-weight="bold">Wide table detected</text>
|
||||
<text x="250" y="224" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">Product has 42 columns, consider splitting or hiding unused</text>
|
||||
|
||||
<!-- Finding 3: Error -->
|
||||
<text x="78" y="250" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#ff6b6b">✖</text>
|
||||
<text x="98" y="250" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#ff6b6b" font-weight="bold">Missing relationship</text>
|
||||
<text x="260" y="250" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Budget table has no join to Calendar (Date column exists)</text>
|
||||
<text x="98" y="250" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#ff6b6b" font-weight="bold">Missing relationship</text>
|
||||
<text x="260" y="250" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">Budget table has no join to Calendar (Date column exists)</text>
|
||||
|
||||
<!-- Finding 4: Error -->
|
||||
<text x="78" y="276" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#ff6b6b">✖</text>
|
||||
<text x="98" y="276" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#ff6b6b" font-weight="bold">Bidirectional filter</text>
|
||||
<text x="262" y="276" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Sales to Product is bidirectional, may cause ambiguity</text>
|
||||
<text x="98" y="276" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#ff6b6b" font-weight="bold">Bidirectional filter</text>
|
||||
<text x="262" y="276" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">Sales to Product is bidirectional, may cause ambiguity</text>
|
||||
|
||||
<!-- Finding 5: OK -->
|
||||
<text x="78" y="302" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">✓</text>
|
||||
<text x="98" y="302" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#06d6a0" font-weight="bold">Date table configured</text>
|
||||
<text x="270" y="302" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Calendar marked as date table with proper hierarchy</text>
|
||||
<text x="98" y="302" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#06d6a0" font-weight="bold">Date table configured</text>
|
||||
<text x="270" y="302" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">Calendar marked as date table with proper hierarchy</text>
|
||||
|
||||
<!-- Finding 6: OK -->
|
||||
<text x="78" y="328" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">✓</text>
|
||||
<text x="98" y="328" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#06d6a0" font-weight="bold">No circular dependencies</text>
|
||||
<text x="278" y="328" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">All measure references resolve correctly</text>
|
||||
<text x="98" y="328" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#06d6a0" font-weight="bold">No circular dependencies</text>
|
||||
<text x="278" y="328" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">All measure references resolve correctly</text>
|
||||
|
||||
<!-- Summary -->
|
||||
<line x1="78" y1="342" x2="680" y2="342" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="362" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#c9d1d9">Summary:</text>
|
||||
<text x="142" y="362" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#ff6b6b">2 errors</text>
|
||||
<text x="205" y="362" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#febc2e">2 warnings</text>
|
||||
<text x="290" y="362" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#06d6a0">2 passed</text>
|
||||
<text x="78" y="362" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#c9d1d9">Summary:</text>
|
||||
<text x="142" y="362" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#ff6b6b">2 errors</text>
|
||||
<text x="205" y="362" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#febc2e">2 warnings</text>
|
||||
<text x="290" y="362" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#06d6a0">2 passed</text>
|
||||
|
||||
<text x="78" y="382" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e">Shall I fix the missing relationship and remove the unused measures?</text>
|
||||
<text x="78" y="382" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Shall I fix the missing relationship and remove the unused measures?</text>
|
||||
|
||||
<!-- Comparison (outside chat container, centered) -->
|
||||
<rect x="225" y="420" width="190" height="32" rx="16" fill="#161b22" stroke="#ff6b6b" stroke-opacity="0.4"/>
|
||||
<text x="320" y="441" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#ff6b6b" text-anchor="middle">Manual review: 30+ minutes</text>
|
||||
<text x="320" y="441" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#ff6b6b" text-anchor="middle">Manual review: 30+ minutes</text>
|
||||
|
||||
<rect x="435" y="420" width="190" height="32" rx="16" fill="#161b22" stroke="#06d6a0" stroke-opacity="0.4"/>
|
||||
<text x="530" y="441" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#06d6a0" text-anchor="middle">pbi-cli: instant audit</text>
|
||||
<text x="530" y="441" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0" text-anchor="middle">pbi-cli: instant audit</text>
|
||||
|
||||
<!-- Footer -->
|
||||
<text x="425" y="480" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Catch model issues before they reach production</text>
|
||||
<text x="425" y="480" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">Catch model issues before they reach production</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
126
assets/report-layer.svg
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="350" viewBox="0 0 850 350">
|
||||
<defs>
|
||||
<linearGradient id="rl-glow" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#06d6a0" stop-opacity="0"/>
|
||||
<stop offset="50%" stop-color="#06d6a0" stop-opacity="0.12"/>
|
||||
<stop offset="100%" stop-color="#06d6a0" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="425" y="30" font-family="'Segoe UI', Arial, sans-serif" font-size="20" fill="#06d6a0" text-anchor="middle" font-weight="bold">Report Layer</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">Build entire Power BI reports from the command line</text>
|
||||
|
||||
<!-- Glow line -->
|
||||
<rect x="100" y="60" width="650" height="1" fill="url(#rl-glow)"/>
|
||||
|
||||
<!-- ============ 4 visual cards with large icons ============ -->
|
||||
|
||||
<!-- Card 1: Visuals -->
|
||||
<rect x="30" y="78" width="185" height="200" rx="10" fill="#161b22" stroke="#d97757" stroke-width="1.5"/>
|
||||
|
||||
<!-- Large bar chart icon -->
|
||||
<g transform="translate(62, 95)">
|
||||
<rect x="0" y="50" width="20" height="35" rx="3" fill="#d97757" opacity="0.4"/>
|
||||
<rect x="28" y="30" width="20" height="55" rx="3" fill="#d97757" opacity="0.6"/>
|
||||
<rect x="56" y="10" width="20" height="75" rx="3" fill="#d97757" opacity="0.8"/>
|
||||
<rect x="84" y="0" width="20" height="85" rx="3" fill="#d97757"/>
|
||||
<!-- Axis lines -->
|
||||
<line x1="-5" y1="86" x2="110" y2="86" stroke="#d97757" stroke-width="1" stroke-opacity="0.3"/>
|
||||
<line x1="-5" y1="0" x2="-5" y2="86" stroke="#d97757" stroke-width="1" stroke-opacity="0.3"/>
|
||||
</g>
|
||||
|
||||
<text x="122" y="210" font-family="'Segoe UI', Arial, sans-serif" font-size="16" fill="#d97757" text-anchor="middle" font-weight="bold">32 Visual Types</text>
|
||||
<text x="122" y="228" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Charts, cards, KPIs,</text>
|
||||
<text x="122" y="243" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">tables, slicers, maps</text>
|
||||
<text x="122" y="265" font-family="'Courier New', Courier, monospace" font-size="11" fill="#58a6ff" text-anchor="middle">pbi visual add</text>
|
||||
|
||||
<!-- Card 2: Data Binding -->
|
||||
<rect x="232" y="78" width="185" height="200" rx="10" fill="#161b22" stroke="#58a6ff" stroke-width="1.5"/>
|
||||
|
||||
<!-- Binding icon: table arrow to chart -->
|
||||
<g transform="translate(267, 100)">
|
||||
<!-- Table icon -->
|
||||
<rect x="0" y="10" width="40" height="50" rx="4" fill="none" stroke="#58a6ff" stroke-width="1.5"/>
|
||||
<line x1="0" y1="24" x2="40" y2="24" stroke="#58a6ff" stroke-width="1" stroke-opacity="0.5"/>
|
||||
<line x1="0" y1="38" x2="40" y2="38" stroke="#58a6ff" stroke-width="1" stroke-opacity="0.5"/>
|
||||
<line x1="16" y1="10" x2="16" y2="60" stroke="#58a6ff" stroke-width="1" stroke-opacity="0.3"/>
|
||||
<text x="20" y="8" font-family="'Segoe UI', Arial, sans-serif" font-size="8" fill="#58a6ff" text-anchor="middle">Table[Col]</text>
|
||||
|
||||
<!-- Arrow -->
|
||||
<line x1="48" y1="35" x2="68" y2="35" stroke="#F2C811" stroke-width="2" stroke-dasharray="3,2"/>
|
||||
<polygon points="72,35 66,30 66,40" fill="#F2C811"/>
|
||||
|
||||
<!-- Chart icon -->
|
||||
<rect x="76" y="10" width="40" height="50" rx="4" fill="none" stroke="#d97757" stroke-width="1.5"/>
|
||||
<rect x="82" y="36" width="8" height="18" rx="1" fill="#d97757" opacity="0.5"/>
|
||||
<rect x="92" y="26" width="8" height="28" rx="1" fill="#d97757" opacity="0.7"/>
|
||||
<rect x="102" y="18" width="8" height="36" rx="1" fill="#d97757"/>
|
||||
</g>
|
||||
|
||||
<text x="324" y="210" font-family="'Segoe UI', Arial, sans-serif" font-size="16" fill="#58a6ff" text-anchor="middle" font-weight="bold">Data Binding</text>
|
||||
<text x="324" y="228" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Connect model fields</text>
|
||||
<text x="324" y="243" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">to visual roles</text>
|
||||
<text x="324" y="265" font-family="'Courier New', Courier, monospace" font-size="11" fill="#58a6ff" text-anchor="middle">pbi visual bind</text>
|
||||
|
||||
<!-- Card 3: Themes -->
|
||||
<rect x="434" y="78" width="185" height="200" rx="10" fill="#161b22" stroke="#F2C811" stroke-width="1.5"/>
|
||||
|
||||
<!-- Large colour palette icon -->
|
||||
<g transform="translate(472, 100)">
|
||||
<!-- Palette shape -->
|
||||
<ellipse cx="55" cy="35" rx="50" ry="35" fill="none" stroke="#F2C811" stroke-width="1.2" stroke-opacity="0.3"/>
|
||||
<!-- Colour dots -->
|
||||
<circle cx="25" cy="20" r="10" fill="#0078D4"/>
|
||||
<circle cx="50" cy="14" r="10" fill="#00BCF2"/>
|
||||
<circle cx="75" cy="18" r="10" fill="#FFB900"/>
|
||||
<circle cx="85" cy="40" r="10" fill="#D83B01"/>
|
||||
<circle cx="68" cy="52" r="10" fill="#8661C5"/>
|
||||
<circle cx="38" cy="52" r="10" fill="#00B294"/>
|
||||
</g>
|
||||
|
||||
<text x="526" y="210" font-family="'Segoe UI', Arial, sans-serif" font-size="16" fill="#F2C811" text-anchor="middle" font-weight="bold">Themes</text>
|
||||
<text x="526" y="228" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Brand colours, formatting</text>
|
||||
<text x="526" y="243" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">rules, diff before apply</text>
|
||||
<text x="526" y="265" font-family="'Courier New', Courier, monospace" font-size="11" fill="#58a6ff" text-anchor="middle">pbi report set-theme</text>
|
||||
|
||||
<!-- Card 4: Filters -->
|
||||
<rect x="636" y="78" width="185" height="200" rx="10" fill="#161b22" stroke="#ff6b6b" stroke-width="1.5"/>
|
||||
|
||||
<!-- Large funnel icon -->
|
||||
<g transform="translate(680, 96)">
|
||||
<polygon points="0,0 90,0 68,28 22,28" fill="none" stroke="#ff6b6b" stroke-width="1.8" opacity="0.4"/>
|
||||
<polygon points="22,28 68,28 58,50 32,50" fill="none" stroke="#ff6b6b" stroke-width="1.8" opacity="0.7"/>
|
||||
<polygon points="32,50 58,50 52,68 38,68" fill="none" stroke="#ff6b6b" stroke-width="1.8"/>
|
||||
<!-- Data dots being filtered -->
|
||||
<circle cx="15" cy="-8" r="3" fill="#ff6b6b" opacity="0.3"/>
|
||||
<circle cx="30" cy="-10" r="3" fill="#ff6b6b" opacity="0.4"/>
|
||||
<circle cx="45" cy="-7" r="3" fill="#ff6b6b" opacity="0.5"/>
|
||||
<circle cx="60" cy="-9" r="3" fill="#ff6b6b" opacity="0.4"/>
|
||||
<circle cx="75" cy="-8" r="3" fill="#ff6b6b" opacity="0.3"/>
|
||||
<!-- Output dot -->
|
||||
<circle cx="45" cy="78" r="4" fill="#06d6a0"/>
|
||||
</g>
|
||||
|
||||
<text x="728" y="210" font-family="'Segoe UI', Arial, sans-serif" font-size="16" fill="#ff6b6b" text-anchor="middle" font-weight="bold">Filters</text>
|
||||
<text x="728" y="228" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">TopN, date range,</text>
|
||||
<text x="728" y="243" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">categorical filters</text>
|
||||
<text x="728" y="265" font-family="'Courier New', Courier, monospace" font-size="11" fill="#58a6ff" text-anchor="middle">pbi filters add-topn</text>
|
||||
|
||||
<!-- ============ Bottom bar: key capabilities ============ -->
|
||||
<rect x="30" y="295" width="790" height="40" rx="8" fill="#161b22" stroke="#06d6a0" stroke-width="1" stroke-opacity="0.3"/>
|
||||
|
||||
<circle cx="70" cy="315" r="4" fill="#06d6a0"/>
|
||||
<text x="82" y="319" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#e6edf3">No connection needed</text>
|
||||
|
||||
<circle cx="260" cy="315" r="4" fill="#06d6a0"/>
|
||||
<text x="272" y="319" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#e6edf3">Auto-syncs with Desktop</text>
|
||||
|
||||
<circle cx="470" cy="315" r="4" fill="#06d6a0"/>
|
||||
<text x="482" y="319" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#e6edf3">Bulk operations</text>
|
||||
|
||||
<circle cx="630" cy="315" r="4" fill="#06d6a0"/>
|
||||
<text x="642" y="319" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#e6edf3">Git-friendly PBIR</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.8 KiB |
124
assets/report-workflow.svg
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="280" viewBox="0 0 850 280">
|
||||
<defs>
|
||||
<linearGradient id="rw-glow" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#06d6a0" stop-opacity="0"/>
|
||||
<stop offset="50%" stop-color="#06d6a0" stop-opacity="0.12"/>
|
||||
<stop offset="100%" stop-color="#06d6a0" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="425" y="28" font-family="'Segoe UI', Arial, sans-serif" font-size="20" fill="#06d6a0" text-anchor="middle" font-weight="bold">Build a Report in 6 Steps</text>
|
||||
<text x="425" y="48" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">From empty folder to a complete, themed report with visuals and filters</text>
|
||||
|
||||
<rect x="60" y="58" width="730" height="1" fill="url(#rw-glow)"/>
|
||||
|
||||
<!-- 6 steps in a horizontal flow -->
|
||||
<!-- Step width=110, arrow=22, total=6*110+5*22=770, start=(850-770)/2=40 -->
|
||||
|
||||
<!-- Step 1: Scaffold -->
|
||||
<rect x="40" y="75" width="110" height="120" rx="8" fill="#161b22" stroke="#58a6ff" stroke-width="1.5"/>
|
||||
<circle cx="95" cy="92" r="11" fill="#58a6ff"/>
|
||||
<text x="95" y="97" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#0d1117" text-anchor="middle" font-weight="bold">1</text>
|
||||
<text x="95" y="118" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#58a6ff" text-anchor="middle" font-weight="bold">Scaffold</text>
|
||||
<!-- Folder icon -->
|
||||
<g transform="translate(72, 128)">
|
||||
<rect x="0" y="6" width="40" height="28" rx="3" fill="none" stroke="#58a6ff" stroke-width="1.2"/>
|
||||
<rect x="0" y="2" width="18" height="8" rx="2" fill="none" stroke="#58a6ff" stroke-width="1"/>
|
||||
</g>
|
||||
<text x="95" y="175" font-family="'Courier New', Courier, monospace" font-size="9" fill="#8b949e" text-anchor="middle">report create</text>
|
||||
|
||||
<!-- Arrow -->
|
||||
<line x1="154" y1="135" x2="168" y2="135" stroke="#F2C811" stroke-width="2"/>
|
||||
<polygon points="172,135 166,130 166,140" fill="#F2C811"/>
|
||||
|
||||
<!-- Step 2: Add Pages -->
|
||||
<rect x="176" y="75" width="110" height="120" rx="8" fill="#161b22" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<circle cx="231" cy="92" r="11" fill="#06d6a0"/>
|
||||
<text x="231" y="97" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#0d1117" text-anchor="middle" font-weight="bold">2</text>
|
||||
<text x="231" y="118" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0" text-anchor="middle" font-weight="bold">Add Pages</text>
|
||||
<!-- Pages icon -->
|
||||
<g transform="translate(210, 126)">
|
||||
<rect x="6" y="0" width="30" height="22" rx="2" fill="none" stroke="#06d6a0" stroke-width="1" opacity="0.4"/>
|
||||
<rect x="3" y="4" width="30" height="22" rx="2" fill="none" stroke="#06d6a0" stroke-width="1" opacity="0.7"/>
|
||||
<rect x="0" y="8" width="30" height="22" rx="2" fill="none" stroke="#06d6a0" stroke-width="1.2"/>
|
||||
</g>
|
||||
<text x="231" y="175" font-family="'Courier New', Courier, monospace" font-size="9" fill="#8b949e" text-anchor="middle">report add-page</text>
|
||||
|
||||
<!-- Arrow -->
|
||||
<line x1="290" y1="135" x2="304" y2="135" stroke="#F2C811" stroke-width="2"/>
|
||||
<polygon points="308,135 302,130 302,140" fill="#F2C811"/>
|
||||
|
||||
<!-- Step 3: Add Visuals -->
|
||||
<rect x="312" y="75" width="110" height="120" rx="8" fill="#161b22" stroke="#d97757" stroke-width="1.5"/>
|
||||
<circle cx="367" cy="92" r="11" fill="#d97757"/>
|
||||
<text x="367" y="97" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#0d1117" text-anchor="middle" font-weight="bold">3</text>
|
||||
<text x="367" y="118" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#d97757" text-anchor="middle" font-weight="bold">Add Visuals</text>
|
||||
<!-- Chart icon -->
|
||||
<g transform="translate(348, 128)">
|
||||
<rect x="0" y="18" width="8" height="16" rx="1" fill="#d97757" opacity="0.5"/>
|
||||
<rect x="12" y="8" width="8" height="26" rx="1" fill="#d97757" opacity="0.7"/>
|
||||
<rect x="24" y="0" width="8" height="34" rx="1" fill="#d97757"/>
|
||||
</g>
|
||||
<text x="367" y="175" font-family="'Courier New', Courier, monospace" font-size="9" fill="#8b949e" text-anchor="middle">visual add</text>
|
||||
|
||||
<!-- Arrow -->
|
||||
<line x1="426" y1="135" x2="440" y2="135" stroke="#F2C811" stroke-width="2"/>
|
||||
<polygon points="444,135 438,130 438,140" fill="#F2C811"/>
|
||||
|
||||
<!-- Step 4: Bind Data -->
|
||||
<rect x="448" y="75" width="110" height="120" rx="8" fill="#161b22" stroke="#F2C811" stroke-width="1.5"/>
|
||||
<circle cx="503" cy="92" r="11" fill="#F2C811"/>
|
||||
<text x="503" y="97" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#0d1117" text-anchor="middle" font-weight="bold">4</text>
|
||||
<text x="503" y="118" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#F2C811" text-anchor="middle" font-weight="bold">Bind Data</text>
|
||||
<!-- Link/bind icon -->
|
||||
<g transform="translate(484, 128)">
|
||||
<rect x="0" y="6" width="16" height="20" rx="2" fill="none" stroke="#8b949e" stroke-width="1"/>
|
||||
<line x1="20" y1="16" x2="30" y2="16" stroke="#F2C811" stroke-width="2"/>
|
||||
<polygon points="32,16 28,12 28,20" fill="#F2C811"/>
|
||||
<rect x="34" y="4" width="8" height="24" rx="1" fill="#d97757" opacity="0.6"/>
|
||||
</g>
|
||||
<text x="503" y="175" font-family="'Courier New', Courier, monospace" font-size="9" fill="#8b949e" text-anchor="middle">visual bind</text>
|
||||
|
||||
<!-- Arrow -->
|
||||
<line x1="562" y1="135" x2="576" y2="135" stroke="#F2C811" stroke-width="2"/>
|
||||
<polygon points="580,135 574,130 574,140" fill="#F2C811"/>
|
||||
|
||||
<!-- Step 5: Theme -->
|
||||
<rect x="584" y="75" width="110" height="120" rx="8" fill="#161b22" stroke="#7b61ff" stroke-width="1.5"/>
|
||||
<circle cx="639" cy="92" r="11" fill="#7b61ff"/>
|
||||
<text x="639" y="97" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#0d1117" text-anchor="middle" font-weight="bold">5</text>
|
||||
<text x="639" y="118" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#7b61ff" text-anchor="middle" font-weight="bold">Apply Theme</text>
|
||||
<!-- Palette dots -->
|
||||
<g transform="translate(616, 132)">
|
||||
<circle cx="8" cy="10" r="6" fill="#0078D4"/>
|
||||
<circle cx="24" cy="10" r="6" fill="#FFB900"/>
|
||||
<circle cx="40" cy="10" r="6" fill="#D83B01"/>
|
||||
</g>
|
||||
<text x="639" y="175" font-family="'Courier New', Courier, monospace" font-size="9" fill="#8b949e" text-anchor="middle">set-theme</text>
|
||||
|
||||
<!-- Arrow -->
|
||||
<line x1="698" y1="135" x2="712" y2="135" stroke="#F2C811" stroke-width="2"/>
|
||||
<polygon points="716,135 710,130 710,140" fill="#F2C811"/>
|
||||
|
||||
<!-- Step 6: Validate -->
|
||||
<rect x="720" y="75" width="110" height="120" rx="8" fill="#161b22" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<circle cx="775" cy="92" r="11" fill="#06d6a0"/>
|
||||
<text x="775" y="97" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#0d1117" text-anchor="middle" font-weight="bold">6</text>
|
||||
<text x="775" y="118" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0" text-anchor="middle" font-weight="bold">Validate</text>
|
||||
<!-- Checkmark icon -->
|
||||
<g transform="translate(757, 128)">
|
||||
<circle cx="18" cy="16" r="16" fill="none" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<polyline points="8,16 15,24 28,10" fill="none" stroke="#06d6a0" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<text x="775" y="175" font-family="'Courier New', Courier, monospace" font-size="9" fill="#8b949e" text-anchor="middle">validate</text>
|
||||
|
||||
<!-- Bottom: full command example -->
|
||||
<rect x="40" y="210" width="770" height="50" rx="8" fill="#161b22" stroke="#06d6a0" stroke-width="1" stroke-opacity="0.3"/>
|
||||
<text x="425" y="233" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff" text-anchor="middle">pbi report create . --name "Sales" && pbi report add-page --display-name "Overview"</text>
|
||||
<text x="425" y="250" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff" text-anchor="middle">pbi visual add --type bar --page overview && pbi visual bind --category --value</text>
|
||||
|
||||
<text x="425" y="275" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e" text-anchor="middle">All steps work offline on PBIR files. Desktop auto-syncs when it's open.</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<!-- Title -->
|
||||
<text x="425" y="32" font-family="'Segoe UI', Arial, sans-serif" font-size="18" fill="#F2C811" text-anchor="middle" font-weight="bold">Test Row-Level Security in Seconds</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Create, configure, and validate RLS roles without touching the GUI</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">Create, configure, and validate RLS roles without touching the GUI</text>
|
||||
|
||||
<!-- Chat container -->
|
||||
<rect x="40" y="65" width="770" height="360" rx="8" fill="#161b22" stroke="#30363d" stroke-width="1"/>
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<circle cx="62" cy="80" r="5" fill="#ff5f57"/>
|
||||
<circle cx="78" cy="80" r="5" fill="#febc2e"/>
|
||||
<circle cx="94" cy="80" r="5" fill="#28c840"/>
|
||||
<text x="116" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#d97757" font-weight="bold">Claude Code</text>
|
||||
<text x="116" y="84" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#d97757" font-weight="bold">Claude Code</text>
|
||||
|
||||
<!-- User message 1 -->
|
||||
<rect x="250" y="106" width="540" height="44" rx="12" fill="#1a3a5c" stroke="#264d73" stroke-width="1"/>
|
||||
|
|
@ -23,13 +23,13 @@
|
|||
|
||||
<!-- Claude response 1 -->
|
||||
<rect x="60" y="162" width="560" height="80" rx="12" fill="#1c1c1c" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="182" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="182" font-family="'Courier New', Courier, monospace" font-size="10" fill="#58a6ff">pbi security-role create "Europe Sales"</text>
|
||||
<text x="78" y="200" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="200" font-family="'Courier New', Courier, monospace" font-size="10" fill="#58a6ff">pbi security-role add-filter "Europe Sales" -t Region</text>
|
||||
<text x="78" y="182" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="182" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff">pbi security-role create "Europe Sales"</text>
|
||||
<text x="78" y="200" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Running:</text>
|
||||
<text x="130" y="200" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff">pbi security-role add-filter "Europe Sales" -t Region</text>
|
||||
<text x="78" y="220" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">✓ Role created with filter:</text>
|
||||
<text x="270" y="220" font-family="'Courier New', Courier, monospace" font-size="10" fill="#d97757">[Region][Continent] = "Europe"</text>
|
||||
<text x="78" y="236" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e">Applied to Region table with DAX filter expression</text>
|
||||
<text x="270" y="220" font-family="'Courier New', Courier, monospace" font-size="12" fill="#d97757">[Region][Continent] = "Europe"</text>
|
||||
<text x="78" y="236" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">Applied to Region table with DAX filter expression</text>
|
||||
|
||||
<!-- User message 2 -->
|
||||
<rect x="370" y="254" width="420" height="28" rx="12" fill="#1a3a5c" stroke="#264d73" stroke-width="1"/>
|
||||
|
|
@ -37,15 +37,15 @@
|
|||
|
||||
<!-- Claude response 2 -->
|
||||
<rect x="60" y="294" width="560" height="118" rx="12" fill="#1c1c1c" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="314" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e">Running as role "Europe Sales":</text>
|
||||
<text x="78" y="332" font-family="'Courier New', Courier, monospace" font-size="10" fill="#58a6ff">pbi dax execute "EVALUATE ROW(\"Sales\", [Total Sales], \"Regions\", COUNTROWS(Region))"</text>
|
||||
<text x="78" y="314" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e">Running as role "Europe Sales":</text>
|
||||
<text x="78" y="332" font-family="'Courier New', Courier, monospace" font-size="12" fill="#58a6ff">pbi dax execute "EVALUATE ROW(\"Sales\", [Total Sales], \"Regions\", COUNTROWS(Region))"</text>
|
||||
|
||||
<!-- Result table -->
|
||||
<line x1="78" y1="342" x2="590" y2="342" stroke="#30363d" stroke-width="1"/>
|
||||
<text x="78" y="360" font-family="'Courier New', Courier, monospace" font-size="11" fill="#F2C811">| Total Sales | Regions |</text>
|
||||
<text x="78" y="378" font-family="'Courier New', Courier, monospace" font-size="11" fill="#c9d1d9">| $3,842,150 | 12 |</text>
|
||||
<text x="78" y="396" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#06d6a0">✓ Filter working: 12 of 48 regions visible (Europe only)</text>
|
||||
<text x="78" y="360" font-family="'Courier New', Courier, monospace" font-size="13" fill="#F2C811">| Total Sales | Regions |</text>
|
||||
<text x="78" y="378" font-family="'Courier New', Courier, monospace" font-size="13" fill="#c9d1d9">| $3,842,150 | 12 |</text>
|
||||
<text x="78" y="396" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#06d6a0">✓ Filter working: 12 of 48 regions visible (Europe only)</text>
|
||||
|
||||
<!-- Footer -->
|
||||
<text x="425" y="450" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Build and validate security roles entirely from conversation. No Security tab needed.</text>
|
||||
<text x="425" y="450" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">Build and validate security roles entirely from conversation. No Security tab needed.</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
|
@ -1,72 +1,140 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="320" viewBox="0 0 850 320">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="480" viewBox="0 0 850 480">
|
||||
<defs>
|
||||
<linearGradient id="glow-line" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#F2C811" stop-opacity="0"/>
|
||||
<stop offset="50%" stop-color="#F2C811" stop-opacity="0.2"/>
|
||||
<stop offset="100%" stop-color="#F2C811" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="425" y="32" font-family="'Segoe UI', Arial, sans-serif" font-size="18" fill="#F2C811" text-anchor="middle" font-weight="bold">7 Skills That Claude Discovers Automatically</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Install once, Claude uses the right skill for every task</text>
|
||||
<text x="425" y="32" font-family="'Segoe UI', Arial, sans-serif" font-size="20" fill="#F2C811" text-anchor="middle" font-weight="bold">12 Skills That Claude Discovers Automatically</text>
|
||||
<text x="425" y="54" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">Two layers, one CLI. Claude picks the right skill for every task.</text>
|
||||
|
||||
<!-- Root node: PBI-CLI -->
|
||||
<rect x="350" y="70" width="150" height="40" rx="8" fill="#161b22" stroke="#F2C811" stroke-width="2"/>
|
||||
<text x="425" y="95" font-family="'Segoe UI', Arial, sans-serif" font-size="15" fill="#F2C811" text-anchor="middle" font-weight="bold">pbi-cli</text>
|
||||
<!-- ======================== SEMANTIC MODEL LAYER ======================== -->
|
||||
|
||||
<!-- Vertical trunk line -->
|
||||
<line x1="425" y1="110" x2="425" y2="140" stroke="#F2C811" stroke-width="2" stroke-opacity="0.3"/>
|
||||
<!-- Layer header -->
|
||||
<rect x="30" y="78" width="790" height="32" rx="6" fill="#161b22" stroke="#58a6ff" stroke-width="1.5"/>
|
||||
<text x="425" y="100" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#58a6ff" text-anchor="middle" font-weight="bold">Semantic Model Layer</text>
|
||||
<text x="740" y="100" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">requires pbi connect</text>
|
||||
|
||||
<!-- Horizontal branch line -->
|
||||
<line x1="75" y1="140" x2="775" y2="140" stroke="#F2C811" stroke-width="2" stroke-opacity="0.3"/>
|
||||
|
||||
<!-- Branch drops -->
|
||||
<line x1="75" y1="140" x2="75" y2="160" stroke="#58a6ff" stroke-width="1.5" stroke-opacity="0.4"/>
|
||||
<line x1="192" y1="140" x2="192" y2="160" stroke="#F2C811" stroke-width="1.5" stroke-opacity="0.4"/>
|
||||
<line x1="308" y1="140" x2="308" y2="160" stroke="#7b61ff" stroke-width="1.5" stroke-opacity="0.4"/>
|
||||
<line x1="425" y1="140" x2="425" y2="160" stroke="#06d6a0" stroke-width="1.5" stroke-opacity="0.4"/>
|
||||
<line x1="542" y1="140" x2="542" y2="160" stroke="#ff6b6b" stroke-width="1.5" stroke-opacity="0.4"/>
|
||||
<line x1="658" y1="140" x2="658" y2="160" stroke="#a0c4ff" stroke-width="1.5" stroke-opacity="0.4"/>
|
||||
<line x1="775" y1="140" x2="775" y2="160" stroke="#ffd166" stroke-width="1.5" stroke-opacity="0.4"/>
|
||||
<!-- 7 skill cards - evenly spaced across 850px -->
|
||||
<!-- Card width=100, gap=15, total=7*100+6*15=790, start x=30 -->
|
||||
|
||||
<!-- Skill 1: DAX -->
|
||||
<rect x="25" y="160" width="100" height="34" rx="8" fill="#161b22" stroke="#58a6ff" stroke-width="1.5"/>
|
||||
<text x="75" y="182" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#58a6ff" text-anchor="middle" font-weight="bold">DAX</text>
|
||||
<text x="75" y="210" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Queries &</text>
|
||||
<text x="75" y="224" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Calculations</text>
|
||||
<rect x="30" y="125" width="100" height="44" rx="8" fill="#161b22" stroke="#58a6ff" stroke-width="1.5"/>
|
||||
<text x="80" y="144" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#58a6ff" text-anchor="middle" font-weight="bold">DAX</text>
|
||||
<text x="80" y="160" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Queries</text>
|
||||
<text x="80" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Execute &</text>
|
||||
<text x="80" y="203" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Validate</text>
|
||||
|
||||
<!-- Skill 2: Modeling -->
|
||||
<rect x="142" y="160" width="100" height="34" rx="8" fill="#161b22" stroke="#F2C811" stroke-width="1.5"/>
|
||||
<text x="192" y="182" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#F2C811" text-anchor="middle" font-weight="bold">Modeling</text>
|
||||
<text x="192" y="210" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Tables &</text>
|
||||
<text x="192" y="224" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Relationships</text>
|
||||
<rect x="145" y="125" width="100" height="44" rx="8" fill="#161b22" stroke="#F2C811" stroke-width="1.5"/>
|
||||
<text x="195" y="144" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#F2C811" text-anchor="middle" font-weight="bold">Modeling</text>
|
||||
<text x="195" y="160" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Tables</text>
|
||||
<text x="195" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Tables &</text>
|
||||
<text x="195" y="203" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Relationships</text>
|
||||
|
||||
<!-- Skill 3: Deployment -->
|
||||
<rect x="258" y="160" width="100" height="34" rx="8" fill="#161b22" stroke="#7b61ff" stroke-width="1.5"/>
|
||||
<text x="308" y="182" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#7b61ff" text-anchor="middle" font-weight="bold">Deployment</text>
|
||||
<text x="308" y="210" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">TMDL &</text>
|
||||
<text x="308" y="224" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Transactions</text>
|
||||
<rect x="260" y="125" width="100" height="44" rx="8" fill="#161b22" stroke="#7b61ff" stroke-width="1.5"/>
|
||||
<text x="310" y="144" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#7b61ff" text-anchor="middle" font-weight="bold">Deployment</text>
|
||||
<text x="310" y="160" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">TMDL</text>
|
||||
<text x="310" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Export, Diff</text>
|
||||
<text x="310" y="203" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">& Transactions</text>
|
||||
|
||||
<!-- Skill 4: Security -->
|
||||
<rect x="375" y="160" width="100" height="34" rx="8" fill="#161b22" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<text x="425" y="182" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0" text-anchor="middle" font-weight="bold">Security</text>
|
||||
<text x="425" y="210" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">RLS &</text>
|
||||
<text x="425" y="224" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Perspectives</text>
|
||||
<rect x="375" y="125" width="100" height="44" rx="8" fill="#161b22" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<text x="425" y="144" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#06d6a0" text-anchor="middle" font-weight="bold">Security</text>
|
||||
<text x="425" y="160" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">RLS</text>
|
||||
<text x="425" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Roles &</text>
|
||||
<text x="425" y="203" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Perspectives</text>
|
||||
|
||||
<!-- Skill 5: Docs -->
|
||||
<rect x="492" y="160" width="100" height="34" rx="8" fill="#161b22" stroke="#ff6b6b" stroke-width="1.5"/>
|
||||
<text x="542" y="182" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#ff6b6b" text-anchor="middle" font-weight="bold">Docs</text>
|
||||
<text x="542" y="210" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Data</text>
|
||||
<text x="542" y="224" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Dictionary</text>
|
||||
<rect x="490" y="125" width="100" height="44" rx="8" fill="#161b22" stroke="#ff6b6b" stroke-width="1.5"/>
|
||||
<text x="540" y="144" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#ff6b6b" text-anchor="middle" font-weight="bold">Docs</text>
|
||||
<text x="540" y="160" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Dictionary</text>
|
||||
<text x="540" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Data</text>
|
||||
<text x="540" y="203" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Dictionary</text>
|
||||
|
||||
<!-- Skill 6: Partitions -->
|
||||
<rect x="608" y="160" width="100" height="34" rx="8" fill="#161b22" stroke="#a0c4ff" stroke-width="1.5"/>
|
||||
<text x="658" y="182" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#a0c4ff" text-anchor="middle" font-weight="bold">Partitions</text>
|
||||
<text x="658" y="210" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Sources &</text>
|
||||
<text x="658" y="224" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">M Queries</text>
|
||||
<rect x="605" y="125" width="100" height="44" rx="8" fill="#161b22" stroke="#a0c4ff" stroke-width="1.5"/>
|
||||
<text x="655" y="144" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#a0c4ff" text-anchor="middle" font-weight="bold">Partitions</text>
|
||||
<text x="655" y="160" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Sources</text>
|
||||
<text x="655" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">M Queries</text>
|
||||
<text x="655" y="203" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">& Expressions</text>
|
||||
|
||||
<!-- Skill 7: Diagnostics -->
|
||||
<rect x="725" y="160" width="100" height="34" rx="8" fill="#161b22" stroke="#ffd166" stroke-width="1.5"/>
|
||||
<text x="775" y="182" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#ffd166" text-anchor="middle" font-weight="bold">Diagnostics</text>
|
||||
<text x="775" y="210" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Tracing &</text>
|
||||
<text x="775" y="224" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Health</text>
|
||||
<rect x="720" y="125" width="100" height="44" rx="8" fill="#161b22" stroke="#ffd166" stroke-width="1.5"/>
|
||||
<text x="770" y="144" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#ffd166" text-anchor="middle" font-weight="bold">Diagnostics</text>
|
||||
<text x="770" y="160" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Tracing</text>
|
||||
<text x="770" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Tracing &</text>
|
||||
<text x="770" y="203" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Health Check</text>
|
||||
|
||||
<!-- ======================== REPORT LAYER ======================== -->
|
||||
|
||||
<!-- Layer header -->
|
||||
<rect x="30" y="225" width="790" height="32" rx="6" fill="#161b22" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<text x="425" y="247" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#06d6a0" text-anchor="middle" font-weight="bold">Report Layer</text>
|
||||
<text x="680" y="247" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">no connection needed</text>
|
||||
<!-- NEW badge -->
|
||||
<rect x="760" y="233" width="38" height="18" rx="9" fill="#06d6a0"/>
|
||||
<text x="779" y="246" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#0d1117" text-anchor="middle" font-weight="bold">NEW</text>
|
||||
|
||||
<!-- 5 skill cards - evenly spaced, wider cards -->
|
||||
<!-- Card width=140, gap=17, total=5*140+4*17=768, start x=41 -->
|
||||
|
||||
<!-- Skill 8: Report -->
|
||||
<rect x="41" y="272" width="140" height="44" rx="8" fill="#161b22" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<text x="111" y="290" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#06d6a0" text-anchor="middle" font-weight="bold">Report</text>
|
||||
<text x="111" y="306" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Scaffold & Validate</text>
|
||||
<text x="111" y="336" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Create, validate,</text>
|
||||
<text x="111" y="349" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">preview reports</text>
|
||||
|
||||
<!-- Skill 9: Visuals -->
|
||||
<rect x="198" y="272" width="140" height="44" rx="8" fill="#161b22" stroke="#d97757" stroke-width="1.5"/>
|
||||
<text x="268" y="290" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#d97757" text-anchor="middle" font-weight="bold">Visuals</text>
|
||||
<text x="268" y="306" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">32 Types</text>
|
||||
<text x="268" y="336" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Add, bind, bulk-</text>
|
||||
<text x="268" y="349" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">manage visuals</text>
|
||||
|
||||
<!-- Skill 10: Pages -->
|
||||
<rect x="355" y="272" width="140" height="44" rx="8" fill="#161b22" stroke="#58a6ff" stroke-width="1.5"/>
|
||||
<text x="425" y="290" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#58a6ff" text-anchor="middle" font-weight="bold">Pages</text>
|
||||
<text x="425" y="306" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Layout & Navigation</text>
|
||||
<text x="425" y="336" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Pages, bookmarks,</text>
|
||||
<text x="425" y="349" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">drillthrough</text>
|
||||
|
||||
<!-- Skill 11: Themes -->
|
||||
<rect x="512" y="272" width="140" height="44" rx="8" fill="#161b22" stroke="#F2C811" stroke-width="1.5"/>
|
||||
<text x="582" y="290" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#F2C811" text-anchor="middle" font-weight="bold">Themes</text>
|
||||
<text x="582" y="306" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Styling</text>
|
||||
<text x="582" y="336" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Branding &</text>
|
||||
<text x="582" y="349" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">formatting rules</text>
|
||||
|
||||
<!-- Skill 12: Filters -->
|
||||
<rect x="669" y="272" width="140" height="44" rx="8" fill="#161b22" stroke="#ff6b6b" stroke-width="1.5"/>
|
||||
<text x="739" y="290" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#ff6b6b" text-anchor="middle" font-weight="bold">Filters</text>
|
||||
<text x="739" y="306" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">TopN & Date</text>
|
||||
<text x="739" y="336" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Page & visual</text>
|
||||
<text x="739" y="349" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">filters</text>
|
||||
|
||||
<!-- ======================== STATS BAR ======================== -->
|
||||
<rect x="60" y="375" width="730" height="1" fill="url(#glow-line)"/>
|
||||
|
||||
<text x="130" y="412" font-family="'Segoe UI', Arial, sans-serif" font-size="30" fill="#F2C811" text-anchor="middle" font-weight="bold">27</text>
|
||||
<text x="130" y="432" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e" text-anchor="middle">Command Groups</text>
|
||||
|
||||
<text x="310" y="412" font-family="'Segoe UI', Arial, sans-serif" font-size="30" fill="#58a6ff" text-anchor="middle" font-weight="bold">125+</text>
|
||||
<text x="310" y="432" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e" text-anchor="middle">Subcommands</text>
|
||||
|
||||
<text x="490" y="412" font-family="'Segoe UI', Arial, sans-serif" font-size="30" fill="#06d6a0" text-anchor="middle" font-weight="bold">32</text>
|
||||
<text x="490" y="432" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e" text-anchor="middle">Visual Types</text>
|
||||
|
||||
<text x="670" y="412" font-family="'Segoe UI', Arial, sans-serif" font-size="30" fill="#d97757" text-anchor="middle" font-weight="bold">12</text>
|
||||
<text x="670" y="432" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e" text-anchor="middle">Claude Skills</text>
|
||||
|
||||
<!-- Footer -->
|
||||
<text x="425" y="265" font-family="'Courier New', Courier, monospace" font-size="14" fill="#58a6ff" text-anchor="middle" font-weight="bold">pbi skills list</text>
|
||||
<text x="425" y="290" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e" text-anchor="middle">Claude reads the skill matching your task and knows exactly which commands to use</text>
|
||||
<text x="425" y="465" font-family="'Courier New', Courier, monospace" font-size="15" fill="#58a6ff" text-anchor="middle" font-weight="bold">pbi skills list</text>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 12 KiB |
87
assets/stats.svg
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="300" viewBox="0 0 850 300">
|
||||
|
||||
<!-- BACKGROUND -->
|
||||
<rect width="100%" height="100%" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- TITLE -->
|
||||
<text x="425" y="33" font-family="'Segoe UI', Arial, sans-serif" font-size="17" fill="#F2C811" text-anchor="middle" font-weight="bold">pbi-cli at a Glance</text>
|
||||
|
||||
<!-- ===================== CELL 1: 30+ CLI Commands (blue) top-left ===================== -->
|
||||
<rect x="18" y="48" width="387" height="110" rx="10" fill="#0d1a2a" stroke="#58a6ff" stroke-width="1.5"/>
|
||||
<rect x="18" y="48" width="5" height="110" rx="2" fill="#58a6ff" fill-opacity="0.8"/>
|
||||
|
||||
<!-- Big number -->
|
||||
<text x="211" y="103" font-family="'Segoe UI', Arial, sans-serif" font-size="58" fill="#58a6ff" text-anchor="middle" font-weight="bold">30+</text>
|
||||
|
||||
<!-- Label -->
|
||||
<text x="211" y="126" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#c9d1d9" text-anchor="middle" font-weight="600">CLI Commands</text>
|
||||
|
||||
<!-- Description -->
|
||||
<text x="211" y="143" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">across Modeling + Reporting layers</text>
|
||||
|
||||
<!-- Decorative accent: row of circles -->
|
||||
<g opacity="0.4">
|
||||
<circle cx="330" cy="68" r="4" fill="#58a6ff"/>
|
||||
<circle cx="345" cy="68" r="4" fill="#58a6ff"/>
|
||||
<circle cx="360" cy="68" r="4" fill="#58a6ff"/>
|
||||
<circle cx="375" cy="68" r="4" fill="#58a6ff"/>
|
||||
<circle cx="390" cy="68" r="4" fill="#58a6ff" fill-opacity="0.3"/>
|
||||
<circle cx="394" cy="68" r="4" fill="#58a6ff" fill-opacity="0.3"/>
|
||||
</g>
|
||||
|
||||
<!-- ===================== CELL 2: 32 Visual Types (green) top-right ===================== -->
|
||||
<rect x="445" y="48" width="387" height="110" rx="10" fill="#0a1a14" stroke="#06d6a0" stroke-width="1.5"/>
|
||||
<rect x="445" y="48" width="5" height="110" rx="2" fill="#06d6a0" fill-opacity="0.8"/>
|
||||
|
||||
<!-- Big number -->
|
||||
<text x="638" y="103" font-family="'Segoe UI', Arial, sans-serif" font-size="58" fill="#06d6a0" text-anchor="middle" font-weight="bold">32</text>
|
||||
|
||||
<!-- Label -->
|
||||
<text x="638" y="126" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#c9d1d9" text-anchor="middle" font-weight="600">Visual Types</text>
|
||||
|
||||
<!-- Description -->
|
||||
<text x="638" y="143" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Power BI visuals with full data binding</text>
|
||||
|
||||
<!-- Decorative accent: mini bar chart -->
|
||||
<rect x="772" y="70" width="8" height="20" rx="2" fill="#06d6a0" fill-opacity="0.5"/>
|
||||
<rect x="783" y="62" width="8" height="28" rx="2" fill="#06d6a0" fill-opacity="0.5"/>
|
||||
<rect x="794" y="58" width="8" height="32" rx="2" fill="#06d6a0" fill-opacity="0.7"/>
|
||||
|
||||
<!-- ===================== CELL 3: ~30 Tokens/Call (gold) bottom-left ===================== -->
|
||||
<rect x="18" y="170" width="387" height="110" rx="10" fill="#1a1600" stroke="#F2C811" stroke-width="1.5"/>
|
||||
<rect x="18" y="170" width="5" height="110" rx="2" fill="#F2C811" fill-opacity="0.8"/>
|
||||
|
||||
<!-- Big number -->
|
||||
<text x="211" y="225" font-family="'Segoe UI', Arial, sans-serif" font-size="58" fill="#F2C811" text-anchor="middle" font-weight="bold">~30</text>
|
||||
|
||||
<!-- Label -->
|
||||
<text x="211" y="248" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#c9d1d9" text-anchor="middle" font-weight="600">Tokens per Call</text>
|
||||
|
||||
<!-- Description -->
|
||||
<text x="211" y="265" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">vs 4,000+ tokens with raw MCP tools</text>
|
||||
|
||||
<!-- Decorative accent: comparison bar -->
|
||||
<rect x="310" y="183" width="80" height="8" rx="4" fill="#ff4444" fill-opacity="0.4"/>
|
||||
<rect x="310" y="183" width="6" height="8" rx="4" fill="#F2C811" fill-opacity="0.9"/>
|
||||
<text x="350" y="203" font-family="'Segoe UI', Arial, sans-serif" font-size="8" fill="#F2C811" fill-opacity="0.7" text-anchor="middle">133x less</text>
|
||||
|
||||
<!-- ===================== CELL 4: 2 Integrated Layers (coral) bottom-right ===================== -->
|
||||
<rect x="445" y="170" width="387" height="110" rx="10" fill="#1a0d08" stroke="#d97757" stroke-width="1.5"/>
|
||||
<rect x="445" y="170" width="5" height="110" rx="2" fill="#d97757" fill-opacity="0.8"/>
|
||||
|
||||
<!-- Big number -->
|
||||
<text x="638" y="225" font-family="'Segoe UI', Arial, sans-serif" font-size="58" fill="#d97757" text-anchor="middle" font-weight="bold">2</text>
|
||||
|
||||
<!-- Label -->
|
||||
<text x="638" y="248" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#c9d1d9" text-anchor="middle" font-weight="600">Integrated Layers</text>
|
||||
|
||||
<!-- Description -->
|
||||
<text x="638" y="265" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">Modeling + Reporting, one unified CLI</text>
|
||||
|
||||
<!-- Decorative accent: two stacked layer bars -->
|
||||
<rect x="760" y="183" width="55" height="12" rx="3" fill="#58a6ff" fill-opacity="0.5"/>
|
||||
<text x="787" y="193" font-family="'Segoe UI', Arial, sans-serif" font-size="7" fill="#58a6ff" text-anchor="middle">MODELING</text>
|
||||
<rect x="760" y="199" width="55" height="12" rx="3" fill="#06d6a0" fill-opacity="0.5"/>
|
||||
<text x="787" y="209" font-family="'Segoe UI', Arial, sans-serif" font-size="7" fill="#06d6a0" text-anchor="middle">REPORTING</text>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
|
@ -1,69 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="420" viewBox="0 0 850 420">
|
||||
<rect width="100%" height="100%" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="425" y="32" font-family="'Segoe UI', Arial, sans-serif" font-size="18" fill="#F2C811" text-anchor="middle" font-weight="bold">100x More Efficient Than MCP</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Token cost per operation: MCP tool call vs CLI command</text>
|
||||
|
||||
<!-- Chart area -->
|
||||
<rect x="40" y="65" width="770" height="300" rx="8" fill="#161b22" stroke="#30363d" stroke-width="1"/>
|
||||
|
||||
<!-- Row 1: connect -->
|
||||
<text x="120" y="100" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#c9d1d9" text-anchor="end">connect</text>
|
||||
<!-- MCP bar -->
|
||||
<rect x="135" y="86" width="600" height="14" rx="3" fill="#ff6b6b" fill-opacity="0.7"/>
|
||||
<text x="742" y="98" font-family="'Courier New', Courier, monospace" font-size="11" fill="#ff6b6b">4,800</text>
|
||||
<!-- CLI bar -->
|
||||
<rect x="135" y="104" width="4" height="14" rx="2" fill="#06d6a0"/>
|
||||
<text x="146" y="116" font-family="'Courier New', Courier, monospace" font-size="11" fill="#06d6a0">~30</text>
|
||||
|
||||
<!-- Row 2: dax query -->
|
||||
<text x="120" y="155" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#c9d1d9" text-anchor="end">dax query</text>
|
||||
<!-- MCP bar -->
|
||||
<rect x="135" y="141" width="575" height="14" rx="3" fill="#ff6b6b" fill-opacity="0.7"/>
|
||||
<text x="717" y="153" font-family="'Courier New', Courier, monospace" font-size="11" fill="#ff6b6b">4,600</text>
|
||||
<!-- CLI bar -->
|
||||
<rect x="135" y="159" width="5" height="14" rx="2" fill="#06d6a0"/>
|
||||
<text x="147" y="171" font-family="'Courier New', Courier, monospace" font-size="11" fill="#06d6a0">~35</text>
|
||||
|
||||
<!-- Row 3: measure -->
|
||||
<text x="120" y="210" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#c9d1d9" text-anchor="end">measure</text>
|
||||
<!-- MCP bar -->
|
||||
<rect x="135" y="196" width="550" height="14" rx="3" fill="#ff6b6b" fill-opacity="0.7"/>
|
||||
<text x="692" y="208" font-family="'Courier New', Courier, monospace" font-size="11" fill="#ff6b6b">4,400</text>
|
||||
<!-- CLI bar -->
|
||||
<rect x="135" y="214" width="4" height="14" rx="2" fill="#06d6a0"/>
|
||||
<text x="146" y="226" font-family="'Courier New', Courier, monospace" font-size="11" fill="#06d6a0">~28</text>
|
||||
|
||||
<!-- Row 4: model stats -->
|
||||
<text x="120" y="265" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#c9d1d9" text-anchor="end">model</text>
|
||||
<!-- MCP bar -->
|
||||
<rect x="135" y="251" width="525" height="14" rx="3" fill="#ff6b6b" fill-opacity="0.7"/>
|
||||
<text x="667" y="263" font-family="'Courier New', Courier, monospace" font-size="11" fill="#ff6b6b">4,200</text>
|
||||
<!-- CLI bar -->
|
||||
<rect x="135" y="269" width="4" height="14" rx="2" fill="#06d6a0"/>
|
||||
<text x="146" y="281" font-family="'Courier New', Courier, monospace" font-size="11" fill="#06d6a0">~25</text>
|
||||
|
||||
<!-- Row 5: export -->
|
||||
<text x="120" y="320" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#c9d1d9" text-anchor="end">export</text>
|
||||
<!-- MCP bar -->
|
||||
<rect x="135" y="306" width="563" height="14" rx="3" fill="#ff6b6b" fill-opacity="0.7"/>
|
||||
<text x="705" y="318" font-family="'Courier New', Courier, monospace" font-size="11" fill="#ff6b6b">4,500</text>
|
||||
<!-- CLI bar -->
|
||||
<rect x="135" y="324" width="4" height="14" rx="2" fill="#06d6a0"/>
|
||||
<text x="146" y="336" font-family="'Courier New', Courier, monospace" font-size="11" fill="#06d6a0">~32</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<rect x="230" y="378" width="14" height="14" rx="2" fill="#ff6b6b" fill-opacity="0.7"/>
|
||||
<text x="250" y="390" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#ff6b6b">MCP tool call</text>
|
||||
|
||||
<rect x="380" y="378" width="14" height="14" rx="2" fill="#06d6a0"/>
|
||||
<text x="400" y="390" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0">CLI command</text>
|
||||
|
||||
<text x="530" y="390" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#8b949e">|</text>
|
||||
|
||||
<text x="550" y="390" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#F2C811" font-weight="bold">~130x savings</text>
|
||||
|
||||
<!-- Footer -->
|
||||
<text x="425" y="414" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#8b949e" text-anchor="middle">Same capabilities, fraction of the cost. Your AI budget goes 100x further.</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.4 KiB |
245
assets/visual-types.svg
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="850" height="440" viewBox="0 0 850 440">
|
||||
<rect width="100%" height="100%" fill="#0d1117" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="425" y="30" font-family="'Segoe UI', Arial, sans-serif" font-size="20" fill="#F2C811" text-anchor="middle" font-weight="bold">32 Visual Types, All from CLI</text>
|
||||
<text x="425" y="50" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">pbi visual add --type <alias> -- use friendly names, pbi-cli handles the rest</text>
|
||||
|
||||
<!-- Row 1: Charts (8) -->
|
||||
<text x="30" y="80" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#d97757" font-weight="bold">CHARTS</text>
|
||||
|
||||
<!-- bar -->
|
||||
<rect x="30" y="90" width="90" height="70" rx="6" fill="#161b22" stroke="#d97757" stroke-width="1"/>
|
||||
<g transform="translate(45, 100)">
|
||||
<rect x="0" y="22" width="12" height="18" rx="2" fill="#d97757" opacity="0.5"/>
|
||||
<rect x="16" y="12" width="12" height="28" rx="2" fill="#d97757" opacity="0.7"/>
|
||||
<rect x="32" y="2" width="12" height="38" rx="2" fill="#d97757"/>
|
||||
</g>
|
||||
<text x="75" y="152" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">bar</text>
|
||||
|
||||
<!-- line -->
|
||||
<rect x="130" y="90" width="90" height="70" rx="6" fill="#161b22" stroke="#d97757" stroke-width="1"/>
|
||||
<g transform="translate(145, 105)">
|
||||
<polyline points="0,28 15,18 30,22 45,5" fill="none" stroke="#d97757" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<circle cx="0" cy="28" r="3" fill="#d97757"/>
|
||||
<circle cx="15" cy="18" r="3" fill="#d97757"/>
|
||||
<circle cx="30" cy="22" r="3" fill="#d97757"/>
|
||||
<circle cx="45" cy="5" r="3" fill="#d97757"/>
|
||||
</g>
|
||||
<text x="175" y="152" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">line</text>
|
||||
|
||||
<!-- column -->
|
||||
<rect x="230" y="90" width="90" height="70" rx="6" fill="#161b22" stroke="#d97757" stroke-width="1"/>
|
||||
<g transform="translate(247, 98)">
|
||||
<rect x="0" y="20" width="14" height="22" rx="2" fill="#d97757" opacity="0.5"/>
|
||||
<rect x="18" y="8" width="14" height="34" rx="2" fill="#d97757" opacity="0.7"/>
|
||||
<rect x="36" y="0" width="14" height="42" rx="2" fill="#d97757"/>
|
||||
</g>
|
||||
<text x="275" y="152" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">column</text>
|
||||
|
||||
<!-- area -->
|
||||
<rect x="330" y="90" width="90" height="70" rx="6" fill="#161b22" stroke="#d97757" stroke-width="1"/>
|
||||
<g transform="translate(345, 105)">
|
||||
<polygon points="0,30 15,18 30,22 50,5 50,30" fill="#d97757" fill-opacity="0.2" stroke="#d97757" stroke-width="1.5"/>
|
||||
</g>
|
||||
<text x="375" y="152" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">area</text>
|
||||
|
||||
<!-- scatter -->
|
||||
<rect x="430" y="90" width="90" height="70" rx="6" fill="#161b22" stroke="#d97757" stroke-width="1"/>
|
||||
<g transform="translate(448, 100)">
|
||||
<circle cx="8" cy="30" r="4" fill="#d97757" opacity="0.4"/>
|
||||
<circle cx="20" cy="15" r="5" fill="#d97757" opacity="0.6"/>
|
||||
<circle cx="35" cy="22" r="3" fill="#d97757" opacity="0.8"/>
|
||||
<circle cx="48" cy="8" r="6" fill="#d97757"/>
|
||||
<circle cx="30" cy="35" r="3" fill="#d97757" opacity="0.5"/>
|
||||
</g>
|
||||
<text x="475" y="152" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">scatter</text>
|
||||
|
||||
<!-- combo -->
|
||||
<rect x="530" y="90" width="90" height="70" rx="6" fill="#161b22" stroke="#d97757" stroke-width="1"/>
|
||||
<g transform="translate(545, 100)">
|
||||
<rect x="0" y="20" width="10" height="20" rx="1" fill="#d97757" opacity="0.5"/>
|
||||
<rect x="14" y="10" width="10" height="30" rx="1" fill="#d97757" opacity="0.7"/>
|
||||
<rect x="28" y="15" width="10" height="25" rx="1" fill="#d97757" opacity="0.6"/>
|
||||
<rect x="42" y="5" width="10" height="35" rx="1" fill="#d97757"/>
|
||||
<polyline points="5,18 19,8 33,12 47,3" fill="none" stroke="#58a6ff" stroke-width="2" stroke-linecap="round"/>
|
||||
</g>
|
||||
<text x="575" y="152" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">combo</text>
|
||||
|
||||
<!-- donut -->
|
||||
<rect x="630" y="90" width="90" height="70" rx="6" fill="#161b22" stroke="#d97757" stroke-width="1"/>
|
||||
<g transform="translate(660, 105)">
|
||||
<circle cx="15" cy="15" r="15" fill="none" stroke="#d97757" stroke-width="6" stroke-dasharray="25 70" stroke-dashoffset="0"/>
|
||||
<circle cx="15" cy="15" r="15" fill="none" stroke="#F2C811" stroke-width="6" stroke-dasharray="20 70" stroke-dashoffset="-25"/>
|
||||
<circle cx="15" cy="15" r="15" fill="none" stroke="#58a6ff" stroke-width="6" stroke-dasharray="15 70" stroke-dashoffset="-45"/>
|
||||
</g>
|
||||
<text x="675" y="152" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">donut</text>
|
||||
|
||||
<!-- waterfall -->
|
||||
<rect x="730" y="90" width="90" height="70" rx="6" fill="#161b22" stroke="#d97757" stroke-width="1"/>
|
||||
<g transform="translate(748, 100)">
|
||||
<rect x="0" y="0" width="10" height="35" rx="1" fill="#06d6a0"/>
|
||||
<rect x="14" y="10" width="10" height="10" rx="1" fill="#06d6a0"/>
|
||||
<rect x="28" y="15" width="10" height="8" rx="1" fill="#ff6b6b"/>
|
||||
<rect x="42" y="20" width="10" height="15" rx="1" fill="#06d6a0"/>
|
||||
</g>
|
||||
<text x="775" y="152" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">waterfall</text>
|
||||
|
||||
<!-- Row 2: Cards & KPIs + Tables (6) -->
|
||||
<text x="30" y="182" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#06d6a0" font-weight="bold">CARDS & KPIs</text>
|
||||
|
||||
<!-- card -->
|
||||
<rect x="30" y="192" width="90" height="70" rx="6" fill="#161b22" stroke="#06d6a0" stroke-width="1"/>
|
||||
<g transform="translate(48, 202)">
|
||||
<rect x="0" y="0" width="52" height="35" rx="4" fill="none" stroke="#06d6a0" stroke-width="1.2"/>
|
||||
<text x="26" y="24" font-family="'Segoe UI', Arial, sans-serif" font-size="18" fill="#06d6a0" text-anchor="middle" font-weight="bold">42K</text>
|
||||
</g>
|
||||
<text x="75" y="254" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">card</text>
|
||||
|
||||
<!-- cardVisual -->
|
||||
<rect x="130" y="192" width="90" height="70" rx="6" fill="#161b22" stroke="#06d6a0" stroke-width="1"/>
|
||||
<g transform="translate(148, 202)">
|
||||
<rect x="0" y="0" width="52" height="35" rx="4" fill="#06d6a0" fill-opacity="0.08" stroke="#06d6a0" stroke-width="1.2"/>
|
||||
<text x="26" y="16" font-family="'Segoe UI', Arial, sans-serif" font-size="8" fill="#8b949e" text-anchor="middle">Revenue</text>
|
||||
<text x="26" y="30" font-family="'Segoe UI', Arial, sans-serif" font-size="14" fill="#06d6a0" text-anchor="middle" font-weight="bold">$1.2M</text>
|
||||
</g>
|
||||
<text x="175" y="254" font-family="'Courier New', Courier, monospace" font-size="9" fill="#8b949e" text-anchor="middle">card_visual</text>
|
||||
|
||||
<!-- kpi -->
|
||||
<rect x="230" y="192" width="90" height="70" rx="6" fill="#161b22" stroke="#06d6a0" stroke-width="1"/>
|
||||
<g transform="translate(250, 204)">
|
||||
<text x="24" y="14" font-family="'Segoe UI', Arial, sans-serif" font-size="16" fill="#06d6a0" text-anchor="middle" font-weight="bold">85%</text>
|
||||
<polyline points="0,28 10,24 20,26 30,20 40,18 50,22" fill="none" stroke="#06d6a0" stroke-width="1.5" stroke-opacity="0.5"/>
|
||||
<text x="24" y="38" font-family="'Segoe UI', Arial, sans-serif" font-size="8" fill="#8b949e" text-anchor="middle">Goal: 80%</text>
|
||||
</g>
|
||||
<text x="275" y="254" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">kpi</text>
|
||||
|
||||
<!-- gauge -->
|
||||
<rect x="330" y="192" width="90" height="70" rx="6" fill="#161b22" stroke="#06d6a0" stroke-width="1"/>
|
||||
<g transform="translate(355, 204)">
|
||||
<path d="M 5,32 A 20,20 0 0,1 45,32" fill="none" stroke="#8b949e" stroke-width="4" stroke-linecap="round"/>
|
||||
<path d="M 5,32 A 20,20 0 0,1 35,16" fill="none" stroke="#06d6a0" stroke-width="4" stroke-linecap="round"/>
|
||||
<text x="25" y="38" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#06d6a0" text-anchor="middle" font-weight="bold">72%</text>
|
||||
</g>
|
||||
<text x="375" y="254" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">gauge</text>
|
||||
|
||||
<text x="440" y="182" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#58a6ff" font-weight="bold">TABLES</text>
|
||||
|
||||
<!-- table -->
|
||||
<rect x="430" y="192" width="90" height="70" rx="6" fill="#161b22" stroke="#58a6ff" stroke-width="1"/>
|
||||
<g transform="translate(442, 202)">
|
||||
<rect x="0" y="0" width="66" height="38" rx="3" fill="none" stroke="#58a6ff" stroke-width="1"/>
|
||||
<line x1="0" y1="10" x2="66" y2="10" stroke="#58a6ff" stroke-width="0.8"/>
|
||||
<line x1="0" y1="20" x2="66" y2="20" stroke="#58a6ff" stroke-width="0.5" stroke-opacity="0.4"/>
|
||||
<line x1="0" y1="30" x2="66" y2="30" stroke="#58a6ff" stroke-width="0.5" stroke-opacity="0.4"/>
|
||||
<line x1="22" y1="0" x2="22" y2="38" stroke="#58a6ff" stroke-width="0.5" stroke-opacity="0.3"/>
|
||||
<line x1="44" y1="0" x2="44" y2="38" stroke="#58a6ff" stroke-width="0.5" stroke-opacity="0.3"/>
|
||||
</g>
|
||||
<text x="475" y="254" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">table</text>
|
||||
|
||||
<!-- matrix -->
|
||||
<rect x="530" y="192" width="90" height="70" rx="6" fill="#161b22" stroke="#58a6ff" stroke-width="1"/>
|
||||
<g transform="translate(542, 202)">
|
||||
<rect x="0" y="0" width="66" height="38" rx="3" fill="none" stroke="#58a6ff" stroke-width="1"/>
|
||||
<rect x="0" y="0" width="18" height="10" rx="2" fill="#58a6ff" fill-opacity="0.15"/>
|
||||
<line x1="0" y1="10" x2="66" y2="10" stroke="#58a6ff" stroke-width="0.8"/>
|
||||
<line x1="18" y1="0" x2="18" y2="38" stroke="#58a6ff" stroke-width="0.8"/>
|
||||
<line x1="0" y1="20" x2="66" y2="20" stroke="#58a6ff" stroke-width="0.5" stroke-opacity="0.4"/>
|
||||
<line x1="0" y1="30" x2="66" y2="30" stroke="#58a6ff" stroke-width="0.5" stroke-opacity="0.4"/>
|
||||
</g>
|
||||
<text x="575" y="254" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">matrix</text>
|
||||
|
||||
<!-- Row 3: Slicers + Maps + Decorative -->
|
||||
<text x="30" y="284" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#F2C811" font-weight="bold">SLICERS</text>
|
||||
|
||||
<!-- slicer -->
|
||||
<rect x="30" y="294" width="90" height="70" rx="6" fill="#161b22" stroke="#F2C811" stroke-width="1"/>
|
||||
<g transform="translate(48, 302)">
|
||||
<rect x="0" y="0" width="52" height="40" rx="3" fill="none" stroke="#F2C811" stroke-width="1"/>
|
||||
<rect x="4" y="6" width="44" height="8" rx="2" fill="#F2C811" fill-opacity="0.2"/>
|
||||
<rect x="4" y="18" width="44" height="8" rx="2" fill="#F2C811" fill-opacity="0.1"/>
|
||||
<rect x="4" y="30" width="44" height="8" rx="2" fill="#F2C811" fill-opacity="0.1"/>
|
||||
<rect x="6" y="7" width="6" height="6" rx="1" fill="#F2C811"/>
|
||||
</g>
|
||||
<text x="75" y="356" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">slicer</text>
|
||||
|
||||
<!-- text_slicer -->
|
||||
<rect x="130" y="294" width="90" height="70" rx="6" fill="#161b22" stroke="#F2C811" stroke-width="1"/>
|
||||
<g transform="translate(148, 306)">
|
||||
<rect x="0" y="0" width="52" height="28" rx="3" fill="none" stroke="#F2C811" stroke-width="1"/>
|
||||
<text x="8" y="18" font-family="'Courier New', Courier, monospace" font-size="10" fill="#F2C811">Search...</text>
|
||||
</g>
|
||||
<text x="175" y="356" font-family="'Courier New', Courier, monospace" font-size="9" fill="#8b949e" text-anchor="middle">text_slicer</text>
|
||||
|
||||
<text x="240" y="284" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#7b61ff" font-weight="bold">MAPS</text>
|
||||
|
||||
<!-- azureMap -->
|
||||
<rect x="230" y="294" width="90" height="70" rx="6" fill="#161b22" stroke="#7b61ff" stroke-width="1"/>
|
||||
<g transform="translate(252, 302)">
|
||||
<circle cx="22" cy="20" r="18" fill="none" stroke="#7b61ff" stroke-width="1.2" stroke-opacity="0.3"/>
|
||||
<circle cx="14" cy="15" r="4" fill="#7b61ff" opacity="0.5"/>
|
||||
<circle cx="28" cy="12" r="6" fill="#7b61ff" opacity="0.7"/>
|
||||
<circle cx="20" cy="28" r="3" fill="#7b61ff" opacity="0.4"/>
|
||||
<circle cx="34" cy="24" r="5" fill="#7b61ff"/>
|
||||
</g>
|
||||
<text x="275" y="356" font-family="'Courier New', Courier, monospace" font-size="9" fill="#8b949e" text-anchor="middle">azure_map</text>
|
||||
|
||||
<text x="340" y="284" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#ff6b6b" font-weight="bold">MORE CHARTS</text>
|
||||
|
||||
<!-- funnel -->
|
||||
<rect x="330" y="294" width="90" height="70" rx="6" fill="#161b22" stroke="#ff6b6b" stroke-width="1"/>
|
||||
<g transform="translate(348, 302)">
|
||||
<polygon points="0,0 52,0 40,14 12,14" fill="#ff6b6b" fill-opacity="0.15" stroke="#ff6b6b" stroke-width="1"/>
|
||||
<polygon points="12,16 40,16 34,28 18,28" fill="#ff6b6b" fill-opacity="0.25" stroke="#ff6b6b" stroke-width="1"/>
|
||||
<polygon points="18,30 34,30 30,40 22,40" fill="#ff6b6b" fill-opacity="0.4" stroke="#ff6b6b" stroke-width="1"/>
|
||||
</g>
|
||||
<text x="375" y="356" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">funnel</text>
|
||||
|
||||
<!-- treemap -->
|
||||
<rect x="430" y="294" width="90" height="70" rx="6" fill="#161b22" stroke="#ff6b6b" stroke-width="1"/>
|
||||
<g transform="translate(442, 302)">
|
||||
<rect x="0" y="0" width="34" height="24" rx="2" fill="#ff6b6b" opacity="0.4"/>
|
||||
<rect x="36" y="0" width="30" height="12" rx="2" fill="#ff6b6b" opacity="0.6"/>
|
||||
<rect x="36" y="14" width="30" height="10" rx="2" fill="#ff6b6b" opacity="0.3"/>
|
||||
<rect x="0" y="26" width="20" height="16" rx="2" fill="#ff6b6b" opacity="0.7"/>
|
||||
<rect x="22" y="26" width="22" height="16" rx="2" fill="#ff6b6b" opacity="0.5"/>
|
||||
<rect x="46" y="26" width="20" height="16" rx="2" fill="#ff6b6b" opacity="0.2"/>
|
||||
</g>
|
||||
<text x="475" y="356" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">treemap</text>
|
||||
|
||||
<!-- ribbon -->
|
||||
<rect x="530" y="294" width="90" height="70" rx="6" fill="#161b22" stroke="#ff6b6b" stroke-width="1"/>
|
||||
<g transform="translate(545, 305)">
|
||||
<path d="M 0,25 Q 15,20 30,22 Q 45,24 60,20" fill="#ff6b6b" fill-opacity="0.3" stroke="#ff6b6b" stroke-width="1.5"/>
|
||||
<path d="M 0,18 Q 15,12 30,15 Q 45,18 60,10" fill="#d97757" fill-opacity="0.3" stroke="#d97757" stroke-width="1.5"/>
|
||||
<path d="M 0,10 Q 15,5 30,8 Q 45,6 60,2" fill="#F2C811" fill-opacity="0.3" stroke="#F2C811" stroke-width="1.5"/>
|
||||
</g>
|
||||
<text x="575" y="356" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">ribbon</text>
|
||||
|
||||
<text x="640" y="284" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="#a0c4ff" font-weight="bold">DECORATIVE</text>
|
||||
|
||||
<!-- image -->
|
||||
<rect x="630" y="294" width="90" height="70" rx="6" fill="#161b22" stroke="#a0c4ff" stroke-width="1"/>
|
||||
<g transform="translate(648, 304)">
|
||||
<rect x="0" y="0" width="50" height="36" rx="3" fill="none" stroke="#a0c4ff" stroke-width="1"/>
|
||||
<circle cx="14" cy="12" r="5" fill="#F2C811" opacity="0.5"/>
|
||||
<polygon points="8,30 22,16 34,26 42,20 50,28 50,36 0,36" fill="#a0c4ff" fill-opacity="0.2"/>
|
||||
</g>
|
||||
<text x="675" y="356" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">image</text>
|
||||
|
||||
<!-- textbox -->
|
||||
<rect x="730" y="294" width="90" height="70" rx="6" fill="#161b22" stroke="#a0c4ff" stroke-width="1"/>
|
||||
<g transform="translate(748, 306)">
|
||||
<rect x="0" y="0" width="50" height="28" rx="3" fill="none" stroke="#a0c4ff" stroke-width="1"/>
|
||||
<line x1="6" y1="9" x2="44" y2="9" stroke="#a0c4ff" stroke-width="1" stroke-opacity="0.4"/>
|
||||
<line x1="6" y1="17" x2="36" y2="17" stroke="#a0c4ff" stroke-width="1" stroke-opacity="0.3"/>
|
||||
<line x1="6" y1="24" x2="26" y2="24" stroke="#a0c4ff" stroke-width="1" stroke-opacity="0.2"/>
|
||||
</g>
|
||||
<text x="775" y="356" font-family="'Courier New', Courier, monospace" font-size="10" fill="#8b949e" text-anchor="middle">textbox</text>
|
||||
|
||||
<!-- Footer: +14 more -->
|
||||
<text x="425" y="392" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">+ stacked_bar, clustered_bar, clustered_column, list_slicer, advanced_slicer,</text>
|
||||
<text x="425" y="410" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#8b949e" text-anchor="middle">card_new, multi_row_card, shape, action_button, page_navigator, and more</text>
|
||||
|
||||
<text x="425" y="435" font-family="'Courier New', Courier, monospace" font-size="14" fill="#58a6ff" text-anchor="middle" font-weight="bold">pbi visual add --type bar --page overview --name my_chart</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
|
@ -1,13 +1,14 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=68.0", "wheel"]
|
||||
requires = ["setuptools>=77.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "pbi-cli-tool"
|
||||
version = "3.10.0"
|
||||
version = "3.10.10"
|
||||
description = "CLI for Power BI semantic models and PBIR reports - direct .NET connection for token-efficient AI agent usage"
|
||||
readme = "README.pypi.md"
|
||||
license = {text = "MIT"}
|
||||
license = "MIT AND LicenseRef-Microsoft-AS-Client-Libraries"
|
||||
license-files = ["LICENSE", "THIRD_PARTY_LICENSES.md", "NOTICE"]
|
||||
requires-python = ">=3.10"
|
||||
authors = [
|
||||
{name = "pbi-cli contributors"},
|
||||
|
|
@ -17,7 +18,6 @@ classifiers = [
|
|||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Console",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
|
|
@ -37,6 +37,7 @@ dependencies = [
|
|||
|
||||
[project.scripts]
|
||||
pbi = "pbi_cli.main:cli"
|
||||
pbi-cli = "pbi_cli.main_pbi_cli:cli"
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/MinaSaad1/pbi-cli"
|
||||
|
|
@ -58,7 +59,7 @@ where = ["src"]
|
|||
|
||||
[tool.setuptools.package-data]
|
||||
"pbi_cli.skills" = ["**/*.md"]
|
||||
"pbi_cli.dlls" = ["*.dll"]
|
||||
"pbi_cli.dlls" = ["*.dll", "*.json", "README.md"]
|
||||
"pbi_cli.templates" = ["**/*.json"]
|
||||
|
||||
[tool.ruff]
|
||||
|
|
@ -94,3 +95,11 @@ strict = true
|
|||
[[tool.mypy.overrides]]
|
||||
module = ["pythonnet", "clr", "clr_loader"]
|
||||
ignore_missing_imports = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = ["win32gui", "win32con", "win32api", "win32process", "win32com.*"]
|
||||
ignore_missing_imports = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = ["websockets", "websockets.*"]
|
||||
ignore_missing_imports = true
|
||||
|
|
|
|||
329
scripts/bake_headers.py
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
"""
|
||||
Bake the Vibe BI header into each feature SVG in assets/.
|
||||
|
||||
For each feature SVG:
|
||||
1. Inject the VIBE BI branding header (ASCII art, connection flow, install cmd)
|
||||
2. Shift original content down by HEADER_HEIGHT
|
||||
3. Write the updated SVG back to disk
|
||||
|
||||
Logos are referenced via <image> to preserve original dimensions.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ASSETS_DIR = Path(__file__).parent.parent / "assets"
|
||||
HEADER_HEIGHT = 210
|
||||
SVG_WIDTH = 850
|
||||
|
||||
# Feature SVGs to process (same list as generate_images.py)
|
||||
FEATURE_SVGS = [
|
||||
"before-after.svg",
|
||||
"architecture-flow.svg",
|
||||
"backup-restore.svg",
|
||||
"bulk-operations.svg",
|
||||
"chat-demo.svg",
|
||||
"dax-debugging.svg",
|
||||
"feature-grid.svg",
|
||||
"model-health-check.svg",
|
||||
"rls-testing.svg",
|
||||
"skills-hub.svg",
|
||||
"token-cost.svg",
|
||||
"how-it-works.svg",
|
||||
"dax-skill.svg",
|
||||
"modeling-skill.svg",
|
||||
"deploy-secure.svg",
|
||||
"docs-diagnostics.svg",
|
||||
"cta-start.svg",
|
||||
"report-layer.svg",
|
||||
"dual-layer.svg",
|
||||
"visual-types.svg",
|
||||
"report-workflow.svg",
|
||||
"auto-sync.svg",
|
||||
"chat-demo-report.svg",
|
||||
"commands.svg",
|
||||
"layers.svg",
|
||||
"release-vibe-bi.svg",
|
||||
"stats.svg",
|
||||
"workflow.svg",
|
||||
]
|
||||
|
||||
# The Vibe BI header SVG fragment (210px tall, 850px wide)
|
||||
HEADER_DEFS = """\
|
||||
<linearGradient id="hdr-bar1" x1="50%" y1="0%" x2="50%" y2="100%">
|
||||
<stop offset="0%" stop-color="#EBBB14"/>
|
||||
<stop offset="100%" stop-color="#B25400"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="hdr-bar2" x1="50%" y1="0%" x2="50%" y2="100%">
|
||||
<stop offset="0%" stop-color="#F9E583"/>
|
||||
<stop offset="100%" stop-color="#DE9800"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="hdr-bar3" x1="50%" y1="0%" x2="50%" y2="100%">
|
||||
<stop offset="0%" stop-color="#F9E68B"/>
|
||||
<stop offset="100%" stop-color="#F3CD32"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="pbi-lg1" x1="50%" y1="0%" x2="50%" y2="100%">
|
||||
<stop offset="0%" stop-color="#EBBB14"/>
|
||||
<stop offset="100%" stop-color="#B25400"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="pbi-lg2" x1="50%" y1="0%" x2="50%" y2="100%">
|
||||
<stop offset="0%" stop-color="#F9E583"/>
|
||||
<stop offset="100%" stop-color="#DE9800"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="pbi-lg5" x1="50%" y1="0%" x2="50%" y2="100%">
|
||||
<stop offset="0%" stop-color="#F9E68B"/>
|
||||
<stop offset="100%" stop-color="#F3CD32"/>
|
||||
</linearGradient>"""
|
||||
|
||||
HEADER_BODY = """\
|
||||
<!-- ==================== VIBE BI HEADER ==================== -->
|
||||
|
||||
<!-- "VIBE BI" block art (shadow) -->
|
||||
<text font-family="'Courier New', Courier, monospace" font-size="9" fill="#7A6508" xml:space="preserve">
|
||||
<tspan x="296" y="22">██╗ ██╗ ██╗ ██████╗ ███████╗ ██████╗ ██╗</tspan>
|
||||
<tspan x="296" y="33">██║ ██║ ██║ ██╔══██╗ ██╔════╝ ██╔══██╗ ██║</tspan>
|
||||
<tspan x="296" y="44">██║ ██║ ██║ ██████╔╝ █████╗ ██████╔╝ ██║</tspan>
|
||||
<tspan x="296" y="55">╚██╗ ██╔╝ ██║ ██╔══██╗ ██╔══╝ ██╔══██╗ ██║</tspan>
|
||||
<tspan x="296" y="66"> ╚████╔╝ ██║ ██████╔╝ ███████╗ ██████╔╝ ██║</tspan>
|
||||
<tspan x="296" y="77"> ╚═══╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝</tspan>
|
||||
</text>
|
||||
<!-- "VIBE BI" block art (main) -->
|
||||
<text font-family="'Courier New', Courier, monospace" font-size="9" fill="#F2C811" xml:space="preserve">
|
||||
<tspan x="295" y="21">██╗ ██╗ ██╗ ██████╗ ███████╗ ██████╗ ██╗</tspan>
|
||||
<tspan x="295" y="32">██║ ██║ ██║ ██╔══██╗ ██╔════╝ ██╔══██╗ ██║</tspan>
|
||||
<tspan x="295" y="43">██║ ██║ ██║ ██████╔╝ █████╗ ██████╔╝ ██║</tspan>
|
||||
<tspan x="295" y="54">╚██╗ ██╔╝ ██║ ██╔══██╗ ██╔══╝ ██╔══██╗ ██║</tspan>
|
||||
<tspan x="295" y="65"> ╚████╔╝ ██║ ██████╔╝ ███████╗ ██████╔╝ ██║</tspan>
|
||||
<tspan x="295" y="76"> ╚═══╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Separator -->
|
||||
<line x1="60" y1="84" x2="790" y2="84" stroke="#F2C811" stroke-opacity="0.15" stroke-width="1"/>
|
||||
|
||||
<!-- Tagline -->
|
||||
<text x="425" y="100" font-family="'Segoe UI', Arial, sans-serif" font-size="13" fill="#e6edf3" text-anchor="middle" font-weight="600">The First CLI for Both Power BI Modeling and Reporting</text>
|
||||
|
||||
<!-- ===== Connection Flow: Claude > PBI-CLI > Power BI ===== -->
|
||||
|
||||
<!-- Claude AI logo (inline, original 1200x1200, displayed as 50x50, centered at x=110) -->
|
||||
<svg x="85" y="110" width="50" height="50" viewBox="0 0 1200 1200">
|
||||
<path fill="#d97757" d="M 233.959793 800.214905 L 468.644287 668.536987 L 472.590637 657.100647 L 468.644287 650.738403 L 457.208069 650.738403 L 417.986633 648.322144 L 283.892639 644.69812 L 167.597321 639.865845 L 54.926208 633.825623 L 26.577238 627.785339 L 3.3e-05 592.751709 L 2.73832 575.27533 L 26.577238 559.248352 L 60.724873 562.228149 L 136.187973 567.382629 L 249.422867 575.194763 L 331.570496 580.026978 L 453.261841 592.671082 L 472.590637 592.671082 L 475.328857 584.859009 L 468.724915 580.026978 L 463.570557 575.194763 L 346.389313 495.785217 L 219.543671 411.865906 L 153.100723 363.543762 L 117.181267 339.060425 L 99.060455 316.107361 L 91.248367 266.01355 L 123.865784 230.093994 L 167.677887 233.073853 L 178.872513 236.053772 L 223.248367 270.201477 L 318.040283 343.570496 L 441.825592 434.738342 L 459.946411 449.798706 L 467.194672 444.64447 L 468.080597 441.020203 L 459.946411 427.409485 L 392.617493 305.718323 L 320.778564 181.932983 L 288.80542 130.630859 L 280.348999 99.865845 C 277.369171 87.221436 275.194641 76.590698 275.194641 63.624268 L 312.322174 13.20813 L 332.8591 6.604126 L 382.389313 13.20813 L 403.248352 31.328979 L 434.013519 101.71814 L 483.865753 212.537048 L 561.181274 363.221497 L 583.812134 407.919434 L 595.892639 449.315491 L 600.40271 461.959839 L 608.214783 461.959839 L 608.214783 454.711609 L 614.577271 369.825623 L 626.335632 265.61084 L 637.771851 131.516846 L 641.718201 93.745117 L 660.402832 48.483276 L 697.530334 24.000122 L 726.52356 37.852417 L 750.362549 72 L 747.060486 94.067139 L 732.886047 186.201416 L 705.100708 330.52356 L 686.979919 427.167847 L 697.530334 427.167847 L 709.61084 415.087341 L 758.496704 350.174561 L 840.644348 247.490051 L 876.885925 206.738342 L 919.167847 161.71814 L 946.308838 140.29541 L 997.61084 140.29541 L 1035.38269 196.429626 L 1018.469849 254.416199 L 965.637634 321.422852 L 921.825562 378.201538 L 859.006714 462.765259 L 819.785278 530.41626 L 823.409424 535.812073 L 832.75177 534.92627 L 974.657776 504.724915 L 1051.328979 490.872559 L 1142.818848 475.167786 L 1184.214844 494.496582 L 1188.724854 514.147644 L 1172.456421 554.335693 L 1074.604126 578.496765 L 959.838989 601.449829 L 788.939636 641.879272 L 786.845764 643.409485 L 789.261841 646.389343 L 866.255127 653.637634 L 899.194702 655.409424 L 979.812134 655.409424 L 1129.932861 666.604187 L 1169.154419 692.537109 L 1192.671265 724.268677 L 1188.724854 748.429688 L 1128.322144 779.194641 L 1046.818848 759.865845 L 856.590759 714.604126 L 791.355774 698.335754 L 782.335693 698.335754 L 782.335693 703.731567 L 836.69812 756.885986 L 936.322205 846.845581 L 1061.073975 962.81897 L 1067.436279 991.490112 L 1051.409424 1014.120911 L 1034.496704 1011.704712 L 924.885986 929.234924 L 882.604126 892.107544 L 786.845764 811.48999 L 780.483276 811.48999 L 780.483276 819.946289 L 802.550415 852.241699 L 919.087341 1027.409424 L 925.127625 1081.127686 L 916.671204 1098.604126 L 886.469849 1109.154419 L 853.288696 1103.114136 L 785.073914 1007.355835 L 714.684631 899.516785 L 657.906067 802.872498 L 650.979858 806.81897 L 617.476624 1167.704834 L 601.771851 1186.147705 L 565.530212 1200 L 535.328857 1177.046997 L 519.302124 1139.919556 L 535.328857 1066.550537 L 554.657776 970.792053 L 570.362488 894.68457 L 584.536926 800.134277 L 592.993347 768.724976 L 592.429626 766.630859 L 585.503479 767.516968 L 514.22821 865.369263 L 405.825531 1011.865906 L 320.053711 1103.677979 L 299.516815 1111.812256 L 263.919525 1093.369263 L 267.221497 1060.429688 L 287.114136 1031.114136 L 405.825531 880.107361 L 477.422913 786.52356 L 523.651062 732.483276 L 523.328918 724.671265 L 520.590698 724.671265 L 205.288605 929.395935 L 149.154434 936.644409 L 124.993355 914.01355 L 127.973183 876.885986 L 139.409409 864.80542 L 234.201385 799.570435 L 233.879227 799.8927 Z"/>
|
||||
</svg>
|
||||
<text x="110" y="175" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#d97757" text-anchor="middle" font-weight="600">Claude Code</text>
|
||||
|
||||
<!-- Left arrow -->
|
||||
<line x1="155" y1="135" x2="290" y2="135" stroke="#F2C811" stroke-width="2" stroke-dasharray="6,4" stroke-opacity="0.5"/>
|
||||
<polygon points="294,135 286,130 286,140" fill="#F2C811" fill-opacity="0.5"/>
|
||||
|
||||
<!-- PBI-CLI block art (shadow) -->
|
||||
<text font-family="'Courier New', Courier, monospace" font-size="7" fill="#7A6508" xml:space="preserve">
|
||||
<tspan x="316" y="118">██████╗ ██████╗ ██╗ ██████╗ ██╗ ██╗</tspan>
|
||||
<tspan x="316" y="128">██╔══██╗ ██╔══██╗ ██║ ██╔════╝ ██║ ██║</tspan>
|
||||
<tspan x="316" y="138">██████╔╝ ██████╔╝ ██║ ███╗ ██║ ██║ ██║</tspan>
|
||||
<tspan x="316" y="148">██╔═══╝ ██╔══██╗ ██║ ╚══╝ ██║ ██║ ██║</tspan>
|
||||
<tspan x="316" y="158">██║ ██████╔╝ ██║ ╚██████╗ ███████╗ ██║</tspan>
|
||||
<tspan x="316" y="168">╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝</tspan>
|
||||
</text>
|
||||
<!-- PBI-CLI block art (main) -->
|
||||
<text font-family="'Courier New', Courier, monospace" font-size="7" fill="#F2C811" xml:space="preserve">
|
||||
<tspan x="315" y="117">██████╗ ██████╗ ██╗ ██████╗ ██╗ ██╗</tspan>
|
||||
<tspan x="315" y="127">██╔══██╗ ██╔══██╗ ██║ ██╔════╝ ██║ ██║</tspan>
|
||||
<tspan x="315" y="137">██████╔╝ ██████╔╝ ██║ ███╗ ██║ ██║ ██║</tspan>
|
||||
<tspan x="315" y="147">██╔═══╝ ██╔══██╗ ██║ ╚══╝ ██║ ██║ ██║</tspan>
|
||||
<tspan x="315" y="157">██║ ██████╔╝ ██║ ╚██████╗ ███████╗ ██║</tspan>
|
||||
<tspan x="315" y="167">╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝</tspan>
|
||||
</text>
|
||||
|
||||
<!-- Right arrow -->
|
||||
<line x1="560" y1="135" x2="655" y2="135" stroke="#F2C811" stroke-width="2" stroke-dasharray="6,4" stroke-opacity="0.5"/>
|
||||
<polygon points="659,135 651,130 651,140" fill="#F2C811" fill-opacity="0.5"/>
|
||||
|
||||
<!-- Power BI logo (inline, original 630x630, displayed as 50x50, centered at x=700) -->
|
||||
<svg x="675" y="110" width="50" height="50" viewBox="0 0 630 630">
|
||||
<g transform="translate(77.5, 0)">
|
||||
<rect fill="url(#pbi-lg1)" x="256" y="0" width="219" height="630" rx="26"/>
|
||||
<path fill="url(#pbi-lg2)" d="M346,604 L346,630 L320,630 L153,630 C138.64,630 127,618.36 127,604 L127,183 C127,168.64 138.64,157 153,157 L320,157 C334.36,157 346,168.64 346,183 L346,604 Z"/>
|
||||
<path fill="url(#pbi-lg5)" d="M219,604 L219,630 L193,630 L26,630 C11.64,630 0,618.36 0,604 L0,341 C0,326.64 11.64,315 26,315 L193,315 C207.36,315 219,326.64 219,341 L219,604 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<text x="700" y="175" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="#F2C811" text-anchor="middle" font-weight="600">Power BI</text>
|
||||
|
||||
<!-- Modeling + Reporting pills -->
|
||||
<rect x="308" y="176" width="100" height="20" rx="10" fill="#58a6ff" fill-opacity="0.1" stroke="#58a6ff" stroke-width="1"/>
|
||||
<text x="358" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#58a6ff" text-anchor="middle" font-weight="600">Modeling</text>
|
||||
<text x="420" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#8b949e" text-anchor="middle">+</text>
|
||||
<rect x="432" y="176" width="100" height="20" rx="10" fill="#06d6a0" fill-opacity="0.1" stroke="#06d6a0" stroke-width="1"/>
|
||||
<text x="482" y="190" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="#06d6a0" text-anchor="middle" font-weight="600">Reporting</text>
|
||||
|
||||
<!-- Bottom separator -->
|
||||
<line x1="60" y1="200" x2="790" y2="200" stroke="#F2C811" stroke-opacity="0.25" stroke-width="2"/>
|
||||
|
||||
<!-- ==================== END HEADER ==================== -->"""
|
||||
|
||||
|
||||
def get_svg_height(svg_text: str) -> int:
|
||||
"""Extract height from SVG viewBox (expects '0 0 W H' format)."""
|
||||
match = re.search(r'viewBox="0 0 \d+ (\d+)"', svg_text)
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
match = re.search(r'height="(\d+)"', svg_text)
|
||||
if match:
|
||||
return int(match.group(1))
|
||||
return 400
|
||||
|
||||
|
||||
def extract_svg_inner(svg_text: str) -> str:
|
||||
"""Extract everything between <svg ...> and </svg> tags."""
|
||||
inner = re.sub(r"<\?xml[^>]*\?>\s*", "", svg_text)
|
||||
inner = re.sub(r"<svg[^>]*>", "", inner, count=1)
|
||||
inner = re.sub(r"</svg>\s*$", "", inner)
|
||||
return inner
|
||||
|
||||
|
||||
def extract_defs_content(svg_text: str) -> str:
|
||||
"""Extract the content inside <defs>...</defs> if present."""
|
||||
match = re.search(r"<defs>(.*?)</defs>", svg_text, re.DOTALL)
|
||||
return match.group(1) if match else ""
|
||||
|
||||
|
||||
def remove_bg_rect(svg_inner: str) -> str:
|
||||
"""Remove the first background rect (fill='#0d1117') from the SVG inner content."""
|
||||
return re.sub(
|
||||
r'\s*<rect[^>]*fill="#0d1117"[^/]*/>\s*',
|
||||
"\n",
|
||||
svg_inner,
|
||||
count=1,
|
||||
)
|
||||
|
||||
|
||||
def remove_defs_block(svg_inner: str) -> str:
|
||||
"""Remove the <defs>...</defs> block from SVG inner content."""
|
||||
return re.sub(r"\s*<defs>.*?</defs>\s*", "\n", svg_inner, flags=re.DOTALL)
|
||||
|
||||
|
||||
def is_already_baked(svg_text: str) -> bool:
|
||||
"""Check if the SVG already contains the Vibe BI header."""
|
||||
return "VIBE BI HEADER" in svg_text
|
||||
|
||||
|
||||
def unbake_header(svg_text: str) -> str:
|
||||
"""Strip the baked header and reconstruct the original feature SVG."""
|
||||
# Extract the feature content from inside <g transform="translate(0, 210)">...</g>
|
||||
match = re.search(
|
||||
r'<g transform="translate\(0, 210\)">\s*(.*?)\s*</g>\s*</svg>',
|
||||
svg_text,
|
||||
re.DOTALL,
|
||||
)
|
||||
if not match:
|
||||
return svg_text
|
||||
|
||||
feature_inner = match.group(1)
|
||||
original_height = get_svg_height(svg_text) - HEADER_HEIGHT
|
||||
|
||||
# Extract feature-specific defs (skip header gradient defs)
|
||||
all_defs = extract_defs_content(svg_text)
|
||||
# Remove header-specific defs (hdr-bar* and pbi-lg*)
|
||||
feature_defs = re.sub(
|
||||
r'\s*<linearGradient id="(hdr-bar|pbi-lg)[^"]*"[^>]*>.*?</linearGradient>',
|
||||
"",
|
||||
all_defs,
|
||||
flags=re.DOTALL,
|
||||
)
|
||||
feature_defs = feature_defs.strip()
|
||||
|
||||
defs_block = ""
|
||||
if feature_defs:
|
||||
defs_block = f"\n <defs>\n{feature_defs}\n </defs>\n"
|
||||
|
||||
return f"""<svg xmlns="http://www.w3.org/2000/svg" width="{SVG_WIDTH}" height="{original_height}" viewBox="0 0 {SVG_WIDTH} {original_height}">
|
||||
{defs_block}
|
||||
<rect width="100%" height="100%" fill="#0d1117" rx="8"/>
|
||||
{feature_inner}
|
||||
</svg>"""
|
||||
|
||||
|
||||
def bake_header(svg_text: str) -> str:
|
||||
"""Inject the Vibe BI header into a feature SVG."""
|
||||
original_height = get_svg_height(svg_text)
|
||||
new_height = original_height + HEADER_HEIGHT
|
||||
|
||||
# Extract parts
|
||||
feature_inner = extract_svg_inner(svg_text)
|
||||
feature_defs = extract_defs_content(svg_text)
|
||||
|
||||
# Clean feature inner: remove defs block and background rect
|
||||
clean_inner = remove_defs_block(feature_inner)
|
||||
clean_inner = remove_bg_rect(clean_inner)
|
||||
|
||||
# Merge defs
|
||||
all_defs = HEADER_DEFS
|
||||
if feature_defs.strip():
|
||||
all_defs += "\n" + feature_defs
|
||||
|
||||
return f"""<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{SVG_WIDTH}" height="{new_height}" viewBox="0 0 {SVG_WIDTH} {new_height}">
|
||||
<defs>
|
||||
{all_defs}
|
||||
</defs>
|
||||
|
||||
<!-- Full background -->
|
||||
<rect width="100%" height="100%" fill="#0d1117"/>
|
||||
|
||||
{HEADER_BODY}
|
||||
|
||||
<!-- Feature content (shifted down by {HEADER_HEIGHT}px) -->
|
||||
<g transform="translate(0, {HEADER_HEIGHT})">
|
||||
{clean_inner}
|
||||
</g>
|
||||
</svg>"""
|
||||
|
||||
|
||||
def process_file(svg_file: str, dry_run: bool = False, force: bool = False) -> None:
|
||||
"""Process a single feature SVG file."""
|
||||
filepath = ASSETS_DIR / svg_file
|
||||
if not filepath.exists():
|
||||
print(f" SKIP {svg_file} (not found)")
|
||||
return
|
||||
|
||||
svg_text = filepath.read_text(encoding="utf-8")
|
||||
|
||||
if is_already_baked(svg_text):
|
||||
if force:
|
||||
svg_text = unbake_header(svg_text)
|
||||
print(f" STRIP {svg_file} (removed old header)")
|
||||
else:
|
||||
print(f" SKIP {svg_file} (already has header, use --force)")
|
||||
return
|
||||
|
||||
original_height = get_svg_height(svg_text)
|
||||
|
||||
result = bake_header(svg_text)
|
||||
new_height = original_height + HEADER_HEIGHT
|
||||
|
||||
if dry_run:
|
||||
print(f" DRY {svg_file}: {original_height} -> {new_height}px")
|
||||
else:
|
||||
filepath.write_text(result, encoding="utf-8")
|
||||
print(f" OK {svg_file}: {original_height} -> {new_height}px")
|
||||
|
||||
|
||||
def main():
|
||||
dry_run = "--dry-run" in sys.argv
|
||||
force = "--force" in sys.argv
|
||||
targets = sys.argv[1:]
|
||||
targets = [t for t in targets if not t.startswith("--")]
|
||||
|
||||
if not targets:
|
||||
targets = FEATURE_SVGS
|
||||
|
||||
mode = "DRY RUN" if dry_run else ("FORCE REBAKE" if force else "BAKING")
|
||||
print(f"\n{mode}: Injecting Vibe BI header into {len(targets)} SVGs\n")
|
||||
|
||||
for svg_file in targets:
|
||||
process_file(svg_file, dry_run=dry_run, force=force)
|
||||
|
||||
print(f"\nDone! Processed {len(targets)} files.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
220
scripts/generate_downloads_chart.py
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
"""Regenerate assets/downloads-chart.svg from pypistats.org data.
|
||||
|
||||
Fetches daily download counts for pbi-cli-tool (mirrors excluded), computes a
|
||||
cumulative series, and writes a dark-theme SVG line chart that matches the
|
||||
visual style of the other assets in this repo.
|
||||
|
||||
Runs with stdlib only so it works in CI without extra dependencies.
|
||||
|
||||
Usage:
|
||||
python scripts/generate_downloads_chart.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
import urllib.request
|
||||
from datetime import date, datetime
|
||||
from pathlib import Path
|
||||
|
||||
PACKAGE = "pbi-cli-tool"
|
||||
API_URL = f"https://pypistats.org/api/packages/{PACKAGE}/overall?mirrors=false"
|
||||
OUTPUT_PATH = Path(__file__).resolve().parent.parent / "assets" / "downloads-chart.svg"
|
||||
|
||||
# Chart geometry
|
||||
WIDTH = 850
|
||||
HEIGHT = 340
|
||||
PLOT_LEFT = 70
|
||||
PLOT_RIGHT = 810
|
||||
PLOT_TOP = 78
|
||||
PLOT_BOTTOM = 280
|
||||
|
||||
# Colors (match stats.svg / banner.svg palette)
|
||||
BG = "#0d1117"
|
||||
ACCENT_YELLOW = "#F2C811"
|
||||
LINE_BLUE = "#58a6ff"
|
||||
GRID = "#21262d"
|
||||
TEXT_PRIMARY = "#c9d1d9"
|
||||
TEXT_SECONDARY = "#8b949e"
|
||||
CARD_BG = "#0d1a2a"
|
||||
|
||||
|
||||
def fetch_downloads() -> list[tuple[date, int]]:
|
||||
"""Return sorted list of (date, daily_downloads) from pypistats."""
|
||||
with urllib.request.urlopen(API_URL, timeout=30) as resp:
|
||||
payload = json.loads(resp.read())
|
||||
|
||||
rows = [
|
||||
(datetime.strptime(r["date"], "%Y-%m-%d").date(), int(r["downloads"]))
|
||||
for r in payload["data"]
|
||||
if r["category"] == "without_mirrors"
|
||||
]
|
||||
rows.sort(key=lambda item: item[0])
|
||||
return rows
|
||||
|
||||
|
||||
def to_cumulative(rows: list[tuple[date, int]]) -> list[tuple[date, int]]:
|
||||
total = 0
|
||||
out: list[tuple[date, int]] = []
|
||||
for d, n in rows:
|
||||
total += n
|
||||
out.append((d, total))
|
||||
return out
|
||||
|
||||
|
||||
def nice_ceiling(value: int) -> int:
|
||||
"""Round up to a nice axis maximum (1-2-5 * 10^n)."""
|
||||
if value <= 0:
|
||||
return 10
|
||||
import math
|
||||
|
||||
exp = math.floor(math.log10(value))
|
||||
base = 10**exp
|
||||
for step in (1, 2, 2.5, 5, 10):
|
||||
candidate = int(step * base)
|
||||
if candidate >= value:
|
||||
return candidate
|
||||
return int(10 * base)
|
||||
|
||||
|
||||
def build_svg(series: list[tuple[date, int]]) -> str:
|
||||
if not series:
|
||||
raise RuntimeError("No download data returned from pypistats")
|
||||
|
||||
n = len(series)
|
||||
max_val = series[-1][1]
|
||||
y_max = nice_ceiling(int(max_val * 1.15))
|
||||
|
||||
plot_width = PLOT_RIGHT - PLOT_LEFT
|
||||
plot_height = PLOT_BOTTOM - PLOT_TOP
|
||||
|
||||
def x_at(i: int) -> float:
|
||||
if n == 1:
|
||||
return PLOT_LEFT + plot_width / 2
|
||||
return PLOT_LEFT + (i / (n - 1)) * plot_width
|
||||
|
||||
def y_at(v: int) -> float:
|
||||
return PLOT_BOTTOM - (v / y_max) * plot_height
|
||||
|
||||
points = [(x_at(i), y_at(v)) for i, (_, v) in enumerate(series)]
|
||||
|
||||
line_path = "M " + " L ".join(f"{x:.2f},{y:.2f}" for x, y in points)
|
||||
area_path = (
|
||||
f"M {points[0][0]:.2f},{PLOT_BOTTOM} "
|
||||
+ "L "
|
||||
+ " L ".join(f"{x:.2f},{y:.2f}" for x, y in points)
|
||||
+ f" L {points[-1][0]:.2f},{PLOT_BOTTOM} Z"
|
||||
)
|
||||
|
||||
# Y-axis gridlines (5 steps)
|
||||
gridlines = []
|
||||
y_labels = []
|
||||
for step in range(6):
|
||||
v = y_max * step / 5
|
||||
y = PLOT_BOTTOM - (v / y_max) * plot_height
|
||||
gridlines.append(
|
||||
f'<line x1="{PLOT_LEFT}" y1="{y:.1f}" x2="{PLOT_RIGHT}" y2="{y:.1f}" '
|
||||
f'stroke="{GRID}" stroke-width="1" stroke-dasharray="2,3"/>'
|
||||
)
|
||||
label = f"{int(v):,}"
|
||||
y_labels.append(
|
||||
f'<text x="{PLOT_LEFT - 8}" y="{y + 4:.1f}" font-family="\'Segoe UI\', Arial, sans-serif" '
|
||||
f'font-size="10" fill="{TEXT_SECONDARY}" text-anchor="end">{label}</text>'
|
||||
)
|
||||
|
||||
# X-axis labels: first, last, and ~3 intermediate
|
||||
label_indices = sorted({0, n - 1, n // 4, n // 2, (3 * n) // 4})
|
||||
x_labels = []
|
||||
for i in label_indices:
|
||||
d, _ = series[i]
|
||||
x = x_at(i)
|
||||
x_labels.append(
|
||||
f'<text x="{x:.1f}" y="{PLOT_BOTTOM + 18}" font-family="\'Segoe UI\', Arial, sans-serif" '
|
||||
f'font-size="10" fill="{TEXT_SECONDARY}" text-anchor="middle">{d.strftime("%b %d")}</text>'
|
||||
)
|
||||
|
||||
# Data point dots + highlight on last
|
||||
dots = []
|
||||
for i, (x, y) in enumerate(points):
|
||||
is_last = i == n - 1
|
||||
r = 4 if is_last else 2.5
|
||||
fill = ACCENT_YELLOW if is_last else LINE_BLUE
|
||||
dots.append(f'<circle cx="{x:.2f}" cy="{y:.2f}" r="{r}" fill="{fill}"/>')
|
||||
|
||||
# Last-value callout
|
||||
last_x, last_y = points[-1]
|
||||
last_val = series[-1][1]
|
||||
callout_x = min(last_x + 10, PLOT_RIGHT - 90)
|
||||
callout_y = max(last_y - 28, PLOT_TOP + 14)
|
||||
|
||||
# Summary stats
|
||||
first_date = series[0][0].strftime("%b %d, %Y")
|
||||
last_date = series[-1][0].strftime("%b %d, %Y")
|
||||
total_str = f"{max_val:,}"
|
||||
|
||||
svg = f"""<svg xmlns="http://www.w3.org/2000/svg" width="{WIDTH}" height="{HEIGHT}" viewBox="0 0 {WIDTH} {HEIGHT}">
|
||||
<defs>
|
||||
<linearGradient id="areaFill" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="{LINE_BLUE}" stop-opacity="0.45"/>
|
||||
<stop offset="100%" stop-color="{LINE_BLUE}" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="100%" height="100%" fill="{BG}" rx="8"/>
|
||||
|
||||
<!-- Title -->
|
||||
<text x="{WIDTH // 2}" y="33" font-family="'Segoe UI', Arial, sans-serif" font-size="17" fill="{ACCENT_YELLOW}" text-anchor="middle" font-weight="bold">pbi-cli Downloads Over Time</text>
|
||||
<text x="{WIDTH // 2}" y="52" font-family="'Segoe UI', Arial, sans-serif" font-size="11" fill="{TEXT_SECONDARY}" text-anchor="middle">Cumulative installs from PyPI \u2022 mirrors excluded \u2022 source: pypistats.org</text>
|
||||
|
||||
<!-- Gridlines & y-labels -->
|
||||
{"".join(gridlines)}
|
||||
{"".join(y_labels)}
|
||||
|
||||
<!-- Area fill -->
|
||||
<path d="{area_path}" fill="url(#areaFill)"/>
|
||||
|
||||
<!-- Line -->
|
||||
<path d="{line_path}" fill="none" stroke="{LINE_BLUE}" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
|
||||
<!-- Data points -->
|
||||
{"".join(dots)}
|
||||
|
||||
<!-- Last-value callout -->
|
||||
<rect x="{callout_x:.1f}" y="{callout_y:.1f}" width="82" height="24" rx="4" fill="{CARD_BG}" stroke="{ACCENT_YELLOW}" stroke-width="1"/>
|
||||
<text x="{callout_x + 41:.1f}" y="{callout_y + 16:.1f}" font-family="'Segoe UI', Arial, sans-serif" font-size="12" fill="{ACCENT_YELLOW}" text-anchor="middle" font-weight="bold">{total_str} total</text>
|
||||
|
||||
<!-- X-axis labels -->
|
||||
{"".join(x_labels)}
|
||||
|
||||
<!-- Footer: date range -->
|
||||
<text x="{PLOT_LEFT}" y="{HEIGHT - 12}" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="{TEXT_SECONDARY}">{first_date} \u2192 {last_date}</text>
|
||||
<text x="{PLOT_RIGHT}" y="{HEIGHT - 12}" font-family="'Segoe UI', Arial, sans-serif" font-size="10" fill="{TEXT_SECONDARY}" text-anchor="end">{n} days of data</text>
|
||||
</svg>
|
||||
"""
|
||||
return svg
|
||||
|
||||
|
||||
def main() -> int:
|
||||
try:
|
||||
daily = fetch_downloads()
|
||||
except Exception as exc: # noqa: BLE001
|
||||
print(f"Failed to fetch pypistats data: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if not daily:
|
||||
print("pypistats returned no rows", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
cumulative = to_cumulative(daily)
|
||||
svg = build_svg(cumulative)
|
||||
|
||||
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT_PATH.write_text(svg, encoding="utf-8")
|
||||
print(f"Wrote {OUTPUT_PATH} ({len(cumulative)} days, {cumulative[-1][1]:,} total downloads)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
"""pbi-cli: CLI for Power BI semantic models via direct .NET interop."""
|
||||
|
||||
__version__ = "3.10.0"
|
||||
__version__ = "3.10.10"
|
||||
|
|
|
|||
|
|
@ -14,10 +14,19 @@ if TYPE_CHECKING:
|
|||
from pbi_cli.main import PbiContext
|
||||
|
||||
# Statuses that indicate a write operation (triggers Desktop sync)
|
||||
_WRITE_STATUSES = frozenset({
|
||||
"created", "deleted", "updated", "applied", "added",
|
||||
"cleared", "bound", "removed", "set",
|
||||
})
|
||||
_WRITE_STATUSES = frozenset(
|
||||
{
|
||||
"created",
|
||||
"deleted",
|
||||
"updated",
|
||||
"applied",
|
||||
"added",
|
||||
"cleared",
|
||||
"bound",
|
||||
"removed",
|
||||
"set",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def run_command(
|
||||
|
|
@ -53,7 +62,7 @@ def run_command(
|
|||
|
||||
|
||||
def _is_report_write(result: Any) -> bool:
|
||||
"""Check if the result indicates a report-layer write."""
|
||||
"""Check if the result indicates a report-layer write that should trigger sync."""
|
||||
if not isinstance(result, dict):
|
||||
return False
|
||||
status = result.get("status", "")
|
||||
|
|
@ -65,11 +74,13 @@ def _is_report_write(result: Any) -> bool:
|
|||
if click_ctx is None:
|
||||
return False
|
||||
|
||||
# Walk up to the group to find report_path
|
||||
# Walk up to the group to find report_path; also check for --no-sync flag
|
||||
parent = click_ctx.parent
|
||||
while parent is not None:
|
||||
obj = parent.obj
|
||||
if isinstance(obj, dict) and "report_path" in obj:
|
||||
if obj.get("no_sync", False):
|
||||
return False
|
||||
return True
|
||||
parent = parent.parent
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -15,11 +15,18 @@ from pbi_cli.main import PbiContext, pass_context
|
|||
default=None,
|
||||
help="Path to .Report folder (auto-detected from CWD if omitted).",
|
||||
)
|
||||
@click.option(
|
||||
"--no-sync",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Skip Desktop auto-sync after write commands. Use for scripted multi-step builds.",
|
||||
)
|
||||
@click.pass_context
|
||||
def bookmarks(ctx: click.Context, path: str | None) -> None:
|
||||
def bookmarks(ctx: click.Context, path: str | None, no_sync: bool) -> None:
|
||||
"""Manage report bookmarks."""
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj["report_path"] = path
|
||||
ctx.obj["no_sync"] = no_sync
|
||||
|
||||
|
||||
@bookmarks.command(name="list")
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||
|
||||
import click
|
||||
|
||||
from pbi_cli.commands.skills_cmd import _get_bundled_skills, _is_installed
|
||||
from pbi_cli.core.connection_store import (
|
||||
ConnectionInfo,
|
||||
add_connection,
|
||||
|
|
@ -44,8 +45,6 @@ def connect(
|
|||
|
||||
If --data-source is omitted, auto-detects a running Power BI Desktop instance.
|
||||
"""
|
||||
_ensure_ready()
|
||||
|
||||
if data_source is None:
|
||||
data_source = _auto_discover_data_source()
|
||||
|
||||
|
|
@ -76,6 +75,13 @@ def connect(
|
|||
)
|
||||
else:
|
||||
print_success(f"Connected: {effective_name} ({data_source})")
|
||||
bundled = _get_bundled_skills()
|
||||
any_missing = any(not _is_installed(name) for name in bundled)
|
||||
if any_missing:
|
||||
print_info(
|
||||
"Tip: Claude Code skills not yet installed. "
|
||||
"Run 'pbi-cli skills install' to register pbi-cli skills with Claude Code."
|
||||
)
|
||||
except Exception as e:
|
||||
print_error(f"Connection failed: {e}")
|
||||
raise SystemExit(1)
|
||||
|
|
@ -184,30 +190,3 @@ def _auto_discover_data_source() -> str:
|
|||
data_source = f"localhost:{port}"
|
||||
print_info(f"Auto-detected Power BI Desktop on {data_source}")
|
||||
return data_source
|
||||
|
||||
|
||||
def _ensure_ready() -> None:
|
||||
"""Auto-install skills if not already done.
|
||||
|
||||
Lets users go straight from install to connect in one step:
|
||||
pipx install pbi-cli-tool
|
||||
pbi connect -d localhost:54321
|
||||
"""
|
||||
from pbi_cli.commands.skills_cmd import SKILLS_TARGET_DIR, _get_bundled_skills
|
||||
from pbi_cli.core.claude_integration import ensure_claude_md_snippet
|
||||
|
||||
bundled = _get_bundled_skills()
|
||||
any_missing = any(not (SKILLS_TARGET_DIR / name / "SKILL.md").exists() for name in bundled)
|
||||
if bundled and any_missing:
|
||||
print_info("Installing Claude Code skills...")
|
||||
for name, source in sorted(bundled.items()):
|
||||
target_dir = SKILLS_TARGET_DIR / name
|
||||
if (target_dir / "SKILL.md").exists():
|
||||
continue
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
source_file = source / "SKILL.md"
|
||||
target_file = target_dir / "SKILL.md"
|
||||
target_file.write_text(source_file.read_text(encoding="utf-8"), encoding="utf-8")
|
||||
print_info("Skills installed.")
|
||||
|
||||
ensure_claude_md_snippet()
|
||||
|
|
|
|||
|
|
@ -15,11 +15,18 @@ from pbi_cli.main import PbiContext, pass_context
|
|||
default=None,
|
||||
help="Path to .Report folder (auto-detected from CWD if omitted).",
|
||||
)
|
||||
@click.option(
|
||||
"--no-sync",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Skip Desktop auto-sync after write commands. Use for scripted multi-step builds.",
|
||||
)
|
||||
@click.pass_context
|
||||
def filters(ctx: click.Context, path: str | None) -> None:
|
||||
def filters(ctx: click.Context, path: str | None, no_sync: bool) -> None:
|
||||
"""Manage page and visual filters."""
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj["report_path"] = path
|
||||
ctx.obj["no_sync"] = no_sync
|
||||
|
||||
|
||||
@filters.command(name="list")
|
||||
|
|
|
|||
|
|
@ -17,11 +17,18 @@ from pbi_cli.main import PbiContext, pass_context
|
|||
default=None,
|
||||
help="Path to .Report folder (auto-detected from CWD if omitted).",
|
||||
)
|
||||
@click.option(
|
||||
"--no-sync",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Skip Desktop auto-sync after write commands. Use for scripted multi-step builds.",
|
||||
)
|
||||
@click.pass_context
|
||||
def report(ctx: click.Context, path: str | None) -> None:
|
||||
def report(ctx: click.Context, path: str | None, no_sync: bool) -> None:
|
||||
"""Manage Power BI PBIR reports (pages, themes, validation)."""
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj["report_path"] = path
|
||||
ctx.obj["no_sync"] = no_sync
|
||||
|
||||
|
||||
@report.command()
|
||||
|
|
@ -46,9 +53,7 @@ def info(ctx: PbiContext, click_ctx: click.Context) -> None:
|
|||
help="Relative path to semantic model folder (e.g. ../MyModel.Dataset).",
|
||||
)
|
||||
@pass_context
|
||||
def create(
|
||||
ctx: PbiContext, target_path: str, name: str, dataset_path: str | None
|
||||
) -> None:
|
||||
def create(ctx: PbiContext, target_path: str, name: str, dataset_path: str | None) -> None:
|
||||
"""Scaffold a new PBIR report project."""
|
||||
from pbi_cli.core.report_backend import report_create
|
||||
|
||||
|
|
@ -168,7 +173,10 @@ def get_theme(ctx: PbiContext, click_ctx: click.Context) -> None:
|
|||
|
||||
@report.command(name="diff-theme")
|
||||
@click.option(
|
||||
"--file", "-f", required=True, type=click.Path(exists=True),
|
||||
"--file",
|
||||
"-f",
|
||||
required=True,
|
||||
type=click.Path(exists=True),
|
||||
help="Proposed theme JSON file.",
|
||||
)
|
||||
@click.pass_context
|
||||
|
|
@ -194,10 +202,18 @@ def diff_theme(ctx: PbiContext, click_ctx: click.Context, file: str) -> None:
|
|||
@report.command(name="set-background")
|
||||
@click.argument("page_name")
|
||||
@click.option("--color", "-c", required=True, help="Hex color e.g. '#F8F9FA'.")
|
||||
@click.option(
|
||||
"--transparency",
|
||||
"-t",
|
||||
default=0,
|
||||
show_default=True,
|
||||
type=click.IntRange(0, 100),
|
||||
help="Transparency 0 (opaque) to 100 (invisible). Defaults to 0 so the color is visible.",
|
||||
)
|
||||
@click.pass_context
|
||||
@pass_context
|
||||
def set_background(
|
||||
ctx: PbiContext, click_ctx: click.Context, page_name: str, color: str
|
||||
ctx: PbiContext, click_ctx: click.Context, page_name: str, color: str, transparency: int
|
||||
) -> None:
|
||||
"""Set the background color of a page."""
|
||||
from pbi_cli.core.pbir_path import resolve_report_path
|
||||
|
|
@ -211,6 +227,7 @@ def set_background(
|
|||
definition_path=definition_path,
|
||||
page_name=page_name,
|
||||
color=color,
|
||||
transparency=transparency,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -223,9 +240,7 @@ def set_background(
|
|||
)
|
||||
@click.pass_context
|
||||
@pass_context
|
||||
def set_visibility(
|
||||
ctx: PbiContext, click_ctx: click.Context, page_name: str, hidden: bool
|
||||
) -> None:
|
||||
def set_visibility(ctx: PbiContext, click_ctx: click.Context, page_name: str, hidden: bool) -> None:
|
||||
"""Hide or show a page in the report navigation."""
|
||||
from pbi_cli.core.pbir_path import resolve_report_path
|
||||
from pbi_cli.core.report_backend import page_set_visibility
|
||||
|
|
|
|||
|
|
@ -88,14 +88,6 @@ def _verify(json_output: bool) -> None:
|
|||
print_json({"status": "error", "errors": errors})
|
||||
raise SystemExit(1)
|
||||
|
||||
# Install skills
|
||||
try:
|
||||
from pbi_cli.commands.connection import _ensure_ready
|
||||
|
||||
_ensure_ready()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if json_output:
|
||||
print_json({"status": "ready"})
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ if TYPE_CHECKING:
|
|||
|
||||
import click
|
||||
|
||||
from pbi_cli.main import pass_context
|
||||
|
||||
SKILLS_TARGET_DIR = Path.home() / ".claude" / "skills"
|
||||
|
||||
|
||||
|
|
@ -38,8 +36,7 @@ def skills() -> None:
|
|||
|
||||
|
||||
@skills.command("list")
|
||||
@pass_context
|
||||
def skills_list(ctx: object) -> None:
|
||||
def skills_list() -> None:
|
||||
"""List available and installed skills."""
|
||||
bundled = _get_bundled_skills()
|
||||
if not bundled:
|
||||
|
|
@ -59,23 +56,32 @@ def skills_list(ctx: object) -> None:
|
|||
@skills.command("install")
|
||||
@click.option("--skill", "skill_name", default=None, help="Install a specific skill.")
|
||||
@click.option("--force", is_flag=True, default=False, help="Overwrite existing installations.")
|
||||
@pass_context
|
||||
def skills_install(ctx: object, skill_name: str | None, force: bool) -> None:
|
||||
"""Install skills to ~/.claude/skills/ for Claude Code discovery."""
|
||||
@click.option("--yes", "-y", is_flag=True, default=False, help="Skip confirmation prompt.")
|
||||
def skills_install(skill_name: str | None, force: bool, yes: bool) -> None:
|
||||
"""Install Power BI skills to ~/.claude/skills/ and register with CLAUDE.md."""
|
||||
bundled = _get_bundled_skills()
|
||||
if not bundled:
|
||||
click.echo("No bundled skills found.", err=True)
|
||||
return
|
||||
|
||||
to_install = (
|
||||
{skill_name: bundled[skill_name]} if skill_name and skill_name in bundled else bundled
|
||||
)
|
||||
|
||||
if skill_name and skill_name not in bundled:
|
||||
raise click.ClickException(
|
||||
f"Unknown skill '{skill_name}'. Available: {', '.join(sorted(bundled))}"
|
||||
)
|
||||
|
||||
to_install = (
|
||||
{skill_name: bundled[skill_name]} if skill_name and skill_name in bundled else bundled
|
||||
)
|
||||
|
||||
if not yes:
|
||||
click.echo("This command will modify your global Claude Code configuration:\n")
|
||||
click.echo(f" {'~/.claude/skills/power-bi-*/':<52} copy {len(to_install)} skill file(s)")
|
||||
click.echo(f" {'~/.claude/CLAUDE.md':<52} append pbi-cli skill trigger block")
|
||||
click.echo("\nThis affects ALL Claude Code sessions, not just Power BI work.")
|
||||
if not click.confirm("\nProceed?", default=False):
|
||||
click.echo("Aborted.")
|
||||
return
|
||||
|
||||
installed_count = 0
|
||||
for name, source in sorted(to_install.items()):
|
||||
target_dir = SKILLS_TARGET_DIR / name
|
||||
|
|
@ -87,18 +93,21 @@ def skills_install(ctx: object, skill_name: str | None, force: bool) -> None:
|
|||
source_file = source / "SKILL.md"
|
||||
target_file = target_dir / "SKILL.md"
|
||||
|
||||
# Read from importlib resource and write to target
|
||||
target_file.write_text(source_file.read_text(encoding="utf-8"), encoding="utf-8")
|
||||
installed_count += 1
|
||||
click.echo(f" {name}: installed", err=True)
|
||||
|
||||
if installed_count > 0:
|
||||
from pbi_cli.core.claude_integration import ensure_claude_md_snippet
|
||||
|
||||
ensure_claude_md_snippet()
|
||||
|
||||
click.echo(f"\n{installed_count} skill(s) installed to {SKILLS_TARGET_DIR}", err=True)
|
||||
|
||||
|
||||
@skills.command("uninstall")
|
||||
@click.option("--skill", "skill_name", default=None, help="Uninstall a specific skill.")
|
||||
@pass_context
|
||||
def skills_uninstall(ctx: object, skill_name: str | None) -> None:
|
||||
def skills_uninstall(skill_name: str | None) -> None:
|
||||
"""Remove installed skills from ~/.claude/skills/."""
|
||||
bundled = _get_bundled_skills()
|
||||
names = [skill_name] if skill_name else sorted(bundled)
|
||||
|
|
|
|||
|
|
@ -15,17 +15,25 @@ from pbi_cli.main import PbiContext, pass_context
|
|||
default=None,
|
||||
help="Path to .Report folder (auto-detected from CWD if omitted).",
|
||||
)
|
||||
@click.option(
|
||||
"--no-sync",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Skip Desktop auto-sync after write commands. Use for scripted multi-step builds.",
|
||||
)
|
||||
@click.pass_context
|
||||
def visual(ctx: click.Context, path: str | None) -> None:
|
||||
def visual(ctx: click.Context, path: str | None, no_sync: bool) -> None:
|
||||
"""Manage visuals in PBIR report pages."""
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj["report_path"] = path
|
||||
ctx.obj["no_sync"] = no_sync
|
||||
|
||||
|
||||
def _get_report_path(click_ctx: click.Context) -> str | None:
|
||||
"""Extract report_path from parent context."""
|
||||
if click_ctx.parent:
|
||||
return click_ctx.parent.obj.get("report_path")
|
||||
result: str | None = click_ctx.parent.obj.get("report_path")
|
||||
return result
|
||||
return None
|
||||
|
||||
|
||||
|
|
|
|||
68
src/pbi_cli/core/banner.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
"""ASCII banner displayed on bare `pbi` invocation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Power BI yellow — closest ANSI equivalent to #FFBE00
|
||||
_YELLOW = "\033[93m"
|
||||
_DIM = "\033[2m"
|
||||
_RESET = "\033[0m"
|
||||
|
||||
_ART = r"""
|
||||
██████╗ ██████╗ ██╗ ██████╗ ██╗ ██╗
|
||||
██╔══██╗ ██╔══██╗ ██║ ██╔════╝ ██║ ██║
|
||||
██████╔╝ ██████╔╝ ██║ ███╗ ██║ ██║ ██║
|
||||
██╔═══╝ ██╔══██╗ ██║ ╚══╝ ██║ ██║ ██║
|
||||
██║ ██████╔╝ ██║ ╚██████╗ ███████╗ ██║
|
||||
╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝
|
||||
"""
|
||||
|
||||
_TAGLINE = "Power BI CLI · Direct .NET interop · Built for Claude Code"
|
||||
|
||||
|
||||
def _color_supported() -> bool:
|
||||
"""Return True if the terminal supports ANSI color codes."""
|
||||
if os.environ.get("NO_COLOR"):
|
||||
return False
|
||||
if os.environ.get("FORCE_COLOR"):
|
||||
return True
|
||||
return sys.stdout.isatty()
|
||||
|
||||
|
||||
def _can_encode(text: str) -> bool:
|
||||
"""Return True if stdout can encode the given text."""
|
||||
enc = getattr(sys.stdout, "encoding", "utf-8") or "utf-8"
|
||||
try:
|
||||
text.encode(enc)
|
||||
return True
|
||||
except (UnicodeEncodeError, LookupError):
|
||||
return False
|
||||
|
||||
|
||||
def print_banner(version: str) -> None:
|
||||
"""Print the PBI-CLI ASCII banner with version and quick-start hints."""
|
||||
use_color = _color_supported()
|
||||
use_art = _can_encode(_ART)
|
||||
|
||||
if use_art:
|
||||
art = f"{_YELLOW}{_ART}{_RESET}" if use_color else _ART
|
||||
else:
|
||||
# Fallback for terminals without Unicode support (e.g. legacy cmd.exe)
|
||||
art = "\n PBI-CLI\n"
|
||||
|
||||
tagline = f" {_DIM}{_TAGLINE}{_RESET}" if use_color else f" {_TAGLINE}"
|
||||
ver_line = f" {_DIM}v{version}{_RESET}" if use_color else f" v{version}"
|
||||
|
||||
print(art)
|
||||
print(tagline)
|
||||
print(ver_line)
|
||||
print()
|
||||
print(" Quick start:")
|
||||
print(" pipx install pbi-cli-tool # already done")
|
||||
print(" pbi-cli skills install # register Claude Code skills")
|
||||
print(" pbi connect # auto-detect Power BI Desktop")
|
||||
print()
|
||||
print(" Run 'pbi --help' for the full command list.")
|
||||
print()
|
||||
|
|
@ -34,7 +34,8 @@ SCHEMA_BOOKMARK = (
|
|||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
"""Read and parse a JSON file."""
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
result: dict[str, Any] = json.loads(path.read_text(encoding="utf-8"))
|
||||
return result
|
||||
|
||||
|
||||
def _write_json(path: Path, data: dict[str, Any]) -> None:
|
||||
|
|
@ -93,11 +94,13 @@ def bookmark_list(definition_path: Path) -> list[dict[str, Any]]:
|
|||
continue
|
||||
bm = _read_json(bm_file)
|
||||
exploration = bm.get("explorationState", {})
|
||||
results.append({
|
||||
"name": name,
|
||||
"display_name": bm.get("displayName", ""),
|
||||
"active_section": exploration.get("activeSection"),
|
||||
})
|
||||
results.append(
|
||||
{
|
||||
"name": name,
|
||||
"display_name": bm.get("displayName", ""),
|
||||
"active_section": exploration.get("activeSection"),
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
|
|
|||
|
|
@ -48,8 +48,7 @@ class VisualTypeError(PbiCliError):
|
|||
def __init__(self, visual_type: str) -> None:
|
||||
self.visual_type = visual_type
|
||||
super().__init__(
|
||||
f"Unknown visual type '{visual_type}'. "
|
||||
"Run 'pbi visual types' to see supported types."
|
||||
f"Unknown visual type '{visual_type}'. Run 'pbi visual types' to see supported types."
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ from pbi_cli.core.pbir_path import get_page_dir, get_visual_dir
|
|||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
"""Read and parse a JSON file."""
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
result: dict[str, Any] = json.loads(path.read_text(encoding="utf-8"))
|
||||
return result
|
||||
|
||||
|
||||
def _write_json(path: Path, data: dict[str, Any]) -> None:
|
||||
|
|
@ -288,9 +289,7 @@ def filter_add_topn(
|
|||
"Select": [
|
||||
{
|
||||
"Column": {
|
||||
"Expression": {
|
||||
"SourceRef": {"Source": cat_alias}
|
||||
},
|
||||
"Expression": {"SourceRef": {"Source": cat_alias}},
|
||||
"Property": column,
|
||||
},
|
||||
"Name": "field",
|
||||
|
|
@ -333,9 +332,7 @@ def filter_add_topn(
|
|||
"Expressions": [
|
||||
{
|
||||
"Column": {
|
||||
"Expression": {
|
||||
"SourceRef": {"Source": cat_alias}
|
||||
},
|
||||
"Expression": {"SourceRef": {"Source": cat_alias}},
|
||||
"Property": column,
|
||||
}
|
||||
}
|
||||
|
|
@ -399,9 +396,7 @@ def filter_add_relative_date(
|
|||
time_unit_lower = time_unit.strip().lower()
|
||||
if time_unit_lower not in _RELATIVE_DATE_TIME_UNITS:
|
||||
valid = ", ".join(_RELATIVE_DATE_TIME_UNITS)
|
||||
raise PbiCliError(
|
||||
f"time_unit must be one of {valid}, got '{time_unit}'."
|
||||
)
|
||||
raise PbiCliError(f"time_unit must be one of {valid}, got '{time_unit}'.")
|
||||
time_unit_code = _RELATIVE_DATE_TIME_UNITS[time_unit_lower]
|
||||
days_code = _RELATIVE_DATE_TIME_UNITS["days"]
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ from pbi_cli.core.pbir_path import get_visual_dir
|
|||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
"""Read and parse a JSON file."""
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
result: dict[str, Any] = json.loads(path.read_text(encoding="utf-8"))
|
||||
return result
|
||||
|
||||
|
||||
def _write_json(path: Path, data: dict[str, Any]) -> None:
|
||||
|
|
@ -42,8 +43,7 @@ def _load_visual(definition_path: Path, page_name: str, visual_name: str) -> dic
|
|||
visual_path = get_visual_dir(definition_path, page_name, visual_name) / "visual.json"
|
||||
if not visual_path.exists():
|
||||
raise PbiCliError(
|
||||
f"Visual '{visual_name}' not found on page '{page_name}'. "
|
||||
f"Expected: {visual_path}"
|
||||
f"Visual '{visual_name}' not found on page '{page_name}'. Expected: {visual_path}"
|
||||
)
|
||||
return _read_json(visual_path)
|
||||
|
||||
|
|
@ -176,20 +176,10 @@ def format_background_gradient(
|
|||
},
|
||||
"FillRule": {
|
||||
"linearGradient2": {
|
||||
"min": {
|
||||
"color": {
|
||||
"Literal": {"Value": f"'{min_color}'"}
|
||||
}
|
||||
},
|
||||
"max": {
|
||||
"color": {
|
||||
"Literal": {"Value": f"'{max_color}'"}
|
||||
}
|
||||
},
|
||||
"min": {"color": {"Literal": {"Value": f"'{min_color}'"}}},
|
||||
"max": {"color": {"Literal": {"Value": f"'{max_color}'"}}},
|
||||
"nullColoringStrategy": {
|
||||
"strategy": {
|
||||
"Literal": {"Value": "'asZero'"}
|
||||
}
|
||||
"strategy": {"Literal": {"Value": "'asZero'"}}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
|
@ -259,9 +249,7 @@ def format_background_conditional(
|
|||
comparison_lower = comparison.strip().lower()
|
||||
if comparison_lower not in _COMPARISON_KINDS:
|
||||
valid = ", ".join(_COMPARISON_KINDS)
|
||||
raise PbiCliError(
|
||||
f"comparison must be one of {valid}, got '{comparison}'."
|
||||
)
|
||||
raise PbiCliError(f"comparison must be one of {valid}, got '{comparison}'.")
|
||||
comparison_kind = _COMPARISON_KINDS[comparison_lower]
|
||||
|
||||
if field_query_ref is None:
|
||||
|
|
@ -302,16 +290,10 @@ def format_background_conditional(
|
|||
"Function": 0,
|
||||
}
|
||||
},
|
||||
"Right": {
|
||||
"Literal": {
|
||||
"Value": threshold_literal
|
||||
}
|
||||
},
|
||||
"Right": {"Literal": {"Value": threshold_literal}},
|
||||
}
|
||||
},
|
||||
"Value": {
|
||||
"Literal": {"Value": f"'{color_hex}'"}
|
||||
},
|
||||
"Value": {"Literal": {"Value": f"'{color_hex}'"}},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -373,9 +355,7 @@ def format_background_measure(
|
|||
"color": {
|
||||
"expr": {
|
||||
"Measure": {
|
||||
"Expression": {
|
||||
"SourceRef": {"Entity": measure_table}
|
||||
},
|
||||
"Expression": {"SourceRef": {"Entity": measure_table}},
|
||||
"Property": measure_property,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,46 +39,48 @@ SCHEMA_BOOKMARK = (
|
|||
|
||||
# -- Visual type identifiers ------------------------------------------------
|
||||
|
||||
SUPPORTED_VISUAL_TYPES: frozenset[str] = frozenset({
|
||||
# Original 9
|
||||
"barChart",
|
||||
"lineChart",
|
||||
"card",
|
||||
"pivotTable",
|
||||
"tableEx",
|
||||
"slicer",
|
||||
"kpi",
|
||||
"gauge",
|
||||
"donutChart",
|
||||
# v3.1.0 additions
|
||||
"columnChart",
|
||||
"areaChart",
|
||||
"ribbonChart",
|
||||
"waterfallChart",
|
||||
"scatterChart",
|
||||
"funnelChart",
|
||||
"multiRowCard",
|
||||
"treemap",
|
||||
"cardNew",
|
||||
"stackedBarChart",
|
||||
"lineStackedColumnComboChart",
|
||||
# v3.4.0 additions
|
||||
"cardVisual",
|
||||
"actionButton",
|
||||
# v3.5.0 additions (confirmed from HR Analysis Desktop export)
|
||||
"clusteredColumnChart",
|
||||
"clusteredBarChart",
|
||||
"textSlicer",
|
||||
"listSlicer",
|
||||
# v3.6.0 additions (confirmed from HR Analysis Desktop export)
|
||||
"image",
|
||||
"shape",
|
||||
"textbox",
|
||||
"pageNavigator",
|
||||
"advancedSlicerVisual",
|
||||
# v3.8.0 additions
|
||||
"azureMap",
|
||||
})
|
||||
SUPPORTED_VISUAL_TYPES: frozenset[str] = frozenset(
|
||||
{
|
||||
# Original 9
|
||||
"barChart",
|
||||
"lineChart",
|
||||
"card",
|
||||
"pivotTable",
|
||||
"tableEx",
|
||||
"slicer",
|
||||
"kpi",
|
||||
"gauge",
|
||||
"donutChart",
|
||||
# v3.1.0 additions
|
||||
"columnChart",
|
||||
"areaChart",
|
||||
"ribbonChart",
|
||||
"waterfallChart",
|
||||
"scatterChart",
|
||||
"funnelChart",
|
||||
"multiRowCard",
|
||||
"treemap",
|
||||
"cardNew",
|
||||
"stackedBarChart",
|
||||
"lineStackedColumnComboChart",
|
||||
# v3.4.0 additions
|
||||
"cardVisual",
|
||||
"actionButton",
|
||||
# v3.5.0 additions (confirmed from HR Analysis Desktop export)
|
||||
"clusteredColumnChart",
|
||||
"clusteredBarChart",
|
||||
"textSlicer",
|
||||
"listSlicer",
|
||||
# v3.6.0 additions (confirmed from HR Analysis Desktop export)
|
||||
"image",
|
||||
"shape",
|
||||
"textbox",
|
||||
"pageNavigator",
|
||||
"advancedSlicerVisual",
|
||||
# v3.8.0 additions
|
||||
"azureMap",
|
||||
}
|
||||
)
|
||||
|
||||
# Mapping from user-friendly names to PBIR visualType identifiers
|
||||
VISUAL_TYPE_ALIASES: dict[str, str] = {
|
||||
|
|
|
|||
|
|
@ -114,9 +114,7 @@ def get_visuals_dir(definition_path: Path, page_name: str) -> Path:
|
|||
return visuals
|
||||
|
||||
|
||||
def get_visual_dir(
|
||||
definition_path: Path, page_name: str, visual_name: str
|
||||
) -> Path:
|
||||
def get_visual_dir(definition_path: Path, page_name: str, visual_name: str) -> Path:
|
||||
"""Return the directory for a specific visual."""
|
||||
return definition_path / "pages" / page_name / "visuals" / visual_name
|
||||
|
||||
|
|
|
|||
|
|
@ -107,11 +107,13 @@ def validate_bindings_against_model(
|
|||
ref = _extract_field_ref(sel, sources)
|
||||
if ref and ref not in valid_fields:
|
||||
rel = f"{page_dir.name}/visuals/{vdir.name}"
|
||||
findings.append(ValidationResult(
|
||||
"warning",
|
||||
rel,
|
||||
f"Field '{ref}' not found in semantic model",
|
||||
))
|
||||
findings.append(
|
||||
ValidationResult(
|
||||
"warning",
|
||||
rel,
|
||||
f"Field '{ref}' not found in semantic model",
|
||||
)
|
||||
)
|
||||
except (json.JSONDecodeError, KeyError, TypeError):
|
||||
continue
|
||||
|
||||
|
|
@ -151,20 +153,15 @@ def _validate_report_json(definition_path: Path) -> list[ValidationResult]:
|
|||
findings.append(ValidationResult("warning", "report.json", "Missing $schema reference"))
|
||||
|
||||
if "themeCollection" not in data:
|
||||
findings.append(ValidationResult(
|
||||
"error", "report.json", "Missing required 'themeCollection'"
|
||||
))
|
||||
findings.append(
|
||||
ValidationResult("error", "report.json", "Missing required 'themeCollection'")
|
||||
)
|
||||
else:
|
||||
tc = data["themeCollection"]
|
||||
if "baseTheme" not in tc:
|
||||
findings.append(ValidationResult(
|
||||
"warning", "report.json", "themeCollection missing 'baseTheme'"
|
||||
))
|
||||
|
||||
if "layoutOptimization" not in data:
|
||||
findings.append(ValidationResult(
|
||||
"error", "report.json", "Missing required 'layoutOptimization'"
|
||||
))
|
||||
findings.append(
|
||||
ValidationResult("warning", "report.json", "themeCollection missing 'baseTheme'")
|
||||
)
|
||||
|
||||
return findings
|
||||
|
||||
|
|
@ -201,9 +198,9 @@ def _validate_pages_metadata(definition_path: Path) -> list[ValidationResult]:
|
|||
|
||||
page_order = data.get("pageOrder", [])
|
||||
if not isinstance(page_order, list):
|
||||
findings.append(ValidationResult(
|
||||
"error", "pages/pages.json", "'pageOrder' must be an array"
|
||||
))
|
||||
findings.append(
|
||||
ValidationResult("error", "pages/pages.json", "'pageOrder' must be an array")
|
||||
)
|
||||
|
||||
return findings
|
||||
|
||||
|
|
@ -234,14 +231,15 @@ def _validate_all_pages(definition_path: Path) -> list[ValidationResult]:
|
|||
findings.append(ValidationResult("error", rel, f"Missing required '{req}'"))
|
||||
|
||||
valid_options = {
|
||||
"FitToPage", "FitToWidth", "ActualSize",
|
||||
"ActualSizeTopLeft", "DeprecatedDynamic",
|
||||
"FitToPage",
|
||||
"FitToWidth",
|
||||
"ActualSize",
|
||||
"ActualSizeTopLeft",
|
||||
"DeprecatedDynamic",
|
||||
}
|
||||
opt = data.get("displayOption")
|
||||
if opt and opt not in valid_options:
|
||||
findings.append(ValidationResult(
|
||||
"warning", rel, f"Unknown displayOption '{opt}'"
|
||||
))
|
||||
findings.append(ValidationResult("warning", rel, f"Unknown displayOption '{opt}'"))
|
||||
|
||||
if opt != "DeprecatedDynamic":
|
||||
if "width" not in data:
|
||||
|
|
@ -251,9 +249,9 @@ def _validate_all_pages(definition_path: Path) -> list[ValidationResult]:
|
|||
|
||||
name = data.get("name", "")
|
||||
if name and len(name) > 50:
|
||||
findings.append(ValidationResult(
|
||||
"warning", rel, f"Name exceeds 50 chars: '{name[:20]}...'"
|
||||
))
|
||||
findings.append(
|
||||
ValidationResult("warning", rel, f"Name exceeds 50 chars: '{name[:20]}...'")
|
||||
)
|
||||
|
||||
return findings
|
||||
|
||||
|
|
@ -294,18 +292,22 @@ def _validate_all_visuals(definition_path: Path) -> list[ValidationResult]:
|
|||
pos = data["position"]
|
||||
for req in ("x", "y", "width", "height"):
|
||||
if req not in pos:
|
||||
findings.append(ValidationResult(
|
||||
"error", rel, f"Position missing required '{req}'"
|
||||
))
|
||||
findings.append(
|
||||
ValidationResult("error", rel, f"Position missing required '{req}'")
|
||||
)
|
||||
|
||||
visual_config = data.get("visual", {})
|
||||
vtype = visual_config.get("visualType", "")
|
||||
if not vtype:
|
||||
# Could be a visualGroup, which is also valid
|
||||
if "visualGroup" not in data:
|
||||
findings.append(ValidationResult(
|
||||
"warning", rel, "Missing 'visual.visualType' (not a visual group either)"
|
||||
))
|
||||
findings.append(
|
||||
ValidationResult(
|
||||
"warning",
|
||||
rel,
|
||||
"Missing 'visual.visualType' (not a visual group either)",
|
||||
)
|
||||
)
|
||||
|
||||
return findings
|
||||
|
||||
|
|
@ -331,26 +333,28 @@ def _validate_page_order_consistency(definition_path: Path) -> list[ValidationRe
|
|||
pages_dir = definition_path / "pages"
|
||||
|
||||
actual_pages = {
|
||||
d.name
|
||||
for d in pages_dir.iterdir()
|
||||
if d.is_dir() and (d / "page.json").exists()
|
||||
d.name for d in pages_dir.iterdir() if d.is_dir() and (d / "page.json").exists()
|
||||
}
|
||||
|
||||
for name in page_order:
|
||||
if name not in actual_pages:
|
||||
findings.append(ValidationResult(
|
||||
"warning",
|
||||
"pages/pages.json",
|
||||
f"pageOrder references '{name}' but no such page folder exists",
|
||||
))
|
||||
findings.append(
|
||||
ValidationResult(
|
||||
"warning",
|
||||
"pages/pages.json",
|
||||
f"pageOrder references '{name}' but no such page folder exists",
|
||||
)
|
||||
)
|
||||
|
||||
unlisted = actual_pages - set(page_order)
|
||||
for name in sorted(unlisted):
|
||||
findings.append(ValidationResult(
|
||||
"info",
|
||||
"pages/pages.json",
|
||||
f"Page '{name}' exists but is not listed in pageOrder",
|
||||
))
|
||||
findings.append(
|
||||
ValidationResult(
|
||||
"info",
|
||||
"pages/pages.json",
|
||||
f"Page '{name}' exists but is not listed in pageOrder",
|
||||
)
|
||||
)
|
||||
|
||||
return findings
|
||||
|
||||
|
|
@ -381,11 +385,13 @@ def _validate_visual_name_uniqueness(definition_path: Path) -> list[ValidationRe
|
|||
name = data.get("name", "")
|
||||
if name in names_seen:
|
||||
rel = f"pages/{page_dir.name}/visuals/{vdir.name}/visual.json"
|
||||
findings.append(ValidationResult(
|
||||
"error",
|
||||
rel,
|
||||
f"Duplicate visual name '{name}' (also in {names_seen[name]})",
|
||||
))
|
||||
findings.append(
|
||||
ValidationResult(
|
||||
"error",
|
||||
rel,
|
||||
f"Duplicate visual name '{name}' (also in {names_seen[name]})",
|
||||
)
|
||||
)
|
||||
else:
|
||||
names_seen[name] = vdir.name
|
||||
except (json.JSONDecodeError, KeyError):
|
||||
|
|
@ -418,16 +424,12 @@ def _build_result(findings: list[ValidationResult]) -> dict[str, Any]:
|
|||
}
|
||||
|
||||
|
||||
def _extract_field_ref(
|
||||
select_item: dict[str, Any], sources: dict[str, str]
|
||||
) -> str | None:
|
||||
def _extract_field_ref(select_item: dict[str, Any], sources: dict[str, str]) -> str | None:
|
||||
"""Extract a Table[Column] reference from a semantic query select item."""
|
||||
for kind in ("Column", "Measure"):
|
||||
if kind in select_item:
|
||||
item = select_item[kind]
|
||||
source_name = (
|
||||
item.get("Expression", {}).get("SourceRef", {}).get("Source", "")
|
||||
)
|
||||
source_name = item.get("Expression", {}).get("SourceRef", {}).get("Source", "")
|
||||
prop = item.get("Property", "")
|
||||
table = sources.get(source_name, source_name)
|
||||
if table and prop:
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ from pbi_cli.core.pbir_path import (
|
|||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
"""Read and parse a JSON file."""
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
result: dict[str, Any] = json.loads(path.read_text(encoding="utf-8"))
|
||||
return result
|
||||
|
||||
|
||||
def _write_json(path: Path, data: dict[str, Any]) -> None:
|
||||
|
|
@ -76,12 +77,14 @@ def report_info(definition_path: Path) -> dict[str, Any]:
|
|||
for v in visuals_dir.iterdir()
|
||||
if v.is_dir() and (v / "visual.json").exists()
|
||||
)
|
||||
pages.append({
|
||||
"name": page_data.get("name", page_dir.name),
|
||||
"display_name": page_data.get("displayName", ""),
|
||||
"ordinal": page_data.get("ordinal", 0),
|
||||
"visual_count": visual_count,
|
||||
})
|
||||
pages.append(
|
||||
{
|
||||
"name": page_data.get("name", page_dir.name),
|
||||
"display_name": page_data.get("displayName", ""),
|
||||
"ordinal": page_data.get("ordinal", 0),
|
||||
"visual_count": visual_count,
|
||||
}
|
||||
)
|
||||
|
||||
theme = report_data.get("themeCollection", {}).get("baseTheme", {})
|
||||
|
||||
|
|
@ -115,39 +118,48 @@ def report_create(
|
|||
pages_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# version.json
|
||||
_write_json(definition_dir / "version.json", {
|
||||
"$schema": SCHEMA_VERSION,
|
||||
"version": "2.0.0",
|
||||
})
|
||||
_write_json(
|
||||
definition_dir / "version.json",
|
||||
{
|
||||
"$schema": SCHEMA_VERSION,
|
||||
"version": "2.0.0",
|
||||
},
|
||||
)
|
||||
|
||||
# report.json (matches Desktop defaults)
|
||||
_write_json(definition_dir / "report.json", {
|
||||
"$schema": SCHEMA_REPORT,
|
||||
"themeCollection": {
|
||||
"baseTheme": dict(DEFAULT_BASE_THEME),
|
||||
_write_json(
|
||||
definition_dir / "report.json",
|
||||
{
|
||||
"$schema": SCHEMA_REPORT,
|
||||
"themeCollection": {
|
||||
"baseTheme": dict(DEFAULT_BASE_THEME),
|
||||
},
|
||||
"layoutOptimization": "None",
|
||||
"settings": {
|
||||
"useStylableVisualContainerHeader": True,
|
||||
"defaultDrillFilterOtherVisuals": True,
|
||||
"allowChangeFilterTypes": True,
|
||||
"useEnhancedTooltips": True,
|
||||
"useDefaultAggregateDisplayName": True,
|
||||
},
|
||||
"slowDataSourceSettings": {
|
||||
"isCrossHighlightingDisabled": False,
|
||||
"isSlicerSelectionsButtonEnabled": False,
|
||||
"isFilterSelectionsButtonEnabled": False,
|
||||
"isFieldWellButtonEnabled": False,
|
||||
"isApplyAllButtonEnabled": False,
|
||||
},
|
||||
},
|
||||
"layoutOptimization": "None",
|
||||
"settings": {
|
||||
"useStylableVisualContainerHeader": True,
|
||||
"defaultDrillFilterOtherVisuals": True,
|
||||
"allowChangeFilterTypes": True,
|
||||
"useEnhancedTooltips": True,
|
||||
"useDefaultAggregateDisplayName": True,
|
||||
},
|
||||
"slowDataSourceSettings": {
|
||||
"isCrossHighlightingDisabled": False,
|
||||
"isSlicerSelectionsButtonEnabled": False,
|
||||
"isFilterSelectionsButtonEnabled": False,
|
||||
"isFieldWellButtonEnabled": False,
|
||||
"isApplyAllButtonEnabled": False,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
# pages.json (empty page order)
|
||||
_write_json(definition_dir / "pages" / "pages.json", {
|
||||
"$schema": SCHEMA_PAGES_METADATA,
|
||||
"pageOrder": [],
|
||||
})
|
||||
_write_json(
|
||||
definition_dir / "pages" / "pages.json",
|
||||
{
|
||||
"$schema": SCHEMA_PAGES_METADATA,
|
||||
"pageOrder": [],
|
||||
},
|
||||
)
|
||||
|
||||
# Scaffold a blank semantic model if no dataset path provided
|
||||
if not dataset_path:
|
||||
|
|
@ -155,38 +167,47 @@ def report_create(
|
|||
_scaffold_blank_semantic_model(target_path, name)
|
||||
|
||||
# definition.pbir (datasetReference is REQUIRED by Desktop)
|
||||
_write_json(report_folder / "definition.pbir", {
|
||||
"version": "4.0",
|
||||
"datasetReference": {
|
||||
"byPath": {"path": dataset_path},
|
||||
_write_json(
|
||||
report_folder / "definition.pbir",
|
||||
{
|
||||
"version": "4.0",
|
||||
"datasetReference": {
|
||||
"byPath": {"path": dataset_path},
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
# .platform file for the report
|
||||
_write_json(report_folder / ".platform", {
|
||||
"$schema": (
|
||||
"https://developer.microsoft.com/json-schemas/"
|
||||
"fabric/gitIntegration/platformProperties/2.0.0/schema.json"
|
||||
),
|
||||
"metadata": {
|
||||
"type": "Report",
|
||||
"displayName": name,
|
||||
_write_json(
|
||||
report_folder / ".platform",
|
||||
{
|
||||
"$schema": (
|
||||
"https://developer.microsoft.com/json-schemas/"
|
||||
"fabric/gitIntegration/platformProperties/2.0.0/schema.json"
|
||||
),
|
||||
"metadata": {
|
||||
"type": "Report",
|
||||
"displayName": name,
|
||||
},
|
||||
"config": {
|
||||
"version": "2.0",
|
||||
"logicalId": "00000000-0000-0000-0000-000000000000",
|
||||
},
|
||||
},
|
||||
"config": {
|
||||
"version": "2.0",
|
||||
"logicalId": "00000000-0000-0000-0000-000000000000",
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
# .pbip project file
|
||||
_write_json(target_path / f"{name}.pbip", {
|
||||
"version": "1.0",
|
||||
"artifacts": [
|
||||
{
|
||||
"report": {"path": f"{name}.Report"},
|
||||
}
|
||||
],
|
||||
})
|
||||
_write_json(
|
||||
target_path / f"{name}.pbip",
|
||||
{
|
||||
"version": "1.0",
|
||||
"artifacts": [
|
||||
{
|
||||
"report": {"path": f"{name}.Report"},
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "created",
|
||||
|
|
@ -219,8 +240,6 @@ def report_validate(definition_path: Path) -> dict[str, Any]:
|
|||
data = _read_json(report_json)
|
||||
if "themeCollection" not in data:
|
||||
errors.append("report.json missing required 'themeCollection'")
|
||||
if "layoutOptimization" not in data:
|
||||
errors.append("report.json missing required 'layoutOptimization'")
|
||||
except json.JSONDecodeError:
|
||||
pass # Already caught above
|
||||
|
||||
|
|
@ -236,9 +255,7 @@ def report_validate(definition_path: Path) -> dict[str, Any]:
|
|||
pdata = _read_json(page_json)
|
||||
for req in ("name", "displayName", "displayOption"):
|
||||
if req not in pdata:
|
||||
errors.append(
|
||||
f"Page '{page_dir.name}' missing required '{req}'"
|
||||
)
|
||||
errors.append(f"Page '{page_dir.name}' missing required '{req}'")
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
|
|
@ -281,21 +298,21 @@ def page_list(definition_path: Path) -> list[dict[str, Any]]:
|
|||
visuals_dir = page_dir / "visuals"
|
||||
if visuals_dir.is_dir():
|
||||
visual_count = sum(
|
||||
1
|
||||
for v in visuals_dir.iterdir()
|
||||
if v.is_dir() and (v / "visual.json").exists()
|
||||
1 for v in visuals_dir.iterdir() if v.is_dir() and (v / "visual.json").exists()
|
||||
)
|
||||
results.append({
|
||||
"name": data.get("name", page_dir.name),
|
||||
"display_name": data.get("displayName", ""),
|
||||
"ordinal": data.get("ordinal", 0),
|
||||
"width": data.get("width", 1280),
|
||||
"height": data.get("height", 720),
|
||||
"display_option": data.get("displayOption", "FitToPage"),
|
||||
"visual_count": visual_count,
|
||||
"is_hidden": data.get("visibility") == "HiddenInViewMode",
|
||||
"page_type": data.get("type", "Default"),
|
||||
})
|
||||
results.append(
|
||||
{
|
||||
"name": data.get("name", page_dir.name),
|
||||
"display_name": data.get("displayName", ""),
|
||||
"ordinal": data.get("ordinal", 0),
|
||||
"width": data.get("width", 1280),
|
||||
"height": data.get("height", 720),
|
||||
"display_option": data.get("displayOption", "FitToPage"),
|
||||
"visual_count": visual_count,
|
||||
"is_hidden": data.get("visibility") == "HiddenInViewMode",
|
||||
"page_type": data.get("type", "Default"),
|
||||
}
|
||||
)
|
||||
|
||||
# Sort by page order if available, then by ordinal
|
||||
if page_order:
|
||||
|
|
@ -327,14 +344,17 @@ def page_add(
|
|||
(page_dir / "visuals").mkdir()
|
||||
|
||||
# Write page.json (no ordinal - Desktop uses pages.json pageOrder instead)
|
||||
_write_json(page_dir / "page.json", {
|
||||
"$schema": SCHEMA_PAGE,
|
||||
"name": page_name,
|
||||
"displayName": display_name,
|
||||
"displayOption": display_option,
|
||||
"height": height,
|
||||
"width": width,
|
||||
})
|
||||
_write_json(
|
||||
page_dir / "page.json",
|
||||
{
|
||||
"$schema": SCHEMA_PAGE,
|
||||
"name": page_name,
|
||||
"displayName": display_name,
|
||||
"displayOption": display_option,
|
||||
"height": height,
|
||||
"width": width,
|
||||
},
|
||||
)
|
||||
|
||||
# Update pages.json
|
||||
_update_page_order(definition_path, page_name, action="add")
|
||||
|
|
@ -375,9 +395,7 @@ def page_get(definition_path: Path, page_name: str) -> dict[str, Any]:
|
|||
visuals_dir = page_dir / "visuals"
|
||||
if visuals_dir.is_dir():
|
||||
visual_count = sum(
|
||||
1
|
||||
for v in visuals_dir.iterdir()
|
||||
if v.is_dir() and (v / "visual.json").exists()
|
||||
1 for v in visuals_dir.iterdir() if v.is_dir() and (v / "visual.json").exists()
|
||||
)
|
||||
|
||||
return {
|
||||
|
|
@ -400,16 +418,21 @@ def page_set_background(
|
|||
definition_path: Path,
|
||||
page_name: str,
|
||||
color: str,
|
||||
transparency: int = 0,
|
||||
) -> dict[str, Any]:
|
||||
"""Set the background color of a page.
|
||||
|
||||
Updates the ``objects.background`` property in ``page.json``.
|
||||
The color must be a hex string, e.g. ``'#F8F9FA'``.
|
||||
|
||||
``transparency`` is 0 (fully opaque) to 100 (fully transparent). Desktop
|
||||
defaults missing transparency to 100 (invisible), so this function always
|
||||
writes it explicitly. Pass a value to override.
|
||||
"""
|
||||
if not re.fullmatch(r"#[0-9A-Fa-f]{3,8}", color):
|
||||
raise PbiCliError(
|
||||
f"Invalid color '{color}' -- expected hex format like '#F8F9FA'."
|
||||
)
|
||||
raise PbiCliError(f"Invalid color '{color}' -- expected hex format like '#F8F9FA'.")
|
||||
if not 0 <= transparency <= 100:
|
||||
raise PbiCliError(f"Invalid transparency '{transparency}' -- must be 0-100.")
|
||||
|
||||
page_dir = get_page_dir(definition_path, page_name)
|
||||
page_json_path = page_dir / "page.json"
|
||||
|
|
@ -419,20 +442,18 @@ def page_set_background(
|
|||
page_data = _read_json(page_json_path)
|
||||
background_entry = {
|
||||
"properties": {
|
||||
"color": {
|
||||
"solid": {
|
||||
"color": {
|
||||
"expr": {
|
||||
"Literal": {"Value": f"'{color}'"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"color": {"solid": {"color": {"expr": {"Literal": {"Value": f"'{color}'"}}}}},
|
||||
"transparency": {"expr": {"Literal": {"Value": f"{transparency}D"}}},
|
||||
}
|
||||
}
|
||||
objects = {**page_data.get("objects", {}), "background": [background_entry]}
|
||||
_write_json(page_json_path, {**page_data, "objects": objects})
|
||||
return {"status": "updated", "page": page_name, "background_color": color}
|
||||
return {
|
||||
"status": "updated",
|
||||
"page": page_name,
|
||||
"background_color": color,
|
||||
"transparency": transparency,
|
||||
}
|
||||
|
||||
|
||||
def page_set_visibility(
|
||||
|
|
@ -464,9 +485,7 @@ def page_set_visibility(
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def theme_set(
|
||||
definition_path: Path, theme_path: Path
|
||||
) -> dict[str, Any]:
|
||||
def theme_set(definition_path: Path, theme_path: Path) -> dict[str, Any]:
|
||||
"""Apply a custom theme JSON to the report."""
|
||||
if not theme_path.exists():
|
||||
raise PbiCliError(f"Theme file not found: {theme_path}")
|
||||
|
|
@ -489,9 +508,7 @@ def theme_set(
|
|||
resources_dir = report_folder / "StaticResources" / "RegisteredResources"
|
||||
resources_dir.mkdir(parents=True, exist_ok=True)
|
||||
theme_dest = resources_dir / theme_path.name
|
||||
theme_dest.write_text(
|
||||
theme_path.read_text(encoding="utf-8"), encoding="utf-8"
|
||||
)
|
||||
theme_dest.write_text(theme_path.read_text(encoding="utf-8"), encoding="utf-8")
|
||||
|
||||
# Update resource packages in report.json
|
||||
resource_packages = report_data.get("resourcePackages", [])
|
||||
|
|
@ -513,15 +530,19 @@ def theme_set(
|
|||
break
|
||||
|
||||
if not found:
|
||||
resource_packages.append({
|
||||
"name": "RegisteredResources",
|
||||
"type": "RegisteredResources",
|
||||
"items": [{
|
||||
"name": theme_path.name,
|
||||
"type": 202,
|
||||
"path": f"BaseThemes/{theme_path.name}",
|
||||
}],
|
||||
})
|
||||
resource_packages.append(
|
||||
{
|
||||
"name": "RegisteredResources",
|
||||
"type": "RegisteredResources",
|
||||
"items": [
|
||||
{
|
||||
"name": theme_path.name,
|
||||
"type": 202,
|
||||
"path": f"BaseThemes/{theme_path.name}",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
report_data["resourcePackages"] = resource_packages
|
||||
|
||||
_write_json(report_json_path, report_data)
|
||||
|
|
@ -670,8 +691,7 @@ def report_convert(
|
|||
|
||||
if report_folder is None:
|
||||
raise PbiCliError(
|
||||
f"No .Report folder found in '{source_path}'. "
|
||||
"Expected a folder ending in .Report."
|
||||
f"No .Report folder found in '{source_path}'. Expected a folder ending in .Report."
|
||||
)
|
||||
|
||||
name = report_folder.name.replace(".Report", "")
|
||||
|
|
@ -680,26 +700,22 @@ def report_convert(
|
|||
# Create .pbip file
|
||||
pbip_path = target / f"{name}.pbip"
|
||||
if pbip_path.exists() and not force:
|
||||
raise PbiCliError(
|
||||
f".pbip file already exists at '{pbip_path}'. Use --force to overwrite."
|
||||
)
|
||||
_write_json(pbip_path, {
|
||||
"version": "1.0",
|
||||
"artifacts": [
|
||||
{"report": {"path": f"{name}.Report"}},
|
||||
],
|
||||
})
|
||||
raise PbiCliError(f".pbip file already exists at '{pbip_path}'. Use --force to overwrite.")
|
||||
_write_json(
|
||||
pbip_path,
|
||||
{
|
||||
"version": "1.0",
|
||||
"artifacts": [
|
||||
{"report": {"path": f"{name}.Report"}},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
# Create .gitignore if not present
|
||||
gitignore = target / ".gitignore"
|
||||
gitignore_created = not gitignore.exists()
|
||||
if gitignore_created:
|
||||
gitignore_content = (
|
||||
"# Power BI local settings\n"
|
||||
".pbi/\n"
|
||||
"*.pbix\n"
|
||||
"*.bak\n"
|
||||
)
|
||||
gitignore_content = "# Power BI local settings\n.pbi/\n*.pbix\n*.bak\n"
|
||||
gitignore.write_text(gitignore_content, encoding="utf-8")
|
||||
|
||||
# Validate the definition.pbir exists
|
||||
|
|
@ -728,38 +744,40 @@ def _scaffold_blank_semantic_model(target_path: Path, name: str) -> None:
|
|||
|
||||
# model.tmdl (minimal valid TMDL)
|
||||
(defn_dir / "model.tmdl").write_text(
|
||||
"model Model\n"
|
||||
" culture: en-US\n"
|
||||
" defaultPowerBIDataSourceVersion: powerBI_V3\n",
|
||||
"model Model\n culture: en-US\n defaultPowerBIDataSourceVersion: powerBI_V3\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# .platform file (required by Desktop)
|
||||
_write_json(model_dir / ".platform", {
|
||||
"$schema": (
|
||||
"https://developer.microsoft.com/json-schemas/"
|
||||
"fabric/gitIntegration/platformProperties/2.0.0/schema.json"
|
||||
),
|
||||
"metadata": {
|
||||
"type": "SemanticModel",
|
||||
"displayName": name,
|
||||
_write_json(
|
||||
model_dir / ".platform",
|
||||
{
|
||||
"$schema": (
|
||||
"https://developer.microsoft.com/json-schemas/"
|
||||
"fabric/gitIntegration/platformProperties/2.0.0/schema.json"
|
||||
),
|
||||
"metadata": {
|
||||
"type": "SemanticModel",
|
||||
"displayName": name,
|
||||
},
|
||||
"config": {
|
||||
"version": "2.0",
|
||||
"logicalId": "00000000-0000-0000-0000-000000000000",
|
||||
},
|
||||
},
|
||||
"config": {
|
||||
"version": "2.0",
|
||||
"logicalId": "00000000-0000-0000-0000-000000000000",
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
# definition.pbism (matches Desktop format)
|
||||
_write_json(model_dir / "definition.pbism", {
|
||||
"version": "4.1",
|
||||
"settings": {},
|
||||
})
|
||||
_write_json(
|
||||
model_dir / "definition.pbism",
|
||||
{
|
||||
"version": "4.1",
|
||||
"settings": {},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _update_page_order(
|
||||
definition_path: Path, page_name: str, action: str
|
||||
) -> None:
|
||||
def _update_page_order(definition_path: Path, page_name: str, action: str) -> None:
|
||||
"""Update pages.json with page add/remove."""
|
||||
pages_meta_path = definition_path / "pages" / "pages.json"
|
||||
|
||||
|
|
|
|||
|
|
@ -128,9 +128,7 @@ def _list_tmdl_names(tables_dir: Path) -> set[str]:
|
|||
return {p.stem for p in tables_dir.glob("*.tmdl")}
|
||||
|
||||
|
||||
def _diff_table_entities(
|
||||
base_text: str, head_text: str
|
||||
) -> dict[str, list[str]]:
|
||||
def _diff_table_entities(base_text: str, head_text: str) -> dict[str, list[str]]:
|
||||
"""Compare entity blocks within two table TMDL files."""
|
||||
base_entities = _parse_table_entities(base_text)
|
||||
head_entities = _parse_table_entities(head_text)
|
||||
|
|
@ -226,7 +224,7 @@ def _extract_entity_name(keyword: str, declaration: str) -> str:
|
|||
# e.g. "measure 'Total Revenue' = ..." -> "Total Revenue"
|
||||
# e.g. "column ProductID" -> "ProductID"
|
||||
# e.g. "partition Sales = m" -> "Sales"
|
||||
rest = declaration[len(keyword):].strip()
|
||||
rest = declaration[len(keyword) :].strip()
|
||||
if rest.startswith("'"):
|
||||
end = rest.find("'", 1)
|
||||
return rest[1:end] if end > 0 else rest[1:]
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ from pbi_cli.core.pbir_path import get_visual_dir, get_visuals_dir
|
|||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
result: dict[str, Any] = json.loads(path.read_text(encoding="utf-8"))
|
||||
return result
|
||||
|
||||
|
||||
def _write_json(path: Path, data: dict[str, Any]) -> None:
|
||||
|
|
@ -87,16 +88,24 @@ VISUAL_DATA_ROLES: dict[str, list[str]] = {
|
|||
}
|
||||
|
||||
# Roles that should default to Measure references (not Column)
|
||||
MEASURE_ROLES: frozenset[str] = frozenset({
|
||||
"Y", "Values", "Fields", # "Fields" is used by cardNew only
|
||||
"Indicator", "Goal",
|
||||
# v3.1.0 additions
|
||||
"ColumnY", "LineY", "X", "Size",
|
||||
# v3.4.0 additions
|
||||
"Data",
|
||||
# v3.8.0 additions
|
||||
"MaxValue",
|
||||
})
|
||||
MEASURE_ROLES: frozenset[str] = frozenset(
|
||||
{
|
||||
"Y",
|
||||
"Values",
|
||||
"Fields", # "Fields" is used by cardNew only
|
||||
"Indicator",
|
||||
"Goal",
|
||||
# v3.1.0 additions
|
||||
"ColumnY",
|
||||
"LineY",
|
||||
"X",
|
||||
"Size",
|
||||
# v3.4.0 additions
|
||||
"Data",
|
||||
# v3.8.0 additions
|
||||
"MaxValue",
|
||||
}
|
||||
)
|
||||
|
||||
# User-friendly role aliases to PBIR role names
|
||||
ROLE_ALIASES: dict[str, dict[str, str]] = {
|
||||
|
|
@ -127,7 +136,11 @@ ROLE_ALIASES: dict[str, dict[str, str]] = {
|
|||
"ribbonChart": {"category": "Category", "value": "Y", "legend": "Legend"},
|
||||
"waterfallChart": {"category": "Category", "value": "Y", "breakdown": "Breakdown"},
|
||||
"scatterChart": {
|
||||
"x": "X", "y": "Y", "detail": "Details", "size": "Size", "legend": "Legend",
|
||||
"x": "X",
|
||||
"y": "Y",
|
||||
"detail": "Details",
|
||||
"size": "Size",
|
||||
"legend": "Legend",
|
||||
"value": "Y",
|
||||
},
|
||||
"funnelChart": {"category": "Category", "value": "Y"},
|
||||
|
|
@ -192,16 +205,16 @@ def _build_visual_json(
|
|||
) -> dict[str, Any]:
|
||||
"""Fill placeholders in a template string and return parsed JSON."""
|
||||
filled = (
|
||||
template_str
|
||||
.replace("__VISUAL_NAME__", name)
|
||||
.replace("__X__", str(x))
|
||||
.replace("__Y__", str(y))
|
||||
.replace("__WIDTH__", str(width))
|
||||
.replace("__HEIGHT__", str(height))
|
||||
template_str.replace("__VISUAL_NAME__", name)
|
||||
.replace("__X__", str(int(x)))
|
||||
.replace("__Y__", str(int(y)))
|
||||
.replace("__WIDTH__", str(int(width)))
|
||||
.replace("__HEIGHT__", str(int(height)))
|
||||
.replace("__Z__", str(z))
|
||||
.replace("__TAB_ORDER__", str(tab_order))
|
||||
)
|
||||
return json.loads(filled)
|
||||
result: dict[str, Any] = json.loads(filled)
|
||||
return result
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -255,9 +268,7 @@ DEFAULT_SIZES: dict[str, tuple[float, float]] = {
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def visual_list(
|
||||
definition_path: Path, page_name: str
|
||||
) -> list[dict[str, Any]]:
|
||||
def visual_list(definition_path: Path, page_name: str) -> list[dict[str, Any]]:
|
||||
"""List all visuals on a page."""
|
||||
visuals_dir = definition_path / "pages" / page_name / "visuals"
|
||||
if not visuals_dir.is_dir():
|
||||
|
|
@ -274,33 +285,35 @@ def visual_list(
|
|||
|
||||
# Group container: has "visualGroup" key instead of "visual"
|
||||
if "visualGroup" in data and "visual" not in data:
|
||||
results.append({
|
||||
"name": data.get("name", vdir.name),
|
||||
"visual_type": "group",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
})
|
||||
results.append(
|
||||
{
|
||||
"name": data.get("name", vdir.name),
|
||||
"visual_type": "group",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
}
|
||||
)
|
||||
continue
|
||||
|
||||
pos = data.get("position", {})
|
||||
visual_config = data.get("visual", {})
|
||||
results.append({
|
||||
"name": data.get("name", vdir.name),
|
||||
"visual_type": visual_config.get("visualType", "unknown"),
|
||||
"x": pos.get("x", 0),
|
||||
"y": pos.get("y", 0),
|
||||
"width": pos.get("width", 0),
|
||||
"height": pos.get("height", 0),
|
||||
})
|
||||
results.append(
|
||||
{
|
||||
"name": data.get("name", vdir.name),
|
||||
"visual_type": visual_config.get("visualType", "unknown"),
|
||||
"x": pos.get("x", 0),
|
||||
"y": pos.get("y", 0),
|
||||
"width": pos.get("width", 0),
|
||||
"height": pos.get("height", 0),
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def visual_get(
|
||||
definition_path: Path, page_name: str, visual_name: str
|
||||
) -> dict[str, Any]:
|
||||
def visual_get(definition_path: Path, page_name: str, visual_name: str) -> dict[str, Any]:
|
||||
"""Get detailed information about a visual."""
|
||||
visual_dir = get_visual_dir(definition_path, page_name, visual_name)
|
||||
vfile = visual_dir / "visual.json"
|
||||
|
|
@ -320,11 +333,13 @@ def visual_get(
|
|||
for proj in projections:
|
||||
field = proj.get("field", {})
|
||||
query_ref = proj.get("queryRef", "")
|
||||
bindings.append({
|
||||
"role": role,
|
||||
"query_ref": query_ref,
|
||||
"field": _summarize_field(field),
|
||||
})
|
||||
bindings.append(
|
||||
{
|
||||
"role": role,
|
||||
"query_ref": query_ref,
|
||||
"field": _summarize_field(field),
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"name": data.get("name", visual_name),
|
||||
|
|
@ -465,16 +480,12 @@ def visual_set_container(
|
|||
visual_dir = get_visual_dir(definition_path, page_name, visual_name)
|
||||
visual_json_path = visual_dir / "visual.json"
|
||||
if not visual_json_path.exists():
|
||||
raise PbiCliError(
|
||||
f"Visual '{visual_name}' not found on page '{page_name}'."
|
||||
)
|
||||
raise PbiCliError(f"Visual '{visual_name}' not found on page '{page_name}'.")
|
||||
|
||||
data = _read_json(visual_json_path)
|
||||
visual = data.get("visual")
|
||||
if visual is None:
|
||||
raise PbiCliError(
|
||||
f"Visual '{visual_name}' has invalid JSON -- missing 'visual' key."
|
||||
)
|
||||
raise PbiCliError(f"Visual '{visual_name}' has invalid JSON -- missing 'visual' key.")
|
||||
|
||||
if border_show is None and background_show is None and title is None:
|
||||
return {
|
||||
|
|
@ -489,26 +500,17 @@ def visual_set_container(
|
|||
vco: dict[str, Any] = dict(visual.get("visualContainerObjects", {}))
|
||||
|
||||
def _bool_entry(value: bool) -> list[dict[str, Any]]:
|
||||
return [{
|
||||
"properties": {
|
||||
"show": {
|
||||
"expr": {"Literal": {"Value": str(value).lower()}}
|
||||
}
|
||||
}
|
||||
}]
|
||||
return [{"properties": {"show": {"expr": {"Literal": {"Value": str(value).lower()}}}}}]
|
||||
|
||||
if border_show is not None:
|
||||
vco = {**vco, "border": _bool_entry(border_show)}
|
||||
if background_show is not None:
|
||||
vco = {**vco, "background": _bool_entry(background_show)}
|
||||
if title is not None:
|
||||
vco = {**vco, "title": [{
|
||||
"properties": {
|
||||
"text": {
|
||||
"expr": {"Literal": {"Value": f"'{title}'"}}
|
||||
}
|
||||
}
|
||||
}]}
|
||||
vco = {
|
||||
**vco,
|
||||
"title": [{"properties": {"text": {"expr": {"Literal": {"Value": f"'{title}'"}}}}}],
|
||||
}
|
||||
|
||||
updated_visual = {**visual, "visualContainerObjects": vco}
|
||||
_write_json(visual_json_path, {**data, "visual": updated_visual})
|
||||
|
|
@ -523,9 +525,7 @@ def visual_set_container(
|
|||
}
|
||||
|
||||
|
||||
def visual_delete(
|
||||
definition_path: Path, page_name: str, visual_name: str
|
||||
) -> dict[str, Any]:
|
||||
def visual_delete(definition_path: Path, page_name: str, visual_name: str) -> dict[str, Any]:
|
||||
"""Delete a visual from a page."""
|
||||
visual_dir = get_visual_dir(definition_path, page_name, visual_name)
|
||||
|
||||
|
|
@ -566,11 +566,6 @@ def visual_bind(
|
|||
query = visual_config.setdefault("query", {})
|
||||
query_state = query.setdefault("queryState", {})
|
||||
|
||||
# Collect existing Commands From/Select to merge (fix: don't overwrite)
|
||||
from_entities: dict[str, dict[str, Any]] = {}
|
||||
select_items: list[dict[str, Any]] = []
|
||||
_collect_existing_commands(query, from_entities, select_items)
|
||||
|
||||
role_map = ROLE_ALIASES.get(visual_type, {})
|
||||
applied: list[dict[str, str]] = []
|
||||
|
||||
|
|
@ -588,14 +583,6 @@ def visual_bind(
|
|||
# Determine measure vs column: explicit flag, or role-based heuristic
|
||||
is_measure = force_measure or pbir_role in MEASURE_ROLES
|
||||
|
||||
# Track source alias for Commands block (use full name to avoid collisions)
|
||||
source_alias = table.replace(" ", "_").lower() if table else "t"
|
||||
from_entities[source_alias] = {
|
||||
"Name": source_alias,
|
||||
"Entity": table,
|
||||
"Type": 0,
|
||||
}
|
||||
|
||||
# Build queryState projection (uses Entity directly, matching Desktop)
|
||||
query_ref = f"{table}.{column}"
|
||||
if is_measure:
|
||||
|
|
@ -613,53 +600,25 @@ def visual_bind(
|
|||
}
|
||||
}
|
||||
|
||||
projection = {
|
||||
projection: dict[str, Any] = {
|
||||
"field": field_expr,
|
||||
"queryRef": query_ref,
|
||||
"nativeQueryRef": column,
|
||||
}
|
||||
if not is_measure:
|
||||
projection["active"] = True
|
||||
|
||||
# Add to query state
|
||||
role_state = query_state.setdefault(pbir_role, {"projections": []})
|
||||
role_state["projections"].append(projection)
|
||||
|
||||
# Build Commands select item (uses Source alias)
|
||||
if is_measure:
|
||||
cmd_field_expr: dict[str, Any] = {
|
||||
"Measure": {
|
||||
"Expression": {"SourceRef": {"Source": source_alias}},
|
||||
"Property": column,
|
||||
}
|
||||
applied.append(
|
||||
{
|
||||
"role": pbir_role,
|
||||
"field": field_ref,
|
||||
"query_ref": query_ref,
|
||||
}
|
||||
else:
|
||||
cmd_field_expr = {
|
||||
"Column": {
|
||||
"Expression": {"SourceRef": {"Source": source_alias}},
|
||||
"Property": column,
|
||||
}
|
||||
}
|
||||
select_items.append({
|
||||
**cmd_field_expr,
|
||||
"Name": query_ref,
|
||||
})
|
||||
|
||||
applied.append({
|
||||
"role": pbir_role,
|
||||
"field": field_ref,
|
||||
"query_ref": query_ref,
|
||||
})
|
||||
|
||||
# Set the semantic query Commands block (merges with existing)
|
||||
if from_entities and select_items:
|
||||
query["Commands"] = [{
|
||||
"SemanticQueryDataShapeCommand": {
|
||||
"Query": {
|
||||
"Version": 2,
|
||||
"From": list(from_entities.values()),
|
||||
"Select": select_items,
|
||||
}
|
||||
}
|
||||
}]
|
||||
)
|
||||
|
||||
data["visual"] = visual_config
|
||||
_write_json(vfile, data)
|
||||
|
|
@ -690,9 +649,7 @@ def _parse_field_ref(ref: str) -> tuple[str, str]:
|
|||
column = match.group(2).strip()
|
||||
return table, column
|
||||
|
||||
raise PbiCliError(
|
||||
f"Invalid field reference '{ref}'. Expected 'Table[Column]' format."
|
||||
)
|
||||
raise PbiCliError(f"Invalid field reference '{ref}'. Expected 'Table[Column]' format.")
|
||||
|
||||
|
||||
def _summarize_field(field: dict[str, Any]) -> str:
|
||||
|
|
@ -710,21 +667,6 @@ def _summarize_field(field: dict[str, Any]) -> str:
|
|||
return str(field)
|
||||
|
||||
|
||||
def _collect_existing_commands(
|
||||
query: dict[str, Any],
|
||||
from_entities: dict[str, dict[str, Any]],
|
||||
select_items: list[dict[str, Any]],
|
||||
) -> None:
|
||||
"""Extract existing From entities and Select items from Commands block."""
|
||||
for cmd in query.get("Commands", []):
|
||||
sq = cmd.get("SemanticQueryDataShapeCommand", {}).get("Query", {})
|
||||
for entity in sq.get("From", []):
|
||||
name = entity.get("Name", "")
|
||||
if name:
|
||||
from_entities[name] = entity
|
||||
select_items.extend(sq.get("Select", []))
|
||||
|
||||
|
||||
def _next_y_position(definition_path: Path, page_name: str) -> float:
|
||||
"""Calculate the next y position to avoid overlap with existing visuals."""
|
||||
visuals_dir = definition_path / "pages" / page_name / "visuals"
|
||||
|
|
@ -878,12 +820,14 @@ def visual_calc_list(
|
|||
for proj in state.get("projections", []):
|
||||
nvc = proj.get("field", {}).get("NativeVisualCalculation")
|
||||
if nvc is not None:
|
||||
results.append({
|
||||
"name": nvc.get("Name", ""),
|
||||
"expression": nvc.get("Expression", ""),
|
||||
"role": role,
|
||||
"query_ref": proj.get("queryRef", "select"),
|
||||
})
|
||||
results.append(
|
||||
{
|
||||
"name": nvc.get("Name", ""),
|
||||
"expression": nvc.get("Expression", ""),
|
||||
"role": role,
|
||||
"query_ref": proj.get("queryRef", "select"),
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
|
@ -905,15 +849,14 @@ def visual_calc_delete(
|
|||
raise PbiCliError(f"Visual '{visual_name}' not found on page '{page_name}'.")
|
||||
|
||||
data = _read_json(vfile)
|
||||
query_state = (
|
||||
data.get("visual", {}).get("query", {}).get("queryState", {})
|
||||
)
|
||||
query_state = data.get("visual", {}).get("query", {}).get("queryState", {})
|
||||
|
||||
found = False
|
||||
for role, state in query_state.items():
|
||||
projections: list[dict[str, Any]] = state.get("projections", [])
|
||||
new_projections = [
|
||||
proj for proj in projections
|
||||
proj
|
||||
for proj in projections
|
||||
if proj.get("field", {}).get("NativeVisualCalculation", {}).get("Name") != calc_name
|
||||
]
|
||||
if len(new_projections) < len(projections):
|
||||
|
|
@ -921,9 +864,7 @@ def visual_calc_delete(
|
|||
found = True
|
||||
|
||||
if not found:
|
||||
raise PbiCliError(
|
||||
f"Visual calculation '{calc_name}' not found in visual '{visual_name}'."
|
||||
)
|
||||
raise PbiCliError(f"Visual calculation '{calc_name}' not found in visual '{visual_name}'.")
|
||||
|
||||
_write_json(vfile, data)
|
||||
return {"status": "deleted", "visual": visual_name, "name": calc_name}
|
||||
|
|
|
|||
25
src/pbi_cli/dlls/README.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Bundled Microsoft Analysis Services Client Libraries
|
||||
|
||||
The `.dll` files in this directory are **not** covered by the MIT License
|
||||
that applies to the rest of pbi-cli. They are unmodified Microsoft
|
||||
Corporation binaries redistributed under the Microsoft Software License
|
||||
Terms for Microsoft Analysis Management Objects (AMO) and Microsoft
|
||||
Analysis Services - ADOMD.NET.
|
||||
|
||||
See the top-level [`THIRD_PARTY_LICENSES.md`](../../../THIRD_PARTY_LICENSES.md)
|
||||
and [`NOTICE`](../../../NOTICE) files for the full license text and
|
||||
attribution details.
|
||||
|
||||
Bundled files:
|
||||
|
||||
- `Microsoft.AnalysisServices.dll`
|
||||
- `Microsoft.AnalysisServices.Core.dll`
|
||||
- `Microsoft.AnalysisServices.Tabular.dll`
|
||||
- `Microsoft.AnalysisServices.Tabular.Json.dll`
|
||||
- `Microsoft.AnalysisServices.AdomdClient.dll`
|
||||
|
||||
Sourced from the official Microsoft NuGet packages
|
||||
`Microsoft.AnalysisServices.NetCore.retail.amd64` and
|
||||
`Microsoft.AnalysisServices.AdomdClient.retail.amd64`.
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
|
@ -24,7 +24,7 @@ class PbiContext:
|
|||
pass_context = click.make_pass_decorator(PbiContext, ensure=True)
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.group(invoke_without_command=True)
|
||||
@click.option(
|
||||
"--json",
|
||||
"json_output",
|
||||
|
|
@ -48,6 +48,12 @@ def cli(ctx: click.Context, json_output: bool, connection: str | None) -> None:
|
|||
ctx.ensure_object(PbiContext)
|
||||
ctx.obj = PbiContext(json_output=json_output, connection=connection)
|
||||
|
||||
if ctx.invoked_subcommand is None and not json_output:
|
||||
from pbi_cli.core.banner import print_banner
|
||||
|
||||
print_banner(__version__)
|
||||
click.echo(ctx.get_help())
|
||||
|
||||
|
||||
def _register_commands() -> None:
|
||||
"""Lazily import and register all command groups."""
|
||||
|
|
@ -72,7 +78,6 @@ def _register_commands() -> None:
|
|||
from pbi_cli.commands.report import report
|
||||
from pbi_cli.commands.security import security_role
|
||||
from pbi_cli.commands.setup_cmd import setup
|
||||
from pbi_cli.commands.skills_cmd import skills
|
||||
from pbi_cli.commands.table import table
|
||||
from pbi_cli.commands.trace import trace
|
||||
from pbi_cli.commands.transaction import transaction
|
||||
|
|
@ -100,7 +105,6 @@ def _register_commands() -> None:
|
|||
cli.add_command(transaction)
|
||||
cli.add_command(advanced)
|
||||
cli.add_command(repl)
|
||||
cli.add_command(skills)
|
||||
cli.add_command(report)
|
||||
cli.add_command(visual)
|
||||
cli.add_command(filters)
|
||||
|
|
|
|||
32
src/pbi_cli/main_pbi_cli.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
"""Entry point for the pbi-cli management command."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import click
|
||||
|
||||
from pbi_cli import __version__
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.version_option(version=__version__, prog_name="pbi-cli")
|
||||
def cli() -> None:
|
||||
"""pbi-cli: CLI management tool for pbi-cli-tool.
|
||||
|
||||
Use this command to set up Claude Code integration before using 'pbi'.
|
||||
|
||||
Typical first-time setup:
|
||||
|
||||
\b
|
||||
pipx install pbi-cli-tool
|
||||
pbi-cli skills install
|
||||
pbi connect
|
||||
"""
|
||||
|
||||
|
||||
def _register_commands() -> None:
|
||||
from pbi_cli.commands.skills_cmd import skills
|
||||
|
||||
cli.add_command(skills)
|
||||
|
||||
|
||||
_register_commands()
|
||||
|
|
@ -29,12 +29,12 @@ def render_report(definition_path: Path) -> str:
|
|||
for page_dir in page_dirs:
|
||||
pages_html.append(_render_page(page_dir))
|
||||
|
||||
pages_content = "\n".join(pages_html) if pages_html else "<p class='empty'>No pages in report</p>"
|
||||
|
||||
return _HTML_TEMPLATE.replace("{{THEME}}", escape(theme)).replace(
|
||||
"{{PAGES}}", pages_content
|
||||
pages_content = (
|
||||
"\n".join(pages_html) if pages_html else "<p class='empty'>No pages in report</p>"
|
||||
)
|
||||
|
||||
return _HTML_TEMPLATE.replace("{{THEME}}", escape(theme)).replace("{{PAGES}}", pages_content)
|
||||
|
||||
|
||||
def render_page(definition_path: Path, page_name: str) -> str:
|
||||
"""Render a single page as HTML."""
|
||||
|
|
@ -218,7 +218,7 @@ def _render_visual_content(vtype: str, w: float, h: float, bindings: list[str])
|
|||
for i in range(num_bars):
|
||||
bar_h = body_h * (0.3 + 0.5 * ((i * 37 + 13) % 7) / 7)
|
||||
bars += (
|
||||
f'<rect x="{i * bar_w * 2 + bar_w/2}" y="{body_h - bar_h}" '
|
||||
f'<rect x="{i * bar_w * 2 + bar_w / 2}" y="{body_h - bar_h}" '
|
||||
f'width="{bar_w}" height="{bar_h}" fill="#4472C4" opacity="0.7"/>'
|
||||
)
|
||||
return f'<svg class="chart-svg" viewBox="0 0 {w} {body_h}">{bars}</svg>'
|
||||
|
|
@ -230,7 +230,9 @@ def _render_visual_content(vtype: str, w: float, h: float, bindings: list[str])
|
|||
px = (w / (num_points - 1)) * i
|
||||
py = body_h * (0.2 + 0.6 * ((i * 47 + 23) % 11) / 11)
|
||||
points.append(f"{px},{py}")
|
||||
polyline = f'<polyline points="{" ".join(points)}" fill="none" stroke="#ED7D31" stroke-width="3"/>'
|
||||
polyline = (
|
||||
f'<polyline points="{" ".join(points)}" fill="none" stroke="#ED7D31" stroke-width="3"/>'
|
||||
)
|
||||
return f'<svg class="chart-svg" viewBox="0 0 {w} {body_h}">{polyline}</svg>'
|
||||
|
||||
if vtype == "card":
|
||||
|
|
@ -286,13 +288,15 @@ def _get_page_order(definition_path: Path) -> list[str]:
|
|||
return []
|
||||
try:
|
||||
data = json.loads(pages_json.read_text(encoding="utf-8"))
|
||||
return data.get("pageOrder", [])
|
||||
order: list[str] = data.get("pageOrder", [])
|
||||
return order
|
||||
except (json.JSONDecodeError, KeyError):
|
||||
return []
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
result: dict[str, Any] = json.loads(path.read_text(encoding="utf-8"))
|
||||
return result
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ def start_preview_server(
|
|||
"""
|
||||
# Check for websockets dependency
|
||||
try:
|
||||
import websockets # type: ignore[import-untyped]
|
||||
import websockets
|
||||
except ImportError:
|
||||
return {
|
||||
"status": "error",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ Execute and validate DAX queries against connected Power BI models.
|
|||
|
||||
```bash
|
||||
pipx install pbi-cli-tool
|
||||
pbi connect # Auto-detects Power BI Desktop and installs skills
|
||||
pbi-cli skills install
|
||||
pbi connect
|
||||
```
|
||||
|
||||
## Executing Queries
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ Manage model lifecycle with TMDL export/import, transactions, and version contro
|
|||
|
||||
```bash
|
||||
pipx install pbi-cli-tool
|
||||
pbi connect # Auto-detects Power BI Desktop and installs skills
|
||||
pbi-cli skills install
|
||||
pbi connect
|
||||
```
|
||||
|
||||
## Connecting to Targets
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ Troubleshoot performance, trace queries, and verify the pbi-cli environment.
|
|||
|
||||
```bash
|
||||
pipx install pbi-cli-tool
|
||||
pbi connect # Auto-detects Power BI Desktop and installs skills
|
||||
pbi-cli skills install
|
||||
pbi connect
|
||||
```
|
||||
|
||||
## Environment Check
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ Generate comprehensive documentation for Power BI semantic models.
|
|||
|
||||
```bash
|
||||
pipx install pbi-cli-tool
|
||||
pbi connect # Auto-detects Power BI Desktop and installs skills
|
||||
pbi-cli skills install
|
||||
pbi connect
|
||||
```
|
||||
|
||||
## Quick Model Overview
|
||||
|
|
|
|||
|
|
@ -130,6 +130,24 @@ pbi filters add-topn --page overview \
|
|||
pbi filters list --page overview
|
||||
```
|
||||
|
||||
## Suppressing Auto-Sync (--no-sync)
|
||||
|
||||
By default, every write command automatically syncs Power BI Desktop. When
|
||||
applying filters to multiple pages or visuals in sequence, Desktop reloads
|
||||
after each command.
|
||||
|
||||
Use `--no-sync` on the `filters` command group to batch all filter changes,
|
||||
then call `pbi report reload` once at the end:
|
||||
|
||||
```bash
|
||||
# Suppress sync while applying filters
|
||||
pbi filters --no-sync add-categorical --page overview --table "Calendar Lookup" --column "Year" --values "2024"
|
||||
pbi filters --no-sync add-categorical --page details --table "Product Lookup" --column "Category" --values "Bikes"
|
||||
|
||||
# Single reload when all filters are done
|
||||
pbi report reload
|
||||
```
|
||||
|
||||
## JSON Output
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -6,13 +6,14 @@ tools: pbi-cli
|
|||
|
||||
# Power BI Modeling Skill
|
||||
|
||||
Use pbi-cli to manage semantic model structure. Requires `pipx install pbi-cli-tool` and `pbi connect`.
|
||||
Use pbi-cli to manage semantic model structure. Requires `pipx install pbi-cli-tool`, `pbi-cli skills install`, and `pbi connect`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
pipx install pbi-cli-tool
|
||||
pbi connect # Auto-detects Power BI Desktop and installs skills
|
||||
pbi-cli skills install
|
||||
pbi connect
|
||||
```
|
||||
|
||||
## Tables
|
||||
|
|
|
|||
|
|
@ -142,6 +142,25 @@ Page commands inherit the report path from the parent `pbi report` group:
|
|||
2. Auto-detect: walks up from CWD looking for `*.Report/definition/`
|
||||
3. From `.pbip`: finds sibling `.Report` folder from `.pbip` file
|
||||
|
||||
## Suppressing Auto-Sync (--no-sync)
|
||||
|
||||
By default, every write command automatically syncs Power BI Desktop. When
|
||||
setting up multiple pages in sequence, Desktop reloads after each one.
|
||||
|
||||
Use `--no-sync` on the `report` command group to batch all page changes, then
|
||||
call `pbi report reload` once at the end:
|
||||
|
||||
```bash
|
||||
# Suppress sync while setting up pages
|
||||
pbi report --no-sync add-page --display-name "Overview" --name overview
|
||||
pbi report --no-sync add-page --display-name "Details" --name details
|
||||
pbi report --no-sync set-background overview --color "#F2F2F2"
|
||||
pbi report --no-sync set-background details --color "#F2F2F2"
|
||||
|
||||
# Single reload when all page setup is done
|
||||
pbi report reload
|
||||
```
|
||||
|
||||
## JSON Output
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ Manage table partitions, named expressions (M queries), and calendar tables.
|
|||
|
||||
```bash
|
||||
pipx install pbi-cli-tool
|
||||
pbi connect # Auto-detects Power BI Desktop and installs skills
|
||||
pbi-cli skills install
|
||||
pbi connect
|
||||
```
|
||||
|
||||
## Partitions
|
||||
|
|
|
|||
|
|
@ -94,6 +94,30 @@ Power BI Desktop's Developer Mode auto-detects TMDL changes but not PBIR
|
|||
changes. This command sends a keyboard shortcut to the Desktop window to
|
||||
trigger a reload. Requires the `reload` optional dependency: `pip install pbi-cli-tool[reload]`
|
||||
|
||||
## Suppressing Auto-Sync (--no-sync)
|
||||
|
||||
By default, every write command (`add-page`, `delete-page`, `set-background`,
|
||||
`set-theme`, etc.) automatically syncs Power BI Desktop after each operation.
|
||||
When building a report in multiple steps, this causes Desktop to reload after
|
||||
every single command.
|
||||
|
||||
Use `--no-sync` on the `report` command group to suppress per-command syncs,
|
||||
then call `pbi report reload` once at the end:
|
||||
|
||||
```bash
|
||||
# BAD: Desktop reloads after every command
|
||||
pbi report add-page --display-name "Overview" --name overview
|
||||
pbi report set-background overview --color "#F2F2F2"
|
||||
|
||||
# GOOD: suppress sync during build, reload once at the end
|
||||
pbi report --no-sync add-page --display-name "Overview" --name overview
|
||||
pbi report --no-sync set-background overview --color "#F2F2F2"
|
||||
pbi report reload
|
||||
```
|
||||
|
||||
`--no-sync` is available on: `report`, `visual`, `filters`, and `bookmarks`
|
||||
command groups.
|
||||
|
||||
## Convert
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ Manage row-level security (RLS) and perspectives for Power BI models.
|
|||
|
||||
```bash
|
||||
pipx install pbi-cli-tool
|
||||
pbi connect # Auto-detects Power BI Desktop and installs skills
|
||||
pbi-cli skills install
|
||||
pbi connect
|
||||
```
|
||||
|
||||
## Security Roles (RLS)
|
||||
|
|
|
|||
|
|
@ -212,6 +212,25 @@ accidental mass operations.
|
|||
| textbox | textbox | (no data binding) |
|
||||
| page_navigator | pageNavigator | (no data binding) |
|
||||
|
||||
## Suppressing Auto-Sync (--no-sync)
|
||||
|
||||
By default, every write command automatically syncs Power BI Desktop. When
|
||||
adding or binding many visuals in sequence, Desktop reloads after each one.
|
||||
|
||||
Use `--no-sync` on the `visual` command group to batch all changes, then call
|
||||
`pbi report reload` once at the end:
|
||||
|
||||
```bash
|
||||
# Suppress sync while building visuals
|
||||
pbi visual --no-sync add --page overview --type card --name rev_card
|
||||
pbi visual --no-sync bind rev_card --page overview --field "Sales[Total Revenue]"
|
||||
pbi visual --no-sync add --page overview --type bar --name sales_bar
|
||||
pbi visual --no-sync bind sales_bar --page overview --category "Product[Category]" --value "Sales[Revenue]"
|
||||
|
||||
# Single reload when all visuals are done
|
||||
pbi report reload
|
||||
```
|
||||
|
||||
## JSON Output
|
||||
|
||||
All commands support `--json` for agent consumption:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
},
|
||||
"visual": {
|
||||
"visualType": "actionButton",
|
||||
"objects": {},
|
||||
"visualContainerObjects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
},
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@
|
|||
"isDefaultSort": true
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"visualContainerObjects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
"Legend": {"projections": []}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
"Legend": {"projections": []}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
},
|
||||
"visual": {
|
||||
"visualType": "image",
|
||||
"objects": {},
|
||||
"visualContainerObjects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
},
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
"Values": {"projections": [], "active": true}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
},
|
||||
"visual": {
|
||||
"visualType": "pageNavigator",
|
||||
"objects": {},
|
||||
"visualContainerObjects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
},
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
},
|
||||
"visual": {
|
||||
"visualType": "shape",
|
||||
"objects": {},
|
||||
"visualContainerObjects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
},
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"objects": {},
|
||||
"drillFilterOtherVisuals": true
|
||||
}
|
||||
}
|
||||
|
|
|
|||