mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
2,938 of 3,342 occurrences renamed across 253 files. What is left is 404, and
every one of them is a contract with something outside this process:
hog_flow / hog_flows / hog_flow_id / hog_flow_version queue-routing key and
the /hog_flows/ API path
hog_invocation_results, insights_hogfunction,
insights_hogflow, hog_templated table and column names
'hogflow', 'hogoverflow' cyclotron queue values,
sitting in rows right now
'tophog' the TOPFN_OUTPUT value
Renaming any of those is a migration with a cutover, not a rename, so they stay
until someone does that deliberately.
The names follow what the files already say. hog-*.ts became script-*.ts in an
earlier pass, so HogTransformerService is ScriptTransformerService and
compileHog is compileScript; TophogOutput follows its own TOPFN_OUTPUT to
TopFnOutput; HogFlow is Flow.
Done with a transform that walks the source and only rewrites CODE — quoted
strings, template text, and comments naming a wire value are left as they are,
with snake_case hog tokens held aside even when they appear unquoted as object
keys, which is exactly how 'hog_flow' survives in `{ hog_flow: flowQueue }`.
Two things that pass needed on top of it, both real breaks otherwise:
* `${...}` inside a template literal is code — renamed there, text left alone.
* obj['hogWatcher'] is a property access by string. Renaming the property and
not the accessor points it at nothing; 14 of those in 6 files.
Co-authored-by: Hanzo Dev <[email protected]>
197 lines
6.6 KiB
TypeScript
197 lines
6.6 KiB
TypeScript
import { AdminClient, CODES, KafkaConsumer, LibrdKafkaError } from 'node-rdkafka'
|
|
|
|
import { defaultConfig, overrideWithEnv } from '~/common/config/config'
|
|
import {
|
|
KAFKA_APP_METRICS_2,
|
|
KAFKA_BUFFER,
|
|
KAFKA_CDP_DATASTORE_BEHAVIORAL_COHORTS_MATCHES,
|
|
KAFKA_CDP_DATASTORE_PREFILTERED_EVENTS,
|
|
KAFKA_DATASTORE_AI_EVENTS_JSON,
|
|
KAFKA_DATASTORE_HEATMAP_EVENTS,
|
|
KAFKA_DATASTORE_SESSION_RECORDING_EVENTS,
|
|
KAFKA_DATASTORE_SESSION_REPLAY_EVENTS,
|
|
KAFKA_DATASTORE_TOPFN,
|
|
KAFKA_COHORT_MEMBERSHIP_CHANGED,
|
|
KAFKA_ERROR_TRACKING_ISSUE_FINGERPRINT_OVERRIDES,
|
|
KAFKA_EVENTS_DEAD_LETTER_QUEUE,
|
|
KAFKA_EVENTS_JSON,
|
|
KAFKA_EVENTS_PLUGIN_INGESTION,
|
|
KAFKA_EVENTS_PLUGIN_INGESTION_ASYNC,
|
|
KAFKA_EVENTS_PLUGIN_INGESTION_DLQ,
|
|
KAFKA_EVENTS_PLUGIN_INGESTION_OVERFLOW,
|
|
KAFKA_EVENTS_RECENT_JSON,
|
|
KAFKA_GROUPS,
|
|
KAFKA_INGESTION_WARNINGS,
|
|
KAFKA_LOG_ENTRIES,
|
|
KAFKA_MESSAGE_ASSETS,
|
|
KAFKA_PERFORMANCE_EVENTS,
|
|
KAFKA_PERSON,
|
|
KAFKA_PERSON_DISTINCT_ID,
|
|
KAFKA_PERSON_DISTINCT_ID_OVERRIDES,
|
|
KAFKA_PERSON_MERGE_EVENTS,
|
|
KAFKA_PERSON_UNIQUE_ID,
|
|
KAFKA_PLUGIN_LOG_ENTRIES,
|
|
KAFKA_SESSION_RECORDING_SNAPSHOT_ITEM_EVENTS,
|
|
} from '~/common/config/kafka-topics'
|
|
|
|
import { PluginsServerConfig } from '../../src/types'
|
|
|
|
function buildKafkaConfig(extraServerConfig?: Partial<PluginsServerConfig>) {
|
|
const config = { ...overrideWithEnv(defaultConfig, process.env), ...extraServerConfig }
|
|
return {
|
|
'client.id': 'nodejs-test',
|
|
'metadata.broker.list': (config.KAFKA_HOSTS || '').split(',').join(','),
|
|
}
|
|
}
|
|
|
|
async function createTopicsWithClient(client: ReturnType<typeof AdminClient.create>, topics: string[]): Promise<void> {
|
|
const timeout = 10000
|
|
for (const topic of topics) {
|
|
await new Promise<void>((resolve, reject) => {
|
|
client.createTopic(
|
|
{ topic, num_partitions: 1, replication_factor: 1 },
|
|
timeout,
|
|
(error: LibrdKafkaError) => {
|
|
if (error) {
|
|
if (error.code === CODES.ERRORS.ERR_TOPIC_ALREADY_EXISTS) {
|
|
resolve()
|
|
} else {
|
|
console.error(`Failed to create topic ${topic}:`, error)
|
|
reject(error)
|
|
}
|
|
} else {
|
|
resolve()
|
|
}
|
|
}
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Topics that the Datastore Kafka engine tables and the ingestion stack expect to
|
|
// exist. Created once (idempotently via ensureKafkaTopics) rather than deleted and
|
|
// recreated per test, so Datastore's Kafka consumers keep their partition assignments.
|
|
export const TEST_KAFKA_TOPICS = [
|
|
KAFKA_DATASTORE_AI_EVENTS_JSON,
|
|
KAFKA_EVENTS_JSON,
|
|
KAFKA_EVENTS_PLUGIN_INGESTION,
|
|
KAFKA_BUFFER,
|
|
KAFKA_GROUPS,
|
|
KAFKA_SESSION_RECORDING_SNAPSHOT_ITEM_EVENTS,
|
|
KAFKA_PERFORMANCE_EVENTS,
|
|
KAFKA_PERSON,
|
|
KAFKA_PERSON_UNIQUE_ID,
|
|
KAFKA_PERSON_DISTINCT_ID,
|
|
KAFKA_PERSON_DISTINCT_ID_OVERRIDES,
|
|
KAFKA_PLUGIN_LOG_ENTRIES,
|
|
KAFKA_EVENTS_DEAD_LETTER_QUEUE,
|
|
KAFKA_EVENTS_PLUGIN_INGESTION_DLQ,
|
|
KAFKA_EVENTS_PLUGIN_INGESTION_OVERFLOW,
|
|
KAFKA_EVENTS_PLUGIN_INGESTION_ASYNC,
|
|
KAFKA_INGESTION_WARNINGS,
|
|
KAFKA_DATASTORE_HEATMAP_EVENTS,
|
|
KAFKA_APP_METRICS_2,
|
|
KAFKA_PERSON,
|
|
KAFKA_DATASTORE_SESSION_RECORDING_EVENTS,
|
|
KAFKA_DATASTORE_SESSION_REPLAY_EVENTS,
|
|
KAFKA_LOG_ENTRIES,
|
|
KAFKA_MESSAGE_ASSETS,
|
|
KAFKA_EVENTS_RECENT_JSON,
|
|
KAFKA_ERROR_TRACKING_ISSUE_FINGERPRINT_OVERRIDES,
|
|
KAFKA_CDP_DATASTORE_BEHAVIORAL_COHORTS_MATCHES,
|
|
KAFKA_CDP_DATASTORE_PREFILTERED_EVENTS,
|
|
KAFKA_COHORT_MEMBERSHIP_CHANGED,
|
|
KAFKA_PERSON_MERGE_EVENTS,
|
|
KAFKA_DATASTORE_TOPFN,
|
|
]
|
|
|
|
export async function resetKafka(extraServerConfig?: Partial<PluginsServerConfig>): Promise<void> {
|
|
const kafkaConfig = buildKafkaConfig(extraServerConfig)
|
|
await createTopics(kafkaConfig, TEST_KAFKA_TOPICS)
|
|
}
|
|
|
|
// Builds a unique topic name for a test so each test can produce to and consume from an
|
|
// isolated input topic without deleting the shared topics Datastore subscribes to.
|
|
export function createKafkaTestTopicName(baseTopic: string): string {
|
|
return `${baseTopic}_${Date.now()}_${Math.random().toString(16).slice(2)}`
|
|
}
|
|
|
|
export async function createTopics(kafkaConfig: any, topics: string[]): Promise<void> {
|
|
const client = AdminClient.create(kafkaConfig)
|
|
await deleteAllTopics(kafkaConfig)
|
|
await createTopicsWithClient(client, topics)
|
|
client.disconnect()
|
|
}
|
|
|
|
/**
|
|
* Create Kafka topics if they don't already exist, without deleting existing topics.
|
|
* Unlike resetKafka, this preserves Datastore Kafka engine consumer connections,
|
|
* avoiding the slow reconnection cycle that causes flaky tests.
|
|
*/
|
|
export async function ensureKafkaTopics(
|
|
topics: string[],
|
|
extraServerConfig?: Partial<PluginsServerConfig>
|
|
): Promise<void> {
|
|
const kafkaConfig = buildKafkaConfig(extraServerConfig)
|
|
const client = AdminClient.create(kafkaConfig)
|
|
await createTopicsWithClient(client, topics)
|
|
client.disconnect()
|
|
}
|
|
|
|
export async function deleteAllTopics(kafkaConfig: any): Promise<void> {
|
|
// Use a consumer to get metadata
|
|
const consumer = new KafkaConsumer(
|
|
{
|
|
...kafkaConfig,
|
|
'group.id': 'temp-metadata-group',
|
|
},
|
|
{}
|
|
)
|
|
|
|
await new Promise<void>((resolve, reject) => {
|
|
consumer.on('ready', () => resolve())
|
|
consumer.on('event.error', (err) => reject(err))
|
|
consumer.connect()
|
|
})
|
|
|
|
// Get list of topics first
|
|
const metadata = await new Promise<any>((resolve, reject) => {
|
|
consumer.getMetadata({}, (err: any, metadata: any) => {
|
|
if (err) {
|
|
reject(err)
|
|
} else {
|
|
resolve(metadata)
|
|
}
|
|
})
|
|
})
|
|
|
|
consumer.disconnect()
|
|
|
|
const topicsToDelete = metadata.topics.map((t: any) => t.name).filter((name: string) => !name.startsWith('__')) // skip internal topics
|
|
|
|
if (topicsToDelete.length === 0) {
|
|
console.log('No topics to delete.')
|
|
return
|
|
}
|
|
|
|
// Use AdminClient to delete topics
|
|
const adminClient = AdminClient.create(kafkaConfig)
|
|
const timeout = 10000
|
|
|
|
// Delete topics one by one
|
|
for (const topic of topicsToDelete) {
|
|
await new Promise<void>((resolve, reject) => {
|
|
adminClient.deleteTopic(topic, timeout, (error: LibrdKafkaError) => {
|
|
if (error) {
|
|
console.error(`Failed to delete topic ${topic}:`, error)
|
|
reject(error)
|
|
} else {
|
|
resolve()
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
adminClient.disconnect()
|
|
}
|