Files
openobserve/web/src/plugins/logs/Index.vue
T
Bhargav 427ee1baf1 fix: 4021 logs page additional error information (#4029)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced `SanitizedHtmlRenderer` for improved and secure HTML
content rendering in search results.

- **Enhancements**
- Improved error handling in the application, including updated error
messages with "TraceID" for better tracking.
- Enhanced user experience by allowing users to view additional error
details during search failures.
- Simplified error output by removing detailed error information from
user interfaces.

- **Bug Fixes**
- Adjusted handling of notifications to more accurately reflect error
states, especially for rate limiting scenarios.

- **Chores**
- Modified dependency version for `rudder-sdk-js` to a specific version,
potentially impacting update capabilities.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2024-07-24 13:46:25 +05:30

1327 lines
41 KiB
Vue

<!-- Copyright 2023 Zinc Labs Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<!-- eslint-disable vue/attribute-hyphenation -->
<!-- eslint-disable vue/v-on-event-hyphenation -->
<template>
<q-page class="logPage q-my-xs" id="logPage">
<div id="secondLevel" class="full-height">
<q-splitter
class="logs-horizontal-splitter full-height"
v-model="splitterModel"
horizontal
>
<template v-slot:before>
<search-bar
data-test="logs-search-bar"
ref="searchBarRef"
:fieldValues="fieldValues"
:key="searchObj.data.transforms.length || -1"
@searchdata="searchData"
@onChangeInterval="onChangeInterval"
@onChangeTimezone="refreshTimezone"
@handleQuickModeChange="handleQuickModeChange"
@handleRunQueryFn="handleRunQueryFn"
@on-auto-interval-trigger="onAutoIntervalTrigger"
/>
</template>
<template v-slot:after>
<div
id="thirdLevel"
class="row scroll relative-position thirdlevel full-height overflow-hidden logsPageMainSection"
style="width: 100%"
v-if="searchObj.meta.logsVisualizeToggle == 'logs'"
>
<!-- Note: Splitter max-height to be dynamically calculated with JS -->
<q-splitter
v-model="searchObj.config.splitterModel"
:limits="searchObj.config.splitterLimit"
style="width: 100%"
class="full-height"
@update:model-value="onSplitterUpdate"
>
<template #before>
<div class="relative-position full-height">
<index-list
v-if="searchObj.meta.showFields"
data-test="logs-search-index-list"
:key="
searchObj.data.stream.selectedStream.join(',') ||
'default'
"
class="full-height"
@setInterestingFieldInSQLQuery="
setInterestingFieldInSQLQuery
"
/>
<q-btn
data-test="logs-search-field-list-collapse-btn"
:icon="
searchObj.meta.showFields
? 'chevron_left'
: 'chevron_right'
"
:title="
searchObj.meta.showFields
? 'Collapse Fields'
: 'Open Fields'
"
dense
size="20px"
round
class="q-mr-xs field-list-collapse-btn"
color="primary"
:style="{
right: searchObj.meta.showFields ? '-20px' : '-24px',
}"
@click="collapseFieldList"
></q-btn>
</div>
</template>
<template #after>
<div
v-if="
searchObj.data.filterErrMsg !== '' &&
searchObj.loading == false
"
>
<h5 class="text-center">
<q-icon name="warning"
color="warning" size="10rem" /><br />
<div
data-test="logs-search-filter-error-message"
v-html="searchObj.data.filterErrMsg"
></div>
</h5>
</div>
<div
v-else-if="
searchObj.data.errorMsg !== '' && searchObj.loading == false
"
>
<h5 class="text-center">
<div
data-test="logs-search-result-not-found-text"
v-if="searchObj.data.errorCode == 0"
>
Result not found.
</div>
<div data-test="logs-search-error-message" v-else>
Error occurred while retrieving search events.
<q-btn
@click="toggleErrorDetails"
size="sm"
data-test="logs-page-result-error-details-btn"
>{{ t("search.histogramErrorBtnLabel") }}</q-btn
><br />
</div>
<span v-if="disableMoreErrorDetails">
<SanitizedHtmlRenderer
data-test="logs-search-detail-error-message"
:htmlContent="searchObj.data.errorMsg"
/>
</span>
<div
data-test="logs-search-error-20003"
v-if="parseInt(searchObj.data.errorCode) == 20003"
>
<q-btn
no-caps
unelevated
size="sm"
bg-secondary
class="no-border bg-secondary text-white"
:to="
'/streams?dialog=' +
searchObj.data.stream.selectedStream.label
"
>Click here</q-btn
>
to configure a full text search field to the stream.
</div>
<br />
<q-item-label>{{
searchObj.data.additionalErrorMsg
}}</q-item-label>
</h5>
</div>
<div
v-else-if="
searchObj.data.stream.selectedStream.length == 0 &&
searchObj.loading == false
"
class="row"
>
<h6
data-test="logs-search-no-stream-selected-text"
class="text-center col-10 q-mx-auto"
>
<q-icon name="info"
color="primary" size="md" /> Select a
stream and press 'Run query' to continue. Additionally, you
can apply additional filters and adjust the date range to
enhance search.
</h6>
</div>
<div
v-else-if="
searchObj.data.queryResults.hasOwnProperty('hits') &&
searchObj.data.queryResults.hits.length == 0 &&
searchObj.loading == false &&
searchObj.meta.searchApplied == true
"
class="row"
>
<h6
data-test="logs-search-error-message"
class="text-center q-mx-auto col-10"
>
<q-icon name="info"
color="primary" size="md" />
{{ t("search.noRecordFound") }}
</h6>
</div>
<div
v-else-if="
searchObj.data.queryResults.hasOwnProperty('hits') &&
searchObj.data.queryResults.hits.length == 0 &&
searchObj.loading == false &&
searchObj.meta.searchApplied == false
"
class="row"
>
<h6
data-test="logs-search-error-message"
class="text-center q-mx-auto col-10"
>
<q-icon name="info"
color="primary" size="md" />
{{ t("search.applySearch") }}
</h6>
</div>
<div
v-else
data-test="logs-search-search-result"
class="full-height search-result-container"
>
<search-result
ref="searchResultRef"
:expandedLogs="expandedLogs"
@update:datetime="setHistogramDate"
@update:scroll="getMoreData"
@update:recordsPerPage="getMoreDataRecordsPerPage"
@expandlog="toggleExpandLog"
/>
</div>
</template>
</q-splitter>
</div>
<div else :style="`height: calc(100vh - ${splitterModel}vh - 40px);`">
<VisualizeLogsQuery
:visualizeChartData="visualizeChartData"
:errorData="visualizeErrorData"
@update:stream-list="streamListUpdated"
></VisualizeLogsQuery>
</div>
</template>
</q-splitter>
</div>
</q-page>
</template>
<script lang="ts">
// @ts-nocheck
import {
defineComponent,
ref,
onActivated,
computed,
nextTick,
onBeforeMount,
watch,
defineAsyncComponent,
provide,
} from "vue";
import { useQuasar } from "quasar";
import { useStore } from "vuex";
import { useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import segment from "@/services/segment_analytics";
import config from "@/aws-exports";
import { verifyOrganizationStatus } from "@/utils/zincutils";
import MainLayoutCloudMixin from "@/enterprise/mixins/mainLayout.mixin";
import SanitizedHtmlRenderer from "@/components/SanitizedHtmlRenderer.vue";
import useLogs from "@/composables/useLogs";
import VisualizeLogsQuery from "@/plugins/logs/VisualizeLogsQuery.vue";
import useDashboardPanelData from "@/composables/useDashboardPanel";
import { reactive } from "vue";
import { getConsumableRelativeTime } from "@/utils/date";
import { cloneDeep } from "lodash-es";
import {
buildSqlQuery,
getFieldsFromQuery,
getValidConditionObj,
} from "@/utils/query/sqlUtils";
import useNotifications from "@/composables/useNotifications";
export default defineComponent({
name: "PageSearch",
components: {
SearchBar: defineAsyncComponent(
() => import("@/plugins/logs/SearchBar.vue")
),
IndexList: defineAsyncComponent(
() => import("@/plugins/logs/IndexList.vue")
),
SearchResult: defineAsyncComponent(
() => import("@/plugins/logs/SearchResult.vue")
),
ConfirmDialog: defineAsyncComponent(
() => import("@/components/ConfirmDialog.vue")
),
SanitizedHtmlRenderer,
VisualizeLogsQuery,
},
mixins: [MainLayoutCloudMixin],
methods: {
setHistogramDate(date: any) {
this.searchBarRef.dateTimeRef.setCustomDate("absolute", date);
},
searchData() {
if (this.searchObj.loading == false) {
this.searchObj.loading = true;
this.searchObj.runQuery = true;
}
if (config.isCloud == "true") {
segment.track("Button Click", {
button: "Search Data",
user_org: this.store.state.selectedOrganization.identifier,
user_id: this.store.state.userInfo.email,
stream_name: this.searchObj.data.stream.selectedStream.join(","),
show_query: this.searchObj.meta.showQuery,
show_histogram: this.searchObj.meta.showHistogram,
sqlMode: this.searchObj.meta.sqlMode,
showFields: this.searchObj.meta.showFields,
page: "Search Logs",
});
}
},
async getMoreDataRecordsPerPage() {
if (this.searchObj.meta.refreshInterval == 0) {
// this.searchObj.data.resultGrid.currentPage =
// ((this.searchObj.data.queryResults?.hits?.length || 0) +
// ((this.searchObj.data.queryResults?.hits?.length || 0) + 150)) /
// 150 -
// 1;
// this.searchObj.data.resultGrid.currentPage =
// this.searchObj.data.resultGrid.currentPage + 1;
this.searchObj.loading = true;
await this.getQueryData(false);
this.refreshHistogramChart();
if (config.isCloud == "true") {
segment.track("Button Click", {
button: "Get More Data",
user_org: this.store.state.selectedOrganization.identifier,
user_id: this.store.state.userInfo.email,
stream_name: this.searchObj.data.stream.selectedStream.join(","),
page: "Search Logs",
});
}
}
},
async getMoreData() {
if (this.searchObj.meta.refreshInterval == 0) {
// this.searchObj.data.resultGrid.currentPage =
// ((this.searchObj.data.queryResults?.hits?.length || 0) +
// ((this.searchObj.data.queryResults?.hits?.length || 0) + 150)) /
// 150 -
// 1;
// this.searchObj.data.resultGrid.currentPage =
// this.searchObj.data.resultGrid.currentPage + 1;
this.searchObj.loading = true;
await this.getQueryData(true);
this.refreshHistogramChart();
if (config.isCloud == "true") {
segment.track("Button Click", {
button: "Get More Data",
user_org: this.store.state.selectedOrganization.identifier,
user_id: this.store.state.userInfo.email,
stream_name: this.searchObj.data.stream.selectedStream.join(","),
page: "Search Logs",
});
}
}
},
async getLessData() {
if (
this.searchObj.meta.sqlMode == false &&
this.searchObj.meta.refreshInterval == 0 &&
this.searchObj.data.queryResults.total >
this.searchObj.data.queryResults.from &&
this.searchObj.data.queryResults.total >
this.searchObj.data.queryResults.size &&
this.searchObj.data.queryResults.total >
this.searchObj.data.queryResults.size +
this.searchObj.data.queryResults.from
) {
// this.searchObj.data.resultGrid.currentPage =
// ((this.searchObj.data.queryResults?.hits?.length || 0) +
// ((this.searchObj.data.queryResults?.hits?.length || 0) + 150)) /
// 150 -
// 1;
this.searchObj.data.resultGrid.currentPage =
this.searchObj.data.resultGrid.currentPage - 1;
await this.getQueryData(true);
this.refreshHistogramChart();
if (config.isCloud == "true") {
segment.track("Button Click", {
button: "Get Less Data",
user_org: this.store.state.selectedOrganization.identifier,
user_id: this.store.state.userInfo.email,
stream_name: this.searchObj.data.stream.selectedStream.join(","),
page: "Search Logs",
});
}
}
},
toggleErrorDetails() {
this.disableMoreErrorDetails = !this.disableMoreErrorDetails;
},
},
setup() {
const { t } = useI18n();
const store = useStore();
const router = useRouter();
const $q = useQuasar();
const disableMoreErrorDetails: boolean = ref(false);
let {
searchObj,
getQueryData,
fieldValues,
updateGridColumns,
refreshData,
updateUrlQueryParams,
loadLogsData,
updateStreams,
restoreUrlQueryParams,
handleRunQuery,
generateHistogramData,
resetSearchObj,
resetStreamData,
getHistogramQueryData,
fnParsedSQL,
addOrderByToQuery,
getRegionInfo,
} = useLogs();
const searchResultRef = ref(null);
const searchBarRef = ref(null);
let parser: any;
const expandedLogs = ref({});
const splitterModel = ref(10);
// flag to know if it is the first time visualize
let firstTimeVisualizeFlag = false;
const { showErrorNotification } = useNotifications();
provide("dashboardPanelDataPageKey", "logs");
const visualizeChartData = ref({});
const {
dashboardPanelData,
validatePanel,
generateLabelFromName,
resetDashboardPanelData,
} = useDashboardPanelData("logs");
const visualizeErrorData: any = reactive({
errors: [],
});
// function restoreUrlQueryParams() {
// const queryParams = router.currentRoute.value.query;
// if (!queryParams.stream) {
// return;
// }
// const date = {
// startTime: queryParams.from,
// endTime: queryParams.to,
// relativeTimePeriod: queryParams.period || null,
// type: queryParams.period ? "relative" : "absolute",
// };
// if (date) {
// searchObj.data.datetime = date;
// }
// if (queryParams.query) {
// searchObj.meta.sqlMode = queryParams.sql_mode == "true" ? true : false;
// searchObj.data.editorValue = b64DecodeUnicode(queryParams.query);
// searchObj.data.query = b64DecodeUnicode(queryParams.query);
// }
// if (queryParams.refresh) {
// searchObj.meta.refreshInterval = queryParams.refresh;
// }
// }
// async function loadPageData() {
// try {
// loadLogsData();
// } catch (e) {
// searchObj.loading = false;
// console.log(e);
// }
// }
// onUnmounted(() => {
// resetSearchObj();
// resetStreamData();
// });
onActivated(async () => {
// if search tab
if (searchObj.meta.logsVisualizeToggle == "logs") {
const queryParams: any = router.currentRoute.value.query;
const isStreamChanged =
queryParams.stream_type !== searchObj.data.stream.streamType ||
queryParams.stream !== searchObj.data.stream.selectedStream.join(",");
if (
isStreamChanged &&
queryParams.type === "stream_explorer" &&
searchObj.loading == false
) {
resetSearchObj();
resetStreamData();
restoreUrlQueryParams();
loadLogsData();
return;
}
if (
searchObj.organizationIdetifier !=
store.state.selectedOrganization.identifier &&
searchObj.loading == false
) {
loadLogsData();
} else if (!searchObj.loading) updateStreams();
refreshHistogramChart();
} else {
// visualize tab
handleRunQueryFn();
}
});
onBeforeMount(async () => {
await importSqlParser();
if (searchObj.meta.logsVisualizeToggle == "logs") {
// searchObj.loading = true;
searchObj.meta.pageType = "logs";
if (
config.isEnterprise == "true" &&
store.state.zoConfig.super_cluster_enabled
) {
await getRegionInfo();
}
searchObj.organizationIdetifier =
store.state.selectedOrganization.identifier;
restoreUrlQueryParams();
if (searchObj.loading == false) {
resetSearchObj();
resetStreamData();
restoreUrlQueryParams();
loadLogsData();
}
if (config.isCloud == "true") {
MainLayoutCloudMixin.setup().getOrganizationThreshold(store);
}
searchObj.meta.quickMode = store.state.zoConfig.quick_mode_enabled;
}
});
/**
* As we are redirecting stream explorer to logs page, we need to check if the user has changed the stream type from stream explorer to logs.
* This watcher is used to check if the user has changed the stream type from stream explorer to logs.
* This gets triggered when stream explorer is active and user clicks on logs icon from left menu sidebar. Then we need to redirect the user to logs page again.
*/
watch(
() => router.currentRoute.value.query.type,
(type, prev) => {
if (
searchObj.shouldIgnoreWatcher == false &&
router.currentRoute.value.name === "logs" &&
prev === "stream_explorer" &&
!type
) {
searchObj.meta.pageType = "logs";
loadLogsData();
}
}
);
const importSqlParser = async () => {
const useSqlParser: any = await import("@/composables/useParser");
const { sqlParser }: any = useSqlParser.default();
parser = await sqlParser();
};
const runQueryFn = async () => {
// searchObj.data.resultGrid.currentPage = 0;
// searchObj.runQuery = false;
// expandedLogs.value = {};
try {
await getQueryData();
refreshHistogramChart();
} catch (e) {
console.log(e);
}
};
const refreshTimezone = () => {
updateGridColumns();
generateHistogramData();
refreshHistogramChart();
};
const refreshHistogramChart = () => {
nextTick(() => {
if (
searchObj.meta.showHistogram &&
searchResultRef.value?.reDrawChart
) {
searchResultRef.value.reDrawChart();
}
});
};
const setQuery = (sqlMode: boolean) => {
if (!searchBarRef.value) {
console.error("searchBarRef is null");
return;
}
try {
if (sqlMode) {
let selectFields = "";
let whereClause = "";
let currentQuery = searchObj.data.query;
const hasSelect =
currentQuery != "" &&
(currentQuery.toLowerCase() === "select" ||
currentQuery.toLowerCase().indexOf("select ") == 0);
//check if user try to applied saved views in which sql mode is enabled.
if (currentQuery.indexOf("SELECT") >= 0) {
return;
}
// Parse the query and check if it is valid
// It should have one column and one table
// const hasSelect =
// currentQuery.toLowerCase() === "select" ||
// currentQuery.toLowerCase().indexOf("select ") == 0;
if (!hasSelect) {
if (currentQuery != "") {
currentQuery = currentQuery.split("|");
if (currentQuery.length > 1) {
selectFields = "," + currentQuery[0].trim();
if (currentQuery[1].trim() != "") {
whereClause = "WHERE " + currentQuery[1].trim();
}
} else if (currentQuery[0].trim() != "") {
if (currentQuery[0].trim() != "") {
whereClause = "WHERE " + currentQuery[0].trim();
}
}
}
searchObj.data.query =
`SELECT [FIELD_LIST]${selectFields} FROM "` +
searchObj.data.stream.selectedStream.join(",") +
`" ` +
whereClause;
if (searchObj.data.stream.selectedStreamFields.length == 0) {
const streamData: any = getStream(
searchObj.data.stream.selectedStream[0],
searchObj.data.stream.streamType || "logs",
true
);
searchObj.data.stream.selectedStreamFields = streamData.schema;
}
const streamFieldNames: any =
searchObj.data.stream.selectedStreamFields.map(
(item: any) => item.name
);
for (
let i = searchObj.data.stream.interestingFieldList.length - 1;
i >= 0;
i--
) {
const fieldName = searchObj.data.stream.interestingFieldList[i];
if (!streamFieldNames.includes(fieldName)) {
searchObj.data.stream.interestingFieldList.splice(i, 1);
}
}
if (
searchObj.data.stream.interestingFieldList.length > 0 &&
searchObj.meta.quickMode
) {
searchObj.data.query = searchObj.data.query.replace(
"[FIELD_LIST]",
searchObj.data.stream.interestingFieldList.join(",")
);
} else {
searchObj.data.query = searchObj.data.query.replace(
"[FIELD_LIST]",
"*"
);
}
}
searchObj.data.query = addOrderByToQuery(
searchObj.data.query,
store.state.zoConfig.timestamp_column,
"DESC",
searchObj.data.stream.selectedStream.join(",")
);
searchObj.data.editorValue = searchObj.data.query;
searchBarRef.value.udpateQuery();
searchObj.data.parsedQuery = parser.astify(searchObj.data.query);
} else {
searchObj.data.query = "";
searchBarRef.value.udpateQuery();
}
} catch (e) {
console.log("Logs : Error in setQuery");
}
};
const collapseFieldList = () => {
if (searchObj.meta.showFields) searchObj.meta.showFields = false;
else searchObj.meta.showFields = true;
};
const areStreamsPresent = computed(() => {
return !!searchObj.data.stream.streamLists.length;
});
const toggleExpandLog = async (index: number) => {
if (expandedLogs.value[index.toString()])
delete expandedLogs.value[index.toString()];
else expandedLogs.value[index.toString()] = true;
};
const onSplitterUpdate = () => {
window.dispatchEvent(new Event("resize"));
};
const onChangeInterval = () => {
updateUrlQueryParams();
refreshData();
};
const onAutoIntervalTrigger = () => {
// handle event for visualization page only
if (searchObj.meta.logsVisualizeToggle == "visualize") {
handleRunQueryFn();
}
};
function removeFieldByName(data, fieldName) {
return data.filter((item: any) => {
if (item.expr) {
if (item.expr.column === fieldName) {
return false;
}
if (
item.expr.type === "aggr_func" &&
item.expr.args.expr.column === fieldName
) {
return false;
}
}
return true;
});
}
const setInterestingFieldInSQLQuery = (
field: any,
isFieldExistInSQL: boolean
) => {
//implement setQuery function using node-sql-parser
//isFieldExistInSQL is used to check if the field is already present in the query or not.
const parsedSQL = fnParsedSQL();
if (parsedSQL) {
if (isFieldExistInSQL) {
//remove the field from the query
if (parsedSQL.columns.length > 0) {
let filteredData = removeFieldByName(parsedSQL.columns, field.name);
const index = searchObj.data.stream.interestingFieldList.indexOf(
field.name
);
if (index > -1) {
searchObj.data.stream.interestingFieldList.splice(index, 1);
field.isInterestingField = false;
}
parsedSQL.columns = filteredData;
}
} else {
//add the field in the query
if (parsedSQL.columns.length > 0) {
// iterate and remove the * from the query
parsedSQL.columns = removeFieldByName(parsedSQL.columns, "*");
}
parsedSQL.columns.push({
expr: {
type: "column_ref",
column: field.name,
},
});
}
if (parsedSQL.columns.length == 0) {
parsedSQL.columns.push({
expr: {
type: "column_ref",
column: "*",
},
});
}
const newQuery = parser
.sqlify(parsedSQL)
.replace(/`/g, "")
.replace(
searchObj.data.stream.selectedStream[0],
`"${searchObj.data.stream.selectedStream[0]}"`
);
searchObj.data.query = newQuery;
searchObj.data.editorValue = newQuery;
searchBarRef.value.udpateQuery();
searchObj.data.parsedQuery = parser.astify(searchObj.data.query);
}
};
const handleQuickModeChange = () => {
if (searchObj.meta.quickMode == true) {
let field_list: string = "*";
if (searchObj.data.stream.interestingFieldList.length > 0) {
field_list = searchObj.data.stream.interestingFieldList.join(",");
}
if (searchObj.meta.sqlMode == true) {
searchObj.data.query = searchObj.data.query.replace(
/SELECT\s+(.*?)\s+FROM/i,
(match, fields) => {
return `SELECT ${field_list} FROM`;
}
);
setQuery(searchObj.meta.quickMode);
updateUrlQueryParams();
}
}
};
//validate the data
const isValid = (onlyChart = false) => {
const errors = visualizeErrorData.errors;
errors.splice(0);
const dashboardData = dashboardPanelData;
// check if name of panel is there
if (!onlyChart) {
if (
dashboardData.data.title == null ||
dashboardData.data.title.trim() == ""
) {
errors.push("Name of Panel is required");
}
}
// will push errors in errors array
validatePanel(errors);
if (errors.length) {
showErrorNotification(
"There are some errors, please fix them and try again"
);
return false;
}
return true;
};
const setFieldsAndConditions = async () => {
let logsQuery = searchObj.data.query ?? "";
// if sql mode is off, then need to make query
if (searchObj.meta.sqlMode == false) {
logsQuery = buildSqlQuery(
searchObj.data.stream.selectedStream[0],
searchObj.meta.quickMode
? searchObj.data.stream.interestingFieldList
: [],
logsQuery
);
}
const { fields, conditions, streamName } = await getFieldsFromQuery(
logsQuery ?? "",
store.state.zoConfig.timestamp_column ?? "_timestamp"
);
// set stream type and stream name
if (streamName && streamName != "undefined") {
// set firstTimeVisualizeFlag as true
firstTimeVisualizeFlag = true;
dashboardPanelData.data.queries[0].fields.stream_type =
searchObj.data.stream.streamType ?? "logs";
dashboardPanelData.data.queries[0].fields.stream = streamName;
}
// set fields
fields.forEach((field) => {
field.alias = field.alias ?? field.column;
field.label = generateLabelFromName(field.column);
// if fields doesnt have aggregation functions, then add it in the x axis fields
if (
field.aggregationFunction === null ||
field.aggregationFunction == "histogram"
) {
dashboardPanelData.data.queries[0].fields.x.push(field);
} else {
dashboardPanelData.data.queries[0].fields.y.push(field);
}
});
// if x axis fields length is 2, then add 2nd x axis field to breakdown fields
if (dashboardPanelData.data.queries[0].fields.x.length == 2) {
dashboardPanelData.data.queries[0].fields.breakdown.push(
dashboardPanelData.data.queries[0].fields.x[1]
);
// remove 2nd x axis field from x axis fields
dashboardPanelData.data.queries[0].fields.x.splice(1, 1);
}
// if x axis fields length is greater than 2, then select chart type as table
else if (dashboardPanelData.data.queries[0].fields.x.length > 2) {
dashboardPanelData.data.type = "table";
}
// set conditions
conditions.forEach((condition) => {
condition.operator = condition.operator.toLowerCase();
// get valid condition object
condition = getValidConditionObj(condition);
dashboardPanelData.data.queries[0].fields.filter.push(condition);
});
};
// watch for changes in the visualize toggle
// if it is in visualize mode, then set the query and stream name in the dashboard panel
watch(
() => [searchObj.meta.logsVisualizeToggle],
async () => {
if (searchObj.meta.logsVisualizeToggle == "visualize") {
// reset old rendered chart
visualizeChartData.value = {};
// hide VRL function editor
searchObj.config.fnSplitterModel = 99.5;
// set fields and conditions
await setFieldsAndConditions();
} else {
// else check if VRL function toggle is true or false
// based on that set the splitter model
if (searchObj.meta.toggleFunction == false) {
searchObj.config.fnSplitterModel = 99.5;
} else {
searchObj.config.fnSplitterModel = 60;
}
}
}
);
watch(
() => dashboardPanelData.data.type,
async () => {
// await nextTick();
visualizeChartData.value = JSON.parse(
JSON.stringify(dashboardPanelData.data)
);
}
);
watch(
() => splitterModel.value,
() => {
// rerender chart
window.dispatchEvent(new Event("resize"));
}
);
watch(
() => [
searchObj.data.datetime.type,
searchObj.data.datetime,
searchObj.data.datetime.relativeTimePeriod,
],
async () => {
const dateTime =
searchObj.data.datetime.type === "relative"
? getConsumableRelativeTime(
searchObj.data.datetime.relativeTimePeriod
)
: cloneDeep(searchObj.data.datetime);
dashboardPanelData.meta.dateTime = {
start_time: new Date(dateTime.startTime),
end_time: new Date(dateTime.endTime),
};
},
{ deep: true }
);
const handleRunQueryFn = () => {
if (searchObj.meta.logsVisualizeToggle == "visualize") {
if (!isValid(true)) {
return;
}
// refresh the date time
searchBarRef.value &&
searchBarRef.value.dateTimeRef &&
searchBarRef.value.dateTimeRef.refresh();
visualizeChartData.value = JSON.parse(
JSON.stringify(dashboardPanelData.data)
);
}
};
const handleChartApiError = (errorMessage: any) => {
const errorList = visualizeErrorData.errors;
errorList.splice(0);
errorList.push(errorMessage);
};
const streamListUpdated = () => {
if (
searchObj.meta.logsVisualizeToggle == "visualize" &&
firstTimeVisualizeFlag
) {
firstTimeVisualizeFlag = false;
// run query
handleRunQueryFn();
}
};
return {
t,
store,
router,
parser,
searchObj,
searchBarRef,
splitterModel,
// loadPageData,
getQueryData,
searchResultRef,
runQueryFn,
refreshData,
setQuery,
verifyOrganizationStatus,
collapseFieldList,
areStreamsPresent,
toggleExpandLog,
expandedLogs,
fieldValues,
onSplitterUpdate,
updateGridColumns,
updateUrlQueryParams,
refreshHistogramChart,
onChangeInterval,
onAutoIntervalTrigger,
handleRunQuery,
refreshTimezone,
resetSearchObj,
resetStreamData,
getHistogramQueryData,
setInterestingFieldInSQLQuery,
handleQuickModeChange,
handleRunQueryFn,
visualizeChartData,
handleChartApiError,
visualizeErrorData,
streamListUpdated,
disableMoreErrorDetails,
};
},
computed: {
showFields() {
return this.searchObj.meta.showFields;
},
showHistogram() {
return this.searchObj.meta.showHistogram;
},
showQuery() {
return this.searchObj.meta.showQuery;
},
moveSplitter() {
return this.searchObj.config.splitterModel;
},
// changeStream() {
// return this.searchObj.data.stream.selectedStream;
// },
changeRelativeDate() {
return (
this.searchObj.data.datetime.relative.value +
this.searchObj.data.datetime.relative.period.value
);
},
updateSelectedColumns() {
return this.searchObj.data.stream.selectedFields.length;
},
runQuery() {
return this.searchObj.runQuery;
},
changeRefreshInterval() {
return this.searchObj.meta.refreshInterval;
},
fullSQLMode() {
return this.searchObj.meta.sqlMode;
},
refreshHistogram() {
return this.searchObj.meta.histogramDirtyFlag;
},
redrawHistogram() {
return (
this.searchObj.data.histogram.hasOwnProperty("xData") &&
this.searchObj.data.histogram.xData.length
);
},
},
watch: {
showFields() {
if (
this.searchObj.meta.showHistogram == true &&
this.searchObj.meta.sqlMode == false
) {
setTimeout(() => {
if (this.searchResultRef) this.searchResultRef.reDrawChart();
}, 100);
}
if (this.searchObj.config.splitterModel > 0) {
this.searchObj.config.lastSplitterPosition =
this.searchObj.config.splitterModel;
}
this.searchObj.config.splitterModel = this.searchObj.meta.showFields
? this.searchObj.config.lastSplitterPosition
: 0;
},
showHistogram() {
if (
this.searchObj.meta.showHistogram &&
!this.searchObj.shouldIgnoreWatcher
) {
setTimeout(() => {
if (this.searchResultRef) this.searchResultRef.reDrawChart();
}, 100);
if (this.searchObj.meta.histogramDirtyFlag == true) {
this.searchObj.meta.histogramDirtyFlag = false;
// this.handleRunQuery();
this.getHistogramQueryData(this.searchObj.data.histogramQuery).then(
(res: any) => {
this.searchResultRef.reDrawChart();
}
);
}
}
this.updateUrlQueryParams();
},
moveSplitter() {
if (this.searchObj.meta.showFields == false) {
this.searchObj.meta.showFields =
this.searchObj.config.splitterModel > 0;
}
},
// changeStream: {
// handler(stream, streamOld) {
// if (
// this.searchObj.data.stream.selectedStream.hasOwnProperty("value") &&
// this.searchObj.data.stream.selectedStream.value != ""
// ) {
// this.searchObj.data.tempFunctionContent = "";
// this.searchBarRef.resetFunctionContent();
// if (streamOld.value) this.searchObj.data.query = "";
// if (streamOld.value) this.setQuery(this.searchObj.meta.sqlMode);
// this.searchObj.loading = true;
// // setTimeout(() => {
// // this.runQueryFn();
// // }, 500);
// }
// },
// immediate: false,
// },
updateSelectedColumns() {
this.searchObj.meta.resultGrid.manualRemoveFields = true;
setTimeout(() => {
this.updateGridColumns();
}, 50);
},
runQuery() {
if (this.store.state.savedViewFlag == true) return;
if (this.searchObj.runQuery == true) {
this.runQueryFn();
}
},
async fullSQLMode(newVal) {
if (newVal) {
await nextTick();
this.setQuery(newVal);
this.updateUrlQueryParams();
} else {
this.searchObj.meta.sqlMode = false;
this.searchObj.data.query = "";
this.searchObj.data.editorValue = "";
if (
this.searchObj.loading == false &&
this.searchObj.shouldIgnoreWatcher == false &&
this.store.state.zoConfig.query_on_stream_selection == false
) {
this.searchObj.loading = true;
this.getQueryData();
}
}
// this.searchResultRef.reDrawChart();
},
refreshHistogram() {
if (
this.searchObj.meta.histogramDirtyFlag == true &&
this.searchObj.meta.showHistogram == true
) {
this.searchObj.meta.histogramDirtyFlag = false;
this.handleRunQuery();
this.refreshHistogramChart();
}
},
redrawHistogram() {
this.refreshHistogramChart();
},
},
});
</script>
<style lang="scss">
$navbarHeight: 64px;
.logPage {
height: calc(100vh - $navbarHeight);
min-height: calc(100vh - $navbarHeight) !important;
.index-menu .field_list .field_overlay .field_label,
.q-field__native,
.q-field__input,
.q-table tbody td {
font-size: 12px !important;
}
.q-splitter__after {
overflow: hidden;
}
.q-item__label span {
/* text-transform: capitalize; */
}
.index-table :hover::-webkit-scrollbar,
#searchGridComponent:hover::-webkit-scrollbar {
height: 13px;
width: 13px;
}
.index-table ::-webkit-scrollbar-track,
#searchGridComponent::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
border-radius: 10px;
}
.index-table ::-webkit-scrollbar-thumb,
#searchGridComponent::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5);
}
.q-table__top {
padding: 0px !important;
}
.q-table__control {
width: 100%;
}
.logsPageMainSection > .q-field__control-container {
padding-top: 0px !important;
}
.logs-horizontal-splitter {
border: 1px solid var(--q-color-grey-3);
.q-splitter__panel {
z-index: auto !important;
}
.q-splitter__before {
overflow: visible !important;
}
}
.thirdlevel {
.field-list-collapse-btn {
z-index: 9;
position: absolute;
top: 5px;
font-size: 12px !important;
}
}
.search-result-container {
position: relative;
width: 100%;
}
}
</style>