diff --git a/src/audit-logs/lib/config.json b/src/audit-logs/lib/config.json
index ae2cdc8b31..a4aed9448c 100644
--- a/src/audit-logs/lib/config.json
+++ b/src/audit-logs/lib/config.json
@@ -3,5 +3,11 @@
"apiOnlyEvents": "This event is not available in the web interface, only via the REST API, audit log streaming, or JSON/CSV exports.",
"apiRequestEvent": "This event is only available via audit log streaming."
},
+ "_categoryNotesDocumentation": "Category notes provide additional context for audit log event categories. Currently, notes are plain text and not version-specific. Future enhancement: add versioning support for different deployment types (GHEC, GHES, FPT).",
+ "categoryNotes": {
+ "members_can_create_pages": "For more information, see \"Managing the publication of GitHub Pages sites for your organization.\"",
+ "git": "Note: Git events have special access requirements and retention policies that differ from other audit log events. For GitHub Enterprise Cloud, access Git events via the REST API only with 7-day retention. For GitHub Enterprise Server, Git events must be enabled in audit log configuration and are not included in search results.",
+ "sso_redirect": "Note: Automatically redirecting users to sign in is currently in beta for Enterprise Managed Users and subject to change."
+ },
"sha": "30f9be27cbe4d9f3729f8fb335ce8b254ca3b54a"
-}
\ No newline at end of file
+}
diff --git a/src/audit-logs/lib/index.ts b/src/audit-logs/lib/index.ts
index 8321e7a845..9f1608e309 100644
--- a/src/audit-logs/lib/index.ts
+++ b/src/audit-logs/lib/index.ts
@@ -8,7 +8,10 @@ import type {
CategorizedEvents,
VersionedAuditLogData,
RawAuditLogEventT,
+ CategoryNotes,
+ AuditLogConfig,
} from '../types'
+import config from './config.json'
export const AUDIT_LOG_DATA_DIR = 'src/audit-logs/data'
@@ -21,6 +24,12 @@ type PipelineConfig = {
appendedDescriptions: Record
}
+// get category notes from config
+export function getCategoryNotes(): CategoryNotes {
+ const auditLogConfig = config as AuditLogConfig
+ return auditLogConfig.categoryNotes || {}
+}
+
type TitleResolutionContext = {
pages: Record
redirects: Record
diff --git a/src/audit-logs/pages/audit-log-events.tsx b/src/audit-logs/pages/audit-log-events.tsx
index d5cf0c4802..ac114c6c37 100644
--- a/src/audit-logs/pages/audit-log-events.tsx
+++ b/src/audit-logs/pages/audit-log-events.tsx
@@ -14,18 +14,20 @@ import {
import { AutomatedPage } from '@/automated-pipelines/components/AutomatedPage'
import { HeadingLink } from '@/frame/components/article/HeadingLink'
import GroupedEvents from '../components/GroupedEvents'
-import type { CategorizedEvents } from '../types'
+import type { CategorizedEvents, CategoryNotes } from '../types'
type Props = {
mainContext: MainContextT
automatedPageContext: AutomatedPageContextT
auditLogEvents: CategorizedEvents
+ categoryNotes: CategoryNotes
}
export default function AuditLogEvents({
mainContext,
automatedPageContext,
auditLogEvents,
+ categoryNotes,
}: Props) {
const content = (
<>
@@ -38,6 +40,7 @@ export default function AuditLogEvents({
key={category}
category={category}
auditLogEvents={auditLogEvents[category]}
+ categoryNote={categoryNotes[category]}
/>
)
})}
@@ -55,7 +58,7 @@ export default function AuditLogEvents({
export const getServerSideProps: GetServerSideProps = async (context) => {
const { getAutomatedPageMiniTocItems } = await import('@/frame/lib/get-mini-toc-items')
- const { getCategorizedAuditLogEvents } = await import('../lib')
+ const { getCategorizedAuditLogEvents, getCategoryNotes } = await import('../lib')
const req = context.req as object
const res = context.res as object
@@ -77,6 +80,8 @@ export const getServerSideProps: GetServerSideProps = async (context) =>
auditLogEvents = getCategorizedAuditLogEvents('organization', currentVersion)
}
+ const categoryNotes = getCategoryNotes()
+
const auditLogEventsMiniTocs = await getAutomatedPageMiniTocItems(
Object.keys(auditLogEvents).map((category) => category),
context,
@@ -86,6 +91,7 @@ export const getServerSideProps: GetServerSideProps = async (context) =>
return {
props: {
auditLogEvents,
+ categoryNotes,
mainContext,
automatedPageContext: getAutomatedPageContextFromRequest(req),
},
diff --git a/src/audit-logs/tests/rendering.ts b/src/audit-logs/tests/rendering.ts
index 349f4c124a..c1e4e51db4 100644
--- a/src/audit-logs/tests/rendering.ts
+++ b/src/audit-logs/tests/rendering.ts
@@ -98,4 +98,49 @@ describe('audit log events docs', () => {
const $lead = $(leadSelector)
expect($lead.length).toBe(1)
})
+
+ test('category notes are rendered when present', async () => {
+ // Test organization page which should have category notes
+ const $ = await getDOM(
+ '/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/audit-log-events-for-your-organization',
+ )
+
+ // Look for category note elements - they should appear before tables
+ const categoryNotes = $('.category-note')
+
+ // If there are categories with notes configured, we should see them rendered
+ if (categoryNotes.length > 0) {
+ categoryNotes.each((_, note) => {
+ const $note = $(note)
+ expect($note.text().length).toBeGreaterThan(0)
+
+ // Should be followed by a div (the category events)
+ const $nextDiv = $note.next('div')
+ expect($nextDiv.length).toBe(1)
+ })
+ }
+ })
+
+ test('git category note is rendered on appropriate pages', async () => {
+ // Test enterprise page which should have git category note for GHES
+ const $ = await getDOM(
+ '/enterprise-server@latest/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/audit-log-events-for-your-enterprise',
+ )
+
+ // Look for git category heading
+ const gitHeading = $('#git')
+ if (gitHeading.length > 0) {
+ // Should have a category note before the div
+ const $noteOrTable = gitHeading.next()
+
+ // Either the next element is a note (followed by div) or directly a div
+ if ($noteOrTable.hasClass('category-note')) {
+ expect($noteOrTable.text()).toContain('Git events')
+ expect($noteOrTable.next('div').length).toBe(1)
+ } else if ($noteOrTable.is('div')) {
+ // Direct div is fine too - means no note for this category
+ expect($noteOrTable.is('div')).toBe(true)
+ }
+ }
+ })
})
diff --git a/src/audit-logs/tests/unit/category-notes.ts b/src/audit-logs/tests/unit/category-notes.ts
new file mode 100644
index 0000000000..2d4e31638b
--- /dev/null
+++ b/src/audit-logs/tests/unit/category-notes.ts
@@ -0,0 +1,76 @@
+import { describe, expect, test } from 'vitest'
+
+import { getCategorizedAuditLogEvents } from '../../lib'
+import config from '../../lib/config.json'
+
+describe('audit log category notes', () => {
+ test('config contains expected category notes', () => {
+ expect(config.categoryNotes).toBeDefined()
+ expect(typeof config.categoryNotes).toBe('object')
+
+ // Check that we have the specific category notes mentioned in the issue
+ expect(config.categoryNotes).toHaveProperty('members_can_create_pages')
+ expect(config.categoryNotes).toHaveProperty('git')
+ expect(config.categoryNotes).toHaveProperty('sso_redirect')
+ })
+
+ test('category notes are strings', () => {
+ if (config.categoryNotes) {
+ Object.values(config.categoryNotes).forEach((note) => {
+ expect(typeof note).toBe('string')
+ expect(note.length).toBeGreaterThan(0)
+ })
+ }
+ })
+
+ test('members_can_create_pages note contains reference to GitHub Pages', () => {
+ const note = config.categoryNotes?.['members_can_create_pages']
+ expect(note).toContain('GitHub Pages')
+ expect(note).toContain('organization')
+ })
+
+ test('git category note contains REST API information', () => {
+ const note = config.categoryNotes?.['git']
+ expect(note).toContain('REST API')
+ expect(note).toContain('7-day retention')
+ })
+
+ test('sso_redirect note mentions beta status', () => {
+ const note = config.categoryNotes?.['sso_redirect']
+ expect(note).toContain('beta')
+ expect(note).toContain('Enterprise Managed Users')
+ })
+
+ test('category notes do not interfere with event categorization', () => {
+ // Test that adding category notes doesn't break existing functionality
+ const organizationEvents = getCategorizedAuditLogEvents('organization', 'free-pro-team@latest')
+ const enterpriseEvents = getCategorizedAuditLogEvents('enterprise', 'enterprise-cloud@latest')
+
+ // Should still have categorized events
+ expect(Object.keys(organizationEvents).length).toBeGreaterThan(0)
+ expect(Object.keys(enterpriseEvents).length).toBeGreaterThan(0)
+
+ // Each category should still contain arrays of events
+ Object.values(organizationEvents).forEach((events) => {
+ expect(Array.isArray(events)).toBe(true)
+ if (events.length > 0) {
+ expect(events[0]).toHaveProperty('action')
+ expect(events[0]).toHaveProperty('description')
+ }
+ })
+ })
+
+ test('category notes are properly typed', () => {
+ // This test will pass once we update the types
+ const notes = config.categoryNotes
+ if (notes) {
+ expect(notes).toEqual(
+ expect.objectContaining({
+ members_can_create_pages: expect.any(String),
+ git: expect.any(String),
+ sso_redirect: expect.any(String),
+ }),
+ )
+ }
+ })
+})
diff --git a/src/audit-logs/types.ts b/src/audit-logs/types.ts
index c7e20efbde..c532b6ad37 100644
--- a/src/audit-logs/types.ts
+++ b/src/audit-logs/types.ts
@@ -1,5 +1,7 @@
export type CategorizedEvents = Record
+export type CategoryNotes = Record
+
export type AuditLogEventT = {
action: string
description: string
@@ -19,3 +21,9 @@ export type RawAuditLogEventT = {
}
export type VersionedAuditLogData = Record>
+
+export type AuditLogConfig = {
+ sha: string
+ appendedDescriptions: Record
+ categoryNotes?: CategoryNotes
+}