Files
insights/products/ai_observability/frontend/aiObservabilitySessionLogic.test.ts
zeekay 1b367e2b62 chore: rebase the tree onto upstream master
The tree was a debranded snapshot of upstream taken around 2026-02-20, and
upstream has moved about five months since. This replaces it with upstream
master, mechanically re-derived: bin/debrand does the rename, and the
pipeline and squashed migrations are restored from the shipped tree.

Not a merge. The previous import replaced the tree wholesale in a single
commit, so there is no shared content history to merge against. Reproducing
the rename on the new tree is what actually converges.

What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints,
compose files, LLM.md, and insights/migrations. The squashed initial
migration is load-bearing — the live database records it by name, so
upstream's ~918 individual migrations cannot come back.

Nine products are dropped as unreferenced. Twenty-one more that we do not run
are kept anyway because the core app still imports them; unpicking those is a
refactor and does not belong in a rebase.

This base does not build yet, and the reasons are not in the rename:
upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against
our 4.2, and the dependency manifests still name upstream packages that we
republish under our own names. Those are decisions, not renames.
2026-08-01 15:44:40 -07:00

294 lines
11 KiB
TypeScript

import { MOCK_TEAM_ID } from 'lib/api.mock'
import { combineUrl, router } from 'kea-router'
import { expectLogic } from 'kea-test-utils'
import { addProjectIdIfMissing } from 'lib/utils/kea-router'
import { sceneLogic } from 'scenes/sceneLogic'
import { urls } from 'scenes/urls'
import { NodeKind, SessionQuery } from '~/queries/schema/schema-general'
import { initKeaTests } from '~/test/init'
import { aiObservabilitySessionLogic } from './aiObservabilitySessionLogic'
const blankScene = (): any => ({ scene: { component: () => null, logic: null } })
const scenes: any = { AIObservabilitySession: blankScene }
describe('aiObservabilitySessionLogic', () => {
let logic: ReturnType<typeof aiObservabilitySessionLogic.build>
beforeEach(async () => {
initKeaTests()
sceneLogic({ scenes }).mount()
logic = aiObservabilitySessionLogic()
logic.mount()
})
afterEach(() => {
logic.unmount()
})
describe('URL routing', () => {
it('properly loads session scene with basic session ID', async () => {
const sessionId = 'session-123'
const sessionUrl = combineUrl(urls.aiObservabilitySession(sessionId))
const finalUrl = addProjectIdIfMissing(sessionUrl.url, MOCK_TEAM_ID)
router.actions.push(finalUrl)
await expectLogic(logic).toFinishAllListeners()
expect(logic.values.sessionId).toBe(sessionId)
})
it('properly loads session scene when session ID contains a colon', async () => {
const sessionIdWithColon = 'session:group:16-16:81008d53ff0a708b'
const sessionUrl = combineUrl(urls.aiObservabilitySession(sessionIdWithColon))
const finalUrl = addProjectIdIfMissing(sessionUrl.url, MOCK_TEAM_ID)
router.actions.push(finalUrl)
await expectLogic(logic).toFinishAllListeners()
expect(logic.values.sessionId).toBe(sessionIdWithColon)
})
it('properly loads session scene when session ID contains multiple colons', async () => {
const sessionIdWithMultipleColons = 'namespace:session:12345:abcdef:xyz'
const sessionUrl = combineUrl(urls.aiObservabilitySession(sessionIdWithMultipleColons))
router.actions.push(addProjectIdIfMissing(sessionUrl.url, MOCK_TEAM_ID))
await expectLogic(logic).toMatchValues({
sessionId: sessionIdWithMultipleColons,
})
})
it('handles session ID with timestamp parameter', async () => {
const sessionId = 'session-456'
const timestamp = '2024-01-01T00:00:00Z'
const sessionUrl = combineUrl(urls.aiObservabilitySession(sessionId, { timestamp }))
router.actions.push(addProjectIdIfMissing(sessionUrl.url, MOCK_TEAM_ID))
await expectLogic(logic).toMatchValues({
sessionId: sessionId,
dateRange: { dateFrom: timestamp, dateTo: null },
})
})
it('handles session ID with UUID format', async () => {
const sessionId = '550e8400-e29b-41d4-a716-446655440000'
const sessionUrl = combineUrl(urls.aiObservabilitySession(sessionId))
router.actions.push(addProjectIdIfMissing(sessionUrl.url, MOCK_TEAM_ID))
await expectLogic(logic).toMatchValues({
sessionId: sessionId,
})
})
})
describe('state management', () => {
it('has correct initial state', () => {
expect(logic.values.sessionId).toBe('')
expect(logic.values.dateRange).toBeNull()
})
it('sets session ID via action', async () => {
await expectLogic(logic, () => {
logic.actions.setSessionId('test-session-789')
}).toMatchValues({
sessionId: 'test-session-789',
})
})
it('sets date range via action', async () => {
await expectLogic(logic, () => {
logic.actions.setDateRange('2024-01-01T00:00:00Z', '2024-01-31T23:59:59Z')
}).toMatchValues({
dateRange: {
dateFrom: '2024-01-01T00:00:00Z',
dateTo: '2024-01-31T23:59:59Z',
},
})
})
it('handles null date range values', async () => {
await expectLogic(logic, () => {
logic.actions.setDateRange(null, null)
}).toMatchValues({
dateRange: {
dateFrom: null,
dateTo: null,
},
})
})
it('handles partial date range (only dateFrom)', async () => {
await expectLogic(logic, () => {
logic.actions.setDateRange('2024-01-01T00:00:00Z')
}).toMatchValues({
dateRange: {
dateFrom: '2024-01-01T00:00:00Z',
dateTo: null,
},
})
})
})
describe('query selector', () => {
it('generates correct SessionQuery with session ID', async () => {
logic.actions.setSessionId('test-session-123')
await expectLogic(logic).toFinishAllListeners()
const query = logic.values.query
const source = query.source as SessionQuery
expect(query.kind).toBe(NodeKind.DataTableNode)
expect(source.kind).toBe(NodeKind.SessionQuery)
expect(source.sessionId).toBe('test-session-123')
expect(source.includeSentiment).toBe(true)
})
it('includes date range in query when dateRange is set', async () => {
logic.actions.setSessionId('test-session-456')
logic.actions.setDateRange('2024-01-01T00:00:00Z', '2024-01-31T23:59:59Z')
await expectLogic(logic).toFinishAllListeners()
const query = logic.values.query
const source = query.source as SessionQuery
expect(source.dateRange).toEqual({
date_from: '2024-01-01T00:00:00Z',
date_to: '2024-01-31T23:59:59Z',
})
})
it('generates date range when dateRange has dateFrom but no dateTo', async () => {
logic.actions.setSessionId('test-session-789')
logic.actions.setDateRange('2024-01-01T00:00:00Z')
await expectLogic(logic).toFinishAllListeners()
const query = logic.values.query
const source = query.source as SessionQuery
expect(source.dateRange?.date_from).toBe('2024-01-01T00:00:00Z')
// date_to is optional when only date_from is provided
expect(source.dateRange?.date_to).toBeUndefined()
})
it('generates query without dateRange when no dateRange is explicitly set', async () => {
logic.actions.setSessionId('test-session-no-dates')
await expectLogic(logic).toFinishAllListeners()
const query = logic.values.query
const source = query.source as SessionQuery
// When no explicit dateRange is set, the query should not have a dateRange
// (it will use the global dateFilter from parent logic when displayed)
expect(source.dateRange).toBeUndefined()
})
it('updates query when session ID changes', async () => {
logic.actions.setSessionId('session-1')
await expectLogic(logic).toFinishAllListeners()
let query = logic.values.query
let source = query.source as SessionQuery
expect(source.sessionId).toBe('session-1')
logic.actions.setSessionId('session-2')
await expectLogic(logic).toFinishAllListeners()
query = logic.values.query
source = query.source as SessionQuery
expect(source.sessionId).toBe('session-2')
})
})
describe('breadcrumbs selector', () => {
it('generates correct breadcrumbs structure', async () => {
logic.actions.setSessionId('test-session-breadcrumbs')
await expectLogic(logic).toFinishAllListeners()
const breadcrumbs = logic.values.breadcrumbs
expect(breadcrumbs).toHaveLength(3)
expect(breadcrumbs[0]).toEqual({
key: 'AIObservability',
name: 'AI observability',
path: urls.aiObservabilityDashboard(),
iconType: 'llm_analytics',
})
// Breadcrumbs path includes the date filter from parent logic
expect(breadcrumbs[1].key).toBe('AIObservabilitySessions')
expect(breadcrumbs[1].name).toBe('Sessions')
expect(breadcrumbs[1].path).toContain(urls.aiObservabilitySessions())
expect(breadcrumbs[1].iconType).toBe('llm_analytics')
expect(breadcrumbs[2]).toEqual({
key: ['AIObservabilitySession', 'test-session-breadcrumbs'],
name: 'test-session-breadcrumbs',
iconType: 'llm_analytics',
})
})
it('updates breadcrumbs when session ID changes', async () => {
logic.actions.setSessionId('session-1')
await expectLogic(logic).toFinishAllListeners()
let breadcrumbs = logic.values.breadcrumbs
expect(breadcrumbs[2].name).toBe('session-1')
logic.actions.setSessionId('session-2')
await expectLogic(logic).toFinishAllListeners()
breadcrumbs = logic.values.breadcrumbs
expect(breadcrumbs[2].name).toBe('session-2')
})
it('handles empty session ID in breadcrumbs', async () => {
logic.actions.setSessionId('')
await expectLogic(logic).toFinishAllListeners()
const breadcrumbs = logic.values.breadcrumbs
expect(breadcrumbs[2]).toEqual({
key: ['AIObservabilitySession', ''],
name: '',
iconType: 'llm_analytics',
})
})
})
describe('integration with URL', () => {
it('extracts both session ID and timestamp from URL', async () => {
const sessionId = 'full-integration-test'
const timestamp = '2024-06-15T12:00:00Z'
const sessionUrl = combineUrl(urls.aiObservabilitySession(sessionId, { timestamp }))
router.actions.push(addProjectIdIfMissing(sessionUrl.url, MOCK_TEAM_ID))
await expectLogic(logic).toMatchValues({
sessionId: sessionId,
dateRange: {
dateFrom: timestamp,
dateTo: null,
},
})
// Query should reflect both values
const query = logic.values.query
const source = query.source as SessionQuery
expect(source.sessionId).toBe(sessionId)
expect(source.dateRange?.date_from).toBe(timestamp)
})
it('handles URL navigation without timestamp', async () => {
const sessionId = 'no-timestamp-test'
const sessionUrl = combineUrl(urls.aiObservabilitySession(sessionId))
router.actions.push(addProjectIdIfMissing(sessionUrl.url, MOCK_TEAM_ID))
await expectLogic(logic).toMatchValues({
sessionId: sessionId,
// When navigating without explicit date params, falls back to parent dateFilter
dateRange: { dateFrom: '-1h', dateTo: null },
})
})
})
})