Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,14 @@ export function ChartRowCardContent({ value, isPublic, pageClassification }: Cha
className="ukhsa-chart-card flex flex-col gap-6"
>
<article>
<ChartRowCardHeader id={column.id} title={column.value.title} description={column.value.body}>
<ChartRowCardHeader
id={column.id}
title={column.value.title}
description={column.value.body}
isPublic={isPublic}
pageClassification={pageClassification}
authEnabled={authEnabled}
>
<Timestamp data={column.value} size={size} />
</ChartRowCardHeader>
<Tabs defaultValue={`${kebabCase(column.value.title)}-chart`} className="govuk-!-margin-bottom-0">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,30 @@ const getAreaSelectorMock = jest.mocked(getAreaSelector)
describe('ChartRowCardHeader', () => {
test('renders correctly with props', async () => {
getAreaSelectorMock.mockResolvedValue([])
render(await ChartRowCardHeader({ id: '1', title: 'Sample Title', description: 'Sample Description' }))
render(
await ChartRowCardHeader({
id: '1',
title: 'Sample Title',
description: 'Sample Description',
pageClassification: 'official',
authEnabled: false,
})
)
expect(screen.getByRole('heading', { level: 3, name: 'Sample Title' })).toBeInTheDocument()
expect(screen.getByText('Sample Description')).toBeInTheDocument()
})

test('displays a location when set', async () => {
getAreaSelectorMock.mockResolvedValue([null, 'Test Area'])
render(await ChartRowCardHeader({ id: '1', title: 'Title', description: 'Description' }))
render(
await ChartRowCardHeader({
id: '1',
title: 'Title',
description: 'Description',
pageClassification: 'official',
authEnabled: false,
})
)
expect(screen.getByRole('heading', { level: 3, name: 'Title (Test Area)' })).toBeInTheDocument()
})

Expand All @@ -31,18 +47,22 @@ describe('ChartRowCardHeader', () => {
id: '1',
title: 'Title',
description: 'Description',
pageClassification: 'official',
authEnabled: false,
children: <div>Child Element</div>,
})
)
expect(screen.getByText('Child Element')).toBeInTheDocument()
})

test('has correct structure and HTML elements', async () => {
test('applies correct classes to description and heading', async () => {
render(
await ChartRowCardHeader({
id: '1',
title: 'Title',
description: 'Description',
pageClassification: 'official',
authEnabled: false,
})
)
expect(screen.getByText('Description')).toHaveClass(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
import { ReactNode } from 'react'

import { DataClassification } from '@/api/models/DataClassification'
import { getAreaSelector } from '@/app/hooks/getAreaSelector'
import { getDataClassification } from '@/app/utils/table.utils'

interface ChartRowCardHeaderProps {
children?: ReactNode
title: string
description?: string
id: string
isPublic?: boolean
pageClassification?: DataClassification
authEnabled?: boolean
}

export async function ChartRowCardHeader({ children, id, title, description }: Readonly<ChartRowCardHeaderProps>) {
export async function ChartRowCardHeader({
children,
id,
title,
description,
isPublic,
pageClassification = 'official_sensitive',
authEnabled,
}: Readonly<ChartRowCardHeaderProps>) {
const [, areaName] = await getAreaSelector()

return (
<header>
<h3 id={`chart-row-card-heading-${id}`} className="govuk-heading-m mb-2 font-bold">
{title} {areaName && `(${areaName})`}
{title} {areaName && `(${areaName})`}{' '}
{getDataClassification(isPublic, pageClassification as DataClassification, authEnabled)}
</h3>
{description ? (
<p className="govuk-body-s govuk-!-margin-bottom-2 pt-0 italic text-dark-grey">{description}</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import SubplotClientChart from '@/app/components/ui/ukhsa/FilterLinkedCards/comp
import { SubplotClientTable } from '@/app/components/ui/ukhsa/Table/SubplotClientTable'
import { formatDate } from '@/app/utils/date.utils'
import { FlattenedGeography, getParentGeography } from '@/app/utils/geography.utils'
import { getDataClassification } from '@/app/utils/table.utils'

import { Card } from '../Card/Card'
import DropdownTab from '../Tabs/DropdownTab'
Expand Down Expand Up @@ -59,14 +60,15 @@ const SubplotFilterCard = ({
const title = `${cardData.title_prefix} between ${timePeriods[currentTimePeriodIndex].value.label} (${geographyParent?.name}, ${geography.name})`
const id = title
const about = cardData.about ? cardData.about : ''
const dataClassification = getDataClassification(isPublic, level ?? 'official_sensitive', authEnabled)

return (
<div key={id} className="mb-4">
<Card asChild aria-labelledby={`chart-row-card-heading-${id}`} className="ukhsa-chart-card flex flex-col gap-6">
<article>
<header>
<h3 id={`chart-row-card-heading-${id}`} className="govuk-heading-m mb-2 font-bold">
{title}
{title} {dataClassification}
</h3>
<p className="govuk-body-s govuk-!-margin-bottom-2 pt-0 italic text-dark-grey">{description}</p>
</header>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ClientTable } from '@/app/components/ui/ukhsa/Table/ClientTable'
import DropdownTab from '@/app/components/ui/ukhsa/Tabs/DropdownTab'
import { formatDate } from '@/app/utils/date.utils'
import { FlattenedGeography, getParentGeography } from '@/app/utils/geography.utils'
import { getDataClassification } from '@/app/utils/table.utils'
import { getMinMaxYears, MinMaxYear } from '@/app/utils/time-period.utils'

import { Card } from '../Card/Card'
Expand Down Expand Up @@ -47,14 +48,15 @@ const TimeseriesFilterCard = ({
const title = `${cardData.title_prefix} between ${minMaxDateRange.minDate} - ${minMaxDateRange.maxDate} (${geographyParent!.name}, ${geography.name})`
const id = title
const about = cardData.about ? cardData.about : ''
const dataClassification = getDataClassification(isPublic, level ?? 'official_sensitive', authEnabled)

return (
<div key={id} className="mb-4">
<Card asChild aria-labelledby={`chart-row-card-heading-${id}`} className="ukhsa-chart-card flex flex-col gap-6">
<article>
<header>
<h3 id={`chart-row-card-heading-${id}`} className="govuk-heading-m mb-2 font-bold">
{title}
{title} {dataClassification}
</h3>
<p className="govuk-body-s govuk-!-margin-bottom-2 pt-0 italic text-dark-grey">{description}</p>
</header>
Expand Down
40 changes: 39 additions & 1 deletion src/app/utils/table.utils.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { render, screen } from '@/config/test-utils'

import { getColumnHeader } from './table.utils'
import { getColumnHeader, getDataClassification } from './table.utils'

const renderHeader = (
chartLabel: string,
Expand Down Expand Up @@ -90,3 +90,41 @@ describe('getColumnHeader', () => {
})
})
})

describe('getDataClassification', () => {
test('returns empty string when authEnabled is false', () => {
expect(getDataClassification(false, 'official_sensitive', false)).toBe('')
})

test('returns classification when authEnabled is true', () => {
expect(getDataClassification(false, 'official_sensitive', true)).toBe('(OFFICIAL SENSITIVE)')
})

test('returns empty string when isPublic is true', () => {
expect(getDataClassification(true, 'official_sensitive', true)).toBe('')
})

test('returns empty string when isPublic is undefined', () => {
expect(getDataClassification(undefined, 'official_sensitive', true)).toBe('')
})

test('returns uppercase classification wrapped in parentheses for official_sensitive', () => {
expect(getDataClassification(false, 'official_sensitive', true)).toBe('(OFFICIAL SENSITIVE)')
})

test('returns uppercase classification wrapped in parentheses for official', () => {
expect(getDataClassification(false, 'official', true)).toBe('(OFFICIAL)')
})

test('returns uppercase classification wrapped in parentheses for secret', () => {
expect(getDataClassification(false, 'secret', true)).toBe('(SECRET)')
})

test('returns uppercase classification wrapped in parentheses for top_secret', () => {
expect(getDataClassification(false, 'top_secret', true)).toBe('(TOP SECRET)')
})

test('returns uppercase classification wrapped in parentheses for protective_marking_not_set', () => {
expect(getDataClassification(false, 'protective_marking_not_set', true)).toBe('(PROTECTIVE MARKING NOT SET)')
})
})
25 changes: 22 additions & 3 deletions src/app/utils/table.utils.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
type Level = 'official' | 'official_sensitive' | 'protective_marking_not_set' | 'secret' | 'top_secret'
import { DataClassification } from '@/api/models/DataClassification'

const levelContent: Record<Level, string> = {
const levelContent: Record<DataClassification, string> = {
official: 'Official',
official_sensitive: 'Official-Sensitive',
protective_marking_not_set: 'Protective marking not set',
secret: 'Secret',
top_secret: 'Top Secret',
}

const levelContentCaps: Record<DataClassification, string> = {
official: 'OFFICIAL',
official_sensitive: 'OFFICIAL SENSITIVE',
protective_marking_not_set: 'PROTECTIVE MARKING NOT SET',
secret: 'SECRET',
top_secret: 'TOP SECRET',
}

export const getColumnHeader = (
chartLabel: string,
axisTitle: string,
fallback: string,
isPublic?: boolean,
level: Level = 'official_sensitive',
level: DataClassification = 'official_sensitive',
authEnabled?: boolean
) => {
const label = chartLabel || axisTitle || fallback
Expand All @@ -28,3 +36,14 @@
</>
)
}

export const getDataClassification = (
isPublic: boolean | undefined,
pageClassification: DataClassification = 'official_sensitive',

Check warning on line 42 in src/app/utils/table.utils.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Default parameters should be last.

See more on https://sonarcloud.io/project/issues?id=UKHSA-Internal_winter-pressures-frontend&issues=AZ13VrFe5HfqUseanNpp&open=AZ13VrFe5HfqUseanNpp&pullRequest=908
authEnabled: boolean | undefined
): string => {
if (authEnabled && isPublic === false) {
return `(${levelContentCaps[pageClassification]})`
}
return ''
}
Loading