diff --git a/apps/studio/components/interfaces/Reports/Reports.constants.test.ts b/apps/studio/components/interfaces/Reports/Reports.constants.test.ts new file mode 100644 index 00000000000..50d40d413fe --- /dev/null +++ b/apps/studio/components/interfaces/Reports/Reports.constants.test.ts @@ -0,0 +1,105 @@ +import { describe, it, expect } from 'vitest' +import { generateRegexpWhere } from './Reports.constants' +import type { ReportFilterItem } from './Reports.types' + +describe('generateRegexpWhere', () => { + it('should return empty string when no filters provided', () => { + const result = generateRegexpWhere([]) + expect(result).toBe('') + }) + + it('should generate WHERE clause for single filter', () => { + const filters: ReportFilterItem[] = [ + { + key: 'request.path', + value: '/api/users', + compare: 'is', + }, + ] + const result = generateRegexpWhere(filters, true) + expect(result).toBe("WHERE request.path = '/api/users'") + }) + + it('should generate AND clause for single filter with prepend=false', () => { + const filters: ReportFilterItem[] = [ + { + key: 'request.path', + value: '/api/users', + compare: 'is', + }, + ] + const result = generateRegexpWhere(filters, false) + expect(result).toBe("AND request.path = '/api/users'") + }) + + it('should handle different comparison operators', () => { + const filters: ReportFilterItem[] = [ + { + key: 'request.path', + value: '/api/*', + compare: 'matches', + }, + { + key: 'response.status_code', + value: 404, + compare: 'is', + }, + ] + const result = generateRegexpWhere(filters, true) + expect(result).toBe( + "WHERE REGEXP_CONTAINS(request.path, '/api/*') AND response.status_code = 404" + ) + }) + + it('should handle values with quotes', () => { + const filters: ReportFilterItem[] = [ + { + key: 'request.path', + value: '"/api/users"', + compare: 'is', + }, + ] + + const result = generateRegexpWhere(filters, true) + expect(result).toBe(`WHERE request.path = "/api/users"`) + }) + + it('should handle values without quotes', () => { + const filters: ReportFilterItem[] = [ + { + key: 'request.path', + value: '/api/users', + compare: 'is', + }, + ] + + const result = generateRegexpWhere(filters, true) + expect(result).toBe("WHERE request.path = '/api/users'") + }) + + it('should handle values with quotes and lowercase', () => { + const filters: ReportFilterItem[] = [ + { + key: 'request.path', + value: '"/Api/Users"', + compare: 'is', + }, + ] + + const result = generateRegexpWhere(filters, true) + expect(result).toBe(`WHERE request.path = "/api/users"`) + }) + + it('should handle numbers', () => { + const filters: ReportFilterItem[] = [ + { + key: 'request.status_code', + value: 200, + compare: 'is', + }, + ] + + const result = generateRegexpWhere(filters, true) + expect(result).toBe(`WHERE request.status_code = 200`) + }) +}) diff --git a/apps/studio/components/interfaces/Reports/Reports.constants.ts b/apps/studio/components/interfaces/Reports/Reports.constants.ts index 010d3096508..b80f349bb6b 100644 --- a/apps/studio/components/interfaces/Reports/Reports.constants.ts +++ b/apps/studio/components/interfaces/Reports/Reports.constants.ts @@ -86,25 +86,34 @@ export const generateRegexpWhere = (filters: ReportFilterItem[], prepend = true) const normalizedKey = [splitKey[splitKey.length - 2], splitKey[splitKey.length - 1]].join('.') const filterKey = filter.key.includes('.') ? normalizedKey : filter.key + const hasQuotes = + filter.value.toString().includes('"') || filter.value.toString().includes("'") + + const valueIsNumber = !isNaN(Number(filter.value)) + const valueWithQuotes = !valueIsNumber && hasQuotes ? filter.value : `'${filter.value}'` + const lowercaseValue = !valueIsNumber && String(valueWithQuotes).toLowerCase() + + const finalValue = valueIsNumber ? filter.value : lowercaseValue + // Handle different comparison operators switch (filter.compare) { case 'matches': - return `REGEXP_CONTAINS(${filterKey}, '${filter.value}')` + return `REGEXP_CONTAINS(${filterKey}, ${finalValue})` case 'is': - return `${filterKey} = ${filter.value}` + return `${filterKey} = ${finalValue}` case '!=': - return `${filterKey} != ${filter.value}` + return `${filterKey} != ${finalValue}` case '>=': - return `${filterKey} >= ${filter.value}` + return `${filterKey} >= ${finalValue}` case '<=': - return `${filterKey} <= ${filter.value}` + return `${filterKey} <= ${finalValue}` case '>': - return `${filterKey} > ${filter.value}` + return `${filterKey} > ${finalValue}` case '<': - return `${filterKey} < ${filter.value}` + return `${filterKey} < ${finalValue}` default: // Fallback to exact match for unknown operators - return `${filterKey} = ${filter.value}` + return `${filterKey} = ${finalValue}` } }) .filter(Boolean) // Remove any null/undefined conditions