1
0
mirror of https://github.com/bitwarden/server synced 2025-12-06 00:03:34 +00:00

[PM-26967] Added new metric properties (#6519)

This commit is contained in:
Vijay Oommen
2025-10-30 16:54:05 -05:00
committed by GitHub
parent b8325414bf
commit e102a7488e
17 changed files with 359 additions and 39 deletions

View File

@@ -1,4 +1,5 @@
using Bit.Core.Context;
using Bit.Api.Dirt.Models.Response;
using Bit.Core.Context;
using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
using Bit.Core.Exceptions;
@@ -61,8 +62,9 @@ public class OrganizationReportsController : Controller
}
var latestReport = await _getOrganizationReportQuery.GetLatestOrganizationReportAsync(organizationId);
var response = latestReport == null ? null : new OrganizationReportResponseModel(latestReport);
return Ok(latestReport);
return Ok(response);
}
[HttpGet("{organizationId}/{reportId}")]
@@ -102,7 +104,8 @@ public class OrganizationReportsController : Controller
}
var report = await _addOrganizationReportCommand.AddOrganizationReportAsync(request);
return Ok(report);
var response = report == null ? null : new OrganizationReportResponseModel(report);
return Ok(response);
}
[HttpPatch("{organizationId}/{reportId}")]
@@ -119,7 +122,8 @@ public class OrganizationReportsController : Controller
}
var updatedReport = await _updateOrganizationReportCommand.UpdateOrganizationReportAsync(request);
return Ok(updatedReport);
var response = new OrganizationReportResponseModel(updatedReport);
return Ok(response);
}
#endregion
@@ -182,10 +186,10 @@ public class OrganizationReportsController : Controller
{
throw new BadRequestException("Report ID in the request body must match the route parameter");
}
var updatedReport = await _updateOrganizationReportSummaryCommand.UpdateOrganizationReportSummaryAsync(request);
var response = new OrganizationReportResponseModel(updatedReport);
return Ok(updatedReport);
return Ok(response);
}
#endregion
@@ -228,7 +232,9 @@ public class OrganizationReportsController : Controller
}
var updatedReport = await _updateOrganizationReportDataCommand.UpdateOrganizationReportDataAsync(request);
return Ok(updatedReport);
var response = new OrganizationReportResponseModel(updatedReport);
return Ok(response);
}
#endregion
@@ -265,7 +271,6 @@ public class OrganizationReportsController : Controller
{
try
{
if (!await _currentContext.AccessReports(organizationId))
{
throw new NotFoundException();
@@ -282,10 +287,9 @@ public class OrganizationReportsController : Controller
}
var updatedReport = await _updateOrganizationReportApplicationDataCommand.UpdateOrganizationReportApplicationDataAsync(request);
var response = new OrganizationReportResponseModel(updatedReport);
return Ok(updatedReport);
return Ok(response);
}
catch (Exception ex) when (!(ex is BadRequestException || ex is NotFoundException))
{

View File

@@ -0,0 +1,38 @@
using Bit.Core.Dirt.Entities;
namespace Bit.Api.Dirt.Models.Response;
public class OrganizationReportResponseModel
{
public Guid Id { get; set; }
public Guid OrganizationId { get; set; }
public string? ReportData { get; set; }
public string? ContentEncryptionKey { get; set; }
public string? SummaryData { get; set; }
public string? ApplicationData { get; set; }
public int? PasswordCount { get; set; }
public int? PasswordAtRiskCount { get; set; }
public int? MemberCount { get; set; }
public DateTime? CreationDate { get; set; } = null;
public DateTime? RevisionDate { get; set; } = null;
public OrganizationReportResponseModel(OrganizationReport organizationReport)
{
if (organizationReport == null)
{
return;
}
Id = organizationReport.Id;
OrganizationId = organizationReport.OrganizationId;
ReportData = organizationReport.ReportData;
ContentEncryptionKey = organizationReport.ContentEncryptionKey;
SummaryData = organizationReport.SummaryData;
ApplicationData = organizationReport.ApplicationData;
PasswordCount = organizationReport.PasswordCount;
PasswordAtRiskCount = organizationReport.PasswordAtRiskCount;
MemberCount = organizationReport.MemberCount;
CreationDate = organizationReport.CreationDate;
RevisionDate = organizationReport.RevisionDate;
}
}

View File

@@ -0,0 +1,48 @@
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
namespace Bit.Core.Dirt.Reports.Models.Data;
public class OrganizationReportMetricsData
{
public Guid OrganizationId { get; set; }
public int? ApplicationCount { get; set; }
public int? ApplicationAtRiskCount { get; set; }
public int? CriticalApplicationCount { get; set; }
public int? CriticalApplicationAtRiskCount { get; set; }
public int? MemberCount { get; set; }
public int? MemberAtRiskCount { get; set; }
public int? CriticalMemberCount { get; set; }
public int? CriticalMemberAtRiskCount { get; set; }
public int? PasswordCount { get; set; }
public int? PasswordAtRiskCount { get; set; }
public int? CriticalPasswordCount { get; set; }
public int? CriticalPasswordAtRiskCount { get; set; }
public static OrganizationReportMetricsData From(Guid organizationId, OrganizationReportMetricsRequest? request)
{
if (request == null)
{
return new OrganizationReportMetricsData
{
OrganizationId = organizationId
};
}
return new OrganizationReportMetricsData
{
OrganizationId = organizationId,
ApplicationCount = request.ApplicationCount,
ApplicationAtRiskCount = request.ApplicationAtRiskCount,
CriticalApplicationCount = request.CriticalApplicationCount,
CriticalApplicationAtRiskCount = request.CriticalApplicationAtRiskCount,
MemberCount = request.MemberCount,
MemberAtRiskCount = request.MemberAtRiskCount,
CriticalMemberCount = request.CriticalMemberCount,
CriticalMemberAtRiskCount = request.CriticalMemberAtRiskCount,
PasswordCount = request.PasswordCount,
PasswordAtRiskCount = request.PasswordAtRiskCount,
CriticalPasswordCount = request.CriticalPasswordCount,
CriticalPasswordAtRiskCount = request.CriticalPasswordAtRiskCount
};
}
}

View File

@@ -35,14 +35,28 @@ public class AddOrganizationReportCommand : IAddOrganizationReportCommand
throw new BadRequestException(errorMessage);
}
var requestMetrics = request.Metrics ?? new OrganizationReportMetricsRequest();
var organizationReport = new OrganizationReport
{
OrganizationId = request.OrganizationId,
ReportData = request.ReportData,
ReportData = request.ReportData ?? string.Empty,
CreationDate = DateTime.UtcNow,
ContentEncryptionKey = request.ContentEncryptionKey,
ContentEncryptionKey = request.ContentEncryptionKey ?? string.Empty,
SummaryData = request.SummaryData,
ApplicationData = request.ApplicationData,
ApplicationCount = requestMetrics.ApplicationCount,
ApplicationAtRiskCount = requestMetrics.ApplicationAtRiskCount,
CriticalApplicationCount = requestMetrics.CriticalApplicationCount,
CriticalApplicationAtRiskCount = requestMetrics.CriticalApplicationAtRiskCount,
MemberCount = requestMetrics.MemberCount,
MemberAtRiskCount = requestMetrics.MemberAtRiskCount,
CriticalMemberCount = requestMetrics.CriticalMemberCount,
CriticalMemberAtRiskCount = requestMetrics.CriticalMemberAtRiskCount,
PasswordCount = requestMetrics.PasswordCount,
PasswordAtRiskCount = requestMetrics.PasswordAtRiskCount,
CriticalPasswordCount = requestMetrics.CriticalPasswordCount,
CriticalPasswordAtRiskCount = requestMetrics.CriticalPasswordAtRiskCount,
RevisionDate = DateTime.UtcNow
};

View File

@@ -1,16 +1,15 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests;
namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests;
public class AddOrganizationReportRequest
{
public Guid OrganizationId { get; set; }
public string ReportData { get; set; }
public string? ReportData { get; set; }
public string ContentEncryptionKey { get; set; }
public string? ContentEncryptionKey { get; set; }
public string SummaryData { get; set; }
public string? SummaryData { get; set; }
public string ApplicationData { get; set; }
public string? ApplicationData { get; set; }
public OrganizationReportMetricsRequest? Metrics { get; set; }
}

View File

@@ -0,0 +1,31 @@
using System.Text.Json.Serialization;
namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests;
public class OrganizationReportMetricsRequest
{
[JsonPropertyName("totalApplicationCount")]
public int? ApplicationCount { get; set; } = null;
[JsonPropertyName("totalAtRiskApplicationCount")]
public int? ApplicationAtRiskCount { get; set; } = null;
[JsonPropertyName("totalCriticalApplicationCount")]
public int? CriticalApplicationCount { get; set; } = null;
[JsonPropertyName("totalCriticalAtRiskApplicationCount")]
public int? CriticalApplicationAtRiskCount { get; set; } = null;
[JsonPropertyName("totalMemberCount")]
public int? MemberCount { get; set; } = null;
[JsonPropertyName("totalAtRiskMemberCount")]
public int? MemberAtRiskCount { get; set; } = null;
[JsonPropertyName("totalCriticalMemberCount")]
public int? CriticalMemberCount { get; set; } = null;
[JsonPropertyName("totalCriticalAtRiskMemberCount")]
public int? CriticalMemberAtRiskCount { get; set; } = null;
[JsonPropertyName("totalPasswordCount")]
public int? PasswordCount { get; set; } = null;
[JsonPropertyName("totalAtRiskPasswordCount")]
public int? PasswordAtRiskCount { get; set; } = null;
[JsonPropertyName("totalCriticalPasswordCount")]
public int? CriticalPasswordCount { get; set; } = null;
[JsonPropertyName("totalCriticalAtRiskPasswordCount")]
public int? CriticalPasswordAtRiskCount { get; set; } = null;
}

View File

@@ -1,11 +1,8 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests;
namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests;
public class UpdateOrganizationReportApplicationDataRequest
{
public Guid Id { get; set; }
public Guid OrganizationId { get; set; }
public string ApplicationData { get; set; }
public string? ApplicationData { get; set; }
}

View File

@@ -1,11 +1,9 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests;
namespace Bit.Core.Dirt.Reports.ReportFeatures.Requests;
public class UpdateOrganizationReportSummaryRequest
{
public Guid OrganizationId { get; set; }
public Guid ReportId { get; set; }
public string SummaryData { get; set; }
public string? SummaryData { get; set; }
public OrganizationReportMetricsRequest? Metrics { get; set; }
}

View File

@@ -53,7 +53,7 @@ public class UpdateOrganizationReportApplicationDataCommand : IUpdateOrganizatio
throw new BadRequestException("Organization report does not belong to the specified organization");
}
var updatedReport = await _organizationReportRepo.UpdateApplicationDataAsync(request.OrganizationId, request.Id, request.ApplicationData);
var updatedReport = await _organizationReportRepo.UpdateApplicationDataAsync(request.OrganizationId, request.Id, request.ApplicationData ?? string.Empty);
_logger.LogInformation(Constants.BypassFiltersEventId, "Successfully updated organization report application data {reportId} for organization {organizationId}",
request.Id, request.OrganizationId);

View File

@@ -1,4 +1,5 @@
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Reports.Models.Data;
using Bit.Core.Dirt.Reports.ReportFeatures.Interfaces;
using Bit.Core.Dirt.Reports.ReportFeatures.Requests;
using Bit.Core.Dirt.Repositories;
@@ -53,7 +54,8 @@ public class UpdateOrganizationReportSummaryCommand : IUpdateOrganizationReportS
throw new BadRequestException("Organization report does not belong to the specified organization");
}
var updatedReport = await _organizationReportRepo.UpdateSummaryDataAsync(request.OrganizationId, request.ReportId, request.SummaryData);
await _organizationReportRepo.UpdateMetricsAsync(request.ReportId, OrganizationReportMetricsData.From(request.OrganizationId, request.Metrics));
var updatedReport = await _organizationReportRepo.UpdateSummaryDataAsync(request.OrganizationId, request.ReportId, request.SummaryData ?? string.Empty);
_logger.LogInformation(Constants.BypassFiltersEventId, "Successfully updated organization report summary {reportId} for organization {organizationId}",
request.ReportId, request.OrganizationId);

View File

@@ -1,5 +1,6 @@
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Models.Data;
using Bit.Core.Dirt.Reports.Models.Data;
using Bit.Core.Repositories;
namespace Bit.Core.Dirt.Repositories;
@@ -21,5 +22,8 @@ public interface IOrganizationReportRepository : IRepository<OrganizationReport,
// ApplicationData methods
Task<OrganizationReportApplicationDataResponse> GetApplicationDataAsync(Guid reportId);
Task<OrganizationReport> UpdateApplicationDataAsync(Guid orgId, Guid reportId, string applicationData);
// Metrics methods
Task UpdateMetricsAsync(Guid reportId, OrganizationReportMetricsData metrics);
}

View File

@@ -4,6 +4,7 @@
using System.Data;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Models.Data;
using Bit.Core.Dirt.Reports.Models.Data;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Settings;
using Bit.Infrastructure.Dapper.Repositories;
@@ -173,4 +174,31 @@ public class OrganizationReportRepository : Repository<OrganizationReport, Guid>
commandType: CommandType.StoredProcedure);
}
}
public async Task UpdateMetricsAsync(Guid reportId, OrganizationReportMetricsData metrics)
{
using var connection = new SqlConnection(ConnectionString);
var parameters = new
{
Id = reportId,
ApplicationCount = metrics.ApplicationCount,
ApplicationAtRiskCount = metrics.ApplicationAtRiskCount,
CriticalApplicationCount = metrics.CriticalApplicationCount,
CriticalApplicationAtRiskCount = metrics.CriticalApplicationAtRiskCount,
MemberCount = metrics.MemberCount,
MemberAtRiskCount = metrics.MemberAtRiskCount,
CriticalMemberCount = metrics.CriticalMemberCount,
CriticalMemberAtRiskCount = metrics.CriticalMemberAtRiskCount,
PasswordCount = metrics.PasswordCount,
PasswordAtRiskCount = metrics.PasswordAtRiskCount,
CriticalPasswordCount = metrics.CriticalPasswordCount,
CriticalPasswordAtRiskCount = metrics.CriticalPasswordAtRiskCount,
RevisionDate = DateTime.UtcNow
};
await connection.ExecuteAsync(
$"[{Schema}].[OrganizationReport_UpdateMetrics]",
parameters,
commandType: CommandType.StoredProcedure);
}
}

View File

@@ -4,6 +4,7 @@
using AutoMapper;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Models.Data;
using Bit.Core.Dirt.Reports.Models.Data;
using Bit.Core.Dirt.Repositories;
using Bit.Infrastructure.EntityFramework.Repositories;
using LinqToDB;
@@ -184,4 +185,31 @@ public class OrganizationReportRepository :
return Mapper.Map<OrganizationReport>(updatedReport);
}
}
public Task UpdateMetricsAsync(Guid reportId, OrganizationReportMetricsData metrics)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
return dbContext.OrganizationReports
.Where(p => p.Id == reportId)
.UpdateAsync(p => new Models.OrganizationReport
{
ApplicationCount = metrics.ApplicationCount,
ApplicationAtRiskCount = metrics.ApplicationAtRiskCount,
CriticalApplicationCount = metrics.CriticalApplicationCount,
CriticalApplicationAtRiskCount = metrics.CriticalApplicationAtRiskCount,
MemberCount = metrics.MemberCount,
MemberAtRiskCount = metrics.MemberAtRiskCount,
CriticalMemberCount = metrics.CriticalMemberCount,
CriticalMemberAtRiskCount = metrics.CriticalMemberAtRiskCount,
PasswordCount = metrics.PasswordCount,
PasswordAtRiskCount = metrics.PasswordAtRiskCount,
CriticalPasswordCount = metrics.CriticalPasswordCount,
CriticalPasswordAtRiskCount = metrics.CriticalPasswordAtRiskCount,
RevisionDate = DateTime.UtcNow
});
}
}
}

View File

@@ -0,0 +1,39 @@
CREATE PROCEDURE [dbo].[OrganizationReport_UpdateMetrics]
@Id UNIQUEIDENTIFIER,
@ApplicationCount INT,
@ApplicationAtRiskCount INT,
@CriticalApplicationCount INT,
@CriticalApplicationAtRiskCount INT,
@MemberCount INT,
@MemberAtRiskCount INT,
@CriticalMemberCount INT,
@CriticalMemberAtRiskCount INT,
@PasswordCount INT,
@PasswordAtRiskCount INT,
@CriticalPasswordCount INT,
@CriticalPasswordAtRiskCount INT,
@RevisionDate DATETIME2(7)
AS
BEGIN
SET NOCOUNT ON;
UPDATE
[dbo].[OrganizationReport]
SET
[ApplicationCount] = @ApplicationCount,
[ApplicationAtRiskCount] = @ApplicationAtRiskCount,
[CriticalApplicationCount] = @CriticalApplicationCount,
[CriticalApplicationAtRiskCount] = @CriticalApplicationAtRiskCount,
[MemberCount] = @MemberCount,
[MemberAtRiskCount] = @MemberAtRiskCount,
[CriticalMemberCount] = @CriticalMemberCount,
[CriticalMemberAtRiskCount] = @CriticalMemberAtRiskCount,
[PasswordCount] = @PasswordCount,
[PasswordAtRiskCount] = @PasswordAtRiskCount,
[CriticalPasswordCount] = @CriticalPasswordCount,
[CriticalPasswordAtRiskCount] = @CriticalPasswordAtRiskCount,
[RevisionDate] = @RevisionDate
WHERE
[Id] = @Id
END

View File

@@ -1,4 +1,5 @@
using Bit.Api.Dirt.Controllers;
using Bit.Api.Dirt.Models.Response;
using Bit.Core.Context;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Models.Data;
@@ -39,7 +40,8 @@ public class OrganizationReportControllerTests
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
Assert.Equal(expectedReport, okResult.Value);
var expectedResponse = new OrganizationReportResponseModel(expectedReport);
Assert.Equivalent(expectedResponse, okResult.Value);
}
[Theory, BitAutoData]
@@ -262,7 +264,8 @@ public class OrganizationReportControllerTests
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
Assert.Equal(expectedReport, okResult.Value);
var expectedResponse = new OrganizationReportResponseModel(expectedReport);
Assert.Equivalent(expectedResponse, okResult.Value);
}
[Theory, BitAutoData]
@@ -365,7 +368,8 @@ public class OrganizationReportControllerTests
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
Assert.Equal(expectedReport, okResult.Value);
var expectedResponse = new OrganizationReportResponseModel(expectedReport);
Assert.Equivalent(expectedResponse, okResult.Value);
}
[Theory, BitAutoData]
@@ -597,7 +601,8 @@ public class OrganizationReportControllerTests
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
Assert.Equal(expectedReport, okResult.Value);
var expectedResponse = new OrganizationReportResponseModel(expectedReport);
Assert.Equivalent(expectedResponse, okResult.Value);
}
[Theory, BitAutoData]
@@ -812,7 +817,8 @@ public class OrganizationReportControllerTests
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
Assert.Equal(expectedReport, okResult.Value);
var expectedResponse = new OrganizationReportResponseModel(expectedReport);
Assert.Equivalent(expectedResponse, okResult.Value);
}
[Theory, BitAutoData]
@@ -1050,7 +1056,8 @@ public class OrganizationReportControllerTests
// Assert
var okResult = Assert.IsType<OkObjectResult>(result);
Assert.Equal(expectedReport, okResult.Value);
var expectedResponse = new OrganizationReportResponseModel(expectedReport);
Assert.Equivalent(expectedResponse, okResult.Value);
}
[Theory, BitAutoData]

View File

@@ -1,6 +1,7 @@
using AutoFixture;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Dirt.Entities;
using Bit.Core.Dirt.Reports.Models.Data;
using Bit.Core.Dirt.Repositories;
using Bit.Core.Repositories;
using Bit.Core.Test.AutoFixture.Attributes;
@@ -489,6 +490,49 @@ public class OrganizationReportRepositoryTests
Assert.Null(result);
}
[CiSkippedTheory, EfOrganizationReportAutoData]
public async Task UpdateMetricsAsync_ShouldUpdateMetricsCorrectly(
OrganizationReportRepository sqlOrganizationReportRepo,
SqlRepo.OrganizationRepository sqlOrganizationRepo)
{
// Arrange
var (org, report) = await CreateOrganizationAndReportAsync(sqlOrganizationRepo, sqlOrganizationReportRepo);
var metrics = new OrganizationReportMetricsData
{
ApplicationCount = 10,
ApplicationAtRiskCount = 2,
CriticalApplicationCount = 5,
CriticalApplicationAtRiskCount = 1,
MemberCount = 20,
MemberAtRiskCount = 4,
CriticalMemberCount = 10,
CriticalMemberAtRiskCount = 2,
PasswordCount = 100,
PasswordAtRiskCount = 15,
CriticalPasswordCount = 50,
CriticalPasswordAtRiskCount = 5
};
// Act
await sqlOrganizationReportRepo.UpdateMetricsAsync(report.Id, metrics);
var updatedReport = await sqlOrganizationReportRepo.GetByIdAsync(report.Id);
// Assert
Assert.Equal(metrics.ApplicationCount, updatedReport.ApplicationCount);
Assert.Equal(metrics.ApplicationAtRiskCount, updatedReport.ApplicationAtRiskCount);
Assert.Equal(metrics.CriticalApplicationCount, updatedReport.CriticalApplicationCount);
Assert.Equal(metrics.CriticalApplicationAtRiskCount, updatedReport.CriticalApplicationAtRiskCount);
Assert.Equal(metrics.MemberCount, updatedReport.MemberCount);
Assert.Equal(metrics.MemberAtRiskCount, updatedReport.MemberAtRiskCount);
Assert.Equal(metrics.CriticalMemberCount, updatedReport.CriticalMemberCount);
Assert.Equal(metrics.CriticalMemberAtRiskCount, updatedReport.CriticalMemberAtRiskCount);
Assert.Equal(metrics.PasswordCount, updatedReport.PasswordCount);
Assert.Equal(metrics.PasswordAtRiskCount, updatedReport.PasswordAtRiskCount);
Assert.Equal(metrics.CriticalPasswordCount, updatedReport.CriticalPasswordCount);
Assert.Equal(metrics.CriticalPasswordAtRiskCount, updatedReport.CriticalPasswordAtRiskCount);
}
private async Task<(Organization, OrganizationReport)> CreateOrganizationAndReportAsync(
IOrganizationRepository orgRepo,
IOrganizationReportRepository orgReportRepo)

View File

@@ -0,0 +1,39 @@
CREATE OR ALTER PROCEDURE [dbo].[OrganizationReport_UpdateMetrics]
@Id UNIQUEIDENTIFIER,
@ApplicationCount INT,
@ApplicationAtRiskCount INT,
@CriticalApplicationCount INT,
@CriticalApplicationAtRiskCount INT,
@MemberCount INT,
@MemberAtRiskCount INT,
@CriticalMemberCount INT,
@CriticalMemberAtRiskCount INT,
@PasswordCount INT,
@PasswordAtRiskCount INT,
@CriticalPasswordCount INT,
@CriticalPasswordAtRiskCount INT,
@RevisionDate DATETIME2(7)
AS
BEGIN
SET NOCOUNT ON;
UPDATE
[dbo].[OrganizationReport]
SET
[ApplicationCount] = @ApplicationCount,
[ApplicationAtRiskCount] = @ApplicationAtRiskCount,
[CriticalApplicationCount] = @CriticalApplicationCount,
[CriticalApplicationAtRiskCount] = @CriticalApplicationAtRiskCount,
[MemberCount] = @MemberCount,
[MemberAtRiskCount] = @MemberAtRiskCount,
[CriticalMemberCount] = @CriticalMemberCount,
[CriticalMemberAtRiskCount] = @CriticalMemberAtRiskCount,
[PasswordCount] = @PasswordCount,
[PasswordAtRiskCount] = @PasswordAtRiskCount,
[CriticalPasswordCount] = @CriticalPasswordCount,
[CriticalPasswordAtRiskCount] = @CriticalPasswordAtRiskCount,
[RevisionDate] = @RevisionDate
WHERE
[Id] = @Id
END