Skip to content

Conversation

@brant-livefront
Copy link
Contributor

📔 Objective

We recently got a few more specifics from the Datadog team on the existing pull-based integration. In order to support identical data being sent as part of our new push-based integration, we need a few more dynamic fields to enrich the data.

This PR adds these new dynamic fields.

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@brant-livefront brant-livefront requested a review from a team as a code owner October 31, 2025 19:16
@brant-livefront brant-livefront changed the title Add template properites for Datadog Add template properties for Datadog Oct 31, 2025
@claude
Copy link
Contributor

claude bot commented Oct 31, 2025

Claude finished @brant-livefront's task —— View job


Code Review Complete

  • Evaluate PR title and description
  • Review recent changes since last review
  • Analyze changed files for issues
  • Check status of previous findings
  • Post comprehensive review

Summary

This PR adds new template properties (UserType and ActingUserType) to support Datadog's push-based integration by enriching event data with user role information. The implementation introduces a new repository method and stored procedure to fetch user details by organization and user ID.

Status of Previous Findings:

  • TOP 1 issue: Resolved - Removed TOP 1 per discussion, assumes uniqueness (pending DB team verification)
  • Missing integration tests: Still outstanding (see Finding 1)
  • Nullable reference type directives: Not addressed (acceptable per author's response)

Findings

Finding 1: Missing integration tests for new repository method

Per CLAUDE.md: "ALWAYS add unit tests (with mocking) for any new feature development"

The new GetDetailsByOrganizationIdUserIdAsync method (src/Core/AdminConsole/Repositories/IOrganizationUserRepository.cs:110) has zero test coverage in both Dapper and EntityFramework implementations. Codecov confirms 0% coverage on this code path:

  • 13 lines missing in Dapper (src/Infrastructure.Dapper/AdminConsole/Repositories/OrganizationUserRepository.cs:692-707)
  • 8 lines missing in EF (src/Infrastructure.EntityFramework/AdminConsole/Repositories/OrganizationUserRepository.cs:974-981)
Required test coverage

Add integration tests covering:

  • Successful retrieval when user exists in organization
  • Null return when user not found
  • Null return when organization not found
  • Behavior with edge cases (invalid GUIDs)

Test files:

  • test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepository/OrganizationUserRepositoryTests.cs (Dapper)
  • test/Infrastructure.EFIntegration.Test/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs (EF)

⚠️ Finding 2: Missing GO statement at end of migration script

The migration script (util/Migrator/DbScripts/2025-11-05_00_OrganizationUserUserDetails_ReadByOrganizationIdUserId.sql:17) has a GO statement on line 17, but this file does not end with a trailing newline. SQL Server migration scripts should end with a newline after the final GO statement to prevent potential concatenation issues with subsequent scripts.

💭 Finding 3: User role information exposure to third-party integrations

The addition of UserType and ActingUserType fields (src/Core/AdminConsole/Models/Data/EventIntegrations/IntegrationTemplateContext.cs:42-47) exposes OrganizationUserType enum values (Owner, Admin, User, etc.) to external integrations like Datadog.

While this appears intentional for enriching integration data, has this been reviewed against:

  • Zero-knowledge security principles (CLAUDE.md)
  • Data minimization requirements (GDPR/CCPA compliance)
  • Customer expectations for what organizational data is shared with integrations?

If approved, consider documenting this in integration configuration UI so administrators understand what data is being shared.

Good Practices Observed

  • Comprehensive unit test coverage for template context properties
  • Template processor tests verify new UserType and ActingUserType tokens
  • Consistent implementation across Dapper and EF repositories
  • Proper use of QuerySingleOrDefaultAsync and SingleOrDefaultAsync to express single-result expectations
  • Good error handling and logging in EventIntegrationHandler

@github-actions
Copy link
Contributor

github-actions bot commented Oct 31, 2025

Logo
Checkmarx One – Scan Summary & Details84995447-d066-4ce1-9cad-4fe2058ff9bb

Great job! No new security vulnerabilities introduced in this pull request

@codecov
Copy link

codecov bot commented Oct 31, 2025

Codecov Report

❌ Patch coverage is 54.09836% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.82%. Comparing base (c0700a6) to head (7bb7419).
⚠️ Report is 12 commits behind head on main.

Files with missing lines Patch % Lines
...Console/Repositories/OrganizationUserRepository.cs 0.00% 13 Missing ⚠️
...Console/Repositories/OrganizationUserRepository.cs 0.00% 8 Missing ⚠️
...SharedWeb/Utilities/ServiceCollectionExtensions.cs 0.00% 4 Missing ⚠️
...tions/EventIntegrations/EventIntegrationHandler.cs 84.21% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6528      +/-   ##
==========================================
- Coverage   56.83%   56.82%   -0.01%     
==========================================
  Files        1909     1909              
  Lines       84810    84859      +49     
  Branches     7633     7639       +6     
==========================================
+ Hits        48199    48223      +24     
- Misses      34785    34810      +25     
  Partials     1826     1826              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Member

@eliykat eliykat left a comment

Choose a reason for hiding this comment

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

I assume my review is not required because this is merging into a feature branch (so codeowner requirements don't apply) and Architecture Team are handling this work. Please re-request my review if this is not the case.

@brant-livefront brant-livefront requested a review from a team as a code owner November 5, 2025 22:57
SET NOCOUNT ON

SELECT
TOP 1 *
Copy link
Contributor

Choose a reason for hiding this comment

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

❓ Curious the purpose of the TOP 1 here? With no ORDER BY clause, this query is non-deterministic and can return inconsistent results any time it's run.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I was being a bit belt + suspenders. There's really no way this query should return more than one. I can remove it so it doesn't cause confusion.

Copy link
Contributor

Choose a reason for hiding this comment

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

@brant-livefront FWIW, I checked the database and there is one (and only one) instance with multiple records with the same UserId and OrgId. The data in both rows are identical (except for the Id), so that leads me to believe that the duplicate row was entered inadvertently. If you leave the TOP 1 in there, it should be fine assuming you don't care about the Id for the OrganizationUser table, since in this case they will be different. Every other User/Org combination has only one row (currently). I was initially concerned about performance but compared execution plans and that doesn't appear to be an issue so it's fine if you want to leave it in. Just be aware that it is currently possible that more than one row can be returned for a given User/Org, so you'd either need the TOP 1 or filter it out in code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mkincaid-bw Thanks for checking on this! I think the best course is to put the TOP 1 back in - we don't care about order (or which id that one row returns) because we're only looking up things like Name, Email and Type here.

Our code currently would fail if it returned more than 1, so given that there's a possible edge case, the TOP 1 is probably a good defense against that rather than code around a case that really shouldn't be there. I'll add it back in.

Copy link
Contributor

Choose a reason for hiding this comment

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

Mark and team are discussing the strange backstory with this table, so this may need to stay open for a bit as they research why there's not a unique index.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I discussed this with Matt in Slack and I moved back (in latest commit) to dropping the TOP 1 and assuming uniqueness. We won't merge until that's a safe assumption.

Copy link
Contributor

Choose a reason for hiding this comment

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

There was only one instance of duplicate data from 2017. Robert did some analysis on code changes and found that this occurred before some additional checks were put in place to handle the unique check.

I just cleaned up the duplicate data so there are currently no dups. After discussion, we are opting to NOT add a unique index at this time, primarily because we can't account for possible duplicates in self-hosted clients. We feel this is pretty safe since there was only one instance of duplicate data in 8 years, before additional checks in code were added.

Comment on lines +690 to +705
public async Task<OrganizationUserUserDetails?> GetDetailsByOrganizationIdUserIdAsync(Guid organizationId, Guid userId)
{
using (var connection = new SqlConnection(ConnectionString))
{
var result = await connection.QuerySingleOrDefaultAsync<OrganizationUserUserDetails>(
"[dbo].[OrganizationUserUserDetails_ReadByOrganizationIdUserId]",
new
{
OrganizationId = organizationId,
UserId = userId
},
commandType: CommandType.StoredProcedure);

return result;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

♻️ Per CLAUDE.md guidelines: "ALWAYS add unit tests (with mocking) for any new feature development." This new repository method lacks unit test coverage. Consider adding tests for:

  • Successful retrieval of user details
  • Null return when user not found
  • Proper parameter handling

Note: The EF implementation of this same method also needs tests.

@sonarqubecloud
Copy link

withinfocus
withinfocus previously approved these changes Nov 10, 2025
Base automatically changed from brant/unit-test-coverage to main November 10, 2025 19:55
@brant-livefront brant-livefront dismissed withinfocus’s stale review November 10, 2025 19:55

The base branch was changed.

/// <remarks>
/// Similar to GetByOrganizationAsync, but returns the user details.
/// </remarks>
Task<OrganizationUserUserDetails?> GetDetailsByOrganizationIdUserIdAsync(Guid organizationId, Guid userId);
Copy link
Contributor

Choose a reason for hiding this comment

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

Finding 2: New repository method lacks unit test coverage

Per CLAUDE.md: "ALWAYS add unit tests (with mocking) for any new feature development"

The new GetDetailsByOrganizationIdUserIdAsync method has no unit tests in either the Dapper or EntityFramework implementations. This violates the project's testing requirements and leaves 0% coverage on this new code path.

Required test coverage

Add tests to verify:

  • Successful retrieval when user exists in organization
  • Null return when user not found
  • Null return when organization not found
  • Proper parameter handling (organizationId, userId)
  • Behavior with edge cases (null parameters, invalid GUIDs)

Tests should be added to:

  • test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepository/OrganizationUserRepositoryTests.cs (for Dapper)
  • test/Infrastructure.EFIntegration.Test/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs (for EF)

withinfocus
withinfocus previously approved these changes Nov 10, 2025
Copy link
Contributor

@withinfocus withinfocus left a comment

Choose a reason for hiding this comment

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

Conditionally. Wait for DBOps.

SET NOCOUNT ON

SELECT
TOP 1 *
Copy link
Contributor

Choose a reason for hiding this comment

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

Mark and team are discussing the strange backstory with this table, so this may need to stay open for a bit as they research why there's not a unique index.

return rowCount > 0;
}

public async Task<OrganizationUserUserDetails?> GetDetailsByOrganizationIdUserIdAsync(Guid organizationId, Guid userId)
Copy link
Contributor

Choose a reason for hiding this comment

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

Finding 2: Missing integration tests for new repository method

Per CLAUDE.md: "ALWAYS add unit tests (with mocking) for any new feature development"

The new GetDetailsByOrganizationIdUserIdAsync method lacks integration test coverage in both Dapper and EntityFramework implementations. Codecov reports 0% coverage on this new code (13 lines missing in Dapper, 8 in EF).

Required test coverage

Add integration tests to verify:

  • Successful retrieval when user exists in organization
  • Null return when user not found
  • Null return when organization not found
  • Proper parameter handling (organizationId, userId)
  • Behavior with invalid GUIDs

Tests should be added to:

  • test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepository/OrganizationUserRepositoryTests.cs (for Dapper)
  • test/Infrastructure.EFIntegration.Test/AdminConsole/Repositories/OrganizationUserRepositoryTests.cs (for EF)

public OrganizationUserUserDetails? User { get; set; }
public string? UserName => User?.Name;
public string? UserEmail => User?.Email;
public OrganizationUserType? UserType => User?.Type;
Copy link
Contributor

Choose a reason for hiding this comment

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

💭 Finding 3: New UserType and ActingUserType fields expose organizational role information

The addition of UserType and ActingUserType fields will expose OrganizationUserType enum values (Owner, Admin, User, etc.) to external integrations like Datadog.

Security consideration

While this appears intentional for enriching integration data, verify that exposing user role information to third-party integrations aligns with:

  • Zero-knowledge security principles (CLAUDE.md)
  • Data minimization requirements (GDPR/CCPA compliance)
  • Customer expectations for what data is shared with integrations

If approved, consider documenting this in the integration configuration UI so administrators understand what organizational data is being shared.

Comment on lines +692 to +707
public async Task<OrganizationUserUserDetails?> GetDetailsByOrganizationIdUserIdAsync(Guid organizationId, Guid userId)
{
using (var connection = new SqlConnection(ConnectionString))
{
var result = await connection.QuerySingleOrDefaultAsync<OrganizationUserUserDetails>(
"[dbo].[OrganizationUserUserDetails_ReadByOrganizationIdUserId]",
new
{
OrganizationId = organizationId,
UserId = userId
},
commandType: CommandType.StoredProcedure);

return result;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Finding 1: This new repository method lacks integration test coverage.

Per CLAUDE.md: "ALWAYS add unit tests (with mocking) for any new feature development"

Codecov shows 13 missing lines here (0% coverage). Add integration tests to test/Infrastructure.IntegrationTest/AdminConsole/Repositories/OrganizationUserRepository/OrganizationUserRepositoryTests.cs covering:

  • Successful retrieval when user exists in organization
  • Null return when user/organization not found
  • Edge cases with invalid GUIDs

AND
[UserId] = @UserId
END
GO
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Finding 2: Missing trailing newline after final GO statement. SQL Server migration scripts should end with a newline to prevent potential concatenation issues with subsequent scripts.

Suggested change
GO
GO

Comment on lines +42 to +47
public OrganizationUserType? UserType => User?.Type;

public User? ActingUser { get; set; }
public OrganizationUserUserDetails? ActingUser { get; set; }
public string? ActingUserName => ActingUser?.Name;
public string? ActingUserEmail => ActingUser?.Email;
public OrganizationUserType? ActingUserType => ActingUser?.Type;
Copy link
Contributor

Choose a reason for hiding this comment

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

💭 Finding 3: These new fields expose user role information (OrganizationUserType enum values like Owner, Admin, User) to external integrations like Datadog.

While this appears intentional for enriching integration data, verify this aligns with:

  • Zero-knowledge security principles (per CLAUDE.md)
  • Data minimization requirements (GDPR/CCPA)
  • Customer expectations for data sharing with integrations

Consider documenting this in integration configuration UI so administrators understand what organizational data is being shared.

Copy link
Contributor

@withinfocus withinfocus left a comment

Choose a reason for hiding this comment

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

This is good to go. We know AC team is working on data discrepancies but the SQL change here should return a single result.

@withinfocus withinfocus requested a review from eliykat November 20, 2025 14:41
Copy link
Contributor

@mkincaid-bw mkincaid-bw left a comment

Choose a reason for hiding this comment

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

LGTM

@brant-livefront brant-livefront merged commit 9573cab into main Nov 24, 2025
88 of 95 checks passed
@brant-livefront brant-livefront deleted the brant/datadog-template-improvements branch November 24, 2025 15:30
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.

5 participants