mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
feat: Get ClickHouse client from AlertProvider (#1183)
HDX-2078 This PR shifts the creation of the ClickHouse client used by alerts to the AlertProvider interface, to support other auth methods in other AlertProvider implementations. Running locally, the default provider creates the client and successfully queries with it: <img width="1716" height="206" alt="Screenshot 2025-09-18 at 3 58 31 PM" src="https://github.com/user-attachments/assets/971a633f-6ddd-42ca-be70-19e303573938" />
This commit is contained in:
parent
21f1aa7567
commit
140e4d2f23
6 changed files with 54 additions and 15 deletions
5
.changeset/selfish-goats-collect.md
Normal file
5
.changeset/selfish-goats-collect.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@hyperdx/api": patch
|
||||
---
|
||||
|
||||
feat: Get ClickHouse client from AlertProvider
|
||||
|
|
@ -41,6 +41,9 @@ describe('CheckAlertTask', () => {
|
|||
asyncDispose: jest.fn(),
|
||||
buildChartLink: jest.fn(),
|
||||
buildLogSearchLink: jest.fn(),
|
||||
getClickHouseClient: jest
|
||||
.fn()
|
||||
.mockResolvedValue({} as ClickhouseClient),
|
||||
};
|
||||
|
||||
jest.mocked(loadProvider).mockResolvedValue(mockAlertProvider);
|
||||
|
|
@ -150,6 +153,9 @@ describe('CheckAlertTask', () => {
|
|||
|
||||
mockAlertProvider.getAlertTasks.mockResolvedValue([mockAlertTask]);
|
||||
mockAlertProvider.getWebhooks.mockResolvedValue(teamWebhooksById);
|
||||
mockAlertProvider.getClickHouseClient.mockResolvedValue(
|
||||
new ClickhouseClient({}),
|
||||
);
|
||||
|
||||
await task.execute();
|
||||
|
||||
|
|
@ -272,6 +278,10 @@ describe('CheckAlertTask', () => {
|
|||
mockAlertTask2,
|
||||
]);
|
||||
|
||||
mockAlertProvider.getClickHouseClient.mockResolvedValue(
|
||||
new ClickhouseClient({}),
|
||||
);
|
||||
|
||||
// Mock getWebhooks to return team-specific webhooks
|
||||
mockAlertProvider.getWebhooks.mockImplementation(
|
||||
(teamId: string | ObjectId): Promise<Map<string, IWebhook>> => {
|
||||
|
|
|
|||
|
|
@ -438,20 +438,7 @@ export default class CheckAlertTask implements HdxTask<CheckAlertsTaskArgs> {
|
|||
alertCount: alerts.length,
|
||||
});
|
||||
|
||||
if (!conn.password && conn.password !== '') {
|
||||
const providerName = this.provider.constructor.name;
|
||||
logger.info({
|
||||
message: `alert provider did not fetch connection password`,
|
||||
providerName,
|
||||
connectionId: conn.id,
|
||||
});
|
||||
}
|
||||
|
||||
const clickhouseClient = new ClickhouseClient({
|
||||
host: conn.host,
|
||||
username: conn.username,
|
||||
password: conn.password,
|
||||
});
|
||||
const clickhouseClient = await this.provider.getClickHouseClient(conn);
|
||||
|
||||
for (const alert of alerts) {
|
||||
await this.task_queue.add(() =>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { ClickhouseClient } from '@hyperdx/common-utils/dist/clickhouse/node';
|
||||
|
||||
import { AlertProvider, isValidProvider } from '../index';
|
||||
|
||||
describe('isValidProvider', () => {
|
||||
|
|
@ -10,6 +12,7 @@ describe('isValidProvider', () => {
|
|||
buildChartLink: () => 'http://example.com/chart',
|
||||
updateAlertState: () => Promise.resolve(),
|
||||
getWebhooks: () => Promise.resolve(new Map()),
|
||||
getClickHouseClient: () => Promise.resolve({} as ClickhouseClient),
|
||||
};
|
||||
|
||||
expect(isValidProvider(validProvider)).toBe(true);
|
||||
|
|
@ -36,6 +39,7 @@ describe('isValidProvider', () => {
|
|||
buildChartLink: () => 'http://example.com/chart',
|
||||
updateAlertState: () => Promise.resolve(),
|
||||
getWebhooks: () => Promise.resolve(new Map()),
|
||||
getClickHouseClient: () => Promise.resolve({} as ClickhouseClient),
|
||||
};
|
||||
|
||||
expect(isValidProvider(invalidProvider)).toBe(false);
|
||||
|
|
@ -49,6 +53,7 @@ describe('isValidProvider', () => {
|
|||
buildChartLink: () => 'http://example.com/chart',
|
||||
updateAlertState: () => Promise.resolve(),
|
||||
getWebhooks: () => Promise.resolve(new Map()),
|
||||
getClickHouseClient: () => Promise.resolve({} as ClickhouseClient),
|
||||
};
|
||||
|
||||
expect(isValidProvider(invalidProvider)).toBe(false);
|
||||
|
|
@ -62,6 +67,7 @@ describe('isValidProvider', () => {
|
|||
buildChartLink: () => 'http://example.com/chart',
|
||||
updateAlertState: () => Promise.resolve(),
|
||||
getWebhooks: () => Promise.resolve(new Map()),
|
||||
getClickHouseClient: () => Promise.resolve({} as ClickhouseClient),
|
||||
};
|
||||
|
||||
expect(isValidProvider(invalidProvider)).toBe(false);
|
||||
|
|
@ -75,6 +81,7 @@ describe('isValidProvider', () => {
|
|||
buildChartLink: () => 'http://example.com/chart',
|
||||
updateAlertState: () => Promise.resolve(),
|
||||
getWebhooks: () => Promise.resolve(new Map()),
|
||||
getClickHouseClient: () => Promise.resolve({} as ClickhouseClient),
|
||||
};
|
||||
|
||||
expect(isValidProvider(invalidProvider)).toBe(false);
|
||||
|
|
@ -88,6 +95,7 @@ describe('isValidProvider', () => {
|
|||
buildLogSearchLink: () => 'http://example.com/search',
|
||||
updateAlertState: () => Promise.resolve(),
|
||||
getWebhooks: () => Promise.resolve(new Map()),
|
||||
getClickHouseClient: () => Promise.resolve({} as ClickhouseClient),
|
||||
};
|
||||
|
||||
expect(isValidProvider(invalidProvider)).toBe(false);
|
||||
|
|
@ -102,6 +110,7 @@ describe('isValidProvider', () => {
|
|||
buildChartLink: () => 'http://example.com/chart',
|
||||
updateAlertState: () => Promise.resolve(),
|
||||
getWebhooks: () => Promise.resolve(new Map()),
|
||||
getClickHouseClient: () => Promise.resolve({} as ClickhouseClient),
|
||||
};
|
||||
|
||||
expect(isValidProvider(invalidProvider)).toBe(false);
|
||||
|
|
@ -116,6 +125,7 @@ describe('isValidProvider', () => {
|
|||
buildChartLink: () => 'http://example.com/chart',
|
||||
updateAlertState: () => Promise.resolve(),
|
||||
getWebhooks: () => Promise.resolve(new Map()),
|
||||
getClickHouseClient: () => Promise.resolve({} as ClickhouseClient),
|
||||
};
|
||||
|
||||
expect(isValidProvider(invalidProvider)).toBe(false);
|
||||
|
|
@ -130,6 +140,7 @@ describe('isValidProvider', () => {
|
|||
buildChartLink: () => 'http://example.com/chart',
|
||||
updateAlertState: () => Promise.resolve(),
|
||||
getWebhooks: () => Promise.resolve(new Map()),
|
||||
getClickHouseClient: () => Promise.resolve({} as ClickhouseClient),
|
||||
extraProperty: 'should not affect validation',
|
||||
anotherMethod: () => {},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { ClickhouseClient } from '@hyperdx/common-utils/dist/clickhouse/node';
|
||||
import { Tile } from '@hyperdx/common-utils/dist/types';
|
||||
import mongoose from 'mongoose';
|
||||
import ms from 'ms';
|
||||
|
|
@ -294,4 +295,24 @@ export default class DefaultAlertProvider implements AlertProvider {
|
|||
});
|
||||
return new Map<string, IWebhook>(webhooks.map(w => [w.id, w]));
|
||||
}
|
||||
|
||||
async getClickHouseClient({
|
||||
host,
|
||||
username,
|
||||
password,
|
||||
id,
|
||||
}: IConnection): Promise<ClickhouseClient> {
|
||||
if (!password && password !== '') {
|
||||
logger.info({
|
||||
message: `connection password not found`,
|
||||
connectionId: id,
|
||||
});
|
||||
}
|
||||
|
||||
return new ClickhouseClient({
|
||||
host,
|
||||
username,
|
||||
password,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { ClickhouseClient } from '@hyperdx/common-utils/dist/clickhouse/node';
|
||||
import { Tile } from '@hyperdx/common-utils/dist/types';
|
||||
|
||||
import { ObjectId } from '@/models';
|
||||
|
|
@ -76,6 +77,9 @@ export interface AlertProvider {
|
|||
|
||||
/** Fetch all webhooks for the given team, returning a map of webhook ID to webhook */
|
||||
getWebhooks(teamId: string | ObjectId): Promise<Map<string, IWebhook>>;
|
||||
|
||||
/** Create and return an authenticated ClickHouse client */
|
||||
getClickHouseClient(connection: IConnection): Promise<ClickhouseClient>;
|
||||
}
|
||||
|
||||
export function isValidProvider(obj: any): obj is AlertProvider {
|
||||
|
|
@ -87,7 +91,8 @@ export function isValidProvider(obj: any): obj is AlertProvider {
|
|||
typeof obj.buildLogSearchLink === 'function' &&
|
||||
typeof obj.buildChartLink === 'function' &&
|
||||
typeof obj.updateAlertState === 'function' &&
|
||||
typeof obj.getWebhooks === 'function'
|
||||
typeof obj.getWebhooks === 'function' &&
|
||||
typeof obj.getClickHouseClient === 'function'
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue