fix: fixed traces and alerts UI issues (#2350)

This commit is contained in:
Omkar Kesarkhane
2024-01-02 15:18:04 +05:30
committed by GitHub
parent fbe83dd25e
commit 81e5db33c8
18 changed files with 549 additions and 284 deletions
+112 -38
View File
@@ -161,7 +161,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/>
</div>
<div class="col-12 flex justify-start items-center q-mt-md">
<div class="col-12 flex justify-start items-center q-mt-xs">
<div
class="q-py-sm showLabelOnTop text-bold text-h7"
data-test="add-alert-delay-title"
@@ -169,39 +169,45 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
>
{{ t("alerts.silenceNotification") + " *" }}
</div>
<div class="col-8 row justify-left align-center q-gutter-sm">
<div style="min-height: 58px">
<div class="col-8 row justify-left align-center q-gutter-sm">
<div
class="flex items-center"
style="border: 1px solid rgba(0, 0, 0, 0.05)"
>
<div
style="width: 87px; margin-left: 0 !important"
class="silence-notification-input"
>
<q-input
data-test="add-alert-delay-input"
v-model="formData.trigger_condition.silence"
type="number"
dense
filled
min="1"
style="background: none"
/>
</div>
<div
style="
min-width: 90px;
margin-left: 0 !important;
background: #f2f2f2;
height: 40px;
"
class="flex justify-center items-center"
>
{{ t("alerts.minutes") }}
</div>
</div>
</div>
<div
class="flex items-center"
style="border: 1px solid rgba(0, 0, 0, 0.05)"
v-if="formData.trigger_condition.silence < 1"
class="text-red-8 q-pt-xs"
style="font-size: 11px; line-height: 12px"
>
<div
style="width: 87px; margin-left: 0 !important"
class="silence-notification-input"
>
<q-input
data-test="add-alert-delay-input"
v-model="formData.trigger_condition.silence"
type="number"
dense
filled
min="1"
style="background: none"
/>
</div>
<div
style="
min-width: 90px;
margin-left: 0 !important;
background: #f2f2f2;
height: 40px;
"
class="flex justify-center items-center"
:class="
store.state.theme === 'dark' ? 'bg-grey-10' : 'bg-grey-2'
"
>
{{ t("alerts.minutes") }}
</div>
Field is required!
</div>
</div>
</div>
@@ -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;
},
},
});
</script>
+175 -142
View File
@@ -113,127 +113,190 @@
/>
</div>
</div>
<div class="flex justify-start items-center q-mb-md">
<div class="flex justify-start items-center q-mb-xs">
<div class="text-bold" style="width: 180px">
{{ t("alerts.threshold") + " *" }}
</div>
<template v-if="_isAggregationEnabled && aggregationData">
<div class="threshould-input q-mr-xs">
<q-select
data-test="add-alert-stream-select"
v-model="aggregationData.function"
:options="aggFunctions"
color="input-border"
bg-color="input-bg"
class="showLabelOnTop no-case q-py-none"
filled
borderless
dense
use-input
hide-selected
fill-input
:rules="[(val: any) => !!val || 'Field is required!']"
style="width: 88px; border: 1px solid rgba(0, 0, 0, 0.05)"
@update:model-value="updateTrigger"
/>
</div>
<div class="threshould-input q-mr-xs">
<q-select
data-test="add-alert-stream-select"
v-model="aggregationData.having.column"
:options="filteredNumericColumns"
color="input-border"
bg-color="input-bg"
class="showLabelOnTop no-case q-py-none"
filled
borderless
dense
use-input
emit-value
hide-selected
fill-input
:rules="[(val: any) => !!val || 'Field is required!']"
@filter="filterNumericColumns"
style="width: 250px; border: 1px solid rgba(0, 0, 0, 0.05)"
@update:model-value="updateTrigger"
/>
</div>
<div class="threshould-input q-mr-xs">
<q-select
data-test="add-alert-stream-select"
v-model="aggregationData.having.operator"
:options="triggerOperators"
color="input-border"
bg-color="input-bg"
class="showLabelOnTop no-case q-py-none"
filled
borderless
dense
use-input
hide-selected
fill-input
:rules="[(val: any) => !!val || 'Field is required!']"
style="width: 88px; border: 1px solid rgba(0, 0, 0, 0.05)"
@update:model-value="updateTrigger"
/>
</div>
<div
class="flex items-center"
style="border: 1px solid rgba(0, 0, 0, 0.05)"
>
<div
style="width: 150px; margin-left: 0 !important"
class="silence-notification-input"
>
<q-input
data-test="add-alert-delay-input"
v-model="aggregationData.having.value"
type="number"
dense
filled
min="0"
style="background: none"
placeholder="Value"
@update:model-value="updateTrigger"
/>
<div style="min-height: 58px">
<template v-if="_isAggregationEnabled && aggregationData">
<div class="flex justify-start items-center">
<div class="threshould-input q-mr-xs">
<q-select
data-test="add-alert-stream-select"
v-model="aggregationData.function"
:options="aggFunctions"
color="input-border"
bg-color="input-bg"
class="showLabelOnTop no-case q-py-none"
filled
borderless
dense
use-input
hide-selected
fill-input
style="width: 88px; border: 1px solid rgba(0, 0, 0, 0.05)"
@update:model-value="updateTrigger"
/>
</div>
<div class="threshould-input q-mr-xs">
<q-select
data-test="add-alert-stream-select"
v-model="aggregationData.having.column"
:options="filteredNumericColumns"
color="input-border"
bg-color="input-bg"
class="showLabelOnTop no-case q-py-none"
filled
borderless
dense
use-input
emit-value
hide-selected
fill-input
@filter="filterNumericColumns"
style="width: 250px; border: 1px solid rgba(0, 0, 0, 0.05)"
@update:model-value="updateTrigger"
/>
</div>
<div class="threshould-input q-mr-xs">
<q-select
data-test="add-alert-stream-select"
v-model="aggregationData.having.operator"
:options="triggerOperators"
color="input-border"
bg-color="input-bg"
class="showLabelOnTop no-case q-py-none"
filled
borderless
dense
use-input
hide-selected
fill-input
style="width: 88px; border: 1px solid rgba(0, 0, 0, 0.05)"
@update:model-value="updateTrigger"
/>
</div>
<div
class="flex items-center"
style="border: 1px solid rgba(0, 0, 0, 0.05)"
>
<div
style="width: 150px; margin-left: 0 !important"
class="silence-notification-input"
>
<q-input
data-test="add-alert-delay-input"
v-model="aggregationData.having.value"
type="number"
dense
filled
min="0"
style="background: none"
placeholder="Value"
@update:model-value="updateTrigger"
/>
</div>
</div>
</div>
</div>
</template>
<template v-else>
<div class="threshould-input">
<q-select
data-test="add-alert-stream-select"
v-model="triggerData.operator"
:options="triggerOperators"
color="input-border"
bg-color="input-bg"
class="showLabelOnTop no-case q-py-none"
filled
borderless
dense
use-input
hide-selected
fill-input
:rules="[(val: any) => !!val || 'Field is required!']"
style="width: 88px; border: 1px solid rgba(0, 0, 0, 0.05)"
@update:model-value="updateTrigger"
/>
</div>
<div
v-if="
!aggregationData.function ||
!aggregationData.having.column ||
!aggregationData.having.operator ||
!aggregationData.having.value.toString().trim().length
"
class="text-red-8 q-pt-xs"
style="font-size: 11px; line-height: 12px"
>
Field is required!
</div>
</template>
<template v-else>
<div class="flex justify-start items-center">
<div class="threshould-input">
<q-select
data-test="add-alert-stream-select"
v-model="triggerData.operator"
:options="triggerOperators"
color="input-border"
bg-color="input-bg"
class="showLabelOnTop no-case q-py-none"
filled
borderless
dense
use-input
hide-selected
fill-input
:rules="[(val: any) => !!val || 'Field is required!']"
style="width: 88px; border: 1px solid rgba(0, 0, 0, 0.05)"
@update:model-value="updateTrigger"
/>
</div>
<div
class="flex items-center"
style="border: 1px solid rgba(0, 0, 0, 0.05); border-left: none"
>
<div
style="width: 89px; margin-left: 0 !important"
class="silence-notification-input"
>
<q-input
data-test="add-alert-delay-input"
v-model="triggerData.threshold"
type="number"
dense
filled
min="0"
style="background: none"
@update:model-value="updateTrigger"
/>
</div>
<div
style="
min-width: 90px;
margin-left: 0 !important;
height: 40px;
font-weight: normal;
"
:class="
store.state.theme === 'dark' ? 'bg-grey-10' : 'bg-grey-2'
"
class="flex justify-center items-center"
>
{{ t("alerts.times") }}
</div>
</div>
</div>
<div
v-if="!triggerData.operator || !Number(triggerData.threshold)"
class="text-red-8 q-pt-xs"
style="font-size: 11px; line-height: 12px"
>
Field is required!
</div>
</template>
</div>
</div>
<div class="flex items-center q-mr-sm">
<div class="text-bold" style="width: 180px">
{{ t("alerts.period") + " *" }}
</div>
<div style="min-height: 58px">
<div
class="flex items-center"
style="border: 1px solid rgba(0, 0, 0, 0.05); border-left: none"
class="flex items-center q-mr-sm"
style="border: 1px solid rgba(0, 0, 0, 0.05); width: fit-content"
>
<div
style="width: 89px; margin-left: 0 !important"
style="width: 87px; margin-left: 0 !important"
class="silence-notification-input"
>
<q-input
data-test="add-alert-delay-input"
v-model="triggerData.threshold"
v-model="triggerData.period"
type="number"
dense
filled
min="0"
min="1"
style="background: none"
@update:model-value="updateTrigger"
/>
@@ -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") }}
</div>
</div>
</template>
</div>
<div class="flex items-center q-mr-sm">
<div class="text-bold" style="width: 180px">
{{ t("alerts.period") + " *" }}
</div>
<div
class="flex items-center q-mr-sm"
style="border: 1px solid rgba(0, 0, 0, 0.05); width: fit-content"
>
<div
style="width: 87px; margin-left: 0 !important"
class="silence-notification-input"
v-if="!Number(triggerData.period)"
class="text-red-8 q-pt-xs"
style="font-size: 11px; line-height: 12px"
>
<q-input
data-test="add-alert-delay-input"
v-model="triggerData.period"
type="number"
dense
filled
min="1"
style="background: none"
@update:model-value="updateTrigger"
/>
</div>
<div
style="
min-width: 90px;
margin-left: 0 !important;
height: 40px;
font-weight: normal;
"
:class="store.state.theme === 'dark' ? 'bg-grey-10' : 'bg-grey-2'"
class="flex justify-center items-center"
>
{{ t("alerts.minutes") }}
Field is required!
</div>
</div>
</div>
@@ -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");
}
+2 -1
View File
@@ -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",
+16 -3
View File
@@ -29,6 +29,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
v-model="searchObj.meta.sqlMode"
:label="t('search.sqlModeLabel')"
/>
<q-btn
label="Reset Filters"
no-caps
size="sm"
icon="restart_alt"
class="q-pr-sm q-pl-xs reset-filters q-ml-md"
@click="resetFilters"
/>
<syntax-guide
data-test="logs-search-bar-sql-mode-toggle-btn"
:sqlmode="searchObj.meta.sqlMode"
@@ -71,8 +79,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
side
@click.stop="handleDeleteSavedView(item)"
>
<q-icon name="delete"
color="grey" size="xs" />
<q-icon name="delete" color="grey" size="xs" />
</q-item-section>
</q-item>
</div>
@@ -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: {
-2
View File
@@ -424,8 +424,6 @@ export default defineComponent({
() => searchObj.data.metrics.metricList,
(metrics) => {
updateMetricKeywords(metrics);
// on page load run query
runQuery();
},
{ deep: true }
);
+48 -7
View File
@@ -26,6 +26,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:isLoading="searchObj.loading"
@searchdata="searchData"
@onChangeTimezone="refreshTimezone"
@shareLink="copyTracesUrl"
/>
<div
id="tracesThirdLevel"
@@ -143,6 +144,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
@update:datetime="setHistogramDate"
@update:scroll="getMoreData"
@get:traceDetails="getTraceDetails"
@shareLink="copyTracesUrl"
/>
</div>
</template>
@@ -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: {
+54 -46
View File
@@ -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)
+18 -4
View File
@@ -74,8 +74,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
size="sm"
icon="restart_alt"
class="q-pr-sm q-pl-xs reset-filters q-ml-md"
@click="resetFilters()"
></q-btn>
@click="resetFilters"
/>
</div>
<div class="float-right col-auto">
<div class="float-left">
@@ -113,7 +113,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
icon="download"
title="Export Traces"
@click="downloadLogs"
></q-btn>
/>
<q-btn
data-test="logs-search-bar-share-link-btn"
class="q-mr-sm download-logs-btn q-px-sm"
size="sm"
icon="share"
:title="t('search.shareLink')"
@click="shareLink"
/>
</div>
</div>
<div
@@ -175,7 +183,7 @@ export default defineComponent({
AppTabs,
ConfirmDialog,
},
emits: ["searchdata"],
emits: ["searchdata", "shareLink"],
props: {
fieldValues: {
type: Object,
@@ -378,12 +386,17 @@ export default defineComponent({
const resetFilters = () => {
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: {
+12 -1
View File
@@ -62,7 +62,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
maximized
@hide="closeTraceDetails"
>
<trace-details />
<trace-details @shareLink="shareLink" />
</q-dialog>
</div>
</div>
@@ -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,
};
},
});
+10 -4
View File
@@ -16,8 +16,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template>
<div
class="flex wrap justify-start items-center bg-white"
:class="defocusSpan ? 'defocus' : ''"
class="flex wrap justify-start items-center"
:class="[
defocusSpan ? 'defocus' : '',
store.state.theme === 'dark' ? 'bg-dark' : 'bg-white',
]"
:style="{
zIndex: 2,
borderBottom:
@@ -25,7 +28,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
}"
>
<div
class="flex justify-between items-end cursor-pointer span-block relative-position bg-white"
class="flex justify-between items-end cursor-pointer span-block relative-position"
:class="[store.state.theme === 'dark' ? 'bg-dark' : 'bg-white']"
:style="{
height: spanDimensions.height + 'px',
width: '100%',
@@ -106,6 +110,7 @@ import { defineComponent, computed, ref } from "vue";
import useTraces from "@/composables/useTraces";
import { getImageURL, formatTimeWithSuffix } from "@/utils/zincutils";
import SpanDetails from "./SpanDetails.vue";
import { useStore } from "vuex";
export default defineComponent({
name: "SpanBlock",
@@ -146,6 +151,7 @@ export default defineComponent({
emits: ["toggleCollapse"],
components: { SpanDetails },
setup(props, { emit }) {
const store = useStore();
const { searchObj } = useTraces();
const spanBlock: any = ref(null);
const spanBlockWidth = ref(0);
@@ -256,6 +262,7 @@ export default defineComponent({
toggleSpanDetails,
defocusSpan,
isSpanSelected,
store,
};
},
});
@@ -267,7 +274,6 @@ export default defineComponent({
}
.collapse-btn {
background-color: #ffffff;
opacity: 0.6;
}
+25 -7
View File
@@ -53,7 +53,7 @@
<template v-for="(val, key) in tags" :key="key">
<span class="text-grey-8">{{ key }}</span>
<span class="text-grey-8">: </span>
<span class="text-grey-10">{{ val }}</span>
<span class="">{{ val }}</span>
<span class="q-mx-xs bg-grey-5" style="padding-left: 1px"></span>
</template>
</div>
@@ -66,8 +66,17 @@
<tbody>
<template v-for="(val, key) in tags" :key="key">
<tr>
<td class="q-py-xs q-px-sm text-grey-9">{{ key }}</td>
<td class="q-py-xs q-px-sm text-primary">
<td
class="q-py-xs q-px-sm"
:class="
store.state.theme === 'dark'
? 'text-red-5'
: 'text-red-10'
"
>
{{ key }}
</td>
<td class="q-py-xs q-px-sm">
{{ val }}
</td>
</tr>
@@ -100,7 +109,7 @@
<template v-for="(val, key) in processes" :key="key">
<span class="text-grey-8">{{ key }}</span>
<span class="text-grey-8">: </span>
<span class="text-grey-10">{{ val }}</span>
<span class="">{{ val }}</span>
<span class="q-mx-xs bg-grey-5" style="padding-left: 1px"></span>
</template>
</div>
@@ -110,8 +119,17 @@
<tbody>
<template v-for="(val, key) in processes" :key="key">
<tr>
<td class="q-py-xs q-px-sm text-grey-9">{{ key }}</td>
<td class="q-py-xs q-px-sm text-primary">
<td
class="q-py-xs q-px-sm"
:class="
store.state.theme === 'dark'
? 'text-red-5'
: 'text-red-10'
"
>
{{ key }}
</td>
<td class="q-py-xs q-px-sm">
{{ val }}
</td>
</tr>
@@ -218,7 +236,7 @@
</div>
<div class="text-right flex items-center justify-end">
<span class="text-grey-7 q-mr-xs">Span Id: </span
><span class="text-grey-10">{{ span.spanId }}</span>
><span class="">{{ span.spanId }}</span>
<q-icon
class="q-ml-xs text-grey-8 cursor-pointer trace-copy-icon"
size="12px"
+25 -3
View File
@@ -46,7 +46,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<div class="q-pb-xs">Spans: {{ spanList.length }}</div>
</div>
<q-btn v-close-popup="true" round flat icon="cancel" size="md" />
<div class="flex items-center">
<q-btn
data-test="logs-search-bar-share-link-btn"
class="q-mr-sm download-logs-btn q-px-sm"
size="sm"
icon="share"
round
flat
no-outline
:title="t('search.shareLink')"
@click="shareLink"
/>
<q-btn v-close-popup="true" round flat icon="cancel" size="md" />
</div>
</div>
<q-separator style="width: 100%" />
<div class="col-12 flex justify-between items-end q-pr-sm q-pt-sm">
@@ -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,
};
},
});
+23 -18
View File
@@ -36,14 +36,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
class="q-px-sm q-pb-none ellipsis non-selectable"
style="font-size: 14px"
>
<span class="text-grey-9">Operation Name: </span>{{ span.operation_name }}
<span class="text-grey-7">Operation Name: </span>{{ span.operation_name }}
</div>
<div
class="q-px-sm ellipsis non-selectable"
:title="span.service_name"
style="font-size: 14px"
>
<span class="text-grey-9">Service Name: </span> {{ span.service_name }}
<span class="text-grey-7">Service Name: </span> {{ span.service_name }}
</div>
</div>
<q-tabs
@@ -80,8 +80,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<tbody>
<template v-for="(val, key) in tags" :key="key">
<tr>
<td class="q-py-xs q-px-sm text-grey-9">{{ key }}</td>
<td class="q-py-xs q-px-sm text-primary">
<td
class="q-py-xs q-px-sm"
:class="
store.state.theme === 'dark' ? 'text-red-5' : 'text-red-10'
"
>
{{ key }}
</td>
<td class="q-py-xs q-px-sm">
{{ val }}
</td>
</tr>
@@ -94,8 +101,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<tbody>
<template v-for="(val, key) in processes" :key="key">
<tr>
<td class="q-py-xs q-px-sm text-grey-9">{{ key }}</td>
<td class="q-py-xs q-px-sm text-primary">
<td
class="q-py-xs q-px-sm"
:class="
store.state.theme === 'dark' ? 'text-red-5' : 'text-red-10'
"
>
{{ key }}
</td>
<td class="q-py-xs q-px-sm">
{{ val }}
</td>
</tr>
@@ -104,18 +118,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</table>
</q-tab-panel>
<q-tab-panel name="attributes">
<table class="q-my-sm">
<tbody>
<template v-for="(val, key) in spanDetails.attrs" :key="key">
<tr>
<td class="q-py-xs q-px-sm text-grey-9">{{ key }}</td>
<td class="q-py-xs q-px-sm text-primary">
{{ val }}
</td>
</tr>
</template>
</tbody>
</table>
<pre class="attr-text">{{
JSON.stringify(spanDetails.attrs, null, 2)
}}</pre>
</q-tab-panel>
<q-tab-panel name="events">
<q-virtual-scroll
+1
View File
@@ -25,6 +25,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
}"
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'"
>
<div
class="flex justify-start items-center no-wrap row q-px-sm"
+2 -1
View File
@@ -41,7 +41,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:title="span.operationName"
>
<div
class="flex no-wrap q-pt-sm bg-white full-width"
class="flex no-wrap q-pt-sm full-width"
:class="store.state.theme === 'dark' ? 'bg-dark' : 'bg-white'"
:style="{ height: '30px' }"
>
<div
+1
View File
@@ -74,6 +74,7 @@ const stream = {
return http().get(url);
},
// Thia API is just used for service_name and operation_name fields
tracesFieldValues: ({
org_identifier,
stream_name,
+20 -6
View File
@@ -1,12 +1,22 @@
<template>
<div>
<SearchBar ref="searchBarRef" :query-data="queryData" @searchdata="getQueryData" @update-query="updateQuery"
@change:date-time="updateDateTime"></SearchBar>
<SearchBar
ref="searchBarRef"
:query-data="queryData"
@searchdata="getQueryData"
@update-query="updateQuery"
:is-loading="!!isLoading.length"
@change:date-time="updateDateTime"
></SearchBar>
<div class="stream-data-table">
<template v-if="isLoading.length">
<div class="full-height flex justify-center items-center">
<div class="q-pb-lg">
<q-spinner-hourglass color="primary" size="40px" style="margin: 0 auto; display: block" />
<q-spinner-hourglass
color="primary"
size="40px"
style="margin: 0 auto; display: block"
/>
<span class="text-center">
Hold on tight, we're fetching your stream data.
</span>
@@ -14,7 +24,11 @@
</div>
</template>
<template v-else>
<StreamDataTable :rows="tableData.rows" :columns="tableData.columns" :is-loading="!!isLoading.length" />
<StreamDataTable
:rows="tableData.rows"
:columns="tableData.columns"
:is-loading="!!isLoading.length"
/>
</template>
</div>
</div>
@@ -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") {