mirror of
https://github.com/MinaSaad1/pbi-cli
synced 2026-04-21 13:37:19 +00:00
fix: add --path option to bookmarks and format command groups
Both groups were missing the --path/-p option that report, visual, and filters already had, making them only work when CWD was inside the .Report folder. Now all report-layer command groups consistently support explicit path override. Found during E2E testing with Power BI Desktop.
This commit is contained in:
parent
f0504bcf51
commit
e757596945
2 changed files with 383 additions and 0 deletions
132
src/pbi_cli/commands/bookmarks.py
Normal file
132
src/pbi_cli/commands/bookmarks.py
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
"""PBIR bookmark management commands."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import click
|
||||
|
||||
from pbi_cli.commands._helpers import run_command
|
||||
from pbi_cli.main import PbiContext, pass_context
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.option(
|
||||
"--path",
|
||||
"-p",
|
||||
default=None,
|
||||
help="Path to .Report folder (auto-detected from CWD if omitted).",
|
||||
)
|
||||
@click.pass_context
|
||||
def bookmarks(ctx: click.Context, path: str | None) -> None:
|
||||
"""Manage report bookmarks."""
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj["report_path"] = path
|
||||
|
||||
|
||||
@bookmarks.command(name="list")
|
||||
@click.pass_context
|
||||
@pass_context
|
||||
def list_bookmarks(ctx: PbiContext, click_ctx: click.Context) -> None:
|
||||
"""List all bookmarks in the report."""
|
||||
from pbi_cli.core.bookmark_backend import bookmark_list
|
||||
from pbi_cli.core.pbir_path import resolve_report_path
|
||||
|
||||
report_path = click_ctx.parent.obj.get("report_path") if click_ctx.parent else None
|
||||
definition_path = resolve_report_path(report_path)
|
||||
run_command(ctx, bookmark_list, definition_path=definition_path)
|
||||
|
||||
|
||||
@bookmarks.command(name="get")
|
||||
@click.argument("name")
|
||||
@click.pass_context
|
||||
@pass_context
|
||||
def get_bookmark(ctx: PbiContext, click_ctx: click.Context, name: str) -> None:
|
||||
"""Get full details for a bookmark by NAME."""
|
||||
from pbi_cli.core.bookmark_backend import bookmark_get
|
||||
from pbi_cli.core.pbir_path import resolve_report_path
|
||||
|
||||
report_path = click_ctx.parent.obj.get("report_path") if click_ctx.parent else None
|
||||
definition_path = resolve_report_path(report_path)
|
||||
run_command(ctx, bookmark_get, definition_path=definition_path, name=name)
|
||||
|
||||
|
||||
@bookmarks.command(name="add")
|
||||
@click.option("--display-name", "-d", required=True, help="Human-readable bookmark name.")
|
||||
@click.option("--page", "-g", required=True, help="Target page name (active section).")
|
||||
@click.option("--name", "-n", default=None, help="Bookmark ID (auto-generated if omitted).")
|
||||
@click.pass_context
|
||||
@pass_context
|
||||
def add_bookmark(
|
||||
ctx: PbiContext,
|
||||
click_ctx: click.Context,
|
||||
display_name: str,
|
||||
page: str,
|
||||
name: str | None,
|
||||
) -> None:
|
||||
"""Add a new bookmark pointing to a page."""
|
||||
from pbi_cli.core.bookmark_backend import bookmark_add
|
||||
from pbi_cli.core.pbir_path import resolve_report_path
|
||||
|
||||
report_path = click_ctx.parent.obj.get("report_path") if click_ctx.parent else None
|
||||
definition_path = resolve_report_path(report_path)
|
||||
run_command(
|
||||
ctx,
|
||||
bookmark_add,
|
||||
definition_path=definition_path,
|
||||
display_name=display_name,
|
||||
target_page=page,
|
||||
name=name,
|
||||
)
|
||||
|
||||
|
||||
@bookmarks.command(name="delete")
|
||||
@click.argument("name")
|
||||
@click.pass_context
|
||||
@pass_context
|
||||
def delete_bookmark(ctx: PbiContext, click_ctx: click.Context, name: str) -> None:
|
||||
"""Delete a bookmark by NAME."""
|
||||
from pbi_cli.core.bookmark_backend import bookmark_delete
|
||||
from pbi_cli.core.pbir_path import resolve_report_path
|
||||
|
||||
report_path = click_ctx.parent.obj.get("report_path") if click_ctx.parent else None
|
||||
definition_path = resolve_report_path(report_path)
|
||||
run_command(ctx, bookmark_delete, definition_path=definition_path, name=name)
|
||||
|
||||
|
||||
@bookmarks.command(name="set-visibility")
|
||||
@click.argument("name")
|
||||
@click.option("--page", "-g", required=True, help="Page name (folder name).")
|
||||
@click.option("--visual", "-v", required=True, help="Visual name (folder name).")
|
||||
@click.option(
|
||||
"--hidden/--visible",
|
||||
default=True,
|
||||
help="Set the visual as hidden (default) or visible in the bookmark.",
|
||||
)
|
||||
@click.pass_context
|
||||
@pass_context
|
||||
def set_visibility(
|
||||
ctx: PbiContext,
|
||||
click_ctx: click.Context,
|
||||
name: str,
|
||||
page: str,
|
||||
visual: str,
|
||||
hidden: bool,
|
||||
) -> None:
|
||||
"""Set a visual hidden or visible inside bookmark NAME.
|
||||
|
||||
NAME is the bookmark identifier (hex folder name).
|
||||
Use --hidden to hide the visual, --visible to show it.
|
||||
"""
|
||||
from pbi_cli.core.bookmark_backend import bookmark_set_visibility
|
||||
from pbi_cli.core.pbir_path import resolve_report_path
|
||||
|
||||
report_path = click_ctx.parent.obj.get("report_path") if click_ctx.parent else None
|
||||
definition_path = resolve_report_path(report_path)
|
||||
run_command(
|
||||
ctx,
|
||||
bookmark_set_visibility,
|
||||
definition_path=definition_path,
|
||||
name=name,
|
||||
page_name=page,
|
||||
visual_name=visual,
|
||||
hidden=hidden,
|
||||
)
|
||||
251
src/pbi_cli/commands/format_cmd.py
Normal file
251
src/pbi_cli/commands/format_cmd.py
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
"""PBIR visual conditional formatting commands."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import click
|
||||
|
||||
from pbi_cli.commands._helpers import run_command
|
||||
from pbi_cli.main import PbiContext, pass_context
|
||||
|
||||
|
||||
@click.group(name="format")
|
||||
@click.option(
|
||||
"--report-path",
|
||||
default=None,
|
||||
help="Path to .Report folder (auto-detected from CWD if omitted).",
|
||||
)
|
||||
@click.pass_context
|
||||
def format_cmd(ctx: click.Context, report_path: str | None) -> None:
|
||||
"""Manage visual conditional formatting."""
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj["report_path"] = report_path
|
||||
|
||||
|
||||
@format_cmd.command(name="get")
|
||||
@click.argument("visual")
|
||||
@click.option("--page", "-p", required=True, help="Page name (folder name, not display name).")
|
||||
@click.pass_context
|
||||
@pass_context
|
||||
def format_get(ctx: PbiContext, click_ctx: click.Context, visual: str, page: str) -> None:
|
||||
"""Show current formatting objects for a visual.
|
||||
|
||||
VISUAL is the visual folder name (e.g. 5b30ba9c6ce5b695a8df).
|
||||
"""
|
||||
from pbi_cli.core.format_backend import format_get as _format_get
|
||||
from pbi_cli.core.pbir_path import resolve_report_path
|
||||
|
||||
report_path = click_ctx.parent.obj.get("report_path") if click_ctx.parent else None
|
||||
definition_path = resolve_report_path(report_path)
|
||||
run_command(
|
||||
ctx,
|
||||
_format_get,
|
||||
definition_path=definition_path,
|
||||
page_name=page,
|
||||
visual_name=visual,
|
||||
)
|
||||
|
||||
|
||||
@format_cmd.command(name="clear")
|
||||
@click.argument("visual")
|
||||
@click.option("--page", "-p", required=True, help="Page name (folder name, not display name).")
|
||||
@click.pass_context
|
||||
@pass_context
|
||||
def format_clear(ctx: PbiContext, click_ctx: click.Context, visual: str, page: str) -> None:
|
||||
"""Remove all conditional formatting from a visual.
|
||||
|
||||
VISUAL is the visual folder name (e.g. 5b30ba9c6ce5b695a8df).
|
||||
"""
|
||||
from pbi_cli.core.format_backend import format_clear as _format_clear
|
||||
from pbi_cli.core.pbir_path import resolve_report_path
|
||||
|
||||
report_path = click_ctx.parent.obj.get("report_path") if click_ctx.parent else None
|
||||
definition_path = resolve_report_path(report_path)
|
||||
run_command(
|
||||
ctx,
|
||||
_format_clear,
|
||||
definition_path=definition_path,
|
||||
page_name=page,
|
||||
visual_name=visual,
|
||||
)
|
||||
|
||||
|
||||
@format_cmd.command(name="background-gradient")
|
||||
@click.argument("visual")
|
||||
@click.option("--page", "-p", required=True, help="Page name (folder name, not display name).")
|
||||
@click.option("--input-table", required=True, help="Table name driving the gradient.")
|
||||
@click.option("--input-column", required=True, help="Column name driving the gradient.")
|
||||
@click.option(
|
||||
"--field",
|
||||
"field_query_ref",
|
||||
required=True,
|
||||
help='queryRef of the target field (e.g. "Sum(financials.Profit)").',
|
||||
)
|
||||
@click.option("--min-color", default="minColor", show_default=True, help="Gradient minimum color.")
|
||||
@click.option("--max-color", default="maxColor", show_default=True, help="Gradient maximum color.")
|
||||
@click.pass_context
|
||||
@pass_context
|
||||
def background_gradient(
|
||||
ctx: PbiContext,
|
||||
click_ctx: click.Context,
|
||||
visual: str,
|
||||
page: str,
|
||||
input_table: str,
|
||||
input_column: str,
|
||||
field_query_ref: str,
|
||||
min_color: str,
|
||||
max_color: str,
|
||||
) -> None:
|
||||
"""Apply a linear gradient background color rule to a visual column.
|
||||
|
||||
VISUAL is the visual folder name (e.g. 5b30ba9c6ce5b695a8df).
|
||||
|
||||
Example:
|
||||
|
||||
pbi format background-gradient MyVisual --page overview
|
||||
--input-table financials --input-column Profit
|
||||
--field "Sum(financials.Profit)"
|
||||
"""
|
||||
from pbi_cli.core.format_backend import format_background_gradient
|
||||
from pbi_cli.core.pbir_path import resolve_report_path
|
||||
|
||||
report_path = click_ctx.parent.obj.get("report_path") if click_ctx.parent else None
|
||||
definition_path = resolve_report_path(report_path)
|
||||
run_command(
|
||||
ctx,
|
||||
format_background_gradient,
|
||||
definition_path=definition_path,
|
||||
page_name=page,
|
||||
visual_name=visual,
|
||||
input_table=input_table,
|
||||
input_column=input_column,
|
||||
field_query_ref=field_query_ref,
|
||||
min_color=min_color,
|
||||
max_color=max_color,
|
||||
)
|
||||
|
||||
|
||||
@format_cmd.command(name="background-conditional")
|
||||
@click.argument("visual")
|
||||
@click.option("--page", "-p", required=True, help="Page name (folder name, not display name).")
|
||||
@click.option("--input-table", required=True, help="Table containing the evaluated column.")
|
||||
@click.option("--input-column", required=True, help="Column whose aggregation is tested.")
|
||||
@click.option(
|
||||
"--threshold",
|
||||
type=float,
|
||||
required=True,
|
||||
help="Numeric threshold value to compare against.",
|
||||
)
|
||||
@click.option(
|
||||
"--color",
|
||||
"color_hex",
|
||||
required=True,
|
||||
help="Hex color to apply when condition is met (e.g. #12239E).",
|
||||
)
|
||||
@click.option(
|
||||
"--comparison",
|
||||
default="gt",
|
||||
show_default=True,
|
||||
help="Comparison: eq, neq, gt, gte, lt, lte.",
|
||||
)
|
||||
@click.option(
|
||||
"--field",
|
||||
"field_query_ref",
|
||||
default=None,
|
||||
help='queryRef of the target field. Defaults to "Sum(table.column)".',
|
||||
)
|
||||
@click.pass_context
|
||||
@pass_context
|
||||
def background_conditional(
|
||||
ctx: PbiContext,
|
||||
click_ctx: click.Context,
|
||||
visual: str,
|
||||
page: str,
|
||||
input_table: str,
|
||||
input_column: str,
|
||||
threshold: float,
|
||||
color_hex: str,
|
||||
comparison: str,
|
||||
field_query_ref: str | None,
|
||||
) -> None:
|
||||
"""Apply a rule-based conditional background color to a visual column.
|
||||
|
||||
VISUAL is the visual folder name (e.g. 5b30ba9c6ce5b695a8df).
|
||||
Colors the cell when Sum(input_column) satisfies the comparison.
|
||||
|
||||
Example:
|
||||
|
||||
pbi format background-conditional MyVisual --page overview
|
||||
--input-table financials --input-column "Units Sold"
|
||||
--threshold 100000 --color "#12239E" --comparison gt
|
||||
"""
|
||||
from pbi_cli.core.format_backend import format_background_conditional
|
||||
from pbi_cli.core.pbir_path import resolve_report_path
|
||||
|
||||
report_path = click_ctx.parent.obj.get("report_path") if click_ctx.parent else None
|
||||
definition_path = resolve_report_path(report_path)
|
||||
run_command(
|
||||
ctx,
|
||||
format_background_conditional,
|
||||
definition_path=definition_path,
|
||||
page_name=page,
|
||||
visual_name=visual,
|
||||
input_table=input_table,
|
||||
input_column=input_column,
|
||||
threshold=threshold,
|
||||
color_hex=color_hex,
|
||||
comparison=comparison,
|
||||
field_query_ref=field_query_ref,
|
||||
)
|
||||
|
||||
|
||||
@format_cmd.command(name="background-measure")
|
||||
@click.argument("visual")
|
||||
@click.option("--page", "-p", required=True, help="Page name (folder name, not display name).")
|
||||
@click.option("--measure-table", required=True, help="Table containing the color measure.")
|
||||
@click.option(
|
||||
"--measure-property", required=True, help="Name of the DAX measure returning hex color."
|
||||
)
|
||||
@click.option(
|
||||
"--field",
|
||||
"field_query_ref",
|
||||
required=True,
|
||||
help='queryRef of the target field (e.g. "Sum(financials.Sales)").',
|
||||
)
|
||||
@click.pass_context
|
||||
@pass_context
|
||||
def background_measure(
|
||||
ctx: PbiContext,
|
||||
click_ctx: click.Context,
|
||||
visual: str,
|
||||
page: str,
|
||||
measure_table: str,
|
||||
measure_property: str,
|
||||
field_query_ref: str,
|
||||
) -> None:
|
||||
"""Apply a DAX measure-driven background color rule to a visual column.
|
||||
|
||||
VISUAL is the visual folder name (e.g. 5b30ba9c6ce5b695a8df).
|
||||
The DAX measure must return a valid hex color string.
|
||||
|
||||
Example:
|
||||
|
||||
pbi format background-measure MyVisual --page overview
|
||||
--measure-table financials
|
||||
--measure-property "Conditional Formatting Sales"
|
||||
--field "Sum(financials.Sales)"
|
||||
"""
|
||||
from pbi_cli.core.format_backend import format_background_measure
|
||||
from pbi_cli.core.pbir_path import resolve_report_path
|
||||
|
||||
report_path = click_ctx.parent.obj.get("report_path") if click_ctx.parent else None
|
||||
definition_path = resolve_report_path(report_path)
|
||||
run_command(
|
||||
ctx,
|
||||
format_background_measure,
|
||||
definition_path=definition_path,
|
||||
page_name=page,
|
||||
visual_name=visual,
|
||||
measure_table=measure_table,
|
||||
measure_property=measure_property,
|
||||
field_query_ref=field_query_ref,
|
||||
)
|
||||
Loading…
Reference in a new issue