Skip to content
This repository was archived by the owner on Jul 8, 2025. It is now read-only.

feat: support PII on the dashboard #326

Merged
merged 11 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from 6 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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## [0.16.0](https://github.com/stacklok/codegate-ui/compare/v0.15.0...v0.16.0) (2025-02-17)


### Features

* add activate workspace button into detail page ([#328](https://github.com/stacklok/codegate-ui/issues/328)) ([c85e1aa](https://github.com/stacklok/codegate-ui/commit/c85e1aa8011c688d435e0abb11b44cf297d05e61))


### Bug Fixes

* cert instructions ([#317](https://github.com/stacklok/codegate-ui/issues/317)) ([2eb9579](https://github.com/stacklok/codegate-ui/commit/2eb95794f63988fa7f7f29daf2e0c41a9cd9c356))

## [0.15.0](https://github.com/stacklok/codegate-ui/compare/v0.14.1...v0.15.0) (2025-02-14)


Expand Down
527 changes: 100 additions & 427 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "vite-project",
"private": true,
"version": "0.15.0",
"version": "0.16.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down Expand Up @@ -69,7 +69,7 @@
"@types/hast": "^3.0.4",
"@types/node": "^22.13.1",
"@types/react": "^19.0.2",
"@types/react-dom": "^19.0.2",
"@types/react-dom": "^19.0.3",
"@typescript-eslint/parser": "^8.23.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"@vitest/coverage-istanbul": "^3.0.5",
Expand All @@ -83,7 +83,7 @@
"eslint-plugin-react-refresh": "^0.4.16",
"eslint-plugin-tailwindcss": "3.17.5",
"globals": "^15.12.0",
"hast": "^0.0.2",
"hast": "^1.0.0",
"husky": "^9.1.6",
"jsdom": "^25.0.1",
"knip": "^5.43.6",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,56 +40,42 @@ test('shows correct count of all packages', async () => {
})
})

test('shows correct count of malicious packages', async () => {
server.use(
http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => {
return HttpResponse.json(
Array.from({ length: 13 }).map(() =>
mockConversation({
alertsConfig: {
type: 'malicious',
numAlerts: 1,
},
})
)
const filteredCases = [
{ tabLabel: /malicious/i, alertType: 'malicious' as const, count: 13 },
{ tabLabel: /secrets/i, alertType: 'secret' as const, count: 10 },
{ tabLabel: /pii/i, alertType: 'pii' as const, count: 9 },
]

filteredCases.forEach(({ tabLabel, alertType, count }) => {
test(`shows correct count of ${alertType} packages`, async () => {
server.use(
http.get(
mswEndpoint('/api/v1/workspaces/:workspace_name/messages'),
() => {
return HttpResponse.json(
Array.from({ length: count }).map(() =>
mockConversation({
alertsConfig: {
type: alertType,
numAlerts: 1,
},
})
)
)
}
)
})
)
)

const { getByRole } = render(
<TabsMessages>
<div>foo</div>
</TabsMessages>
)
const { getByRole } = render(
<TabsMessages>
<div>foo</div>
</TabsMessages>
)

await waitFor(() => {
expect(getByRole('tab', { name: /malicious/i })).toHaveTextContent('13')
})
})

test('shows correct count of secret packages', async () => {
server.use(
http.get(mswEndpoint('/api/v1/workspaces/:workspace_name/messages'), () => {
return HttpResponse.json(
Array.from({ length: 13 }).map(() =>
mockConversation({
alertsConfig: {
type: 'secret',
numAlerts: 1,
},
})
)
await waitFor(() => {
expect(getByRole('tab', { name: tabLabel })).toHaveTextContent(
String(count)
)
})
)

const { getByRole } = render(
<TabsMessages>
<div>foo</div>
</TabsMessages>
)

await waitFor(() => {
expect(getByRole('tab', { name: /secrets/i })).toHaveTextContent('13')
})
})
6 changes: 6 additions & 0 deletions src/features/dashboard-messages/components/tabs-messages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ import {
import { SearchFieldMessages } from './search-field-messages'
import { tv } from 'tailwind-variants'
import { useQueryGetWorkspaceMessages } from '@/hooks/use-query-get-workspace-messages'
import { isConversationWithPII } from '@/lib/is-alert-pii'

type AlertsCount = {
all: number
malicious: number
secrets: number
pii: number
}

function select(data: V1GetWorkspaceMessagesResponse): AlertsCount {
Expand All @@ -36,10 +38,13 @@ function select(data: V1GetWorkspaceMessagesResponse): AlertsCount {
isConversationWithSecretAlerts,
]).length

const pii: number = multiFilter(data, [isConversationWithPII]).length

return {
all,
malicious,
secrets,
pii,
}
}

Expand Down Expand Up @@ -103,6 +108,7 @@ export function TabsMessages({ children }: { children: React.ReactNode }) {
count={data?.secrets ?? 0}
id={AlertsFilterView.SECRETS}
/>
<Tab title="PII" count={data?.pii ?? 0} id={AlertsFilterView.PII} />
</TabList>

<SearchFieldMessages className="ml-auto" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum AlertsFilterView {
ALL = 'all',
MALICIOUS = 'malicious',
SECRETS = 'secrets',
PII = 'pii',
}

const alertsFilterSchema = z.object({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { isConversationWithMaliciousAlerts } from '../../../lib/is-alert-malicio
import { isConversationWithSecretAlerts } from '../../../lib/is-alert-secret'
import { filterMessagesBySubstring } from '../lib/filter-messages-by-substring'
import { useQueryGetWorkspaceMessages } from '@/hooks/use-query-get-workspace-messages'
import { isConversationWithPII } from '@/lib/is-alert-pii'

const FILTER: Record<
AlertsFilterView,
Expand All @@ -17,6 +18,7 @@ const FILTER: Record<
all: () => true,
malicious: isConversationWithMaliciousAlerts,
secrets: isConversationWithSecretAlerts,
pii: isConversationWithPII,
}

export function useQueryGetWorkspaceMessagesTable() {
Expand Down
14 changes: 14 additions & 0 deletions src/lib/is-alert-pii.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Alert, AlertConversation, Conversation } from '@/api/generated'

export function isConversationWithPII(
conversation: Conversation | null
): boolean {
return conversation?.alerts?.some(isAlertPii) ?? false
}

export function isAlertPii(alert: Alert | AlertConversation | null) {
return (
alert?.trigger_category === 'critical' &&
alert.trigger_type === 'codegate-pii'
)
}
19 changes: 16 additions & 3 deletions src/mocks/msw/mockers/alert.mock.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { Alert, AlertSeverity } from "@/api/generated";
import { faker } from "@faker-js/faker";
import { Alert, AlertSeverity } from '@/api/generated'
import { faker } from '@faker-js/faker'

const ALERT_SECRET_FIELDS = {
trigger_string: 'foo',
trigger_type: 'codegate-secrets',
} satisfies Pick<Alert, 'trigger_string' | 'trigger_type'>

const ALERT_PII_FIELDS = {
trigger_string: '[email protected]',
trigger_type: 'codegate-pii',
} satisfies Pick<Alert, 'trigger_string' | 'trigger_type'>

const ALERT_MALICIOUS_FIELDS = {
trigger_string: {
name: 'invokehttp',
Expand All @@ -31,7 +36,7 @@ const getBaseAlert = ({
export const mockAlert = ({
type,
}: {
type: 'secret' | 'malicious'
type: 'secret' | 'malicious' | 'pii'
}): Alert => {
const timestamp = faker.date.recent().toISOString()

Expand All @@ -54,6 +59,14 @@ export const mockAlert = ({
...ALERT_SECRET_FIELDS,
}

return result
}
case 'pii': {
const result: Alert = {
...base,
...ALERT_PII_FIELDS,
}

return result
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/mocks/msw/mockers/conversation.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function mockConversation({
withTokenUsage?: boolean
alertsConfig?: {
numAlerts?: number
type?: 'secret' | 'malicious' | 'any'
type?: 'secret' | 'malicious' | 'any' | 'pii'
}
} = {}) {
const timestamp = faker.date.recent().toISOString()
Expand Down
Loading