diff --git a/backend/Config/openapi.json b/backend/Config/openapi.json
index 0a59c3203..d5587da3d 100644
--- a/backend/Config/openapi.json
+++ b/backend/Config/openapi.json
@@ -67057,8 +67057,25 @@
"tags": [
"Email-Exchange > Spamfilter"
],
- "description": "Lists user reported email threat submissions (Defender Submissions with source 'user') for a tenant.",
+ "description": "Lists user reported messages (Outlook Report button) for a tenant. Source=AuditLog (default) reads the unified\naudit log two weeks per page, Source=Submissions reads the Defender Submissions list from Graph.",
"parameters": [
+ {
+ "name": "nextLink",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "Source",
+ "in": "query",
+ "description": "AuditLog (default) or Submissions",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
{
"$ref": "#/components/parameters/tenantFilter"
}
@@ -67152,9 +67169,9 @@
"type": "string",
"x-cipp-field-source": "graph-entity"
},
- "source": {
+ "Source": {
"type": "object",
- "x-cipp-field-source": "graph-entity"
+ "x-cipp-field-source": "graph-entity,frontend"
},
"Status": {
"type": "object",
diff --git a/backend/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-ListUserReportedMessages.ps1 b/backend/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-ListUserReportedMessages.ps1
index 2baf39a04..673bd9bcf 100644
--- a/backend/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-ListUserReportedMessages.ps1
+++ b/backend/Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Email-Exchange/Spamfilter/Invoke-ListUserReportedMessages.ps1
@@ -5,12 +5,15 @@ function Invoke-ListUserReportedMessages {
.ROLE
Exchange.SpamFilter.Read
.DESCRIPTION
- Lists user reported email threat submissions (Defender Submissions with source 'user') for a tenant.
+ Lists user reported messages (Outlook Report button) for a tenant. Source=AuditLog (default) reads the unified
+ audit log two weeks per page, Source=Submissions reads the Defender Submissions list from Graph.
#>
[CmdletBinding()]
param($Request, $TriggerMetadata)
$TenantFilter = $Request.Query.tenantFilter
+ # AuditLog (default) or Submissions
+ $Source = $Request.Query.Source
try {
if ($TenantFilter -eq 'AllTenants') {
@@ -18,12 +21,12 @@ function Invoke-ListUserReportedMessages {
$Metadata = [PSCustomObject]@{
QueueMessage = 'User reported messages are loaded per tenant. Select a tenant to view its reported messages.'
}
- } else {
+ } elseif ($Source -eq 'Submissions') {
+ # Defender Submissions via Graph: only what reached Microsoft analysis, Outlook reports mostly show up once an
+ # admin submits them (source 'administrator')
$Submissions = New-GraphGetRequest -uri 'https://graph.microsoft.com/beta/security/threatSubmission/emailThreats' -tenantid $TenantFilter -AsApp $true
$Results = [System.Collections.Generic.List[object]]::new()
foreach ($Submission in $Submissions) {
- # Admin submissions share the same Graph collection; this page only covers user reports
- if ($Submission.source -ne 'user') { continue }
$Results.Add([PSCustomObject]@{
ReportedDateTime = $Submission.createdDateTime
ReceivedDateTime = $Submission.receivedDateTime
@@ -31,8 +34,10 @@ function Invoke-ListUserReportedMessages {
Sender = $Submission.sender
SenderIP = $Submission.senderIP
RecipientEmail = $Submission.recipientEmailAddress
- ReportedBy = $Submission.createdBy.user.displayName
- ReporterEmail = $Submission.createdBy.user.email
+ # Graph returns createdBy flat ({ id, displayName, email }), the docs sample nests it under 'user'
+ ReportedBy = $Submission.createdBy.displayName ?? $Submission.createdBy.user.displayName ?? $Submission.createdBy.email ?? $Submission.createdBy.user.email
+ ReporterEmail = $Submission.createdBy.email ?? $Submission.createdBy.user.email
+ Source = $Submission.source
Category = $Submission.category
OriginalCategory = $Submission.originalCategory
Status = $Submission.status
@@ -45,6 +50,78 @@ function Invoke-ListUserReportedMessages {
})
}
$GraphRequest = $Results | Sort-Object -Property ReportedDateTime -Descending
+ } else {
+ # Graph's threatSubmission list skips Outlook Report button submissions, the unified audit log has them all.
+ # One two-week window per call, Metadata.nextLink points at the previous window so the table pages back
+ # until the audit log's 180-day standard retention.
+ # ponytail: plain searches capped at 5000 rows (no ReturnLargeSet session, that timed out at ~100s on a real
+ # tenant). A capped window is halved until it fits. Move to a cached DB job if this becomes more than a demo
+ $WindowDays = 14
+ $Now = (Get-Date).ToUniversalTime()
+ $Oldest = $Now.AddDays(-180)
+ $EndDate = if ($Request.Query.nextLink) { ([datetime]::Parse($Request.Query.nextLink, [cultureinfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AdjustToUniversal -bor [System.Globalization.DateTimeStyles]::AssumeUniversal)) } else { $Now }
+ $StartDate = $EndDate.AddDays(-$WindowDays)
+ if ($StartDate -lt $Oldest) { $StartDate = $Oldest }
+
+ $ResultSize = 5000
+ # A capped search drops rows, so halve the window toward EndDate until it fits. The next page then starts at the
+ # narrowed StartDate, nothing between the windows is skipped. Below an hour it stays capped and says so.
+ do {
+ $SearchParams = @{
+ StartDate = $StartDate
+ EndDate = $EndDate
+ RecordType = 'MailSubmission'
+ Operations = @('UserSubmission', 'CustomSubmission', 'AttackSimUserSubmission')
+ ResultSize = $ResultSize
+ }
+ # A search with no hits returns nothing, not an empty array
+ $Records = @(New-ExoRequest -tenantid $TenantFilter -cmdlet 'Search-UnifiedAuditLog' -cmdParams $SearchParams | Where-Object { $_.AuditData })
+ $Capped = $Records.Count -ge $ResultSize
+ $CanSplit = ($EndDate - $StartDate).TotalHours -gt 1
+ if ($Capped -and $CanSplit) { $StartDate = $StartDate.AddTicks([long](($EndDate - $StartDate).Ticks / 2)) }
+ } while ($Capped -and $CanSplit)
+ # Audit timestamps are UTC without an offset (ConvertFrom-Json makes them Unspecified datetimes), stamp them as UTC
+ $AsUtc = { param($Value) if ($Value -is [datetime]) { [datetime]::SpecifyKind($Value, 'Utc').ToString('o') } elseif ($Value) { "$($Value)Z" } }
+ $Results = [System.Collections.Generic.List[object]]::new()
+ # A reported phish simulation logs both UserSubmission and AttackSimUserSubmission for the same submission,
+ # keep one row per submission and prefer the simulation record
+ $Seen = [System.Collections.Generic.HashSet[string]]::new()
+ foreach ($Record in ($Records | Sort-Object { $_.Operations -ne 'AttackSimUserSubmission' })) {
+ $Data = try { $Record.AuditData | ConvertFrom-Json -ErrorAction Stop } catch { $null }
+ if (-not $Data) { continue }
+ if (-not $Seen.Add([string]($Data.SubmissionId ?? $Data.Id))) { continue }
+ $Results.Add([PSCustomObject]@{
+ ReportedDateTime = & $AsUtc ($Data.CreationTime ?? $Record.CreationDate)
+ ReceivedDateTime = & $AsUtc $Data.MessageDate
+ Subject = $Data.Subject
+ Sender = $Data.P2Sender ?? $Data.P1Sender
+ SenderIP = $Data.SenderIP
+ RecipientEmail = @($Data.Recipients) -join ', '
+ ReportedBy = $Data.UserId
+ ReporterEmail = $Data.UserId
+ Source = $Data.Operation
+ # SubmissionType is an undocumented number (0/1 seen), shown raw for the demo
+ Category = "$($Data.SubmissionType)"
+ OriginalCategory = ($Data.ExtendedProperties | Where-Object Name -EQ 'OriginalVerdict').Value
+ Status = $Data.SubmissionState
+ ResultCategory = $Data.RescanResult.RescanVerdict ?? ($Data.ExtendedProperties | Where-Object Name -EQ 'RescanVerdict').Value
+ ResultDetail = $Data.DeliveryMessageInfo.FinalFilterVerdict
+ AdminReviewResult = $null
+ InternetMessageId = $Data.InternetMessageId
+ Id = $Data.SubmissionId ?? $Data.Id
+ Tenant = $TenantFilter
+ })
+ }
+ $GraphRequest = $Results | Sort-Object -Property ReportedDateTime -Descending
+ $Metadata = [PSCustomObject]@{
+ WindowStart = $StartDate.ToString('o')
+ WindowEnd = $EndDate.ToString('o')
+ Complete = -not $Capped
+ }
+ if ($Capped) {
+ $Metadata | Add-Member -NotePropertyName Warning -NotePropertyValue "More than $ResultSize reports between $($StartDate.ToString('u')) and $($EndDate.ToString('u')), only the first $ResultSize are listed."
+ }
+ if ($StartDate -gt $Oldest) { $Metadata | Add-Member -NotePropertyName nextLink -NotePropertyValue $StartDate.ToString('o') }
}
$Body = [PSCustomObject]@{
Results = @($GraphRequest)
diff --git a/backend/Tests/Endpoint/Invoke-ListUserReportedMessages.Tests.ps1 b/backend/Tests/Endpoint/Invoke-ListUserReportedMessages.Tests.ps1
new file mode 100644
index 000000000..90f7ad85e
--- /dev/null
+++ b/backend/Tests/Endpoint/Invoke-ListUserReportedMessages.Tests.ps1
@@ -0,0 +1,94 @@
+# Pester tests for Invoke-ListUserReportedMessages.
+#
+# Pins the audit log paging contract: a capped two-week window is halved until it fits and the next
+# page continues from the narrowed start (no reports skipped), paging stops at the 180-day retention,
+# and a reported phish simulation logged twice collapses to one row.
+
+BeforeAll {
+ $RepoRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSCommandPath))
+ $FunctionPath = Get-ChildItem -Path (Join-Path $RepoRoot 'Modules') -Recurse -Filter 'Invoke-ListUserReportedMessages.ps1' -File -ErrorAction SilentlyContinue |
+ Select-Object -First 1 -ExpandProperty FullName
+ if (-not $FunctionPath) { throw 'Could not locate Invoke-ListUserReportedMessages.ps1 under Modules/' }
+
+ class HttpResponseContext {
+ [int]$StatusCode
+ [object]$Body
+ }
+ $Accelerators = [PSObject].Assembly.GetType('System.Management.Automation.TypeAccelerators')
+ if (-not ('HttpStatusCode' -as [type])) {
+ $Accelerators::Add('HttpStatusCode', [System.Net.HttpStatusCode])
+ }
+
+ function New-ExoRequest { param($tenantid, $cmdlet, $cmdParams) }
+ function New-GraphGetRequest { param($uri, $tenantid, $AsApp) }
+ function Get-NormalizedError { param($Message) $Message }
+
+ . $FunctionPath
+
+ function New-AuditRecord {
+ param([string]$Operation = 'UserSubmission', [string]$SubmissionId = [guid]::NewGuid().ToString())
+ [pscustomobject]@{
+ Operations = $Operation
+ AuditData = (@{
+ CreationTime = '2026-10-09T07:34:03'
+ Operation = $Operation
+ UserId = 'user@contoso.com'
+ Subject = 'Reported'
+ SubmissionId = $SubmissionId
+ RescanResult = @{ RescanVerdict = 'Phish' }
+ } | ConvertTo-Json -Compress)
+ }
+ }
+
+ function Invoke-List {
+ param([hashtable]$Query)
+ $Request = [pscustomobject]@{ Query = [pscustomobject]$Query }
+ Invoke-ListUserReportedMessages -Request $Request -TriggerMetadata $null
+ }
+}
+
+Describe 'Invoke-ListUserReportedMessages (audit log source)' {
+ It 'halves a capped window and continues paging from the narrowed start' {
+ # More than 5000 records in any window over 3.5 days, a handful below that
+ Mock New-ExoRequest {
+ $Span = ($cmdParams.EndDate - $cmdParams.StartDate).TotalDays
+ $Count = if ($Span -gt 3.5) { 5000 } else { 3 }
+ 1..$Count | ForEach-Object { New-AuditRecord }
+ }
+
+ $Response = Invoke-List @{ tenantFilter = 'contoso.onmicrosoft.com' }
+
+ # 14 -> 7 -> 3.5 days: three searches, the last one fits
+ Should -Invoke New-ExoRequest -Times 3 -Exactly
+ $Response.Body.Results.Count | Should -Be 3
+ $Response.Body.Metadata.Complete | Should -BeTrue
+ $Start = [datetime]::Parse($Response.Body.Metadata.WindowStart).ToUniversalTime()
+ $End = [datetime]::Parse($Response.Body.Metadata.WindowEnd).ToUniversalTime()
+ ($End - $Start).TotalDays | Should -Be 3.5
+ $Response.Body.Metadata.nextLink | Should -Be $Response.Body.Metadata.WindowStart
+ }
+
+ It 'stops linking further pages at the 180-day retention' {
+ Mock New-ExoRequest { New-AuditRecord }
+
+ $Response = Invoke-List @{ tenantFilter = 'contoso.onmicrosoft.com'; nextLink = (Get-Date).ToUniversalTime().AddDays(-175).ToString('o') }
+
+ $Response.Body.Metadata.PSObject.Properties.Name | Should -Not -Contain 'nextLink'
+ $Start = [datetime]::Parse($Response.Body.Metadata.WindowStart).ToUniversalTime()
+ ((Get-Date).ToUniversalTime() - $Start).TotalDays | Should -BeLessOrEqual 180.01
+ }
+
+ It 'collapses a reported phish simulation logged under both operations into one row' {
+ Mock New-ExoRequest {
+ New-AuditRecord -Operation 'UserSubmission' -SubmissionId 'sim-1'
+ New-AuditRecord -Operation 'AttackSimUserSubmission' -SubmissionId 'sim-1'
+ New-AuditRecord -Operation 'UserSubmission' -SubmissionId 'real-1'
+ }
+
+ $Response = Invoke-List @{ tenantFilter = 'contoso.onmicrosoft.com' }
+
+ $Response.Body.Results.Count | Should -Be 2
+ ($Response.Body.Results | Where-Object Id -EQ 'sim-1').Source | Should -Be 'AttackSimUserSubmission'
+ $Response.Body.Results[0].ReportedDateTime | Should -Match 'Z$'
+ }
+}
diff --git a/docs/user-documentation/email/administration/quarantine/user-reported.md b/docs/user-documentation/email/administration/quarantine/user-reported.md
index 7a929c995..85809169c 100644
--- a/docs/user-documentation/email/administration/quarantine/user-reported.md
+++ b/docs/user-documentation/email/administration/quarantine/user-reported.md
@@ -2,26 +2,55 @@
This tab lists the messages users have reported as phishing, junk, or not junk through the Microsoft report buttons in Outlook, so reports can be reviewed with the same tooling the Email tab offers for quarantined messages: preview the message safely, read its headers, download the EML, and trace its delivery, all without going into the Defender portal.
-The list comes from the Microsoft Defender Submissions store, so it carries every user submission the tenant has, including Microsoft's verdict where the report was sent on to Microsoft for analysis.
+## Sources
+
+A toggle in the top right of the table switches between two sources.
+
+| Source | Shows |
+| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| Audit Log (default) | Every message reported with the Outlook Report button, read from the tenant's unified audit log. Reported phishing simulation messages are included and marked as such in the Source column. Each row carries Microsoft's rescan verdict. |
+| Submissions | The Microsoft Defender Submissions list. Microsoft only exposes submissions that reached Microsoft analysis here, so Outlook reports usually only appear once an admin has submitted them to Microsoft from the Defender portal. Those rows list with the source `administrator`. |
+
+The Audit Log is the default because the Submissions list leaves out most Outlook reports, even when the tenant sends reported messages to Microsoft. Admin submissions in the Submissions list usually carry no message ID either, so the message actions below are greyed out for them.
+
+{% hint style="info" %}
+The Audit Log loads two weeks at a time and keeps loading older windows in the background, up to the audit log's 180-day retention. New reports can take up to 90 minutes to appear in the audit log. Unified audit logging must be turned on in the tenant, which is the Microsoft default.
+{% endhint %}
+
+The tip above the table links to the User reported tab of the Defender Submissions page for the selected tenant.
## Filters
-| Filter | Shows |
-| -------------------- | --------------------------------------------------- |
-| Reported as Phishing | Messages the user reported as phishing. |
-| Reported as Junk | Messages the user reported as junk. |
-| Reported as Malware | Messages the user reported as malware. |
-| Reported as Not Junk | Messages the user reported as incorrectly caught. |
+With the Audit Log source:
+
+| Filter | Shows |
+| ----------------- | -------------------------------------------------------------- |
+| Verdict: Phish | Reports Microsoft's rescan judged to be phishing. |
+| Verdict: Spam | Reports Microsoft's rescan judged to be spam. |
+| Verdict: Not Spam | Reports Microsoft's rescan judged to be legitimate. |
+
+With the Submissions source:
+
+| Filter | Shows |
+| -------------------- | ------------------------------------------------- |
+| Reported as Phishing | Messages reported as phishing. |
+| Reported as Junk | Messages reported as junk. |
+| Reported as Malware | Messages reported as malware. |
+| Reported as Not Junk | Messages reported as incorrectly caught. |
+
+Switching the source clears any filters you applied.
## Table Details
-The properties returned are for the Microsoft Graph email threat submissions API. For more information please see the [Microsoft documentation](https://learn.microsoft.com/graph/api/security-emailthreatsubmission-list-emailthreats).
+With the Audit Log source, the rows come from the `MailSubmission` audit records (operations `UserSubmission`, `CustomSubmission` and `AttackSimUserSubmission`). For more information please see the [Microsoft documentation](https://learn.microsoft.com/office/office-365-management-api/office-365-management-activity-api-schema#submission-schema). The Category column shows the record's raw submission type number, which Microsoft does not document, so use the verdict instead.
+
+With the Submissions source, the properties returned are for the Microsoft Graph email threat submissions API. For more information please see the [Microsoft documentation](https://learn.microsoft.com/graph/api/security-emailthreatsubmission-list-emailthreats).
Reports are listed newest first. This list is loaded per tenant, so select a specific tenant to view its reported messages.
## Report Details
-**More Info** opens a flyout with the submission record: when the message was received and reported, the sender, sending IP, recipient, who reported it and as what category, the submission status, and Microsoft's analysis verdict where one exists. Fields with no value are left out rather than shown empty. The actions offered in the table are repeated at the foot of the flyout.
+**More Info** opens a flyout with the report record: when the message was received and reported, the sender, sending IP, recipient, who reported it, the source, the submission status, and Microsoft's verdict where one exists. Fields with no value are left out rather than shown empty. The actions offered in the table are repeated at the foot of the flyout.
## Table Actions
@@ -29,7 +58,7 @@ Reports are listed newest first. This list is loaded per tenant, so select a spe
## Where the message content comes from
-The Submissions store itself only holds the report, not the message, so for the preview, header, and download actions CIPP retrieves the message content in two steps: it first looks for the message in quarantine and exports it from there, and when the message was never quarantined it falls back to reading the copy still in the recipient's (or reporter's) mailbox through the Graph API.
+Neither source holds the message itself, only the report, so for the preview, header, and download actions CIPP retrieves the message content in two steps: it first looks for the message in quarantine and exports it from there, and when the message was never quarantined it falls back to reading the copy still in the recipient's (or reporter's) mailbox through the Graph API.
{% hint style="info" %}
The mailbox fallback needs the `Mail.Read` application permission on your Secure Application Model application, which CIPP does not request by default. Without it, preview and download still work for reported messages that are in quarantine, and the actions report a clear error for the rest. The remaining actions on this page work without it.
diff --git a/frontend/src/components/CippComponents/CippUserReportedMessagesTable.jsx b/frontend/src/components/CippComponents/CippUserReportedMessagesTable.jsx
index fb0636656..00c898456 100644
--- a/frontend/src/components/CippComponents/CippUserReportedMessagesTable.jsx
+++ b/frontend/src/components/CippComponents/CippUserReportedMessagesTable.jsx
@@ -1,12 +1,18 @@
import { useEffect, useState } from 'react'
import { CippIcons } from '../../utils/icon-registry'
import {
+ Alert,
+ Box,
CircularProgress,
Dialog,
DialogContent,
DialogTitle,
IconButton,
+ Link,
Skeleton,
+ ToggleButton,
+ ToggleButtonGroup,
+ Tooltip,
Typography,
} from '@mui/material'
import { CippTablePage } from './CippTablePage.jsx'
@@ -23,21 +29,84 @@ const traceDetailColumns = [
'RecipientAddress',
]
-const categoryFilters = [
+const columnFilters = (column, entries) =>
+ entries.map(({ filterName, value }) => ({
+ filterName,
+ value: [{ id: column, value }],
+ type: 'column',
+ filterType: 'equal',
+ }))
+
+// Audit log: Microsoft's rescan verdict, the reported category there is an undocumented number
+const auditLogFilters = columnFilters('ResultCategory', [
+ { filterName: 'Verdict: Phish', value: 'Phish' },
+ { filterName: 'Verdict: Spam', value: 'Spam' },
+ { filterName: 'Verdict: Not Spam', value: 'NotSpam' },
+])
+
+// Defender Submissions (Graph): the submission category
+const submissionFilters = columnFilters('Category', [
{ filterName: 'Reported as Phishing', value: 'phishing' },
{ filterName: 'Reported as Junk', value: 'spam' },
{ filterName: 'Reported as Malware', value: 'malware' },
{ filterName: 'Reported as Not Junk', value: 'notJunk' },
-].map(({ filterName, value }) => ({
- filterName,
- value: [{ id: 'Category', value }],
- type: 'column',
- filterType: 'equal',
-}))
+])
+
+const sourceTips = {
+ AuditLog:
+ 'Loaded from the unified audit log, two weeks at a time, up to 180 days back. New reports can take up to 90 minutes to show up.',
+ Submissions:
+ 'Loaded from the Defender Submissions list. Microsoft only exposes submissions that reached Microsoft analysis, so Outlook reports often only show up here after an admin submits them to Microsoft.',
+}
export const CippUserReportedMessagesTable = () => {
const tenantFilter = useSettings().currentTenant
- const queryKey = `UserReportedMessages-${tenantFilter}`
+ // AuditLog: every Outlook report from the unified audit log. Submissions: the Defender Submissions list via Graph
+ const [source, setSource] = useState('AuditLog')
+ const queryKey = `UserReportedMessages-${tenantFilter}-${source}`
+
+ // Defender's tid wants the tenant GUID. Same request as the tenant selector, so it shares that cache entry
+ const tenantList = ApiGetCall({
+ url: '/api/listTenants',
+ data: { AllTenantSelector: true },
+ queryKey: 'TenantSelector',
+ })
+ const tenantId = Array.isArray(tenantList.data)
+ ? tenantList.data.find((t) => t.defaultDomainName === tenantFilter)?.customerId
+ : undefined
+
+ const sourceToggle = (
+
+ {
+ if (newSource !== null) setSource(newSource)
+ }}
+ sx={{ '& .MuiToggleButton-root': { py: 0.25, px: 1, fontSize: '0.75rem' } }}
+ >
+
+
+
+
+ Audit Log
+
+
+
+
+
+
+
+ Submissions
+
+
+
+
+
+ )
// In the AllTenants view each row belongs to a different tenant (row.Tenant); per-message
// actions must target that tenant rather than the page-level "AllTenants" selection. Falls back
@@ -71,7 +140,10 @@ export const CippUserReportedMessagesTable = () => {
const receivedMs = new Date(receivedTime).getTime()
if (Number.isNaN(receivedMs)) return null
const receivedSeconds = Math.floor(receivedMs / 1000)
- return { startDate: receivedSeconds - 86400, endDate: receivedSeconds + 86400 }
+ return {
+ startDate: receivedSeconds - 86400,
+ endDate: receivedSeconds + 86400,
+ }
}
const contentParams = (row) => ({
@@ -277,6 +349,7 @@ export const CippUserReportedMessagesTable = () => {
'Sender',
'RecipientEmail',
'ReportedBy',
+ 'Source',
'Category',
'Status',
'ResultCategory',
@@ -298,14 +371,36 @@ export const CippUserReportedMessagesTable = () => {
return (
<>
+ {sourceTips[source]}
+ {tenantId && (
+ <>
+ {' '}
+
+ Open the user reported submissions in Defender
+
+ >
+ )}
+
+ }
/>