Clean up the clickhouse client
This commit is contained in:
135
src/lib/clickhouse-client.test.ts
Normal file
135
src/lib/clickhouse-client.test.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { FetchActiveJobs, FetchBlueprintQueue, FetchHistoricJobs } from './clickhouse-client';
|
||||
|
||||
describe('ClickHouse Client FetchManufacturingJobs', () => {
|
||||
let fetchSpy: ReturnType<typeof vi.spyOn>;
|
||||
beforeEach(() => { fetchSpy = vi.spyOn(globalThis, 'fetch'); });
|
||||
afterEach(() => { fetchSpy.mockRestore(); });
|
||||
|
||||
it('fetches manufacturing jobs from latest snapshot', async () => {
|
||||
const result = await FetchActiveJobs(['John D Pipe']);
|
||||
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(fetchSpy).toHaveBeenCalledOnce();
|
||||
const url = new URL(fetchSpy.mock.calls[0][0] as string);
|
||||
expect(url.hostname).toBe('mclickhouse.site.quack-lab.dev');
|
||||
});
|
||||
|
||||
it('returns array of ManufacturingJobRow with correct shape', async () => {
|
||||
const result = await FetchActiveJobs(['John D Pipe']);
|
||||
|
||||
if (result.length > 0) {
|
||||
const job = result[0];
|
||||
expect(job).toHaveProperty('activity_id');
|
||||
expect(job).toHaveProperty('blueprint_id');
|
||||
expect(job).toHaveProperty('job_id');
|
||||
expect(job).toHaveProperty('name');
|
||||
expect(job).toHaveProperty('product_type_id');
|
||||
expect(job).toHaveProperty('start_date');
|
||||
expect(job).toHaveProperty('end_date');
|
||||
expect(job).toHaveProperty('status');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('ClickHouse Client FetchHistoricJobs', () => {
|
||||
let fetchSpy: ReturnType<typeof vi.spyOn>;
|
||||
beforeEach(() => { fetchSpy = vi.spyOn(globalThis, 'fetch'); });
|
||||
afterEach(() => { fetchSpy.mockRestore(); });
|
||||
|
||||
it('fetches historic jobs with character filter', async () => {
|
||||
const characterNames = ['John D Pipe'];
|
||||
const result = await FetchHistoricJobs(characterNames);
|
||||
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(fetchSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('fetches historic jobs for multiple characters', async () => {
|
||||
const characterNames = ['John D Pipe', 'BastardSlavDave', 'Primuskov'];
|
||||
const result = await FetchHistoricJobs(characterNames);
|
||||
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(fetchSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('returns array of ManufacturingJobRow with correct shape', async () => {
|
||||
const characterNames = ['John D Pipe'];
|
||||
const result = await FetchHistoricJobs(characterNames);
|
||||
|
||||
if (result.length > 0) {
|
||||
const job = result[0];
|
||||
expect(job).toHaveProperty('activity_id');
|
||||
expect(job).toHaveProperty('blueprint_id');
|
||||
expect(job).toHaveProperty('job_id');
|
||||
expect(job).toHaveProperty('name');
|
||||
expect(job).toHaveProperty('product_type_id');
|
||||
expect(job).toHaveProperty('start_date');
|
||||
expect(job).toHaveProperty('end_date');
|
||||
expect(job).toHaveProperty('status');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('ClickHouse Client FetchBlueprintQueue', () => {
|
||||
let fetchSpy: ReturnType<typeof vi.spyOn>;
|
||||
beforeEach(() => { fetchSpy = vi.spyOn(globalThis, 'fetch'); });
|
||||
afterEach(() => { fetchSpy.mockRestore(); });
|
||||
|
||||
it('fetches blueprint queue items', async () => {
|
||||
const result = await FetchBlueprintQueue();
|
||||
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(fetchSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('returns array of BlueprintQueueRow with correct shape', async () => {
|
||||
const result = await FetchBlueprintQueue();
|
||||
|
||||
if (result.length > 0) {
|
||||
const item = result[0];
|
||||
expect(item).toHaveProperty('id');
|
||||
expect(item).toHaveProperty('type_id');
|
||||
expect(item).toHaveProperty('added_at');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('ClickHouse Client FetchHistoricJobs', () => {
|
||||
let fetchSpy: ReturnType<typeof vi.spyOn>;
|
||||
beforeEach(() => { fetchSpy = vi.spyOn(globalThis, 'fetch'); });
|
||||
afterEach(() => { fetchSpy.mockRestore(); });
|
||||
|
||||
it('fetches completed jobs with character filter', async () => {
|
||||
const characterNames = ['John D Pipe'];
|
||||
const result = await FetchHistoricJobs(characterNames);
|
||||
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(fetchSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('fetches completed jobs for multiple characters', async () => {
|
||||
const characterNames = ['John D Pipe', 'BastardSlavDave'];
|
||||
const result = await FetchHistoricJobs(characterNames);
|
||||
|
||||
expect(Array.isArray(result)).toBe(true);
|
||||
expect(fetchSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('returns array of ManufacturingJobRow with correct shape', async () => {
|
||||
const characterNames = ['John D Pipe'];
|
||||
const result = await FetchHistoricJobs(characterNames);
|
||||
|
||||
if (result.length > 0) {
|
||||
const job = result[0];
|
||||
expect(job).toHaveProperty('activity_id');
|
||||
expect(job).toHaveProperty('blueprint_id');
|
||||
expect(job).toHaveProperty('job_id');
|
||||
expect(job).toHaveProperty('name');
|
||||
expect(job).toHaveProperty('product_type_id');
|
||||
expect(job).toHaveProperty('start_date');
|
||||
expect(job).toHaveProperty('end_date');
|
||||
expect(job).toHaveProperty('status');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ClickHouseResponse, ManufacturingJobRow, BlueprintQueueRow } from "./clickhouse-model";
|
||||
import { ClickHouseResponse, ManufacturingJob, BlueprintQueueItem } from "./clickhouse-model";
|
||||
|
||||
const CLICKHOUSE_URL = "https://mclickhouse.site.quack-lab.dev";
|
||||
const CLICKHOUSE_USER = "indy_jobs_ro_user";
|
||||
@@ -20,121 +20,104 @@ async function queryClickHouse<T>(query: string): Promise<ClickHouseResponse<T>>
|
||||
return await response.json() as ClickHouseResponse<T>;
|
||||
}
|
||||
|
||||
export async function FetchManufacturingJobs(): Promise<ManufacturingJobRow[]> {
|
||||
export async function FetchActiveJobs(activeCharacters: string[]): Promise<ManufacturingJob[]> {
|
||||
const characterFilter = activeCharacters.join(', ');
|
||||
|
||||
const query = `
|
||||
WITH latest_snapshot AS (
|
||||
SELECT MAX(created_at) AS max_created_at
|
||||
FROM default.manufacturing_jobs
|
||||
SELECT MAX(created_at) AS max_created_at
|
||||
FROM default.manufacturing_jobs
|
||||
)
|
||||
SELECT * FROM default.manufacturing_jobs
|
||||
SELECT
|
||||
job_id,
|
||||
activity_id,
|
||||
blueprint_id,
|
||||
blueprint_location_id,
|
||||
blueprint_type_id,
|
||||
cost,
|
||||
duration,
|
||||
end_date,
|
||||
facility_id,
|
||||
installer_id,
|
||||
licensed_runs,
|
||||
location_id,
|
||||
output_location_id,
|
||||
probability,
|
||||
product_type_id,
|
||||
runs,
|
||||
start_date,
|
||||
status,
|
||||
birthday,
|
||||
bloodline_id,
|
||||
corporation_id,
|
||||
description,
|
||||
gender,
|
||||
name,
|
||||
race_id,
|
||||
security_status,
|
||||
max(created_at) as created_at
|
||||
FROM default.manufacturing_jobs
|
||||
WHERE created_at = (SELECT max_created_at FROM latest_snapshot)
|
||||
AND name IN (${characterFilter})
|
||||
GROUP BY job_id
|
||||
ORDER BY end_date DESC
|
||||
FORMAT JSON
|
||||
`;
|
||||
|
||||
const result = await queryClickHouse<ManufacturingJobRow>(query);
|
||||
const result = await queryClickHouse<ManufacturingJob>(query);
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function FetchHistoricJobs(characterNames: string[], limit: number): Promise<ManufacturingJobRow[]> {
|
||||
const characterFilter = characterNames.map(n => `'${n.replace(/'/g, "''")}'`).join(', ');
|
||||
export async function FetchHistoricJobs(activeCharacters: string[]): Promise<ManufacturingJob[]> {
|
||||
const characterFilter = activeCharacters.join(', ');
|
||||
|
||||
const query = `
|
||||
SELECT
|
||||
job_id,
|
||||
argMax(activity_id, created_at) as activity_id,
|
||||
argMax(blueprint_id, created_at) as blueprint_id,
|
||||
argMax(blueprint_location_id, created_at) as blueprint_location_id,
|
||||
argMax(blueprint_type_id, created_at) as blueprint_type_id,
|
||||
argMax(cost, created_at) as cost,
|
||||
argMax(duration, created_at) as duration,
|
||||
argMax(end_date, created_at) as end_date,
|
||||
argMax(facility_id, created_at) as facility_id,
|
||||
argMax(installer_id, created_at) as installer_id,
|
||||
argMax(licensed_runs, created_at) as licensed_runs,
|
||||
argMax(location_id, created_at) as location_id,
|
||||
argMax(output_location_id, created_at) as output_location_id,
|
||||
argMax(probability, created_at) as probability,
|
||||
argMax(product_type_id, created_at) as product_type_id,
|
||||
argMax(runs, created_at) as runs,
|
||||
argMax(start_date, created_at) as start_date,
|
||||
argMax(status, created_at) as status,
|
||||
argMax(birthday, created_at) as birthday,
|
||||
argMax(bloodline_id, created_at) as bloodline_id,
|
||||
argMax(corporation_id, created_at) as corporation_id,
|
||||
argMax(description, created_at) as description,
|
||||
argMax(gender, created_at) as gender,
|
||||
argMax(name, created_at) as name,
|
||||
argMax(race_id, created_at) as race_id,
|
||||
argMax(security_status, created_at) as security_status,
|
||||
max(created_at) as created_at
|
||||
job_id,
|
||||
argMax(activity_id, job_id) as activity_id,
|
||||
argMax(blueprint_id, job_id) as blueprint_id,
|
||||
argMax(blueprint_location_id, job_id) as blueprint_location_id,
|
||||
argMax(blueprint_type_id, job_id) as blueprint_type_id,
|
||||
argMax(cost, job_id) as cost,
|
||||
argMax(duration, job_id) as duration,
|
||||
argMax(end_date, job_id) as end_date,
|
||||
argMax(facility_id, job_id) as facility_id,
|
||||
argMax(installer_id, job_id) as installer_id,
|
||||
argMax(licensed_runs, job_id) as licensed_runs,
|
||||
argMax(location_id, job_id) as location_id,
|
||||
argMax(output_location_id, job_id) as output_location_id,
|
||||
argMax(probability, job_id) as probability,
|
||||
argMax(product_type_id, job_id) as product_type_id,
|
||||
argMax(runs, job_id) as runs,
|
||||
argMax(start_date, job_id) as start_date,
|
||||
argMax(status, job_id) as status,
|
||||
argMax(birthday, job_id) as birthday,
|
||||
argMax(bloodline_id, job_id) as bloodline_id,
|
||||
argMax(corporation_id, job_id) as corporation_id,
|
||||
argMax(description, job_id) as description,
|
||||
argMax(gender, job_id) as gender,
|
||||
argMax(name, job_id) as name,
|
||||
argMax(race_id, job_id) as race_id,
|
||||
argMax(security_status, job_id) as security_status,
|
||||
max(created_at) as created_at
|
||||
FROM default.manufacturing_jobs
|
||||
WHERE name IN (${characterFilter})
|
||||
GROUP BY job_id
|
||||
ORDER BY end_date DESC
|
||||
LIMIT ${limit}
|
||||
FORMAT JSON
|
||||
`;
|
||||
|
||||
const result = await queryClickHouse<ManufacturingJobRow>(query);
|
||||
const result = await queryClickHouse<ManufacturingJob>(query);
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function FetchBlueprintQueue(): Promise<BlueprintQueueRow[]> {
|
||||
export async function FetchBlueprintQueue(): Promise<BlueprintQueueItem[]> {
|
||||
const query = `
|
||||
SELECT * FROM default.blueprint_queue
|
||||
ORDER BY added_at DESC
|
||||
FORMAT JSON
|
||||
`;
|
||||
|
||||
const result = await queryClickHouse<BlueprintQueueRow>(query);
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function FetchCompletedJobs(characterNames: string[], limit: number): Promise<ManufacturingJobRow[]> {
|
||||
const characterFilter = characterNames.map(n => `'${n.replace(/'/g, "''")}'`).join(', ');
|
||||
|
||||
const query = `
|
||||
WITH latest_snapshot AS (
|
||||
SELECT MAX(created_at) AS latest_created_at
|
||||
FROM default.manufacturing_jobs
|
||||
)
|
||||
SELECT
|
||||
job_id,
|
||||
argMax(activity_id, created_at) as activity_id,
|
||||
argMax(blueprint_id, created_at) as blueprint_id,
|
||||
argMax(blueprint_location_id, created_at) as blueprint_location_id,
|
||||
argMax(blueprint_type_id, created_at) as blueprint_type_id,
|
||||
argMax(cost, created_at) as cost,
|
||||
argMax(duration, created_at) as duration,
|
||||
argMax(end_date, created_at) as end_date,
|
||||
argMax(facility_id, created_at) as facility_id,
|
||||
argMax(installer_id, created_at) as installer_id,
|
||||
argMax(licensed_runs, created_at) as licensed_runs,
|
||||
argMax(location_id, created_at) as location_id,
|
||||
argMax(output_location_id, created_at) as output_location_id,
|
||||
argMax(probability, created_at) as probability,
|
||||
argMax(product_type_id, created_at) as product_type_id,
|
||||
argMax(runs, created_at) as runs,
|
||||
argMax(start_date, created_at) as start_date,
|
||||
argMax(status, created_at) as status,
|
||||
argMax(birthday, created_at) as birthday,
|
||||
argMax(bloodline_id, created_at) as bloodline_id,
|
||||
argMax(corporation_id, created_at) as corporation_id,
|
||||
argMax(description, created_at) as description,
|
||||
argMax(gender, created_at) as gender,
|
||||
argMax(name, created_at) as name,
|
||||
argMax(race_id, created_at) as race_id,
|
||||
argMax(security_status, created_at) as security_status,
|
||||
max(created_at) as created_at
|
||||
FROM default.manufacturing_jobs
|
||||
WHERE name IN (${characterFilter})
|
||||
GROUP BY job_id
|
||||
HAVING created_at < (SELECT latest_created_at FROM latest_snapshot)
|
||||
ORDER BY end_date DESC
|
||||
LIMIT ${limit}
|
||||
FORMAT JSON
|
||||
`;
|
||||
|
||||
const result = await queryClickHouse<ManufacturingJobRow>(query);
|
||||
const result = await queryClickHouse<BlueprintQueueItem>(query);
|
||||
return result.data;
|
||||
}
|
||||
|
||||
@@ -5,41 +5,38 @@ export interface ClickHouseResponse<T> {
|
||||
statistics: any;
|
||||
}
|
||||
|
||||
export interface ManufacturingJobRow {
|
||||
export interface ManufacturingJob {
|
||||
activity_id: number;
|
||||
blueprint_id: string;
|
||||
blueprint_location_id: string;
|
||||
blueprint_id: number;
|
||||
blueprint_location_id: number;
|
||||
blueprint_type_id: number;
|
||||
cost: number;
|
||||
duration: number;
|
||||
end_date: string;
|
||||
facility_id: string;
|
||||
installer_id: string;
|
||||
job_id: string;
|
||||
end_date: Date;
|
||||
facility_id: number;
|
||||
installer_id: number;
|
||||
job_id: number;
|
||||
licensed_runs: number;
|
||||
location_id: string;
|
||||
output_location_id: string;
|
||||
location_id: number;
|
||||
output_location_id: number;
|
||||
probability: number;
|
||||
product_type_id: number;
|
||||
runs: number;
|
||||
start_date: string;
|
||||
start_date: Date;
|
||||
status: string;
|
||||
birthday: string;
|
||||
birthday: Date;
|
||||
bloodline_id: number;
|
||||
corporation_id: string;
|
||||
corporation_id: number;
|
||||
description: string;
|
||||
gender: string;
|
||||
name: string;
|
||||
race_id: number;
|
||||
security_status: number;
|
||||
created_at: string;
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
export interface BlueprintQueueRow {
|
||||
id: string;
|
||||
export interface BlueprintQueueItem {
|
||||
id: number;
|
||||
type_id: number;
|
||||
quantity: number | null;
|
||||
added_by: string;
|
||||
added_at: string;
|
||||
completed: number;
|
||||
added_at: Date;
|
||||
}
|
||||
|
||||
@@ -278,4 +278,4 @@ describe('Typesense Client GetTypeIconURL', () => {
|
||||
expect(results).toBe(`https://proxy.site.quack-lab.dev/?url=https://images.evetech.net/types/11135/bpo?size=64`);
|
||||
expect(fetchSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user