mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2026-10-12 02:01:26 +00:00
fix query log filters
This commit is contained in:
@@ -1,54 +0,0 @@
|
||||
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
||||
import { getAdditionalLogs, setFilteredLogs, queryLogsState } from 'panel/stores/queryLogs';
|
||||
import { apiClient } from 'panel/api/Api';
|
||||
|
||||
vi.mock('panel/api/Api', () => ({
|
||||
apiClient: {
|
||||
getQueryLog: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('panel/stores/toasts', () => ({
|
||||
addErrorToast: vi.fn(),
|
||||
addSuccessToast: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('queryLogs store', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('does not mark the log as complete when additional loading stops', async () => {
|
||||
// Mock the API to return a non-complete log
|
||||
(apiClient.getQueryLog as any).mockResolvedValue({
|
||||
data: [],
|
||||
oldest: '',
|
||||
is_entire_log: false,
|
||||
});
|
||||
|
||||
// Simulate the state before additional loading
|
||||
// The store starts with processingAdditionalLogs: false
|
||||
await getAdditionalLogs();
|
||||
|
||||
expect(queryLogsState.processingAdditionalLogs).toBe(false);
|
||||
expect(queryLogsState.isEntireLog).toBe(false);
|
||||
});
|
||||
|
||||
it('accumulates pages until oldest is empty (short-polling)', async () => {
|
||||
(apiClient.getQueryLog as any)
|
||||
.mockResolvedValueOnce({
|
||||
data: [{ reason: 'Rewrite' }],
|
||||
oldest: 'cur',
|
||||
is_entire_log: false,
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
data: [{ reason: 'Rewrite' }],
|
||||
oldest: '',
|
||||
is_entire_log: true,
|
||||
});
|
||||
|
||||
await setFilteredLogs({ search: '', status: 'rewritten', reason: 'all' });
|
||||
expect(apiClient.getQueryLog).toHaveBeenCalledTimes(2);
|
||||
expect(queryLogsState.processingGetLogs).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -18,16 +18,80 @@ describe('queryLogs store', () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("getAdditionalLogs appends filtered rows using the oldest cursor and filter", async () => {
|
||||
(apiClient.getQueryLog as any).mockReset();
|
||||
|
||||
// Seed state: a full first page (20 rewritten) with more behind it.
|
||||
const fullPage = Array.from({ length: 20 }, () => ({
|
||||
reason: "Rewrite",
|
||||
question: {},
|
||||
}));
|
||||
(apiClient.getQueryLog as any).mockResolvedValueOnce({
|
||||
data: fullPage,
|
||||
oldest: "cur",
|
||||
});
|
||||
await setFilteredLogs({ search: "", status: "rewritten", reason: "all" });
|
||||
expect(queryLogsState.isEntireLog).toBe(false);
|
||||
expect(queryLogsState.logs).toHaveLength(20);
|
||||
|
||||
// Load more: one extra rewritten entry, then end of log.
|
||||
(apiClient.getQueryLog as any).mockResolvedValueOnce({
|
||||
data: [{ reason: "Rewrite", question: {} }],
|
||||
oldest: "",
|
||||
});
|
||||
|
||||
await getAdditionalLogs();
|
||||
|
||||
// The load-more request must carry the cursor and the reason filter.
|
||||
expect(apiClient.getQueryLog).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
older_than: "cur",
|
||||
reason: expect.arrayContaining([
|
||||
"Rewrite",
|
||||
"RewriteEtcHosts",
|
||||
"RewriteRule",
|
||||
"FilteredSafeSearch",
|
||||
]),
|
||||
}),
|
||||
);
|
||||
// Must NOT send the deprecated response_status
|
||||
const lastCall = (apiClient.getQueryLog as any).mock.calls.at(-1)[0];
|
||||
expect(lastCall).not.toHaveProperty("response_status");
|
||||
|
||||
expect(queryLogsState.logs).toHaveLength(21);
|
||||
expect(queryLogsState.isEntireLog).toBe(true);
|
||||
expect(queryLogsState.processingAdditionalLogs).toBe(false);
|
||||
});
|
||||
|
||||
it("setFilteredLogs sends reason strings for blocked status", async () => {
|
||||
(apiClient.getQueryLog as any).mockReset();
|
||||
(apiClient.getQueryLog as any).mockResolvedValue({
|
||||
data: [{ reason: "FilteredBlackList", question: {} }],
|
||||
oldest: "",
|
||||
});
|
||||
|
||||
await setFilteredLogs({ search: "", status: "blocked", reason: "all" });
|
||||
|
||||
const lastCall = (apiClient.getQueryLog as any).mock.calls.at(-1)[0];
|
||||
expect(apiClient.getQueryLog).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
reason: expect.arrayContaining([
|
||||
"FilteredBlackList",
|
||||
"FilteredSafeBrowsing",
|
||||
"FilteredParental",
|
||||
"FilteredBlockedService",
|
||||
]),
|
||||
}),
|
||||
);
|
||||
expect(lastCall).not.toHaveProperty("response_status");
|
||||
});
|
||||
|
||||
it('does not mark the log as complete when additional loading stops', async () => {
|
||||
// Mock the API to return a non-complete log
|
||||
(apiClient.getQueryLog as any).mockResolvedValue({
|
||||
data: [],
|
||||
oldest: '',
|
||||
is_entire_log: false,
|
||||
oldest: 'next-cursor',
|
||||
});
|
||||
|
||||
// Simulate the state before additional loading
|
||||
// The store starts with processingAdditionalLogs: false
|
||||
await getAdditionalLogs();
|
||||
|
||||
expect(queryLogsState.processingAdditionalLogs).toBe(false);
|
||||
|
||||
@@ -199,7 +199,7 @@ export const QueryLog = () => {
|
||||
return;
|
||||
}
|
||||
setIsIncrementalLoad(true);
|
||||
getAdditionalLogs(currentSearch());
|
||||
getAdditionalLogs();
|
||||
};
|
||||
|
||||
const allowedClients = (): string[] => {
|
||||
|
||||
@@ -55,55 +55,69 @@ const [state, setState] = createStore<QueryLogsState>(initialState);
|
||||
|
||||
// ---------- Short-poll helpers (v2 parity) ----------
|
||||
|
||||
const REASON_TO_RESPONSE_STATUS: Record<string, string> = {
|
||||
[QUERY_LOG_REASON_FILTER.BLOCKED_BY_FILTER.QUERY]: 'blocked',
|
||||
[QUERY_LOG_REASON_FILTER.BLOCKED_SERVICES.QUERY]: 'blocked_services',
|
||||
[QUERY_LOG_REASON_FILTER.BLOCKED_BY_THREATS.QUERY]: 'blocked_safebrowsing',
|
||||
[QUERY_LOG_REASON_FILTER.BLOCKED_BY_PARENTAL_CONTROL.QUERY]: 'blocked_parental',
|
||||
[QUERY_LOG_REASON_FILTER.SAFE_SEARCH.QUERY]: 'safe_search',
|
||||
[QUERY_LOG_REASON_FILTER.DNS_REWRITES.QUERY]: 'rewritten',
|
||||
/** Maps frontend status filter → exact backend reason strings */
|
||||
const STATUS_TO_REASONS: Record<string, string[]> = {
|
||||
blocked: [
|
||||
'FilteredBlackList',
|
||||
'FilteredSafeBrowsing',
|
||||
'FilteredParental',
|
||||
'FilteredBlockedService',
|
||||
],
|
||||
rewritten: [
|
||||
'Rewrite',
|
||||
'RewriteEtcHosts',
|
||||
'RewriteRule',
|
||||
'FilteredSafeSearch',
|
||||
],
|
||||
processed: ['NotFilteredNotFound'],
|
||||
allowed: ['NotFilteredWhiteList'],
|
||||
error: ['NotFilteredError', 'FilteredInvalid'],
|
||||
all: [],
|
||||
};
|
||||
|
||||
const STATUS_TO_RESPONSE_STATUS: Record<string, string> = {
|
||||
allowed: 'whitelisted',
|
||||
processed: 'processed',
|
||||
blocked: 'blocked',
|
||||
rewritten: 'rewritten',
|
||||
/** Maps frontend reason filter query → exact backend reason strings */
|
||||
const REASON_FILTER_TO_REASONS: Record<string, string[]> = {
|
||||
[QUERY_LOG_REASON_FILTER.BLOCKED_BY_FILTER.QUERY]: ['FilteredBlackList'],
|
||||
[QUERY_LOG_REASON_FILTER.BLOCKED_SERVICES.QUERY]: ['FilteredBlockedService'],
|
||||
[QUERY_LOG_REASON_FILTER.BLOCKED_BY_THREATS.QUERY]: ['FilteredSafeBrowsing'],
|
||||
[QUERY_LOG_REASON_FILTER.BLOCKED_BY_PARENTAL_CONTROL.QUERY]: [
|
||||
'FilteredParental',
|
||||
],
|
||||
[QUERY_LOG_REASON_FILTER.SAFE_SEARCH.QUERY]: ['FilteredSafeSearch'],
|
||||
[QUERY_LOG_REASON_FILTER.DNS_REWRITES.QUERY]: [
|
||||
'Rewrite',
|
||||
'RewriteEtcHosts',
|
||||
'RewriteRule',
|
||||
],
|
||||
};
|
||||
|
||||
const getEffectiveResponseStatus = (filter?: any) => {
|
||||
const getReasons = (filter?: any): string[] => {
|
||||
const reason = filter?.reason ?? DEFAULT_LOGS_FILTER.reason;
|
||||
const status = filter?.status ?? DEFAULT_LOGS_FILTER.status;
|
||||
if (reason !== 'all') return REASON_TO_RESPONSE_STATUS[reason] ?? 'all';
|
||||
return STATUS_TO_RESPONSE_STATUS[status] ?? 'all';
|
||||
if (reason !== 'all') {
|
||||
return REASON_FILTER_TO_REASONS[reason] ?? [];
|
||||
}
|
||||
return STATUS_TO_REASONS[status] ?? [];
|
||||
};
|
||||
|
||||
const fetchLogsWithParams = async (olderThan: string, filter?: any) => {
|
||||
const raw = await apiClient.getQueryLog({
|
||||
const params: Record<string, any> = {
|
||||
search: filter?.search ?? DEFAULT_LOGS_FILTER.search,
|
||||
response_status: getEffectiveResponseStatus(filter),
|
||||
older_than: olderThan,
|
||||
});
|
||||
};
|
||||
const reasons = getReasons(filter);
|
||||
if (reasons.length > 0) {
|
||||
params.reason = reasons;
|
||||
}
|
||||
const raw = await apiClient.getQueryLog(params);
|
||||
return { logs: normalizeLogs(raw.data || []), oldest: raw.oldest || '' };
|
||||
};
|
||||
|
||||
/** Simple stateless filter: count entries matching the status */
|
||||
const filterLogsByStatus = (logs: any[], status: string): any[] => {
|
||||
if (!status || status === 'all') return logs;
|
||||
const statusToReason: Record<string, string[]> = {
|
||||
processed: ['Filtered', 'NotFiltered'],
|
||||
allowed: ['NotFilteredNotFound', 'NotFilteredWhiteList'],
|
||||
blocked: [
|
||||
'FilteredBlackList',
|
||||
'FilteredBlockedService',
|
||||
'FilteredSafeBrowsing',
|
||||
'FilteredParental',
|
||||
'FilteredInvalid',
|
||||
],
|
||||
rewritten: ['Rewrite', 'RewriteEtcHosts', 'RewriteRule'],
|
||||
};
|
||||
const reasons = statusToReason[status];
|
||||
if (!reasons) return logs;
|
||||
const reasons = STATUS_TO_REASONS[status];
|
||||
if (!reasons || reasons.length === 0) return logs;
|
||||
return logs.filter((log: any) => reasons.includes(log.reason));
|
||||
};
|
||||
|
||||
@@ -154,14 +168,15 @@ export const getLogs = async (currentQuery?: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getAdditionalLogs = async (params?: any) => {
|
||||
export const getAdditionalLogs = async () => {
|
||||
setState('processingAdditionalLogs', true);
|
||||
try {
|
||||
const data = await apiClient.getQueryLog(params);
|
||||
const { filter, oldest } = untrack(() => state);
|
||||
const data = await fetchLogsWithParams(oldest, filter);
|
||||
setState({
|
||||
logs: [...state.logs, ...normalizeLogs(data.data || [])],
|
||||
oldest: data.oldest || '',
|
||||
isEntireLog: data.is_entire_log || false,
|
||||
logs: [...state.logs, ...data.logs],
|
||||
oldest: data.oldest,
|
||||
isEntireLog: data.oldest === '',
|
||||
processingAdditionalLogs: false,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user