Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/audit-logs/components/GroupedEvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import type { AuditLogEventT } from '../types'
type Props = {
auditLogEvents: AuditLogEventT[]
category: string
categoryNote?: string
}

export default function GroupedEvents({ auditLogEvents, category }: Props) {
export default function GroupedEvents({ auditLogEvents, category, categoryNote }: Props) {
const { t } = useTranslation('audit_logs')
const eventSlug = slug(category)

Expand Down Expand Up @@ -39,6 +40,11 @@ export default function GroupedEvents({ auditLogEvents, category }: Props) {
<HeadingLink as="h3" slug={eventSlug}>
{category}
</HeadingLink>
{categoryNote && (
<div className="category-note mb-3 p-3 color-border-default border rounded-2">
<p className="mb-0">{categoryNote}</p>
</div>
)}
<div>
{auditLogEvents.map((event) => (
<div key={event.action} style={{ marginBottom: '3rem' }}>
Expand Down
8 changes: 7 additions & 1 deletion src/audit-logs/lib/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
9 changes: 9 additions & 0 deletions src/audit-logs/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -21,6 +24,12 @@ type PipelineConfig = {
appendedDescriptions: Record<string, string>
}

// get category notes from config
export function getCategoryNotes(): CategoryNotes {
const auditLogConfig = config as AuditLogConfig
return auditLogConfig.categoryNotes || {}
}

type TitleResolutionContext = {
pages: Record<string, any>
redirects: Record<string, string>
Expand Down
10 changes: 8 additions & 2 deletions src/audit-logs/pages/audit-log-events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
<>
Expand All @@ -38,6 +40,7 @@ export default function AuditLogEvents({
key={category}
category={category}
auditLogEvents={auditLogEvents[category]}
categoryNote={categoryNotes[category]}
/>
)
})}
Expand All @@ -55,7 +58,7 @@ export default function AuditLogEvents({

export const getServerSideProps: GetServerSideProps<Props> = 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
Expand All @@ -77,6 +80,8 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
auditLogEvents = getCategorizedAuditLogEvents('organization', currentVersion)
}

const categoryNotes = getCategoryNotes()

const auditLogEventsMiniTocs = await getAutomatedPageMiniTocItems(
Object.keys(auditLogEvents).map((category) => category),
context,
Expand All @@ -86,6 +91,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
return {
props: {
auditLogEvents,
categoryNotes,
mainContext,
automatedPageContext: getAutomatedPageContextFromRequest(req),
},
Expand Down
45 changes: 45 additions & 0 deletions src/audit-logs/tests/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
})
})
76 changes: 76 additions & 0 deletions src/audit-logs/tests/unit/category-notes.ts
Original file line number Diff line number Diff line change
@@ -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),
}),
)
}
})
})
8 changes: 8 additions & 0 deletions src/audit-logs/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export type CategorizedEvents = Record<string, AuditLogEventT[]>

export type CategoryNotes = Record<string, string>

export type AuditLogEventT = {
action: string
description: string
Expand All @@ -19,3 +21,9 @@ export type RawAuditLogEventT = {
}

export type VersionedAuditLogData = Record<string, Record<string, AuditLogEventT[]>>

export type AuditLogConfig = {
sha: string
appendedDescriptions: Record<string, string>
categoryNotes?: CategoryNotes
}
Loading