feat(frontend): add technique/platform/outcome/date filters to Validated Tests
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
The "200" badge on the Validated Tests page was a hardcoded query limit, not the real total — silently hiding results beyond the 200th once there were more validated tests than that. Now shows the true total via the new /tests/count endpoint, and warns when the fetched page doesn't cover all matches. Adds filter controls beyond the existing name/technique text search: technique (MITRE ID or name), platform, attack outcome, detection outcome, and a validated-date range — all pushed down to the server via the new GET /tests filters instead of only ever searching the current page client-side.
This commit is contained in:
@@ -67,12 +67,19 @@ export interface TestValidatePayload {
|
||||
export interface TestListFilters {
|
||||
state?: TestState;
|
||||
technique_id?: string;
|
||||
/** Free-text filter on technique MITRE ID or name — doesn't require knowing the technique's UUID. */
|
||||
technique_search?: string;
|
||||
platform?: string;
|
||||
created_by?: string;
|
||||
pending_validation_side?: "red" | "blue";
|
||||
/** "My reviews" queue — tests assigned to this reviewer at the red_review/blue_review gate. */
|
||||
reviewer_id?: string;
|
||||
not_in_any_campaign?: boolean;
|
||||
attack_success?: AttackSuccessResult;
|
||||
detection_result?: TestResult;
|
||||
/** ISO date/datetime — filters on whichever lead validated last. */
|
||||
validated_from?: string;
|
||||
validated_to?: string;
|
||||
assigned_to_me?: boolean;
|
||||
unassigned_red?: boolean;
|
||||
unassigned_blue?: boolean;
|
||||
@@ -80,18 +87,28 @@ export interface TestListFilters {
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
// ── CRUD ───────────────────────────────────────────────────────────
|
||||
|
||||
/** List tests with optional filters. */
|
||||
export async function getTests(filters?: TestListFilters): Promise<Test[]> {
|
||||
function buildTestListParams(filters?: TestListFilters): URLSearchParams {
|
||||
const params = new URLSearchParams();
|
||||
if (filters?.state) params.append("state", filters.state);
|
||||
if (filters?.technique_id) params.append("technique_id", filters.technique_id);
|
||||
if (filters?.technique_search) params.append("technique_search", filters.technique_search);
|
||||
if (filters?.platform) params.append("platform", filters.platform);
|
||||
if (filters?.created_by) params.append("created_by", filters.created_by);
|
||||
if (filters?.pending_validation_side) params.append("pending_validation_side", filters.pending_validation_side);
|
||||
if (filters?.reviewer_id) params.append("reviewer_id", filters.reviewer_id);
|
||||
if (filters?.not_in_any_campaign) params.append("not_in_any_campaign", "true");
|
||||
if (filters?.attack_success) params.append("attack_success", filters.attack_success);
|
||||
if (filters?.detection_result) params.append("detection_result", filters.detection_result);
|
||||
if (filters?.validated_from) params.append("validated_from", filters.validated_from);
|
||||
if (filters?.validated_to) params.append("validated_to", filters.validated_to);
|
||||
return params;
|
||||
}
|
||||
|
||||
// ── CRUD ───────────────────────────────────────────────────────────
|
||||
|
||||
/** List tests with optional filters. */
|
||||
export async function getTests(filters?: TestListFilters): Promise<Test[]> {
|
||||
const params = buildTestListParams(filters);
|
||||
if (filters?.offset !== undefined) params.append("offset", String(filters.offset));
|
||||
if (filters?.limit !== undefined) params.append("limit", String(filters.limit));
|
||||
|
||||
@@ -101,6 +118,15 @@ export async function getTests(filters?: TestListFilters): Promise<Test[]> {
|
||||
return data;
|
||||
}
|
||||
|
||||
/** Return the true total of tests matching the given filters (unaffected by pagination limits). */
|
||||
export async function getTestsCount(filters?: TestListFilters): Promise<number> {
|
||||
const params = buildTestListParams(filters);
|
||||
const { data } = await client.get<{ total: number }>(
|
||||
`/tests/count${params.toString() ? `?${params}` : ""}`,
|
||||
);
|
||||
return data.total;
|
||||
}
|
||||
|
||||
/** Create a new test. */
|
||||
export async function createTest(payload: TestCreatePayload): Promise<Test> {
|
||||
const { data } = await client.post<Test>("/tests", payload);
|
||||
|
||||
Reference in New Issue
Block a user