From 81e5db33c8994fb4abeebbee06ed27fc0ea3ce25 Mon Sep 17 00:00:00 2001
From: Omkar Kesarkhane <127078473+omkarK06@users.noreply.github.com>
Date: Tue, 2 Jan 2024 15:18:04 +0530
Subject: [PATCH] fix: fixed traces and alerts UI issues (#2350)
---
web/src/components/alerts/AddAlert.vue | 150 ++++++---
web/src/components/alerts/ScheduledAlert.vue | 317 ++++++++++--------
.../logstream/explore/SearchBar.vue | 6 +-
web/src/locales/languages/en.json | 3 +-
web/src/plugins/logs/SearchBar.vue | 19 +-
web/src/plugins/metrics/Index.vue | 2 -
web/src/plugins/traces/Index.vue | 55 ++-
web/src/plugins/traces/IndexList.vue | 100 +++---
web/src/plugins/traces/SearchBar.vue | 22 +-
web/src/plugins/traces/SearchResult.vue | 13 +-
web/src/plugins/traces/SpanBlock.vue | 14 +-
web/src/plugins/traces/SpanDetails.vue | 32 +-
web/src/plugins/traces/TraceDetails.vue | 28 +-
.../plugins/traces/TraceDetailsSidebar.vue | 41 ++-
web/src/plugins/traces/TraceHeader.vue | 1 +
web/src/plugins/traces/TraceTree.vue | 3 +-
web/src/services/stream.ts | 1 +
web/src/views/StreamExplorer.vue | 26 +-
18 files changed, 549 insertions(+), 284 deletions(-)
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") + " *" }}
+
+
@@ -248,45 +311,15 @@
:class="store.state.theme === 'dark' ? 'bg-grey-10' : 'bg-grey-2'"
class="flex justify-center items-center"
>
- {{ t("alerts.times") }}
+ {{ t("alerts.minutes") }}
-
-
-
-
- {{ 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 .
{{ key }}
:
- {{ val }}
+ {{ val }}
@@ -66,8 +66,17 @@
- | {{ key }} |
-
+ |
+ {{ key }}
+ |
+
{{ val }}
|
@@ -100,7 +109,7 @@
{{ key }}
:
- {{ val }}
+ {{ val }}
@@ -110,8 +119,17 @@
- | {{ key }} |
-
+ |
+ {{ key }}
+ |
+
{{ val }}
|
@@ -218,7 +236,7 @@
Span Id: {{ span.spanId }}
+ >
{{ span.spanId }}
.
Spans: {{ spanList.length }}
-
+
+
+
+
@@ -195,6 +208,7 @@ import {
import ChartRenderer from "@/components/dashboards/panels/ChartRenderer.vue";
import { throttle } from "lodash";
import { copyToClipboard, useQuasar } from "quasar";
+import { useI18n } from "vue-i18n";
export default defineComponent({
name: "TraceDetails",
@@ -214,8 +228,8 @@ export default defineComponent({
ServiceMapIcon,
ChartRenderer,
},
-
- setup() {
+ emits: ["shareLink"],
+ setup(props, { emit }) {
const traceTree: any = ref([]);
const spanMap: any = ref({});
const { searchObj } = useTraces();
@@ -242,6 +256,8 @@ export default defineComponent({
colors: ["#b7885e", "#1ab8be", "#ffcb99", "#f89570", "#839ae2"],
};
+ const { t } = useI18n();
+
const $q = useQuasar();
const traceVisuals = [
@@ -696,7 +712,12 @@ export default defineComponent({
copyToClipboard(spanList.value[0]["trace_id"]);
};
+ const shareLink = () => {
+ emit("shareLink");
+ };
+
return {
+ t,
traceTree,
collapseMapping,
traceRootSpan,
@@ -726,6 +747,7 @@ export default defineComponent({
toggleTimeline,
copyToClipboard,
copyTraceId,
+ shareLink,
};
},
});
diff --git a/web/src/plugins/traces/TraceDetailsSidebar.vue b/web/src/plugins/traces/TraceDetailsSidebar.vue
index 35fcd65213..b2423c8569 100644
--- a/web/src/plugins/traces/TraceDetailsSidebar.vue
+++ b/web/src/plugins/traces/TraceDetailsSidebar.vue
@@ -36,14 +36,14 @@ along with this program. If not, see .
class="q-px-sm q-pb-none ellipsis non-selectable"
style="font-size: 14px"
>
- Operation Name: {{ span.operation_name }}
+ Operation Name: {{ span.operation_name }}
- Service Name: {{ span.service_name }}
+ Service Name: {{ span.service_name }}
.
- | {{ key }} |
-
+ |
+ {{ key }}
+ |
+
{{ val }}
|
@@ -94,8 +101,15 @@ along with this program. If not, see .
- | {{ key }} |
-
+ |
+ {{ key }}
+ |
+
{{ val }}
|
@@ -104,18 +118,9 @@ along with this program. If not, see .
-
-
-
-
- | {{ key }} |
-
- {{ val }}
- |
-
-
-
-
+ {{
+ JSON.stringify(spanDetails.attrs, null, 2)
+ }}
.
}"
style="top: 0; z-index: 999; position: sticky"
class="flex justify-start items-center header-bg bg-grey-2"
+ :class="store.state.theme === 'dark' ? 'bg-grey-9' : 'bg-grey-2'"
>
.
:title="span.operationName"
>
-
+
-
+
Hold on tight, we're fetching your stream data.
@@ -14,7 +24,11 @@
-
+
@@ -226,8 +240,8 @@ export default defineComponent({
} | null =
queryData.value.dateTime.type === "relative"
? getConsumableRelativeTime(
- queryData.value.dateTime.relativeTimePeriod
- ) || null
+ queryData.value.dateTime.relativeTimePeriod
+ ) || null
: cloneDeep(queryData.value.dateTime);
if (streamData?.value?.stream_type === "enrichment_tables") {