Skip to content

feat: add find command for catalog search#174

Open
nadavs123 wants to merge 47 commits intomicrosoft:mainfrom
nadavs123:feature/catalog-search-find-command
Open

feat: add find command for catalog search#174
nadavs123 wants to merge 47 commits intomicrosoft:mainfrom
nadavs123:feature/catalog-search-find-command

Conversation

@nadavs123
Copy link
Copy Markdown

@nadavs123 nadavs123 commented Feb 15, 2026

Pull Request

Closes #172

Summary

Adds a new find command that searches for Fabric items across all accessible workspaces using the Catalog Search API (POST /v1/catalog/search).

Usage

# Basic search
fab find "sales report"

# Filter by item type
fab find "data" -P type=Lakehouse

# Multiple types (bracket syntax)
fab find "dashboard" -P type=[Report,SemanticModel]

# Exclude a type
fab find "data" -P type!=Dashboard

# Exclude multiple types
fab find "data" -P type!=[Dashboard,Datamart]

# Show detailed output with IDs
fab find "sales" -l

# Project specific fields with JMESPath
fab find "data" -q "[].{name: name, workspace: workspace}"

Flags

Flag Description
-P/--params Filter in key=value or key!=value format. Brackets for multiple values: type=[Lakehouse,Notebook]
-l/--long Show detailed table with IDs (name, id, type, workspace, workspace_id, description)
-q/--query JMESPath query for client-side filtering

Implementation details

  • Follows existing CLI patterns: @handle_exceptions decorator, print_output_format(), FabricCLIError
  • Item type list in type_supported.yaml (included in package-data, matching command_supported.yaml pattern)
  • Error messages in ErrorMessages.Find class (matches repo pattern)
  • Interactive mode: page-by-page with "Press Enter to continue..." prompt
  • Command-line mode: fetches all pages automatically
  • _parse_type_from_params uses get_dict_from_params for parameter parsing with != regex support
  • _fetch_results shared helper for response parsing and pagination
  • truncate_columns in fab_util.py for terminal-width-aware column sizing (reusable by other commands)
  • JMESPath filtering applied before column truncation; truncation only affects text output

Files added

  • src/fabric_cli/client/fab_api_catalog.py — API client (search() function)
  • src/fabric_cli/commands/find/__init__.py — Package init
  • src/fabric_cli/commands/find/fab_find.py — Command logic
  • src/fabric_cli/commands/find/type_supported.yaml — Supported/unsupported item types
  • src/fabric_cli/errors/find.pyFindErrors class
  • src/fabric_cli/parsers/fab_find_parser.py — Argument parser
  • docs/commands/find.md — Command documentation
  • tests/test_commands/test_find.py — 15 e2e VCR tests
  • tests/test_commands/recordings/test_commands/test_find/*.yaml — VCR cassettes
  • tests/test_commands/api_processors/catalog_search_api_processor.py — Cassette processor for catalog search responses

Files modified

  • src/fabric_cli/core/fab_parser_setup.py — Register find parser
  • src/fabric_cli/errors/__init__.py — Register Find error class
  • src/fabric_cli/utils/fab_util.py — Add truncate_columns(), != regex support in get_dict_from_params
  • tests/test_commands/commands_parser.py — Register find parser + Windows PromptSession fix (scoped with try/finally)
  • tests/test_commands/api_processors/api_processor_handler.py — Register CatalogSearchAPIProcessor
  • pyproject.toml — Add type_supported.yaml to package-data
  • MANIFEST.in — Add type_supported.yaml to source distribution

Notes

  • Dashboard is the only unsupported item type (not searchable)
  • Dataflow Gen1/Gen2 are not searchable; only Dataflow Gen2 CI/CD is returned (as type Dataflow)
  • Scorecards are returned as type Report
  • Requires Catalog.Read.All scope

Nadav Schachter added 21 commits February 9, 2026 17:51
Features:
- Search across all workspaces by displayName, workspaceName, or description
- Filter by item type with --type flag
- Limit results with --limit flag
- Detailed output with --detailed flag (includes id, workspaceId)
- Custom endpoint support with --endpoint flag or FAB_CATALOG_ENDPOINT env var

Output columns (default): name, type, workspace, description
Output columns (detailed): + workspaceId, id

Required scope: Catalog.Read.All
Unsupported types: Dashboard, Dataflow, Scorecard

Includes unit tests (12 tests passing)
…dling

Changes based on issue microsoft#172 feedback:
- Changed --type from comma-separated to nargs='+' (space-separated)
- Removed --endpoint flag (use internal mechanism instead)
- Added FabricCLIError for invalid/unsupported item types
- Added error handling for API failures
- Updated tests to match new patterns (15 tests passing)
- Added complete_item_types() completer for searchable types
- Tab completion excludes unsupported types (Dashboard, Dataflow, Scorecard)
- Restored unsupported type validation with clear error message
- Updated ALL_ITEM_TYPES list from official API spec
- Added SEARCHABLE_ITEM_TYPES for valid filter types
- 20 tests passing
- Keep tab-completion for --type flag
- Custom FabricCLIError for unsupported types (Dashboard, Dataflow, Scorecard)
- Custom FabricCLIError for unknown types
- Cleaner error messages vs argparse choices listing all 40+ types
- 22 tests passing
- Changed from data= to json= for request payload
- Added raw_response=True to avoid auto-pagination hanging
- Added fallback from displayName to name (API bug workaround)
- Updated tests to use dict instead of JSON string
- Successfully tested against dailyapi.fabric.microsoft.com
@nadavs123 nadavs123 requested a review from a team as a code owner February 15, 2026 09:54
Nadav Schachter and others added 7 commits February 24, 2026 16:25
…ination and -P params

- Interactive mode: pages 50 items at a time with 'Press any key to continue...'
- Command-line mode: fetches up to 1000 items in a single request
- Replace --type with -P type=Report,Lakehouse (key=value pattern)
- Remove --max-items and --next-token flags

Co-authored-by: Copilot <[email protected]>
The Catalog Search API returns an empty string continuationToken
on the last page instead of null/omitting it. This caused the
interactive pagination loop to send an empty token on the next
request, which the API treats as a fresh empty search — returning
unrelated results from the entire tenant.

Co-authored-by: Copilot <[email protected]>
Long descriptions caused the table separator line to wrap, appearing
as a double separator. Descriptions are now truncated with ellipsis
to keep the table within the terminal width. Full descriptions are
still available via -l/--long mode.

Co-authored-by: Copilot <[email protected]>
Command-line mode now fetches all pages instead of stopping at one
page of 1000. Uses the same continuation token pattern as interactive
mode with 'or None' guard against empty string tokens.

Co-authored-by: Copilot <[email protected]>
- Use contractions and active voice for friendlier tone
- Suggest close matches for unknown types instead of dumping all 43
- Remove redundant type list from unsupported type error

Co-authored-by: Copilot <[email protected]>
required=False,
metavar="",
nargs="*",
help="Parameters in key=value or key!=value format. Use brackets for multiple values: type=[Lakehouse,Notebook]. Use != to exclude: type!=Dashboard",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you parse it with get_dict_from_params, I think you will need to use quats: type=["Lakehouse","Notebook"]

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I'm keeping a dedicated parser, no quotes are needed.

Copy link
Copy Markdown
Collaborator

@aviatco aviatco Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

considering the option I suggest above, using get_dict_from_params, will required this change

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since I adopted get_dict_from_params with the updated regex, bracket values like type=[Report,SemanticModel] work without quotes. The regex treats brackets as part of the value.

assert payload["filter"] == "(Type ne 'Dashboard' and Type ne 'Datamart')"


class TestParseTypeParam:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should create e2e tests that call find command and record the tests and validate all different scenarios instead of those unit tests.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Added 5 e2e tests with VCR cassettes covering basic search, type filter, long output, no results, and ne filter. I kept the unit tests for pure-function logic since those test edge cases and error paths that are hard to cover with e2e alone. Removed TestFindCommandline and TestFindInteractive.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should have only the e2e tests - and using that test all the different scenarios that will covered the unit tests.
@ayeshurun please keep me honest

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I converted the test suite to be e2e-first (16 e2e tests now) and removed every unit test that was redundant with an e2e equivalent. 13 remaining unit tests cover edge cases that can't practically be e2e.

fab find 'finance' -P type=[Warehouse,Lakehouse] -l

# filter results client-side with JMESPath
fab find 'sales' -q "[?type=='Report']"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn’t this effectively the same as -P type='Report', but with poorer performance due to fetching all items before filtering? We might want to skip this example since it’s not a best practice.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not even sure why we added -q in the first place, as all filters should be done server-side anyways imo... I added it as part of the initial review requests.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-q is usefull for column filtering.
find command return the name, ws name. desc etc... if user want to see only the name, for example, user can use -q for that.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the docs example to focus on column filtering.

"""Search for nonexistent term shows 'No items found'."""
cli_executor.exec_command("find 'xyznonexistent98765zzz'")

# print_grey is used for "No items found." but it's not mocked here
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you have mock_print_grey you can use, but I do believe also in mock_questionary_print you can see the output. if you dont see it that means something is wrong with the test

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the no-results test to assert through mock_print_grey that No items found. is printed.

mock_questionary_print,
):
"""Search with type!=Dashboard excludes Dashboard items."""
cli_executor.exec_command("find 'report' -P type!=Dashboard")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should also test with multiple values like: type!=[Report,Notebook] and verify those types didn't return in the result

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a test for multi-value negation with bracket syntax: type!=[Report,Notebook].

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the test I can see it is working without the quotes... did you verify it in command_line as well? I dontmran here in the tests, I mean manually from the console

# ---------------------------------------------------------------------------


class TestFindE2E:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing failure scenarios where the API return an error, where the json parse failed, where the type is unknomn etc...
any error we have in the code should be tested.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added failure scenario tests for API error responses, malformed JSON responses, and unknown type errors.

# ---------------------------------------------------------------------------


class TestFindE2E:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relevant for all tests - the name should have prefix of failure or success - depend on the test. you can see examples in any other test's file

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed all test methods to use _success or _failure suffixes.

Nadav Schachter and others added 3 commits March 19, 2026 23:13
…st improvements

- Replace _split_params with shared get_dict_from_params utility
- Update get_dict_from_params regex to support != operator
- Move unsupported_parameter/invalid_parameter_format to CommonErrors
- Remove unsearchable_type from FindErrors (use CommonErrors.type_not_supported)
- Use ERROR_UNEXPECTED_ERROR constant in _raise_on_error
- Extract _print_search_summary helper function
- Reorder has_more assignment in _find_interactive
- Remove JMESPath type filter example and Notes section from docs
- Remove TestSplitParams class and legacy comma-syntax tests
- Add e2e tests: case-insensitive type, multi-value !=, unknown type, unsupported type
- Rename all e2e tests with _success/_failure suffix
- Add _assert_strings_in_mock_calls helper for cleaner assertions
- Rename cassettes to match new test names, add 2 new cassettes

Co-authored-by: Copilot <[email protected]>
- Add e2e tests: invalid param format, unsupported param, unsupported param !=, JMESPath query
- Remove redundant unit tests now covered by e2e (payload single type, ne single type, invalid format, unknown param, unknown param !=)
- Test split: 26 unit + 13 e2e = 39 tests

Co-authored-by: Copilot <[email protected]>
- Add e2e: multi-type eq, JSON output, search summary with count
- Remove 14 unit tests now fully covered by e2e (DisplayItems, parse/payload overlaps)
- Remove unused imports and sample data constants
- Final split: 13 unit + 16 e2e = 29 tests

Co-authored-by: Copilot <[email protected]>
uri: https://api.fabric.microsoft.com/v1/catalog/search
response:
body:
string: '{"value": [{"id": "bb0df270-bbc3-4713-a3f3-47010daa20d5", "type": "KQLQueryset",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When recording no private information should be visible in the recording.
Please use this guide: https://github.com/microsoft/fabric-cli/blob/main/tests/authoring_tests.md

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sanitized all cassettes with mock values from static_test_data.py



# Sample API responses for testing
SAMPLE_RESPONSE_WITH_RESULTS = {
Copy link
Copy Markdown
Collaborator

@aviatco aviatco Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't want to have a sample response - we should trigger the API and confirm the actual response.
In each test we should add a setup phase where we created/set/update the needed items them call find and rely on the items we created to verify they are in the results. also it's better to use 'mock' data and not actual values of items

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above responses on this issue.

- Pass max_depth=1 to get_dict_from_params (find uses flat keys only)
- Sanitize all VCR cassettes: replace real IDs, workspace names, display names with mock values
- Delete unused ErrorMessages.Config.invalid_parameter_format (now in Common)

Co-authored-by: Copilot <[email protected]>
@nadavs123
Copy link
Copy Markdown
Author

nadavs123 commented Mar 24, 2026

@aviatco , while testing locally I noticed that print_output_format breaks the table layout when item names exceed the terminal width. I did a small patch for "description" but the issue also reproduces with long item names. I think a more robust fix would be terminal-width-aware column sizing in the shared table renderer (_print_output_format_result_text). I prefer not to do it as part of this PR as I don't know what other functions it might affect. Filed as #201 .

Nadav Schachter and others added 2 commits March 25, 2026 22:34
Support both current flat fields (workspaceName, workspaceId) and
upcoming nested format (hierarchy.workspace.displayName, .id).

Co-authored-by: Copilot <[email protected]>
- Replace truncate_descriptions with truncate_columns: priority-based
  truncation (description -> workspace -> name), with absolute max
  width cap (50 chars) and terminal-width fitting
- Strip trailing spaces before ellipsis
- Show running total in interactive pagination instead of per-page count

Co-authored-by: Copilot <[email protected]>
Copilot AI review requested due to automatic review settings March 29, 2026 12:05
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new top-level fab find command that searches Fabric items across all accessible workspaces via the Catalog Search API (POST /v1/catalog/search), integrating with existing CLI output/error patterns and adding unit + VCR-backed E2E coverage.

Changes:

  • Introduces find command implementation, parser registration, and supported-type allow/deny lists.
  • Adds a dedicated Catalog API client wrapper for catalog/search.
  • Extends shared utilities (get_dict_from_params to support !=, plus a new truncate_columns helper) and adds tests + VCR recordings + docs.

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/fabric_cli/commands/find/fab_find.py Implements find command logic, paging, filtering, JMESPath support, and output formatting.
src/fabric_cli/commands/find/type_supported.yaml Declares searchable vs unsupported item types for catalog search.
src/fabric_cli/commands/find/__init__.py Adds find command package.
src/fabric_cli/client/fab_api_catalog.py Adds API client wrapper for POST /v1/catalog/search.
src/fabric_cli/parsers/fab_find_parser.py Adds argparse parser, flags, help, and examples for find.
src/fabric_cli/core/fab_parser_setup.py Registers find with the global parser setup.
src/fabric_cli/errors/find.py Adds FindErrors message helpers.
src/fabric_cli/errors/__init__.py Registers ErrorMessages.Find.
src/fabric_cli/errors/common.py Adds common error helpers used by param parsing (unsupported_parameter, invalid_parameter_format).
src/fabric_cli/errors/config.py Removes config-specific invalid_parameter_format helper (superseded by common).
src/fabric_cli/utils/fab_util.py Updates param parsing to accept != and adds truncate_columns() for table-fit truncation.
docs/commands/find.md Documents find usage, flags, and examples.
tests/test_commands/commands_parser.py Registers find parser in test CLI harness and adjusts Windows PromptSession handling.
tests/test_commands/test_find.py Adds unit tests + VCR-backed E2E tests for find behaviors.
tests/test_commands/recordings/test_commands/test_find/test_find_basic_search_success.yaml VCR cassette for basic search.
tests/test_commands/recordings/test_commands/test_find/test_find_with_type_filter_success.yaml VCR cassette for type= filtering.
tests/test_commands/recordings/test_commands/test_find/test_find_type_case_insensitive_success.yaml VCR cassette for case-insensitive type=.
tests/test_commands/recordings/test_commands/test_find/test_find_with_long_output_success.yaml VCR cassette for --long output.
tests/test_commands/recordings/test_commands/test_find/test_find_no_results_success.yaml VCR cassette for empty results.
tests/test_commands/recordings/test_commands/test_find/test_find_with_ne_filter_success.yaml VCR cassette for type!= filtering.
tests/test_commands/recordings/test_commands/test_find/test_find_ne_multi_type_success.yaml VCR cassette for type!=[...].
tests/test_commands/recordings/test_commands/test_find/test_find_multi_type_eq_success.yaml VCR cassette for type=[...].
tests/test_commands/recordings/test_commands/test_find/test_find_with_jmespath_query_success.yaml VCR cassette for -q/--query behavior.
tests/test_commands/recordings/test_commands/test_find/test_find_json_output_success.yaml VCR cassette for --output_format json.
tests/test_commands/recordings/test_commands/test_find/test_find_search_summary_success.yaml VCR cassette for summary output.
.changes/unreleased/added-20260209-171617.yaml Adds unreleased changelog entry for the new command.

Comment on lines 73 to 95
params_dict: dict = {}
# Split the params using a regular expression that matches a comma that is not inside a pair of quotes, brackets or braces
# Example key1.key2=hello,key2={"hello":"testing","bye":2},key3=[1,2,3],key4={"key5":"value5"}
# Result ['key1.key2=hello', 'key2={"hello":"testing","bye":2}', 'key3=[1,2,3]', 'key4={"key5":"value5"}']
# Example key1.key2=hello
# Result ['key1.key=hello']
pattern = r"((?:[\w\.]+=.+?)(?=(?:,\s*[\w\.]+=)|$))"
pattern = r"((?:[\w\.]+!?=.+?)(?=(?:,\s*[\w\.]+!?=)|$))"

if params:
if isinstance(params, list):
norm_params = " ".join(params)
else:
norm_params = params

# Remove from multiline
norm_params = norm_params.replace("\\", "").strip()

matches = re.findall(pattern, norm_params)
if not matches:
raise FabricCLIError(
ErrorMessages.Config.invalid_parameter_format(norm_params),
ErrorMessages.Common.invalid_parameter_format(norm_params),
fab_constant.ERROR_INVALID_INPUT,
)
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new parameter-regex allows key!=value by treating it as a key! key. Since this changes get_dict_from_params() behavior (and error text) across the CLI, it would help to document the != support directly here (docstring/comment), and consider whether downstream callers should be insulated from !-suffixed keys unless they explicitly opted into that behavior.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a docstring note in get_dict_from_params documenting the != operator support and the !-suffixed key convention (already present at line 72-73 of fab_util.py).

Comment on lines +34 to +36
assert match_found, f"Expected strings {strings} to {'all be present together' if require_all_in_same_args else 'be present'} in mock calls, but not found."
else:
assert not match_found, f"Expected strings {strings} to {'not all be present together' if require_all_in_same_args else 'not be present'} in mock calls, but found."
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test file contains a couple of very long f-strings (e.g., the assertion messages in _assert_strings_in_mock_calls) that don't appear to be Black-formatted and may fail black --check in CI. Reformat these assertions (let Black wrap them) to keep the file compliant with the repo formatter.

Suggested change
assert match_found, f"Expected strings {strings} to {'all be present together' if require_all_in_same_args else 'be present'} in mock calls, but not found."
else:
assert not match_found, f"Expected strings {strings} to {'not all be present together' if require_all_in_same_args else 'not be present'} in mock calls, but found."
assert match_found, (
f"Expected strings {strings} to "
f"{'all be present together' if require_all_in_same_args else 'be present'} "
"in mock calls, but not found."
)
else:
assert not match_found, (
f"Expected strings {strings} to "
f"{'not all be present together' if require_all_in_same_args else 'not be present'} "
"in mock calls, but found."
)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Ran black on the test file and source file. Both pass black --check now.


try:
utils_ui.print_grey("")
input("Press any key to continue... (Ctrl+C to stop)")
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prompt says "Press any key to continue..." but the implementation uses input(...), which requires pressing Enter (and will echo a newline). Either adjust the prompt text to reflect this, or use an input mechanism that truly reads a single keypress (or a consistent fab_ui prompt helper) in interactive mode.

Suggested change
input("Press any key to continue... (Ctrl+C to stop)")
input("Press Enter to continue... (Ctrl+C to stop)")

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code already says "Press Enter to continue..." (fixed in a prior commit). Updated the docs to match.

Comment on lines +293 to +298
if not show_details:
utils.truncate_columns(display_items, ["description", "workspace", "name"])

if getattr(args, "query", None):
display_items = utils_jmespath.search(display_items, args.query)

Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_display_items() truncates columns unconditionally for non---long output, which will also mutate the data returned in --output_format json (and can affect -q/--query results). Truncation should be applied only for text/table output (and ideally after any JMESPath filtering/projection), leaving JSON output unmodified for scripting.

Suggested change
if not show_details:
utils.truncate_columns(display_items, ["description", "workspace", "name"])
if getattr(args, "query", None):
display_items = utils_jmespath.search(display_items, args.query)
if getattr(args, "query", None):
display_items = utils_jmespath.search(display_items, args.query)
# Apply truncation only for non-detailed, non-JSON outputs, after any JMESPath filtering.
output_format = getattr(args, "output", None) or getattr(args, "output_format", None)
is_json_output = output_format == "json"
if not show_details and not is_json_output:
utils.truncate_columns(display_items, ["description", "workspace", "name"])

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. JMESPath filtering now runs before truncation, and truncation is guarded with output_format == "text".

all_items.extend(items)
has_more = continuation_token is not None
if has_more:
payload = {"continuationToken": continuation_token}
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When paging, the code replaces the request payload with only {"continuationToken": ...}. This drops pageSize (and any filter), which can change paging behavior (e.g., command-line mode may fall back to the API default page size on subsequent requests) and increases request count. Keep pageSize/filter consistent across pages (or explicitly include them alongside continuationToken) rather than relying on continuation token semantics.

Suggested change
payload = {"continuationToken": continuation_token}
payload["continuationToken"] = continuation_token

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. continuation payloads now preserve pageSize. The filter is embedded in the continuation token by the API, so re-sending it isn't needed (and the API ignores it on continuation requests).

mock_questionary_print,
):
"""Search with type!=Dashboard excludes Dashboard items."""
cli_executor.exec_command("find 'report' -P type!=Dashboard")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the test I can see it is working without the quotes... did you verify it in command_line as well? I dontmran here in the tests, I mean manually from the console

Nadav Schachter and others added 2 commits April 13, 2026 15:22
Source changes:
- Restructure _raise_on_error -> _parse_response (check 200 first)
- Only truncate columns for text output, not JSON
- Change 'Press any key' to 'Press Enter'
- Preserve pageSize in continuation payloads
- Document != support in get_dict_from_params docstring

Test changes:
- Switch to item_factory pattern per reviewer request
- Remove all unit tests (reviewer: verify via e2e only)
- Use ErrorMessage constants in failure assertions
- Check errors via mock_fab_ui_print_error (not concatenated mocks)
- Merge search summary into basic_search test
- Add CatalogSearchAPIProcessor for response filtering
- Add dailyapi URI normalization in processors.py
- Re-record all cassettes with create-then-search pattern
- 15 e2e tests, all passing

Co-authored-by: Copilot <[email protected]>
- Fix empty last page: show final count without '(more available)'
- Use post-filter count in summary (matches JMESPath -q results)
- Move summary before table with correct count via prepare/display split
- Proper singular/plural: '1 item found' vs '50 items found'
- Fix error hint: replace 'tab completion' with 'find --help'
- Add empty line after table output

Co-authored-by: Copilot <[email protected]>
Copilot AI review requested due to automatic review settings April 13, 2026 15:06
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 7 comments.

Comment on lines +23 to +27
def _load_type_config() -> dict[str, list[str]]:
"""Load item type definitions from type_supported.yaml."""
yaml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "type_supported.yaml")
with open(yaml_path, "r") as f:
return yaml.safe_load(f)
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type_supported.yaml is loaded via a direct filesystem path at import time. In the current packaging config, only fabric_cli.core.fab_config/command_support.yaml and fabric_cli.commands.fs/payloads/* are included as package data, so this YAML is likely missing in the installed distribution and will raise FileNotFoundError on import. Consider loading it via importlib.resources (or similar) and ensure the YAML is included in package data/MANIFEST so the command works when installed from PyPI.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Added type_supported.yaml to both pyproject.toml package-data and MANIFEST.in (69451ec).

Comment on lines +203 to +209
# Parse bracket syntax: [val1,val2] or plain: val1
if type_value.startswith("[") and type_value.endswith("]"):
inner = type_value[1:-1]
types = [t.strip() for t in inner.split(",") if t.strip()]
else:
types = [type_value.strip()]

Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description mentions supporting legacy comma syntax like type=Report,Lakehouse, but _parse_type_from_params only splits multiple values when the bracket form ([A,B]) is used. As written, type=Report,Lakehouse will be treated as a single type and fail validation. Either add support for comma-separated values here or update the documented behavior.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By design — bracket syntax (type=[Report,Lakehouse]) is the documented way to specify multiple types, consistent with other CLI commands like mkdir -P paths=[Files,Tables]. The comma in get_dict_from_params is a key-value pair separator, not a value list separator, so type=Report,Lakehouse would be ambiguous.

Comment on lines +294 to +301
output_format = getattr(args, "output_format", "text") or "text"
if not show_details and output_format == "text":
utils.truncate_columns(display_items, ["description", "workspace", "name"])

if getattr(args, "query", None):
display_items = utils_jmespath.search(display_items, args.query)

return display_items if isinstance(display_items, list) else []
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JMESPath querying is applied after truncate_columns, and non-list query results are discarded (return ... if isinstance(..., list) else []). This can break valid queries that return a dict/scalar and can also cause filtering/projection to operate on already-truncated strings. Apply the JMESPath query before truncation, and pass through dict/scalar results to print_output_format instead of forcing a list.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Same as #21 above. JMESPath now runs first (69451ec).

"-P",
"--params",
metavar="",
nargs="?",
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--params/-P is defined with nargs='?', so a user can pass -P with no value and the command will silently ignore the flag (no validation error, no filter applied). If -P is intended to always take a value, define it without an optional nargs and/or validate that args.params is not None when the flag is present.

Suggested change
nargs="?",

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is consistent with how optional params work in the CLI. Passing -P alone is equivalent to not passing it. Adding validation for this edge case would break the argparse pattern used by other commands.


**Behavior:**

- In interactive mode (`fab` shell), results are paged 50 at a time with "Press any key to continue..." prompts.
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs say interactive mode prompts "Press any key to continue...", but the implementation currently prompts with "Press Enter to continue...". Please align the documentation with the actual prompt (or update the prompt to match the docs) so users aren’t confused.

Suggested change
- In interactive mode (`fab` shell), results are paged 50 at a time with "Press any key to continue..." prompts.
- In interactive mode (`fab` shell), results are paged 50 at a time with "Press Enter to continue..." prompts.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — docs now say "Press Enter to continue..." (69451ec).

Comment on lines +9 to +26
class CatalogSearchAPIProcessor(BaseAPIProcessor):
CATALOG_SEARCH_URI = "https://api.fabric.microsoft.com/v1/catalog/search"

def __init__(self, generated_name_mapping):
self.generated_name_mapping = generated_name_mapping

def try_process_request(self, request) -> bool:
return False

def try_process_response(self, request, response) -> bool:
uri = request.uri

if uri.lower() == self.CATALOG_SEARCH_URI.lower():
method = request.method
if method == "POST":
self._handle_search_response(response)
return True

Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API processor isn’t currently wired into APIProcessorHandler (which only registers capacities/workspaces/gateways/etc.), so it will never run and the catalog/search responses won’t be scrubbed/trimmed during recording. Register this processor in the handler so the find VCR cassettes only include deterministic, test-generated catalog entries (and avoid leaking unrelated tenant data).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — CatalogSearchAPIProcessor is now registered in APIProcessorHandler (69451ec). It will scrub catalog search responses during future recordings.

Comment on lines +90 to 92

self._interactiveCLI = InteractiveCLI(customArgumentParser, self._parser)

Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Windows workaround patches fabric_cli.core.fab_interactive.PromptSession at module scope and never restores it. This can leak into other tests in the same process and make failures harder to diagnose. Consider restoring _interactive_mod.PromptSession to _orig_ps after InteractiveCLI(...) is constructed (or patch via a fixture/monkeypatch context) to keep the change scoped to this executor.

Suggested change
self._interactiveCLI = InteractiveCLI(customArgumentParser, self._parser)
try:
self._interactiveCLI = InteractiveCLI(
customArgumentParser, self._parser
)
finally:
_interactive_mod.PromptSession = _orig_ps
else:
self._interactiveCLI = InteractiveCLI(customArgumentParser, self._parser)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — wrapped in try/finally so the original PromptSession is restored after the singleton is created (69451ec).

Nadav Schachter and others added 2 commits April 13, 2026 21:53
- Validate empty bracket syntax (-P type=[]) with clear error
- Apply JMESPath filtering before column truncation
- Include header name length in truncate_columns width calc
- Add type_supported.yaml to package-data and MANIFEST.in
- Register CatalogSearchAPIProcessor in handler
- Restore PromptSession patch in test executor (try/finally)
- Fix docs: 'Press any key' -> 'Press Enter'
- Black formatting pass

Co-authored-by: Copilot <[email protected]>
Remove query text echo from status message. The user already sees
what they typed in the terminal — echoing the search text without
filters was inconsistent, and echoing everything would be noisy.

Co-authored-by: Copilot <[email protected]>
Copilot AI review requested due to automatic review settings April 14, 2026 10:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

…ead code

- Collapse 4-way eq/ne filter branch into uniform clause builder
- Extract _next_page_payload helper to deduplicate continuation logic
- Simplify pagination loops to while True + break (no has_more flag)
- Remove unused SEARCHABLE_ITEM_TYPES constant
- Delete orphaned test_find_search_summary_success.yaml cassette

Co-authored-by: Copilot <[email protected]>
has_more = True

while has_more:
while True:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you change it back to while Ture? better to use has_more since this is the exit condition

has_more = True

while has_more:
while True:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here -

while continuation_token:

error_code = fab_constant.ERROR_UNEXPECTED_ERROR
error_message = response.text
raise FabricCLIError(
ErrorMessages.Find.invalid_response(),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundent - you have invalid_json_format error in ErrorMessages.Common

error_data = json.loads(response.text)
error_code = error_data.get("errorCode", fab_constant.ERROR_UNEXPECTED_ERROR)
error_message = error_data.get("message", response.text)
except json.JSONDecodeError:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what can be the values of response.text? json/string?
cause if we got to this point this is not ERROR_UNEXPECTED_ERROR it is ERROR_INVALID_JSON

error_message = response.text

raise FabricCLIError(
ErrorMessages.Find.search_failed(error_message),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider print the error message from the API with logger.debug and for the user use something simple and clear

def _raise_on_error(response) -> None:
"""Raise FabricCLIError if the API response indicates failure."""
if response.status_code != 200:
def _parse_response(response) -> dict:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - consider rewrite the function (considering response.text is always a json object and getting json.JSONDecodeError is always thrown cause the response.text is an invalid json format ):

"""Parse a successful API response or raise FabricCLIError on failure."""
    try:
        data = json.loads(response.text)

        if response.status_code == 200:
            return data

        raise FabricCLIError(
            ErrorMessages.Find.search_failed(data.get("message", response.text)),
            data.get("errorCode", fab_constant.ERROR_UNEXPECTED_ERROR),
        )

    except json.JSONDecodeError:
        if response.status_code == 200:
            raise FabricCLIError(
                ErrorMessages.Find.invalid_response(),
                fab_constant.ERROR_INVALID_JSON,
            )

return []

output_format = getattr(args, "output_format", "text") or "text"
if not show_details and output_format == "text":
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you shouldn't use output_format in any command logic.
fab_ui is the only component that should be aware of this field.


output_format = getattr(args, "output_format", "text") or "text"
if not show_details and output_format == "text":
utils.truncate_columns(display_items, ["description", "workspace", "name"])
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

paraphs move this logic to fab_ui where output_format is text and show_details is false.
but need to verify this will not cause regression in other commands
Or any other solution, but you should not use output_format value here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Add find command for catalog search

3 participants