Skip to content

Commit a04d1fa

Browse files
authored
Merge pull request #874 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents 888bafb + 5f2ca52 commit a04d1fa

8 files changed

Lines changed: 372 additions & 6 deletions

Modules/CIPPCore/Public/Entrypoints/HTTP Functions/Tenant/Reports/Invoke-ListGraphReports.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ function Invoke-ListGraphReports {
9494
} catch {
9595
$UriBare = "https://graph.microsoft.com/beta/reports/$Report"
9696
Write-Information "Period-based fetch failed, retrying: $UriBare"
97-
$Data = New-GraphGetRequest -uri $UriBare -tenantid $TenantFilter -AsApp $true
97+
$Data = New-GraphGetRequest -uri $UriBare -tenantid $TenantFilter
9898
}
9999
}
100100

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
function Invoke-ExecGenerateReportBuilderReport {
2+
<#
3+
.FUNCTIONALITY
4+
Entrypoint
5+
.ROLE
6+
CIPP.Core.ReadWrite
7+
#>
8+
[CmdletBinding()]
9+
param($Request, $TriggerMetadata)
10+
11+
$APIName = $TriggerMetadata.FunctionName
12+
$Headers = $Request.Headers
13+
14+
try {
15+
$Body = $Request.Body
16+
$Action = $Body.Action
17+
18+
if ($Action -eq 'delete') {
19+
if ([string]::IsNullOrEmpty($Body.ReportGUID)) {
20+
throw 'ReportGUID is required for deletion'
21+
}
22+
$ReportTable = Get-CippTable -tablename 'ReportBuilderReports'
23+
$ExistingEntity = Get-CIPPAzDataTableEntity @ReportTable -Filter "RowKey eq '$($Body.ReportGUID)'"
24+
if ($ExistingEntity) {
25+
Remove-AzDataTableEntity @ReportTable -Entity $ExistingEntity
26+
Write-LogMessage -headers $Headers -API $APIName -message "Deleted generated report '$($Body.ReportGUID)'" -Sev 'Info'
27+
$Result = @{ Results = 'Successfully deleted generated report' }
28+
} else {
29+
$Result = @{ Results = 'Report not found' }
30+
}
31+
$StatusCode = [HttpStatusCode]::OK
32+
} else {
33+
34+
$TenantFilter = $Body.TenantFilter ?? $Request.Query.TenantFilter
35+
$TemplateName = $Body.TemplateName ?? $Request.Query.TemplateName
36+
37+
if ([string]::IsNullOrEmpty($TenantFilter)) {
38+
throw 'TenantFilter is required'
39+
}
40+
41+
# Delegate to the scheduler-callable function
42+
$GenerateParams = @{
43+
TenantFilter = $TenantFilter
44+
TemplateName = $TemplateName
45+
}
46+
if ($Body.Blocks) {
47+
$GenerateParams.Blocks = if ($Body.Blocks -is [string]) { $Body.Blocks } else { ConvertTo-Json -InputObject @($Body.Blocks) -Depth 20 -Compress }
48+
}
49+
if ($Body.TemplateGUID) {
50+
$GenerateParams.TemplateGUID = $Body.TemplateGUID
51+
}
52+
53+
$GenerateResult = Push-ExecGenerateReportBuilderReport @GenerateParams
54+
Write-LogMessage -headers $Headers -API $APIName -tenant $TenantFilter -message "Generated report builder report '$TemplateName'" -Sev 'Info'
55+
56+
$Result = @{
57+
Results = $GenerateResult
58+
}
59+
$StatusCode = [HttpStatusCode]::OK
60+
61+
} # end else (generate)
62+
} catch {
63+
$ErrorMessage = Get-CippException -Exception $_
64+
Write-LogMessage -headers $Headers -API $APIName -message "Report generation error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage
65+
$Result = @{ Results = "Error: $($ErrorMessage.NormalizedError)" }
66+
$StatusCode = [HttpStatusCode]::BadRequest
67+
}
68+
69+
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
70+
StatusCode = $StatusCode
71+
Body = ConvertTo-Json -InputObject $Result -Depth 20
72+
})
73+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
function Invoke-ExecReportBuilderTemplate {
2+
<#
3+
.FUNCTIONALITY
4+
Entrypoint
5+
.ROLE
6+
CIPP.Core.ReadWrite
7+
#>
8+
[CmdletBinding()]
9+
param($Request, $TriggerMetadata)
10+
11+
$APIName = $TriggerMetadata.FunctionName
12+
$Headers = $Request.Headers
13+
14+
try {
15+
$Body = $Request.Body
16+
$Action = $Body.Action
17+
18+
$Table = Get-CippTable -tablename 'ReportBuilderTemplates'
19+
20+
switch ($Action) {
21+
'save' {
22+
if ([string]::IsNullOrEmpty($Body.Name)) {
23+
throw 'Template name is required'
24+
}
25+
26+
$GUID = if ($Body.GUID) { $Body.GUID } else { (New-Guid).GUID }
27+
$BlocksJson = ConvertTo-Json -InputObject @($Body.Blocks) -Depth 20 -Compress
28+
29+
$Entity = @{
30+
PartitionKey = 'ReportBuilderTemplate'
31+
RowKey = [string]$GUID
32+
Name = [string]$Body.Name
33+
Blocks = [string]$BlocksJson
34+
CreatedAt = [string](Get-Date).ToString('o')
35+
GUID = [string]$GUID
36+
}
37+
38+
Add-CIPPAzDataTableEntity @Table -Entity $Entity -Force
39+
Write-LogMessage -headers $Headers -API $APIName -message "Saved report builder template '$($Body.Name)' with GUID $GUID" -Sev 'Info'
40+
41+
$Result = @{
42+
Results = "Successfully saved report builder template '$($Body.Name)'"
43+
GUID = $GUID
44+
}
45+
}
46+
'delete' {
47+
if ([string]::IsNullOrEmpty($Body.GUID)) {
48+
throw 'Template GUID is required for deletion'
49+
}
50+
51+
$ExistingEntity = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq 'ReportBuilderTemplate' and RowKey eq '$($Body.GUID)'"
52+
if ($ExistingEntity) {
53+
Remove-AzDataTableEntity @Table -Entity $ExistingEntity
54+
Write-LogMessage -headers $Headers -API $APIName -message "Deleted report builder template '$($Body.GUID)'" -Sev 'Info'
55+
$Result = @{ Results = 'Successfully deleted report builder template' }
56+
} else {
57+
throw 'Template not found'
58+
}
59+
}
60+
default {
61+
throw "Unknown action: $Action"
62+
}
63+
}
64+
65+
$StatusCode = [HttpStatusCode]::OK
66+
} catch {
67+
$ErrorMessage = Get-CippException -Exception $_
68+
Write-LogMessage -headers $Headers -API $APIName -message "Report builder template error: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage
69+
$Result = @{ Results = "Error: $($ErrorMessage.NormalizedError)" }
70+
$StatusCode = [HttpStatusCode]::BadRequest
71+
}
72+
73+
return ([HttpResponseContext]@{
74+
StatusCode = $StatusCode
75+
Body = ConvertTo-Json -InputObject $Result -Depth 10
76+
})
77+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
function Invoke-ListGeneratedReports {
2+
<#
3+
.FUNCTIONALITY
4+
Entrypoint
5+
.ROLE
6+
CIPP.Core.Read
7+
#>
8+
[CmdletBinding()]
9+
param($Request, $TriggerMetadata)
10+
11+
$APIName = $TriggerMetadata.FunctionName
12+
Write-LogMessage -user $Request.Headers.'x-ms-client-principal' -API $APIName -message 'Accessed this API' -Sev 'Debug'
13+
14+
try {
15+
$TenantFilter = $Request.Query.TenantFilter ?? $Request.Query.tenantFilter
16+
$ReportGUID = $Request.Query.ReportGUID
17+
18+
$Table = Get-CippTable -tablename 'ReportBuilderReports'
19+
20+
if ($ReportGUID) {
21+
# Fetch specific report
22+
$Filter = "RowKey eq '$ReportGUID'"
23+
$Entities = @(Get-CIPPAzDataTableEntity @Table -Filter $Filter)
24+
} elseif ($TenantFilter) {
25+
# Fetch all reports for tenant
26+
$Filter = "PartitionKey eq '$TenantFilter'"
27+
$Entities = @(Get-CIPPAzDataTableEntity @Table -Filter $Filter)
28+
} else {
29+
# Fetch all reports
30+
$Entities = @(Get-CIPPAzDataTableEntity @Table)
31+
}
32+
33+
$Reports = @($Entities | ForEach-Object {
34+
$Blocks = @()
35+
if ($_.Blocks) {
36+
try {
37+
$Blocks = @(ConvertFrom-Json -InputObject $_.Blocks)
38+
} catch {
39+
$Blocks = @()
40+
}
41+
}
42+
$SectionCount = $Blocks.Count
43+
$TestCount = @($Blocks | Where-Object { $_.type -eq 'test' }).Count
44+
$CustomCount = @($Blocks | Where-Object { $_.type -eq 'blank' }).Count
45+
[PSCustomObject]@{
46+
RowKey = $_.RowKey
47+
TemplateName = $_.TemplateName
48+
TenantFilter = $_.TenantFilter
49+
Blocks = $Blocks
50+
Sections = $SectionCount
51+
TestCount = $TestCount
52+
CustomCount = $CustomCount
53+
GeneratedAt = $_.GeneratedAt
54+
Status = $_.Status
55+
ReportURL = "/tools/report-builder?reportId=$($_.RowKey)"
56+
}
57+
})
58+
59+
$StatusCode = [HttpStatusCode]::OK
60+
$Body = @($Reports)
61+
} catch {
62+
$ErrorMessage = Get-CippException -Exception $_
63+
Write-LogMessage -user $Request.Headers.'x-ms-client-principal' -API $APIName -message "Failed to list generated reports: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage
64+
$Body = @{ Results = "Error: $($ErrorMessage.NormalizedError)" }
65+
$StatusCode = [HttpStatusCode]::BadRequest
66+
}
67+
68+
return ([HttpResponseContext]@{
69+
StatusCode = $StatusCode
70+
Body = ConvertTo-Json -InputObject $Body -Depth 20 -Compress
71+
})
72+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
function Invoke-ListReportBuilderTemplates {
2+
<#
3+
.FUNCTIONALITY
4+
Entrypoint
5+
.ROLE
6+
CIPP.Core.Read
7+
#>
8+
[CmdletBinding()]
9+
param($Request, $TriggerMetadata)
10+
11+
$APIName = $TriggerMetadata.FunctionName
12+
Write-LogMessage -user $Request.Headers.'x-ms-client-principal' -API $APIName -message 'Accessed this API' -Sev 'Debug'
13+
14+
try {
15+
$Table = Get-CippTable -tablename 'ReportBuilderTemplates'
16+
$Filter = "PartitionKey eq 'ReportBuilderTemplate'"
17+
$Entities = Get-CIPPAzDataTableEntity @Table -Filter $Filter
18+
19+
$Templates = @($Entities | ForEach-Object {
20+
$Blocks = @()
21+
if ($_.Blocks) {
22+
try {
23+
$Blocks = @(ConvertFrom-Json -InputObject $_.Blocks)
24+
} catch {
25+
$Blocks = @()
26+
}
27+
}
28+
$TestCount = @($Blocks | Where-Object { $_.type -eq 'test' }).Count
29+
$CustomCount = @($Blocks | Where-Object { $_.type -eq 'blank' }).Count
30+
[PSCustomObject]@{
31+
RowKey = $_.RowKey
32+
Name = $_.Name
33+
Blocks = $Blocks
34+
Sections = $Blocks.Count
35+
TestCount = $TestCount
36+
CustomCount = $CustomCount
37+
CreatedAt = $_.CreatedAt
38+
GUID = $_.GUID
39+
}
40+
})
41+
42+
$StatusCode = [HttpStatusCode]::OK
43+
$Body = @($Templates)
44+
} catch {
45+
$ErrorMessage = Get-CippException -Exception $_
46+
Write-LogMessage -user $Request.Headers.'x-ms-client-principal' -API $APIName -message "Failed to list report builder templates: $($ErrorMessage.NormalizedError)" -Sev 'Error' -LogData $ErrorMessage
47+
$Body = @{ Results = "Error: $($ErrorMessage.NormalizedError)" }
48+
$StatusCode = [HttpStatusCode]::BadRequest
49+
}
50+
51+
return ([HttpResponseContext]@{
52+
StatusCode = $StatusCode
53+
Body = ConvertTo-Json -InputObject $Body -Depth 20 -Compress
54+
})
55+
}

Modules/CIPPCore/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady016.ps1

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ function Invoke-CippTestCopilotReady016 {
3737
$Result += "|-----|-------------|`n"
3838

3939
foreach ($App in ($AppCounts | Sort-Object Value -Descending)) {
40-
# Format the property name to be more readable
41-
$AppName = $App.Name -replace '([A-Z])', ' $1' -replace '^ ', '' -replace 'Active Users', ''
40+
# Format the property name to be more readable — insert space before each capital
41+
# that follows a lowercase letter to avoid double-spacing sequences like 'AI' -> 'A I'
42+
$AppName = $App.Name -replace '([a-z])([A-Z])', '$1 $2' -replace 'Active Users', ''
4243
$Result += "| $($AppName.Trim()) | $($App.Value) |`n"
4344
}
4445

Modules/CIPPCore/Public/Tests/CopilotReadiness/Identity/Invoke-CippTestCopilotReady017.ps1

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ function Invoke-CippTestCopilotReady017 {
4343
}
4444

4545
# Determine trend direction if we have a count field and at least 2 data points
46+
$Status = 'Informational'
4647
if ($CountField -and $TrendPoints.Count -ge 2) {
4748
$Earliest = [int]($TrendPoints[0].$CountField)
4849
$Latest = [int]($TrendPoints[-1].$CountField)
@@ -51,15 +52,12 @@ function Invoke-CippTestCopilotReady017 {
5152
if ($Delta -gt 0) {
5253
$TrendIcon = '📈'
5354
$TrendText = "**Trending up** — active Copilot users increased by $Delta over the 7-day window."
54-
$Status = 'Informational'
5555
} elseif ($Delta -eq 0) {
5656
$TrendIcon = '➡️'
5757
$TrendText = '**Stable** — active Copilot user count is unchanged over the 7-day window.'
58-
$Status = 'Informational'
5958
} else {
6059
$TrendIcon = '📉'
6160
$TrendText = "**Trending down** — active Copilot users decreased by $([math]::Abs($Delta)) over the 7-day window. Consider reviewing adoption activities to re-engage users."
62-
$Status = 'Informational'
6361
}
6462

6563
$Result += "`n$TrendIcon $TrendText"

0 commit comments

Comments
 (0)