From 8d72af70a6595047804daa484e8dae4b34cdf2b9 Mon Sep 17 00:00:00 2001
From: Omkar Kesarkhane <127078473+omkarK06@users.noreply.github.com>
Date: Fri, 23 Aug 2024 17:12:51 +0530
Subject: [PATCH] fix: improved query and VRL function validation in alerts
(#4308)
fix #4305 #4304 #4315
## Summary by CodeRabbit
- **New Features**
- Enhanced error handling for SQL queries with specific error messages
for better user feedback.
- Introduced a section for VRL function errors with a toggle feature for
expanded details, improving clarity.
- Added a `debounceTime` property to optimize input processing in the
query editor.
- **Improvements**
- Streamlined error message display for SQL queries, moving from generic
to context-sensitive messages.
- Adjusted control flow to ensure proper validation before form
submission, preventing invalid entries.
- Improved notification messages for clarity, specifically regarding
search operations.
---
web/src/components/alerts/AddAlert.vue | 125 ++++++++++++++-----
web/src/components/alerts/ScheduledAlert.vue | 84 +++++++++++--
web/src/composables/useLogs.ts | 41 +++---
3 files changed, 195 insertions(+), 55 deletions(-)
diff --git a/web/src/components/alerts/AddAlert.vue b/web/src/components/alerts/AddAlert.vue
index 49e60834dd..d28f92187a 100644
--- a/web/src/components/alerts/AddAlert.vue
+++ b/web/src/components/alerts/AddAlert.vue
@@ -188,7 +188,8 @@ along with this program. If not, see .
:columns="filteredColumns"
:conditions="formData.query_condition.conditions"
:alertData="formData"
- :isValidSqlQuery="isValidSqlQuery"
+ :sqlQueryErrorMsg="sqlQueryErrorMsg"
+ :vrlFunctionError="vrlFunctionError"
v-model:trigger="formData.trigger_condition"
v-model:sql="formData.query_condition.sql"
v-model:promql="formData.query_condition.promql"
@@ -204,6 +205,7 @@ along with this program. If not, see .
@field:remove="removeField"
@input:update="onInputUpdate"
@validate-sql="validateSqlQuery"
+ @update:showVrlFunction="updateFunctionVisibility"
class="q-mt-sm"
/>
@@ -493,7 +495,7 @@ const defaultValue: any = () => {
},
},
promql_condition: null,
- vrl_function: "",
+ vrl_function: null,
},
trigger_condition: {
period: 10,
@@ -586,7 +588,7 @@ export default defineComponent({
const previewQuery = ref("");
- const isValidSqlQuery = ref(true);
+ const sqlQueryErrorMsg = ref("");
const validateSqlQueryPromise = ref>();
@@ -601,6 +603,8 @@ export default defineComponent({
let parser: any = null;
+ const vrlFunctionError = ref("");
+
onBeforeMount(async () => {
await importSqlParser();
await getAllFunctions();
@@ -944,16 +948,24 @@ export default defineComponent({
};
const getParser = (sqlQuery: string) => {
try {
- const columns = parser.astify(sqlQuery).columns;
+ // As default is a reserved keyword in sql-parser, we are replacing it with default1
+ const regex = /\bdefault\b/g;
+ const columns = parser.astify(
+ sqlQuery.replace(regex, "default1"),
+ ).columns;
for (const column of columns) {
if (column.expr.column === "*") {
+ sqlQueryErrorMsg.value = "Selecting all columns is not allowed";
return false;
}
}
return true;
} catch (error) {
+ // In catch block we are returning true, as we just wanted to validate if user have added * in the query to select all columns
+ // select field from default // here default is not wrapped in "" so node sql parser will throw error as default is a reserved keyword. But our Backend supports this query without quotes
+ // Query will be validated in the backend
console.log(error);
- return false;
+ return true;
}
};
@@ -1009,9 +1021,11 @@ export default defineComponent({
payload.query_condition.sql = "";
}
- payload.query_condition.vrl_function = b64EncodeUnicode(
- formData.value.query_condition.vrl_function,
- );
+ if (formData.value.query_condition.vrl_function) {
+ payload.query_condition.vrl_function = b64EncodeUnicode(
+ formData.value.query_condition.vrl_function,
+ );
+ }
if (beingUpdated) {
payload.updatedAt = new Date().toISOString();
@@ -1090,7 +1104,14 @@ export default defineComponent({
return true;
};
- const validateSqlQuery = () => {
+ const validateSqlQuery = async () => {
+ // Delaying the validation by 300ms, as editor has debounce of 300ms. Else old value will be used for validation
+ await new Promise((resolve) => setTimeout(resolve, 300));
+
+ if (!getParser(formData.value.query_condition.sql)) {
+ return;
+ }
+
const query = buildQueryPayload({
sqlMode: true,
streamName: formData.value.stream_name,
@@ -1100,6 +1121,11 @@ export default defineComponent({
query.query.sql = formData.value.query_condition.sql;
+ if (formData.value.query_condition.vrl_function)
+ query.query.query_fn = b64EncodeUnicode(
+ formData.value.query_condition.vrl_function,
+ );
+
validateSqlQueryPromise.value = new Promise((resolve, reject) => {
searchService
.search({
@@ -1108,25 +1134,47 @@ export default defineComponent({
page_type: "logs",
})
.then((res: any) => {
- isValidSqlQuery.value = true;
+ sqlQueryErrorMsg.value = "";
+
+ if (res.data?.function_error) {
+ vrlFunctionError.value = res.data.function_error;
+ q.notify({
+ type: "negative",
+ message: "Invalid VRL Function",
+ timeout: 3000,
+ });
+ reject("function_error");
+ } else vrlFunctionError.value = "";
+
resolve("");
})
.catch((err: any) => {
- if (err.response.data.code === 500) {
- isValidSqlQuery.value = false;
+ sqlQueryErrorMsg.value = err.response?.data?.message
+ ? err.response?.data?.message
+ : "Invalid SQL Query";
+
+ // Show error only if it is not real time alert
+ // This case happens when user enters invalid query and then switches to real time alert
+ if (formData.value.query_condition.type === "sql")
q.notify({
type: "negative",
- message: "Invalid SQL Query : " + err.response.data.message,
+ message: "Invalid SQL Query : " + err.response?.data?.message,
timeout: 3000,
});
- reject("");
- } else isValidSqlQuery.value = true;
- resolve("");
+ reject("sql_error");
});
});
};
+ const updateFunctionVisibility = () => {
+ // if validateSqlQueryPromise has error "function_error" then reset the promise when function is disabled
+ if (!showVrlFunction.value && vrlFunctionError.value) {
+ validateSqlQueryPromise.value = Promise.resolve("");
+ vrlFunctionError.value = "";
+ }
+ };
+
return {
t,
q,
@@ -1182,9 +1230,11 @@ export default defineComponent({
getTimezoneOffset,
showVrlFunction,
validateSqlQuery,
- isValidSqlQuery,
validateSqlQueryPromise,
isValidResourceName,
+ sqlQueryErrorMsg,
+ vrlFunctionError,
+ updateFunctionVisibility,
};
},
@@ -1206,12 +1256,12 @@ export default defineComponent({
this.disableColor = "grey-5";
this.formData = cloneDeep(this.modelValue);
this.isAggregationEnabled = !!this.formData.query_condition.aggregation;
- this.formData.query_condition.vrl_function = b64DecodeUnicode(
- this.formData.query_condition.vrl_function,
- );
- if (this.formData.query_condition.vrl_function?.trim()?.length) {
+ if (this.formData.query_condition.vrl_function) {
this.showVrlFunction = true;
+ this.formData.query_condition.vrl_function = b64DecodeUnicode(
+ this.formData.query_condition.vrl_function,
+ );
}
}
@@ -1250,8 +1300,13 @@ export default defineComponent({
},
async onSubmit() {
+ // Delaying submission by 500ms to allow the form to validate, as query is validated in validateSqlQuery method
+ // When user updated query and click on save
+ await new Promise((resolve) => setTimeout(resolve, 500));
+
if (
- this.formData.is_real_time == "false" && this.formData.query_condition.type == 'sql' &&
+ this.formData.is_real_time == "false" &&
+ this.formData.query_condition.type == "sql" &&
!this.getParser(this.formData.query_condition.sql)
) {
this.q.notify({
@@ -1261,6 +1316,7 @@ export default defineComponent({
});
return false;
}
+
if (this.formData.stream_name == "") {
this.q.notify({
type: "negative",
@@ -1277,13 +1333,7 @@ export default defineComponent({
this.formData.tz_offset = this.getTimezoneOffset();
}
- try {
- await this.validateSqlQueryPromise;
- } catch (e) {
- return false;
- }
-
- this.addAlertForm.validate().then((valid: any) => {
+ this.addAlertForm.validate().then(async (valid: any) => {
if (!valid) {
return false;
}
@@ -1297,6 +1347,23 @@ export default defineComponent({
timeout: 2000,
});
+ if (
+ this.formData.is_real_time == "false" &&
+ this.formData.query_condition.type == "sql"
+ ) {
+ try {
+ // Wait for the promise to resolve
+ // Storing the SQL query validation promise in a variable
+ // Case: When user edits the query and directly saves it without waiting for the validation to complete
+ // So waiting here for sql validation to complete
+ await this.validateSqlQueryPromise;
+ } catch (error) {
+ dismiss();
+ console.log("Error while validating sql query");
+ return false;
+ }
+ }
+
if (this.beingUpdated) {
callAlert = alertsService.update(
this.store.state.selectedOrganization.identifier,
diff --git a/web/src/components/alerts/ScheduledAlert.vue b/web/src/components/alerts/ScheduledAlert.vue
index a02bb87acc..9c87020a5c 100644
--- a/web/src/components/alerts/ScheduledAlert.vue
+++ b/web/src/components/alerts/ScheduledAlert.vue
@@ -68,6 +68,7 @@ along with this program. If not, see .
title="Toggle Function Editor"
class="q-pl-xs"
size="30px"
+ @update:model-value="updateFunctionVisibility"
:disable="tab === 'promql'"
/>
@@ -80,6 +81,7 @@ along with this program. If not, see .
ref="queryEditorRef"
editor-id="alerts-query-editor"
class="monaco-editor"
+ :debounceTime="300"
v-model:query="query"
:class="
query == '' && queryEditorPlaceholderFlag ? 'empty-query' : ''
@@ -88,8 +90,10 @@ along with this program. If not, see .
@focus="queryEditorPlaceholderFlag = false"
@blur="onBlurQueryEditor"
/>
-
- Invalid SQL Query
+
+
+ Error: {{ sqlQueryErrorMsg }}
@@ -99,6 +103,7 @@ along with this program. If not, see .
ref="queryEditorRef"
editor-id="alerts-query-editor"
class="monaco-editor q-mb-md"
+ :debounceTime="300"
v-model:query="promqlQuery"
@update:query="updateQueryValue"
/>
@@ -143,6 +148,7 @@ along with this program. If not, see .
ref="fnEditorRef"
editor-id="fnEditor"
class="monaco-editor"
+ :debounceTime="300"
v-model:query="vrlFunctionContent"
:class="
vrlFunctionContent == '' && functionEditorPlaceholderFlag
@@ -151,8 +157,43 @@ along with this program. If not, see .
"
language="ruby"
@focus="functionEditorPlaceholderFlag = false"
- @blur="functionEditorPlaceholderFlag = true"
+ @blur="onBlurFunctionEditor"
/>
+
+
+
+
+
+
+ Invalid VRL function
+
+
+
+
{{
+ vrlFunctionError
+ }}
+
+
+
@@ -729,7 +770,7 @@ along with this program. If not, see .