Skip to content

Commit

Permalink
feat(ui): add pepr details (#107)
Browse files Browse the repository at this point in the history
## Description
Add details to the monitor > pepr view

## Related Issue

Resolve #35
\
  • Loading branch information
TristanHoladay authored Aug 1, 2024
1 parent 34e7e6c commit a4ed8cd
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 2 deletions.
2 changes: 1 addition & 1 deletion ui/src/lib/components/k8s/Drawer/component.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<script lang="ts">
import type { KubernetesObject } from '@kubernetes/client-node'
import { Close, Table } from 'carbon-icons-svelte'
import { Close } from 'carbon-icons-svelte'
import { onMount } from 'svelte'
import { goto } from '$app/navigation'
Expand Down
17 changes: 17 additions & 0 deletions ui/src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
import type { SvelteComponent } from 'svelte'

export type PatchOperation = {
op: string
path: string
value: string
}

export type PeprDetails = {
component: SvelteComponent
messages?: string[]
operations?: { [key: string]: PatchOperation[] }
}

export type PeprEvent = {
_name: string
count: number
Expand All @@ -6,4 +20,7 @@ export type PeprEvent = {
repeated?: number
ts?: string
epoch: number
msg: string
res?: Record<string, unknown>
details?: PeprDetails | undefined
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!-- SPDX-License-Identifier: Apache-2.0 -->
<!-- SPDX-FileCopyrightText: 2024-Present The UDS Authors -->

<script lang="ts">
import type { PeprDetails } from '$lib/types'
export let details: PeprDetails
</script>

<span class="whitespace-nowrap relative group py-4 flex items-center">
<span class="text-blue-400">Details</span>
<div class="tooltip tooltip-left">
{#each details.messages || [] as msg}
<div class="text-red-400">{msg}</div>
{/each}
</div>
</span>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { render } from '@testing-library/svelte'

import type { SvelteComponent } from 'svelte'
import DeniedDetails from './DeniedDetails.svelte'

// Mock the carbon-icons-svelte module

const comp = vi.fn().mockImplementation(() => ({
$$: {
on_mount: [],
on_destroy: [],
before_update: [],
after_update: [],
},
})) as unknown as SvelteComponent

describe('Denied Details', () => {
test('renders denied messages', () => {
const peprDetails = { component: comp, messages: ['Authorized: test', 'Found: test'] }
const { getByText } = render(DeniedDetails, { props: { details: peprDetails } })
expect(getByText('Details')).toBeInTheDocument()
expect(getByText(peprDetails.messages[0])).toBeInTheDocument()
expect(getByText(peprDetails.messages[1])).toBeInTheDocument()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!-- SPDX-License-Identifier: Apache-2.0 -->
<!-- SPDX-FileCopyrightText: 2024-Present The UDS Authors -->

<script lang="ts">
import type { PeprDetails } from '$lib/types'
export let details: PeprDetails
</script>

<div class="whitespace-nowrap relative group py-4 flex">
<span class="text-blue-400">Details</span>
<div class="tooltip tooltip-left">
{#each Object.entries(details.operations || {}) as [key, ops], index}
{#if index > 0}
<br />
{/if}
<h3 class="font-bold">{key}:</h3>
{#each ops as op}
<p class="text-pretty tooltip-w">
{op.path}
{#if op.value}
<span class="text-blue-400">={JSON.stringify(op.value)}</span>
{/if}
</p>
{/each}
{/each}
</div>
</div>

<style>
.tooltip-w {
width: 496px;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { render, screen } from '@testing-library/svelte'

import type { SvelteComponent } from 'svelte'
import MutatedDetails from './MutatedDetails.svelte'

// Mock the carbon-icons-svelte module

const comp = vi.fn().mockImplementation(() => ({
$$: {
on_mount: [],
on_destroy: [],
before_update: [],
after_update: [],
},
})) as unknown as SvelteComponent

describe('Denied Details', () => {
test('renders exemption title', () => {
const peprDetails = {
component: comp,
operations: {
ADDED: [{ op: 'add', path: '/path', value: 'value' }],
REPLACED: [{ op: 'add', path: '/path', value: 'value' }],
REMOVED: [{ op: 'add', path: '/path', value: '' }],
},
}
render(MutatedDetails, { props: { details: peprDetails } })

expect(screen.getByText('Details')).toBeInTheDocument()
//todo: figure out assertions for split up text
})
})
11 changes: 10 additions & 1 deletion ui/src/routes/monitor/pepr/[[stream]]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { page } from '$app/stores'
import { type PeprEvent } from '$lib/types'
import './page.postcss'
import { getDetails } from './helpers'
let loaded = false
let streamFilter = ''
Expand Down Expand Up @@ -45,9 +46,9 @@
eventSource.onmessage = (e) => {
try {
const payload: PeprEvent = JSON.parse(e.data)
// The event type is the first word in the header
payload.event = payload.header.split(' ')[0]
payload.details = getDetails(payload)
// If this is a repeated event, update the count
if (payload.repeated) {
Expand Down Expand Up @@ -141,6 +142,7 @@
<tr>
<th>Event</th>
<th>Resource</th>
<th>Details</th>
<th>Count</th>
<th>Timestamp</th>
</tr>
Expand All @@ -158,6 +160,13 @@
<span class="pepr-event {item.event}">{item.event}</span>
</td>
<td>{item._name}</td>
<td class="flex flex-row items-center">
{#if item.details}
<svelte:component this={item.details.component} details={item.details} />
{:else}
-
{/if}
</td>
<td>{item.count || 1}</td>
<td>{item.ts}</td>
</tr>
Expand Down
62 changes: 62 additions & 0 deletions ui/src/routes/monitor/pepr/[[stream]]/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { PatchOperation, PeprDetails, PeprEvent } from '$lib/types'
import type { SvelteComponent } from 'svelte'
import DeniedDetails from './(details)/denied-details/DeniedDetails.svelte'
import MutatedDetails from './(details)/mutated-details/MutatedDetails.svelte'

// Utility function to decode base64
function decodeBase64(base64String: string) {
try {
return atob(base64String)
} catch (e) {
console.error('Failed to decode base64 string:', e)
return ''
}
}

export function getDetails(payload: PeprEvent): PeprDetails | undefined {
if (!payload.res) {
return undefined
}

if (payload.event === 'DENIED') {
if (payload.res) {
const status = payload.res.status as Record<string, string>
const split = status.message.split(' Authorized: ')

// No "Authorized" or "Found" in the message
if (split.length !== 2) {
return { component: DeniedDetails as unknown as SvelteComponent, messages: split }
}

const authorized = `Authorized: ${split[1].split(' Found: ')[0]}`
const found = `Found: ${split[1].split(' Found:')[1]}`
return { component: DeniedDetails as unknown as SvelteComponent, messages: [authorized, found] }
}
}

if (payload.event === 'MUTATED') {
if (payload.res.patch) {
const decodedPatch = decodeBase64(payload.res.patch as string)
const parsedPatch = JSON.parse(decodedPatch)

const opMap: { [key: string]: string } = {
add: 'ADDED',
remove: 'REMOVED',
replace: 'REPLACED',
}

// Group by operation type
const groups: { [key: string]: PatchOperation[] } = {}
for (const op of parsedPatch) {
if (!groups[opMap[op.op]]) {
groups[opMap[op.op]] = []
}
groups[opMap[op.op]].push(op)
}

return { component: MutatedDetails as unknown as SvelteComponent, operations: groups }
}
}

return undefined
}

0 comments on commit a4ed8cd

Please sign in to comment.