diff --git a/studio/components/interfaces/Settings/Logs/Logs.constants.ts b/studio/components/interfaces/Settings/Logs/Logs.constants.ts index 34d62b627a3..75577262633 100644 --- a/studio/components/interfaces/Settings/Logs/Logs.constants.ts +++ b/studio/components/interfaces/Settings/Logs/Logs.constants.ts @@ -1,5 +1,4 @@ -import { get } from 'lodash' -import { Filters, FilterTableSet, LogTemplate } from '.' +import { FilterTableSet, LogTemplate } from '.' export const TEMPLATES: LogTemplate[] = [ { @@ -187,135 +186,6 @@ export const LOG_TYPE_LABEL_MAPPING: { [k: string]: string } = { } -/** - * Recursively retrieve all nested object key paths. - * - * TODO: move to utils - * - * @param obj any object - * @param parent a string representing the parent key - * @returns string[] all dot paths for keys. - */ -const getDotKeys = (obj: { [k: string]: unknown }, parent?: string): string[] => { - const keys = Object.keys(obj).filter(k => obj[k]) - return keys.flatMap(k => { - const currKey = parent ? `${parent}.${k}` : k - if (typeof obj[k] === 'object') { - return getDotKeys(obj[k] as any, currKey) - } else { - return [currKey] - } - }) -} - -/** - * Root keys in the filter object are considered to be AND filters. - * Nested keys under a root key are considered to be OR filters. - * - * For example: - * ``` - * {my_value: 'something', nested: {id: 123, test: 123 }} - * ``` - * This would be converted into `WHERE (my_value = 'something') and (id = 123 or test = 123) - * - * The template of the filter determines the actual filter statement. If no template is provided, a generic equality statement will be used. - * This only applies for root keys of the filter. - * For example: - * ``` - * {'my.nested.value': 123} - * ``` - * with no template, it will be converted into `WHERE (my.nested.value = 123) - * - * @returns a whewhere statement with WHERE clause. - */ -const _genWhereStatement = (table: LogsTableName, filters: Filters) => { - const keys = Object.keys(filters) - const filterTemplates = SQL_FILTER_TEMPLATES[table] - const _resolveTemplateToStatement = (dotKey: string) => { - const template = filterTemplates[dotKey] - const value = get(filters, dotKey) - if (value !== undefined && typeof template === 'function') { - return template(value) - } else if (template === undefined) { - // resolve unknwon filters (possibly from filter overrides) - // no template, set a default - if (typeof value === 'string') { - return `${dotKey} = '${value}'` - } else { - return `${dotKey} = ${value}` - } - } else { - return template - } - } - - const statement = keys.map(rootKey => { - if (typeof filters[rootKey] === 'object') { - // join all statements with an OR - const nestedStatement = getDotKeys(filters[rootKey] as Filters, rootKey).map(_resolveTemplateToStatement).join(" or ") - return `(${nestedStatement})` - } else { - const nestedStatement = _resolveTemplateToStatement(rootKey) - return `(${nestedStatement})` - } - - }).filter(Boolean) - // join all root statements with AND - .join(" and ") - - if (statement) { - return 'where ' + statement - } else { - return '' - } -} - -export const genDefaultQuery = (table: LogsTableName, filters: Filters) => { - const where = _genWhereStatement(table, filters) - - switch (table) { - case 'edge_logs': - return `select id, timestamp, event_message, metadata, request, response, request.method, request.path, response.status_code -from ${table} -cross join unnest(metadata) as m -cross join unnest(m.request) as request -cross join unnest(m.response) as response -${where} -limit 100 -` - break - - case 'postgres_logs': - return `select postgres_logs.timestamp, id, event_message, metadata, metadataparsed.error_severity from ${table} -cross join unnest(metadata) as m -cross join unnest(m.parsed) as metadataparsed -${where} -limit 100 -` - break - - case 'function_logs': - return `select id, ${table}.timestamp, event_message, metadata.event_type, metadata.function_id, metadata.level, metadata from ${table} -cross join unnest(metadata) as metadata -${where} -limit 100 - ` - break - - case 'function_edge_logs': - return `select id, ${table}.timestamp, event_message, response.status_code, response, request, request.method, m.function_id, m.execution_time_ms, m.deployment_id, m.version from ${table} -cross join unnest(metadata) as m -cross join unnest(m.response) as response -cross join unnest(m.request) as request -${where} -limit 100 -` - - default: - return "" - break - } -} const _SQL_FILTER_COMMON = { 'search_query': (value: string) => `regexp_contains(event_message, '${value}')` diff --git a/studio/components/interfaces/Settings/Logs/Logs.utils.ts b/studio/components/interfaces/Settings/Logs/Logs.utils.ts new file mode 100644 index 00000000000..ec15be1e2d1 --- /dev/null +++ b/studio/components/interfaces/Settings/Logs/Logs.utils.ts @@ -0,0 +1,132 @@ +import { get } from "lodash" +import { Filters, LogsTableName, SQL_FILTER_TEMPLATES } from "." + +/** + * Recursively retrieve all nested object key paths. + * + * TODO: move to utils + * + * @param obj any object + * @param parent a string representing the parent key + * @returns string[] all dot paths for keys. + */ +const getDotKeys = (obj: { [k: string]: unknown }, parent?: string): string[] => { + const keys = Object.keys(obj).filter(k => obj[k]) + return keys.flatMap(k => { + const currKey = parent ? `${parent}.${k}` : k + if (typeof obj[k] === 'object') { + return getDotKeys(obj[k] as any, currKey) + } else { + return [currKey] + } + }) +} + +/** + * Root keys in the filter object are considered to be AND filters. + * Nested keys under a root key are considered to be OR filters. + * + * For example: + * ``` + * {my_value: 'something', nested: {id: 123, test: 123 }} + * ``` + * This would be converted into `WHERE (my_value = 'something') and (id = 123 or test = 123) + * + * The template of the filter determines the actual filter statement. If no template is provided, a generic equality statement will be used. + * This only applies for root keys of the filter. + * For example: + * ``` + * {'my.nested.value': 123} + * ``` + * with no template, it will be converted into `WHERE (my.nested.value = 123) + * + * @returns a whewhere statement with WHERE clause. + */ +const _genWhereStatement = (table: LogsTableName, filters: Filters) => { + const keys = Object.keys(filters) + const filterTemplates = SQL_FILTER_TEMPLATES[table] + const _resolveTemplateToStatement = (dotKey: string) => { + const template = filterTemplates[dotKey] + const value = get(filters, dotKey) + if (value !== undefined && typeof template === 'function') { + return template(value) + } else if (template === undefined) { + // resolve unknwon filters (possibly from filter overrides) + // no template, set a default + if (typeof value === 'string') { + return `${dotKey} = '${value}'` + } else { + return `${dotKey} = ${value}` + } + } else { + return template + } + } + + const statement = keys.map(rootKey => { + if (typeof filters[rootKey] === 'object') { + // join all statements with an OR + const nestedStatement = getDotKeys(filters[rootKey] as Filters, rootKey).map(_resolveTemplateToStatement).join(" or ") + return `(${nestedStatement})` + } else { + const nestedStatement = _resolveTemplateToStatement(rootKey) + return `(${nestedStatement})` + } + + }).filter(Boolean) + // join all root statements with AND + .join(" and ") + + if (statement) { + return 'where ' + statement + } else { + return '' + } +} + +export const genDefaultQuery = (table: LogsTableName, filters: Filters) => { + const where = _genWhereStatement(table, filters) + + switch (table) { + case 'edge_logs': + return `select id, timestamp, event_message, metadata, request, response, request.method, request.path, response.status_code + from ${table} + cross join unnest(metadata) as m + cross join unnest(m.request) as request + cross join unnest(m.response) as response + ${where} + limit 100 + ` + break + + case 'postgres_logs': + return `select postgres_logs.timestamp, id, event_message, metadata, metadataparsed.error_severity from ${table} + cross join unnest(metadata) as m + cross join unnest(m.parsed) as metadataparsed + ${where} + limit 100 + ` + break + + case 'function_logs': + return `select id, ${table}.timestamp, event_message, metadata.event_type, metadata.function_id, metadata.level, metadata from ${table} + cross join unnest(metadata) as metadata + ${where} + limit 100 + ` + break + + case 'function_edge_logs': + return `select id, ${table}.timestamp, event_message, response.status_code, response, request, request.method, m.function_id, m.execution_time_ms, m.deployment_id, m.version from ${table} + cross join unnest(metadata) as m + cross join unnest(m.response) as response + cross join unnest(m.request) as request + ${where} + limit 100 + ` + + default: + return "" + break + } +} \ No newline at end of file diff --git a/studio/components/interfaces/Settings/Logs/index.ts b/studio/components/interfaces/Settings/Logs/index.ts index bda9400736b..a6ebf8dac2d 100644 --- a/studio/components/interfaces/Settings/Logs/index.ts +++ b/studio/components/interfaces/Settings/Logs/index.ts @@ -1,5 +1,6 @@ export * from './Logs.types' export * from './Logs.constants' +export * from './Logs.utils' export { default as LogsSavedQueriesItem } from './Logs.SavedQueriesItem' export { default as LogsQueryPanel } from './LogsQueryPanel' export { default as LogTable } from './LogTable'