diff --git a/web/src/components/alerts/AddAlert.vue b/web/src/components/alerts/AddAlert.vue index 1ff207cc17..142b60d588 100644 --- a/web/src/components/alerts/AddAlert.vue +++ b/web/src/components/alerts/AddAlert.vue @@ -161,7 +161,7 @@ along with this program. If not, see . /> -
+
. > {{ t("alerts.silenceNotification") + " *" }}
-
+
+
+
+
+ +
+
+ {{ t("alerts.minutes") }} +
+
+
-
- -
-
- {{ t("alerts.minutes") }} -
+ Field is required!
@@ -332,7 +338,7 @@ import ScheduledAlert from "./ScheduledAlert.vue"; import RealTimeAlert from "./RealTimeAlert.vue"; import VariablesInput from "./VariablesInput.vue"; import { getUUID } from "@/utils/zincutils"; -import { cloneDeep } from "lodash-es"; +import { clone, cloneDeep } from "lodash-es"; import { useRouter } from "vue-router"; const defaultValue: any = () => { @@ -719,11 +725,6 @@ export default defineComponent({ }); return false; } - const dismiss = this.q.notify({ - spinner: true, - message: "Please wait...", - timeout: 2000, - }); this.addAlertForm.validate().then((valid: any) => { if (!valid) { @@ -767,6 +768,14 @@ export default defineComponent({ payload.query_condition.conditions = []; } + if (!this.validateInputs(payload)) return; + + const dismiss = this.q.notify({ + spinner: true, + message: "Please wait...", + timeout: 2000, + }); + callAlert = alertsService.create( this.store.state.selectedOrganization.identifier, payload.stream_name, @@ -802,6 +811,71 @@ export default defineComponent({ }); }); }, + + validateInputs(input: any) { + if ( + Number(input.trigger_condition.silence) < 1 || + isNaN(Number(input.trigger_condition.silence)) + ) { + this.q.notify({ + type: "negative", + message: "Silence Notification should be greater than 0", + timeout: 1500, + }); + return false; + } + + if (input.is_real_time) return true; + + if ( + Number(input.trigger_condition.period) < 1 || + isNaN(Number(input.trigger_condition.period)) + ) { + this.q.notify({ + type: "negative", + message: "Period should be greater than 0", + timeout: 1500, + }); + return false; + } + + if (input.query_condition.aggregation) { + if ( + !input.query_condition.aggregation.having.value.toString().trim() + .length || + !input.query_condition.aggregation.having.column || + !input.query_condition.aggregation.having.operator + ) { + this.q.notify({ + type: "negative", + message: "Threshold should not be empty", + timeout: 1500, + }); + return false; + } + + return true; + } + + console.log( + input.trigger_condition.threshold.toString().trim().length, + input.query_condition.operator + ); + + if ( + !input.trigger_condition.threshold.toString().trim().length || + !input.trigger_condition.operator + ) { + this.q.notify({ + type: "negative", + message: "Threshold should not be empty", + timeout: 1500, + }); + return false; + } + + return true; + }, }, }); diff --git a/web/src/components/alerts/ScheduledAlert.vue b/web/src/components/alerts/ScheduledAlert.vue index 415ff9b0fe..668438ec68 100644 --- a/web/src/components/alerts/ScheduledAlert.vue +++ b/web/src/components/alerts/ScheduledAlert.vue @@ -113,127 +113,190 @@ />
-
+
{{ t("alerts.threshold") + " *" }}
- -
-
-
- {{ t("alerts.period") + " *" }} -
-
- -
-
- {{ t("alerts.minutes") }} + Field is required!
diff --git a/web/src/components/logstream/explore/SearchBar.vue b/web/src/components/logstream/explore/SearchBar.vue index f6aa90e1c4..536142c7e0 100644 --- a/web/src/components/logstream/explore/SearchBar.vue +++ b/web/src/components/logstream/explore/SearchBar.vue @@ -104,6 +104,10 @@ export default defineComponent({ return {}; }, }, + isLoading: { + type: Boolean, + default: false, + }, }, components: { DateTime, @@ -112,7 +116,7 @@ export default defineComponent({ emits: ["searchdata", "update-query", "change:date-time"], methods: { searchData() { - if (this.searchObj.loading == false) { + if (!this.isLoading) { // this.searchObj.runQuery = true; this.$emit("searchdata"); } diff --git a/web/src/locales/languages/en.json b/web/src/locales/languages/en.json index 6aefc88a47..f476e8b4e0 100644 --- a/web/src/locales/languages/en.json +++ b/web/src/locales/languages/en.json @@ -82,7 +82,8 @@ "savedViewsNotFound": "Saved view not found!", "savedViewAction": "Create/Update Saved View", "savedViewDropdownLabel": "Saved Views", - "savedViewName": "View Name" + "savedViewName": "View Name", + "shareLink": "Share Link" }, "menu": { "home": "Home", diff --git a/web/src/plugins/logs/SearchBar.vue b/web/src/plugins/logs/SearchBar.vue index 1a16ac9415..e71c5dfa42 100644 --- a/web/src/plugins/logs/SearchBar.vue +++ b/web/src/plugins/logs/SearchBar.vue @@ -29,6 +29,14 @@ along with this program. If not, see . v-model="searchObj.meta.sqlMode" :label="t('search.sqlModeLabel')" /> + . side @click.stop="handleDeleteSavedView(item)" > - +
@@ -879,7 +886,7 @@ export default defineComponent({ fnEditorobj.layout(); searchObj.config.fnSplitterModel = 60; } - + window.addEventListener("click", () => { fnEditorobj.layout(); // queryEditorRef.value.resetEditorLayout(); @@ -1495,6 +1502,11 @@ export default defineComponent({ }); }; + const resetFilters = () => { + searchObj.data.query = ""; + searchObj.data.editorValue = ""; + }; + return { t, store, @@ -1547,6 +1559,7 @@ export default defineComponent({ saveFunctionLoader, shareLink, getImageURL, + resetFilters, }; }, computed: { diff --git a/web/src/plugins/metrics/Index.vue b/web/src/plugins/metrics/Index.vue index 12d1be9bda..964f7a6618 100644 --- a/web/src/plugins/metrics/Index.vue +++ b/web/src/plugins/metrics/Index.vue @@ -424,8 +424,6 @@ export default defineComponent({ () => searchObj.data.metrics.metricList, (metrics) => { updateMetricKeywords(metrics); - // on page load run query - runQuery(); }, { deep: true } ); diff --git a/web/src/plugins/traces/Index.vue b/web/src/plugins/traces/Index.vue index e1ff619834..7151750b7b 100644 --- a/web/src/plugins/traces/Index.vue +++ b/web/src/plugins/traces/Index.vue @@ -26,6 +26,7 @@ along with this program. If not, see . :isLoading="searchObj.loading" @searchdata="searchData" @onChangeTimezone="refreshTimezone" + @shareLink="copyTracesUrl" />
. @update:datetime="setHistogramDate" @update:scroll="getMoreData" @get:traceDetails="getTraceDetails" + @shareLink="copyTracesUrl" />
@@ -162,7 +164,7 @@ import { onBeforeMount, nextTick, } from "vue"; -import { useQuasar, date } from "quasar"; +import { useQuasar, date, copyToClipboard } from "quasar"; import { useStore } from "vuex"; import { useI18n } from "vue-i18n"; import { useRouter } from "vue-router"; @@ -556,7 +558,9 @@ export default defineComponent({ req.query.sql = b64EncodeUnicode(req.query.sql); - updateUrlQueryParams(); + const queryParams = getUrlQueryParams(); + console.log(queryParams); + router.push({ query: queryParams }); return req; } catch (e) { searchObj.loading = false; @@ -732,8 +736,6 @@ export default defineComponent({ const durationFilter = indexListRef.value.duration.input; - console.log(durationFilter); - let filter = searchObj.data.editorValue.trim(); let duration = ""; @@ -1193,11 +1195,49 @@ export default defineComponent({ } } - function updateUrlQueryParams() { + const copyTracesUrl = (customTimeRange = null) => { + const queryParams = getUrlQueryParams(true); + + if (customTimeRange) { + queryParams.from = customTimeRange.from; + queryParams.to = customTimeRange.to; + } + + const queryString = Object.entries(queryParams) + .map( + ([key, value]) => + `${encodeURIComponent(key)}=${encodeURIComponent(value)}` + ) + .join("&"); + + let shareURL = window.location.origin + window.location.pathname; + + if (queryString != "") { + shareURL += "?" + queryString; + } + + copyToClipboard(shareURL) + .then(() => { + $q.notify({ + type: "positive", + message: "Link Copied Successfully!", + timeout: 5000, + }); + }) + .catch(() => { + $q.notify({ + type: "negative", + message: "Error while copy link.", + timeout: 5000, + }); + }); + }; + + function getUrlQueryParams(getShareLink: false) { const date = searchObj.data.datetime; const query = {}; - if (date.type == "relative") { + if (date.type == "relative" && !getShareLink) { query["period"] = date.relativeTimePeriod; } else { query["from"] = date.startTime; @@ -1212,7 +1252,7 @@ export default defineComponent({ query["trace_id"] = router.currentRoute.value.query.trace_id; - router.push({ query }); + return query; } const onSplitterUpdate = () => { @@ -1302,6 +1342,7 @@ export default defineComponent({ onSplitterUpdate, refreshTimezone, indexListRef, + copyTracesUrl, }; }, computed: { diff --git a/web/src/plugins/traces/IndexList.vue b/web/src/plugins/traces/IndexList.vue index 8a1f6435ab..55688cf77d 100644 --- a/web/src/plugins/traces/IndexList.vue +++ b/web/src/plugins/traces/IndexList.vue @@ -301,17 +301,13 @@ export default defineComponent({ const getFieldValues = (name: string) => { fieldValues.value[name].size *= 2; - let query_context = "SELECT * FROM 'default'"; + let query_context = `SELECT * FROM 'default' WHERE ${name} is not null`; - if (searchObj.data.editorValue) { - query_context += " WHERE " + searchObj.data.editorValue; - } + if (searchObj.data.editorValue.trim().length) + query_context += " AND " + searchObj.data.editorValue; - if (searchObj.data.editorValue && fieldValues.value[name].searchKeyword) { + if (fieldValues.value[name].searchKeyword.trim().length) query_context += ` AND ${name} ILIKE '%${fieldValues.value[name].searchKeyword}%'`; - } else if (fieldValues.value[name].searchKeyword) { - query_context += ` WHERE ${name} ILIKE '%${fieldValues.value[name].searchKeyword}%'`; - } fieldValues.value[name]["isLoading"] = true; @@ -330,26 +326,7 @@ export default defineComponent({ query_context: b64EncodeUnicode(query_context), }) .then((res: any) => { - const values: any = new Set([]); - - fieldValues.value[name]["values"] = - res.data.hits - .find((field: any) => field.field === name) - ?.values.map((value: any) => { - return { - key: value.zo_sql_key ? value.zo_sql_key : "null", - count: formatLargeNumber(value.zo_sql_num), - }; - }) || []; - - fieldValues.value[name].selectedValues.forEach((value: string) => { - if (values.has(value)) return; - else - fieldValues.value[name]["values"].unshift({ - key: value, - count: "0", - }); - }); + updateSelectedValues(res.data.hits); }) .catch(() => { $q.notify({ @@ -473,12 +450,6 @@ export default defineComponent({ }; const filterExpandedFieldValues = () => { - let query_context = "SELECT * FROM 'default'"; - - if (searchObj.data.editorValue) { - query_context += " WHERE " + searchObj.data.editorValue; - } - const fields = [ ...expandedFilters.value.filter( @@ -494,6 +465,17 @@ export default defineComponent({ getSpecialFieldsValues("service_name"); } + let query_context = "SELECT * FROM 'default'"; + + fields.forEach((field, index) => { + if (index) query_context += ` AND ${field} is not null `; + else query_context += ` WHERE ${field} is not null `; + }); + + if (searchObj.data.editorValue) { + query_context += " AND " + searchObj.data.editorValue; + } + if (!fields.length) return; streamService @@ -508,23 +490,21 @@ export default defineComponent({ query_context: b64EncodeUnicode(query_context), }) .then((res: any) => { + let fieldsToUpdate = []; + fields.forEach((field) => { fieldValues.value[field]["values"] = []; fieldValues.value[field].size = 10; }); - if (res.data.hits.length) { - res.data.hits.forEach((field: { field: string; values: any[] }) => { - fieldValues.value[field.field]["values"] = field.values.map( - (value: any) => { - return { - key: value.zo_sql_key ? value.zo_sql_key : "null", - count: formatLargeNumber(value.zo_sql_num), - }; - } - ); - }); - } + fieldsToUpdate = fields.map((field) => ({ + field, + values: [], + })); + + fieldsToUpdate.push(...res.data.hits); + + updateSelectedValues(fieldsToUpdate); }) .catch(() => { $q.notify({ @@ -612,6 +592,34 @@ export default defineComponent({ } }; + // Update the values of the selected fields and add the values which are not present in the selected values + // Also we filter out null values from the values array + const updateSelectedValues = (hits: any[]) => { + hits.forEach((field: any) => { + const values: any = new Set([]); + + fieldValues.value[field.field]["values"] = + field.values.map((value: any) => { + values.add(value.zo_sql_key); + return { + key: value.zo_sql_key ? value.zo_sql_key : "null", + count: formatLargeNumber(value.zo_sql_num), + }; + }) || []; + + fieldValues.value[field.field].selectedValues.forEach( + (value: string) => { + if (values.has(value)) return; + else + fieldValues.value[field.field]["values"].unshift({ + key: value, + count: "0", + }); + } + ); + }); + }; + // const getFullQuery = () => { // let query = "SELECT * FROM 'default'"; // if (searchObj.data.editorValue) diff --git a/web/src/plugins/traces/SearchBar.vue b/web/src/plugins/traces/SearchBar.vue index b77f0a38b6..d8d1736329 100644 --- a/web/src/plugins/traces/SearchBar.vue +++ b/web/src/plugins/traces/SearchBar.vue @@ -74,8 +74,8 @@ along with this program. If not, see . size="sm" icon="restart_alt" class="q-pr-sm q-pl-xs reset-filters q-ml-md" - @click="resetFilters()" - > + @click="resetFilters" + />
@@ -113,7 +113,15 @@ along with this program. If not, see . icon="download" title="Export Traces" @click="downloadLogs" - > + /> +
{ searchObj.data.editorValue = ""; + searchObj.data.advanceFiltersQuery = ""; Object.values(searchObj.data.stream.fieldValues).forEach((field) => { field.selectedValues = []; field.searchKeyword = ""; }); }; + const shareLink = () => { + emit("shareLink"); + }; + return { t, router, @@ -404,6 +417,7 @@ export default defineComponent({ showWarningDialog, changeToggle, resetFilters, + shareLink, }; }, computed: { diff --git a/web/src/plugins/traces/SearchResult.vue b/web/src/plugins/traces/SearchResult.vue index 875e1ea5ca..2a026c4000 100644 --- a/web/src/plugins/traces/SearchResult.vue +++ b/web/src/plugins/traces/SearchResult.vue @@ -62,7 +62,7 @@ along with this program. If not, see . maximized @hide="closeTraceDetails" > - +
@@ -97,6 +97,7 @@ export default defineComponent({ "remove:searchTerm", "search:timeboxed", "get:traceDetails", + "shareLink", ], methods: { closeColumn(col: any) { @@ -239,6 +240,15 @@ export default defineComponent({ expandRowDetail(searchObj.data.queryResults.hits[data.dataIndex]); }; + const shareLink = () => { + if (!searchObj.data.traceDetails.selectedTrace) return; + const trace = searchObj.data.traceDetails.selectedTrace as any; + emit("shareLink", { + from: trace.trace_start_time - 60000000, + to: trace.trace_end_time + 60000000, + }); + }; + return { t, store, @@ -258,6 +268,7 @@ export default defineComponent({ showTraceDetails, closeTraceDetails, onChartClick, + shareLink, }; }, }); diff --git a/web/src/plugins/traces/SpanBlock.vue b/web/src/plugins/traces/SpanBlock.vue index 490102e056..caece60713 100644 --- a/web/src/plugins/traces/SpanBlock.vue +++ b/web/src/plugins/traces/SpanBlock.vue @@ -16,8 +16,11 @@ along with this program. If not, see . @@ -66,8 +66,17 @@