Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new Dataverse plugin project to enforce governance rules around Activity Log (new_activitylogs) creation and immutability for the System Intake workflow.
Changes:
- Introduces three new plugin steps: sync target step to Review/Request, block no-op step changes, and block updates to Activity Logs.
- Adds a new .NET Framework 4.7.1 plugin project/solution with NuGet dependencies and config.
- Documents intended plugin registration details and behavior in a new README.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/it-governance/plugins/ActivityLogs.Plugins/ActivityLogs.Plugins/packages.config | Adds NuGet package dependencies for the new plugin project. |
| apps/it-governance/plugins/ActivityLogs.Plugins/ActivityLogs.Plugins/app.config | Adds runtime binding redirect configuration for the plugin assembly. |
| apps/it-governance/plugins/ActivityLogs.Plugins/ActivityLogs.Plugins/README.md | Documents plugin purpose, triggers, and behavior for registration/deployment. |
| apps/it-governance/plugins/ActivityLogs.Plugins/ActivityLogs.Plugins/Properties/AssemblyInfo.cs | Defines assembly metadata and versioning for the new project. |
| apps/it-governance/plugins/ActivityLogs.Plugins/ActivityLogs.Plugins/ActivityLogs.Plugins.csproj | Creates the new .NET Framework plugin project and references required SDK assemblies. |
| apps/it-governance/plugins/ActivityLogs.Plugins/ActivityLogs.Plugins/ActivityLog_Update_BlockAll.cs | New plugin to block all updates to Activity Log records (immutability). |
| apps/it-governance/plugins/ActivityLogs.Plugins/ActivityLogs.Plugins/ActivityLog_Create_ValidateTargetStepNotCurrent.cs | New plugin to prevent creating Activity Logs that set the current step again. |
| apps/it-governance/plugins/ActivityLogs.Plugins/ActivityLogs.Plugins/ActivityLog_Create_SyncTargetStepToReviewAndRequest.cs | New plugin to sync the target step and clear “Ready for Review” on related records. |
| apps/it-governance/plugins/ActivityLogs.Plugins/ActivityLogs.Plugins.sln | Adds a Visual Studio solution for the new plugin project. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
apps/it-governance/plugins/ActivityLogs.Plugins/ActivityLogs.Plugins/README.md
Show resolved
Hide resolved
.../it-governance/plugins/ActivityLogs.Plugins/ActivityLogs.Plugins/ActivityLogs.Plugins.csproj
Show resolved
Hide resolved
.../it-governance/plugins/ActivityLogs.Plugins/ActivityLogs.Plugins/ActivityLogs.Plugins.csproj
Show resolved
Hide resolved
apps/it-governance/plugins/ActivityLogs.Plugins/ActivityLogs.Plugins/app.config
Show resolved
Hide resolved
| public void Execute(IServiceProvider serviceProvider) | ||
| { | ||
| var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); | ||
| if (context == null) return; | ||
|
|
||
| if (!string.Equals(context.PrimaryEntityName, ActivityLogEntity, StringComparison.OrdinalIgnoreCase)) return; | ||
| if (!string.Equals(context.MessageName, "Update", StringComparison.OrdinalIgnoreCase)) return; | ||
|
|
||
| // PreOperation only | ||
| if (context.Stage != 20) return; | ||
|
|
||
| // If you want to allow SYSTEM/INTEGRATION updates, you can add exceptions here later. | ||
| throw new InvalidPluginExecutionException( | ||
| "Activity Logs are immutable and cannot be updated. Create a new Activity Log entry instead." | ||
| ); |
There was a problem hiding this comment.
These plugins don’t use ITracingService at all. The repo’s plugin guide recommends tracing for diagnostics, and the existing plugins use it. Consider adding basic trace statements (start/end, key IDs, and before throwing exceptions) to make production troubleshooting feasible.
| public void Execute(IServiceProvider serviceProvider) | ||
| { | ||
| var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); | ||
| if (context == null) return; | ||
|
|
||
| if (!string.Equals(context.PrimaryEntityName, ActivityLogEntity, StringComparison.OrdinalIgnoreCase)) return; | ||
| if (!string.Equals(context.MessageName, "Create", StringComparison.OrdinalIgnoreCase)) return; | ||
|
|
||
| // PreOperation only | ||
| if (context.Stage != 20) return; | ||
|
|
||
| object targetObj; | ||
| if (!context.InputParameters.TryGetValue("Target", out targetObj)) return; | ||
|
|
||
| var target = targetObj as Entity; | ||
| if (target == null) return; | ||
|
|
||
| var step = target.GetAttributeValue<OptionSetValue>(TargetStepField); | ||
| if (step == null) return; // if no step provided, nothing to validate | ||
|
|
||
| var reviewRef = target.GetAttributeValue<EntityReference>(ReviewLookupField); | ||
| if (reviewRef == null) return; // if no review link, nothing to validate | ||
|
|
||
| var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); | ||
| var service = serviceFactory.CreateOrganizationService(context.UserId); | ||
|
|
||
| var review = service.Retrieve(ReviewEntity, reviewRef.Id, new ColumnSet(ReviewStepField)); | ||
| var current = review.GetAttributeValue<OptionSetValue>(ReviewStepField); | ||
|
|
||
| if (current != null && current.Value == step.Value) | ||
| { | ||
| throw new InvalidPluginExecutionException( | ||
| "That step is already the current step. Please choose a different target step." | ||
| ); | ||
| } |
There was a problem hiding this comment.
This plugin performs an IOrganizationService.Retrieve but has no ITracingService logging. Adding tracing around the retrieve and the comparison (and before throwing) will make it much easier to diagnose permission issues or unexpected data in Dataverse plugin trace logs.
| public void Execute(IServiceProvider serviceProvider) | ||
| { | ||
| var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); | ||
| if (context == null) return; | ||
|
|
||
| if (!string.Equals(context.PrimaryEntityName, ActivityLogEntity, StringComparison.OrdinalIgnoreCase)) return; | ||
| if (!string.Equals(context.MessageName, "Create", StringComparison.OrdinalIgnoreCase)) return; | ||
|
|
||
| // PostOperation only | ||
| if (context.Stage != 40) return; | ||
|
|
||
| object targetObj; | ||
| if (!context.InputParameters.TryGetValue("Target", out targetObj)) return; | ||
|
|
||
| var target = targetObj as Entity; | ||
| if (target == null) return; | ||
|
|
||
| var reviewRef = target.GetAttributeValue<EntityReference>(ReviewLookupField); | ||
| var requestRef = target.GetAttributeValue<EntityReference>(RequestLookupField); | ||
|
|
||
| // Step is optional now (we still want to clear Ready for Review even if step isn't set) | ||
| var step = target.GetAttributeValue<OptionSetValue>(TargetStepField); | ||
|
|
||
| var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); | ||
| var service = serviceFactory.CreateOrganizationService(context.UserId); | ||
|
|
||
| // Update Review (if present) | ||
| if (reviewRef != null) | ||
| { | ||
| var reviewUpdate = new Entity(ReviewEntity, reviewRef.Id); | ||
|
|
||
| // Sync step only if provided on the log | ||
| if (step != null) | ||
| { | ||
| reviewUpdate[ReviewStepField] = new OptionSetValue(step.Value); | ||
| } | ||
|
|
||
| // Always set Ready for Review = false when an Activity Log is created | ||
| reviewUpdate[ReviewReadyForReviewField] = false; | ||
|
|
||
| service.Update(reviewUpdate); | ||
| } | ||
|
|
||
| // Update Request (if present) | ||
| if (requestRef != null) | ||
| { | ||
| var requestUpdate = new Entity(RequestEntity, requestRef.Id); | ||
|
|
||
| // Sync step only if provided on the log | ||
| if (step != null) | ||
| { | ||
| requestUpdate[RequestStepField] = new OptionSetValue(step.Value); | ||
| } | ||
|
|
||
| // Always set Ready for Review = false when an Activity Log is created | ||
| requestUpdate[RequestReadyForReviewField] = false; | ||
|
|
||
| service.Update(requestUpdate); | ||
| } |
There was a problem hiding this comment.
This plugin updates two related records but has no ITracingService logging. Add tracing (IDs for Activity Log / Review / Request, whether step was present, and update outcomes) so support can troubleshoot failures (permissions, missing references) using plugin trace logs.
…ise/power-platform-webres into activity-log-plugins
EASI-0000
Power Platform scope
App / Solution:
Environment(s) impacted:
Change type (check all that apply):
Description
This PR creates three plugins.
What changed
Deployment notes
How to test this change
Verified in environment(s)
Risk / impact
PR Author Checklist
PR Reviewer Guidelines