Skip to content

Activity log plugins#6

Merged
cheinrichs-oddball merged 11 commits intomainfrom
activity-log-plugins
Feb 26, 2026
Merged

Activity log plugins#6
cheinrichs-oddball merged 11 commits intomainfrom
activity-log-plugins

Conversation

@cheinrichs-oddball
Copy link
Collaborator

@cheinrichs-oddball cheinrichs-oddball commented Feb 9, 2026

EASI-0000

Power Platform scope

App / Solution:
Environment(s) impacted:
Change type (check all that apply):

  • Model-driven app (forms/views/commands)
  • Web resources (JS/HTML/CSS/images)
  • Dataverse schema (tables/columns/relationships/choices)
  • Business logic (Business Rules / Power Automate / Workflows)
  • Plugins (C# / registered steps)
  • Data migration (dataflows / M scripts / scripts)
  • Security (roles/teams/field security)
  • Integrations (connections / connectors / DLP)
  • Other:

Description

This PR creates three plugins.

What changed

Deployment notes

  • Delivery mechanism:
  • Solution name + version:
  • Manual steps required:
  • Secrets / connections / DLP needed:
  • Rollback plan:

How to test this change

Verified in environment(s)

  • Dev

Risk / impact

  • Data impact:
  • Backward compatibility:
  • Performance considerations:

PR Author Checklist

  • I described what changed and why.
  • I included clear test steps and validated in at least one environment.
  • I updated documentation/README if this changes how we deploy or develop.
  • If schema changed, I noted the Solution + components affected.
  • If plugin changed, I noted the message/step/stage and confirmed it runs as expected.
  • If data migration/dataflow changed, I tested with representative data.
  • No secrets, tokens, or environment-specific IDs were committed.

PR Reviewer Guidelines

  • Prefer pulling the branch and validating in the listed environment(s), not only reading code.
  • When approving, state why (tested in Dev, reviewed deployment notes, etc.).
  • For platform changes: verify solution/deploy steps are clear and complete.
  • For plugins: sanity-check registration details (Message, Stage, Filtering Attributes, Secure/Unsecure config).

@cheinrichs-oddball cheinrichs-oddball requested a review from a team as a code owner February 9, 2026 21:24
@cheinrichs-oddball cheinrichs-oddball requested review from WyattEmme-Oddball and removed request for a team February 9, 2026 21:24
Copy link
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 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.

Comment on lines 10 to 24
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."
);
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines 19 to 53
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."
);
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines 25 to 83
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);
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Copy link

@WyattEmme-Oddball WyattEmme-Oddball left a comment

Choose a reason for hiding this comment

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

Looks good to me

@cheinrichs-oddball cheinrichs-oddball merged commit d2d2fee into main Feb 26, 2026
1 check passed
@cheinrichs-oddball cheinrichs-oddball deleted the activity-log-plugins branch February 26, 2026 15:06
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.

3 participants