Skip to content

fix: metadata-aware data ops, OptionSet surface, recovery hints, MCP logging#35

Merged
TomProkop merged 1 commit intomasterfrom
fix/conference-feedback
Apr 29, 2026
Merged

fix: metadata-aware data ops, OptionSet surface, recovery hints, MCP logging#35
TomProkop merged 1 commit intomasterfrom
fix/conference-feedback

Conversation

@TomProkop
Copy link
Copy Markdown
Member

@TomProkop TomProkop commented Apr 29, 2026

Conference Session Feedback — CLI fixes

Data operations — metadata-aware type wrapping

  • EntityJsonConverter gains metadata-aware overload (pure converter, no runtime dependency)
  • Auto-wraps int→OptionSetValue for picklist/status/state, decimal→Money, bare GUID →EntityReference (single-target lookups), int[]→OptionSetValueCollection (multi-select)
  • DataverseBulkService + DataverseRecordService fetch fresh EntityMetadata per operation (no cache — CLI mutates schema)
  • Each service has private RetrieveAttributeMetadataAsync (proper layering — converter has no runtime dependency)
  • Null-valued options filtered with .Where(o => o.Value.HasValue)

OptionSet command restructure

Moved from entity optionset global/option (4–5 levels) to environment optionset (2 levels):

txc environment optionset list
txc environment optionset show --name <name>                    # global
txc environment optionset show --entity E --attribute A         # local
txc environment optionset show --label "Box"                    # label → value
txc environment optionset show --value 375970000                # value → label
txc environment optionset show --language 1029                  # specific LCID
txc environment optionset create --name X --display-name Y --options "..."
txc environment optionset delete --name X
txc environment optionset option add --name X --label Y
txc environment optionset option add --entity E --attribute A --label Y
txc environment optionset option remove --name X --value N
txc environment optionset option remove --entity E --attribute A --value N
  • --name everywhere (no --global-optionset)
  • show (not describe — reserved for metamodel)
  • option add/remove follows noun/verb convention (like solution component add/remove)
  • --language flag for multi-language environments (LCID, defaults to user language)
  • --label/--value for piping lookups
  • optionSetName in interface (was globalOptionSetName)
  • LabelHelper for consistent language-aware label resolution
  • ParseOptions extracted to shared OptionSetParseHelper

Entity describe enriched

  • EntityAttributeRecord gains OptionSetName + OptionValues fields
  • Text table shows OptionSet name column; values available in --format json

Other

  • RootsService warns when MCP client returns no workspace roots
  • AddProjectsToSlnPostActionProcessor reads stdout/stderr before WaitForExit() (deadlock fix)
  • Data tool descriptions document auto-type-detection + bare GUID for single-target lookups

Tested against live Dataverse

All commands verified against udpp26-txc-demo.crm4.dynamics.com:

  • optionset list/show/option add/option remove
  • record create + bulk create with OptionSet values ✅
  • --label/--value lookups ✅
  • --language flag ✅
  • entity describe OptionSet column ✅
  • Error paths (missing args, conflicting args, not found) ✅
  • Production guard on destructive ops ✅

@TomProkop TomProkop force-pushed the fix/conference-feedback branch from e552afc to 21be216 Compare April 29, 2026 20:02
@TomProkop TomProkop requested a review from Copilot April 29, 2026 20:18
@TomProkop TomProkop force-pushed the fix/conference-feedback branch 2 times, most recently from b2e055c to 4d8ae28 Compare April 29, 2026 20:22
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

This PR improves Dataverse data operations and schema inspection in txc by making record/bulk create/update metadata-aware (to auto-wrap special SDK types) and by expanding OptionSet visibility/commands, plus a few operational/logging tweaks.

Changes:

  • Add metadata-aware JSON→Entity conversion (OptionSet/Money/lookup wrapping) and fetch fresh EntityMetadata per data operation.
  • Surface OptionSet information in entity describe output and add a new environment entity optionset global describe command.
  • Improve operational logging/recovery hints (MCP roots warning, template post-action recovery hints, dotnet sln add stdout logging).

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/TALXIS.CLI.Platform.Dataverse.Data/EntityJsonConverter.cs Adds metadata-aware conversions and a helper to fetch entity metadata per operation.
src/TALXIS.CLI.Platform.Dataverse.Data/DataverseRecordService.cs Fetches metadata before create/update to enable type-aware attribute wrapping.
src/TALXIS.CLI.Platform.Dataverse.Data/DataverseBulkService.cs Fetches metadata once per bulk operation and reuses it for record conversion.
src/TALXIS.CLI.Platform.Dataverse.Application/Services/DataverseOptionSetService.cs Adds global OptionSet “describe” implementation.
src/TALXIS.CLI.Platform.Dataverse.Application/Services/DataverseEntityMetadataService.cs Adds OptionSet name/value surfacing onto attribute records.
src/TALXIS.CLI.MCP/RootsService.cs Adds a warning when MCP client provides no workspace roots.
src/TALXIS.CLI.Features.Workspace/TemplateEngine/AddProjectsToSlnPostActionProcessor.cs Logs dotnet sln add stdout on success.
src/TALXIS.CLI.Features.Workspace/ComponentCreateCliCommand.cs Adds recovery hints when template post-actions leave behind temp dirs.
src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetDescribeGlobalCliCommand.cs New CLI command to describe a global option set (values + labels).
src/TALXIS.CLI.Features.Environment/Entity/EntityOptionSetCliCommand.cs Wires the new global describe command into the CLI tree.
src/TALXIS.CLI.Features.Environment/Entity/EntityDescribeCliCommand.cs Adds OptionSet column to the attribute table output.
src/TALXIS.CLI.Features.Environment/Data/Record/EnvDataRecordCreateCliCommand.cs Updates help text to document auto type-detection behavior.
src/TALXIS.CLI.Features.Environment/Data/Bulk/EnvDataBulkCreateCliCommand.cs Updates help text to document auto type-detection behavior.
src/TALXIS.CLI.Core/Contracts/Dataverse/IDataverseOptionSetService.cs Adds the describe method + new output records.
src/TALXIS.CLI.Core/Contracts/Dataverse/IDataverseEntityMetadataService.cs Extends EntityAttributeRecord to include OptionSet fields.

Comment thread src/TALXIS.CLI.Features.Environment/Data/Record/EnvDataRecordCreateCliCommand.cs Outdated
Comment thread src/TALXIS.CLI.Features.Environment/Data/Bulk/EnvDataBulkCreateCliCommand.cs Outdated
@TomProkop TomProkop force-pushed the fix/conference-feedback branch 5 times, most recently from 94665cf to 917bd8f Compare April 29, 2026 21:24
@TomProkop TomProkop requested a review from Copilot April 29, 2026 21:26
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 24 out of 24 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (2)

src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetAddOptionCliCommand.cs:15

  • The XML usage examples refer to txc environment optionset add-option, but this command is registered as optionset option add (via OptionSetOptionCliCommand + Name="add"). Update the usage strings to match the actual CLI route so users don’t copy an invocation that won’t parse.
    src/TALXIS.CLI.Features.Environment/OptionSet/OptionSetRemoveOptionCliCommand.cs:15
  • The XML usage examples refer to txc environment optionset remove-option, but this command is registered as optionset option remove (via OptionSetOptionCliCommand + Name="remove"). Update the usage strings to match the actual CLI route so users don’t copy an invocation that won’t parse.

Comment on lines +20 to +23
#pragma warning disable TXC003
public class OptionSetShowCliCommand : ProfiledCliCommand
{
protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(OptionSetShowCliCommand));
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

The file-level TXC003 suppression hides direct OutputWriter usage from within ExecuteAsync (e.g., the --label/--value lookup paths). This bypasses OutputFormatter and will not respect --format json (it prints a bare scalar instead of a JSON envelope). Prefer using OutputFormatter (e.g., WriteValue/WriteResult) for these lookup outputs, and scope any TXC003 suppression only to the specific text-renderer methods passed to OutputFormatter (with a short explanatory comment).

Copilot uses AI. Check for mistakes.
/// <summary>
/// Parent command for option set (choice) operations.
/// Covers both global option sets and local (entity-attribute) option sets.
/// Usage: <c>txc environment optionset [list|describe|create|delete|add-option|remove-option]</c>
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

The parent command’s usage string lists subcommands that don’t match the actual command tree (e.g., it mentions describe, add-option, remove-option, but the children are show and the nested option add/remove). Update the usage string to reflect the real verbs so txc docs and source stay consistent.

Suggested change
/// Usage: <c>txc environment optionset [list|describe|create|delete|add-option|remove-option]</c>
/// Usage: <c>txc environment optionset [list|show|create|delete|option]</c>

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +13
/// Usage: <c>txc environment optionset describe --name &lt;schema-name&gt;</c>
/// Usage: <c>txc environment optionset describe --entity &lt;name&gt; --attribute &lt;name&gt;</c>
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

The XML usage docs say optionset describe, but the actual command name is show (and the parent command summary also mentions describe). This mismatch makes it hard to discover the correct invocation from source/docs; update the usage strings (or rename the command) so the documented verb matches the registered CliCommand name.

Suggested change
/// Usage: <c>txc environment optionset describe --name &lt;schema-name&gt;</c>
/// Usage: <c>txc environment optionset describe --entity &lt;name&gt; --attribute &lt;name&gt;</c>
/// Usage: <c>txc environment optionset show --name &lt;schema-name&gt;</c>
/// Usage: <c>txc environment optionset show --entity &lt;name&gt; --attribute &lt;name&gt;</c>

Copilot uses AI. Check for mistakes.
Comment on lines +196 to +199
foreach (var opt in options)
{
OutputWriter.WriteLine($" {opt.Value.ToString().PadRight(valueWidth)} | {(opt.Label ?? "-").PadRight(labelWidth)}");
}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

PrintOptions() clamps labelWidth, but it never truncates labels before PadRight(). If any label exceeds labelWidth, the row will spill past the intended width and the table separators won’t align. Consider truncating (similar to OptionSetListCliCommand.Truncate) before padding.

Copilot uses AI. Check for mistakes.
Comment on lines +149 to +152
var label = dict.GetValueOrDefault("Label")?.ToString();
var value = dict.GetValueOrDefault("Value") is int v ? v : 0;
records.Add(new OptionValueRecord(value, label, null));
}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

When reading local option-set options from GetAttributeDetailAsync(), null option values are currently mapped to 0. Since Dataverse option values are nullable in metadata, this can produce misleading output (and duplicate “0” rows) if any option has no Value. Prefer skipping options with null Value (consistent with DescribeGlobalOptionSetAsync) or preserving null explicitly in the rendered output.

Copilot uses AI. Check for mistakes.
Comment on lines +77 to +81
private static async Task<EntityMetadata> RetrieveAttributeMetadataAsync(
DataverseConnection conn, string entityLogicalName, CancellationToken ct)
{
var request = new RetrieveEntityRequest
{
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

RetrieveAttributeMetadataAsync is duplicated in DataverseRecordService and DataverseBulkService. To keep this logic consistent (and avoid future drift if request flags need to change), extract it into a shared helper (e.g., internal metadata utility/service in the Dataverse.Data project) and reuse it from both call sites.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +117 to +121
private static async Task<EntityMetadata> RetrieveAttributeMetadataAsync(
DataverseConnection conn, string entityLogicalName, CancellationToken ct)
{
var request = new RetrieveEntityRequest
{
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

RetrieveAttributeMetadataAsync is duplicated in DataverseBulkService and DataverseRecordService. To follow DRY and reduce maintenance overhead, consider moving this into a shared helper (or a small internal service) and calling it from both places.

Copilot generated this review using guidance from repository custom instructions.
@TomProkop TomProkop force-pushed the fix/conference-feedback branch from 917bd8f to e0fede5 Compare April 29, 2026 21:36
Data operations — metadata-aware type wrapping:
- EntityJsonConverter gains metadata-aware overload (pure converter, no
  runtime dependency). Wraps int→OptionSetValue, decimal→Money, bare GUID
  →EntityReference, int[]→OptionSetValueCollection for multi-select.
- OptionSetValueCollection serialized as JSON array in WriteAttribute.
- DataverseBulkService + DataverseRecordService fetch fresh EntityMetadata
  per operation (no cache — CLI mutates schema via imports/creates).
- Each service has private RetrieveAttributeMetadataAsync (proper layering).

OptionSet command restructure:
- Moved from 'entity optionset global/option' (4-5 levels deep) to
  'environment optionset' (2 levels). Option sets aren't entity children.
- --global-optionset renamed to --name everywhere.
- 'option add/delete' flattened to 'add-option/remove-option'.
- 'describe' works for both global (--name) and local (--entity + --attribute).
- Cleaned interface: globalOptionSetName→optionSetName in IDataverseOptionSetService.
- ParseOptions extracted to shared OptionSetParseHelper.

Entity describe enriched:
- EntityAttributeRecord gains OptionSetName + OptionValues fields.
- Text table shows OptionSet name column. Values in JSON format output.

Other:
- RootsService warns when no workspace roots available from MCP client.
- AddProjectsToSlnPostActionProcessor logs stdout on success path.
- Data tool descriptions document auto-type-detection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

2 participants