fix: metadata-aware data ops, OptionSet surface, recovery hints, MCP logging#35
fix: metadata-aware data ops, OptionSet surface, recovery hints, MCP logging#35
Conversation
e552afc to
21be216
Compare
b2e055c to
4d8ae28
Compare
There was a problem hiding this comment.
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→
Entityconversion (OptionSet/Money/lookup wrapping) and fetch freshEntityMetadataper data operation. - Surface OptionSet information in entity describe output and add a new
environment entity optionset global describecommand. - Improve operational logging/recovery hints (MCP roots warning, template post-action recovery hints,
dotnet sln addstdout 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. |
94665cf to
917bd8f
Compare
There was a problem hiding this comment.
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 asoptionset 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 asoptionset 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.
| #pragma warning disable TXC003 | ||
| public class OptionSetShowCliCommand : ProfiledCliCommand | ||
| { | ||
| protected override ILogger Logger { get; } = TxcLoggerFactory.CreateLogger(nameof(OptionSetShowCliCommand)); |
There was a problem hiding this comment.
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).
| /// <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> |
There was a problem hiding this comment.
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.
| /// 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> |
| /// Usage: <c>txc environment optionset describe --name <schema-name></c> | ||
| /// Usage: <c>txc environment optionset describe --entity <name> --attribute <name></c> |
There was a problem hiding this comment.
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.
| /// Usage: <c>txc environment optionset describe --name <schema-name></c> | |
| /// Usage: <c>txc environment optionset describe --entity <name> --attribute <name></c> | |
| /// Usage: <c>txc environment optionset show --name <schema-name></c> | |
| /// Usage: <c>txc environment optionset show --entity <name> --attribute <name></c> |
| foreach (var opt in options) | ||
| { | ||
| OutputWriter.WriteLine($" {opt.Value.ToString().PadRight(valueWidth)} | {(opt.Label ?? "-").PadRight(labelWidth)}"); | ||
| } |
There was a problem hiding this comment.
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.
| var label = dict.GetValueOrDefault("Label")?.ToString(); | ||
| var value = dict.GetValueOrDefault("Value") is int v ? v : 0; | ||
| records.Add(new OptionValueRecord(value, label, null)); | ||
| } |
There was a problem hiding this comment.
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.
| private static async Task<EntityMetadata> RetrieveAttributeMetadataAsync( | ||
| DataverseConnection conn, string entityLogicalName, CancellationToken ct) | ||
| { | ||
| var request = new RetrieveEntityRequest | ||
| { |
There was a problem hiding this comment.
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.
| private static async Task<EntityMetadata> RetrieveAttributeMetadataAsync( | ||
| DataverseConnection conn, string entityLogicalName, CancellationToken ct) | ||
| { | ||
| var request = new RetrieveEntityRequest | ||
| { |
There was a problem hiding this comment.
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.
917bd8f to
e0fede5
Compare
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>
e0fede5 to
3412d00
Compare
Conference Session Feedback — CLI fixes
Data operations — metadata-aware type wrapping
EntityJsonConvertergains metadata-aware overload (pure converter, no runtime dependency)int→OptionSetValuefor picklist/status/state,decimal→Money, bare GUID→EntityReference(single-target lookups),int[]→OptionSetValueCollection(multi-select)DataverseBulkService+DataverseRecordServicefetch freshEntityMetadataper operation (no cache — CLI mutates schema)RetrieveAttributeMetadataAsync(proper layering — converter has no runtime dependency).Where(o => o.Value.HasValue)OptionSet command restructure
Moved from
entity optionset global/option(4–5 levels) toenvironment optionset(2 levels):--nameeverywhere (no--global-optionset)show(notdescribe— reserved for metamodel)option add/removefollows noun/verb convention (likesolution component add/remove)--languageflag for multi-language environments (LCID, defaults to user language)--label/--valuefor piping lookupsoptionSetNamein interface (wasglobalOptionSetName)LabelHelperfor consistent language-aware label resolutionParseOptionsextracted to sharedOptionSetParseHelperEntity describe enriched
EntityAttributeRecordgainsOptionSetName+OptionValuesfields--format jsonOther
RootsServicewarns when MCP client returns no workspace rootsAddProjectsToSlnPostActionProcessorreads stdout/stderr beforeWaitForExit()(deadlock fix)Tested against live Dataverse
All commands verified against
udpp26-txc-demo.crm4.dynamics.com:optionset list/show/option add/option remove✅record create+bulk createwith OptionSet values ✅--label/--valuelookups ✅--languageflag ✅entity describeOptionSet column ✅