mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-06 00:09:22 +00:00
319 lines
9.7 KiB
Vue
319 lines
9.7 KiB
Vue
<!-- Copyright 2023 OpenObserve 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/no-unused-components -->
|
|
<template>
|
|
<div style="height: 100%; width: 100%">
|
|
<!-- PanelEditor Content Area (no header for logs visualization) -->
|
|
<PanelEditor
|
|
ref="panelEditorRef"
|
|
pageType="logs"
|
|
:editMode="true"
|
|
:dashboardData="{}"
|
|
:variablesData="{}"
|
|
:selectedDateTime="dashboardPanelData.meta.dateTime"
|
|
:externalChartData="chartData"
|
|
:searchResponse="searchResponse"
|
|
:isUiHistogram="is_ui_histogram"
|
|
:shouldRefreshWithoutCache="shouldRefreshWithoutCache"
|
|
:allowedChartTypes="allowedChartTypes"
|
|
@addToDashboard="addToDashboard"
|
|
@chartApiError="handleChartApiError"
|
|
@searchRequestTraceIdsUpdated="handleSearchRequestTraceIdsUpdated"
|
|
/>
|
|
|
|
<!-- Add to Dashboard Dialog -->
|
|
<q-dialog
|
|
v-model="showAddToDashboardDialog"
|
|
position="right"
|
|
full-height
|
|
maximized
|
|
>
|
|
<add-to-dashboard
|
|
@save="addPanelToDashboard"
|
|
:dashboardPanelData="dashboardPanelData"
|
|
/>
|
|
</q-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import {
|
|
defineComponent,
|
|
ref,
|
|
watch,
|
|
defineAsyncComponent,
|
|
computed,
|
|
} from "vue";
|
|
import { useI18n } from "vue-i18n";
|
|
import { useStore } from "vuex";
|
|
import useDashboardPanelData from "@/composables/dashboard/useDashboardPanel";
|
|
import { provide, inject, toRefs, onActivated } from "vue";
|
|
import useNotifications from "@/composables/useNotifications";
|
|
import { isSimpleSelectAllQuery } from "@/utils/query/sqlUtils";
|
|
import { useSearchStream } from "@/composables/useLogs/useSearchStream";
|
|
import { searchState } from "@/composables/useLogs/searchState";
|
|
import { PanelEditor } from "@/components/dashboards/PanelEditor";
|
|
|
|
const AddToDashboard = defineAsyncComponent(() => {
|
|
return import("./../metrics/AddToDashboard.vue");
|
|
});
|
|
|
|
export default defineComponent({
|
|
name: "VisualizeLogsQuery",
|
|
props: {
|
|
visualizeChartData: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
errorData: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
searchResponse: {
|
|
type: Object,
|
|
required: false,
|
|
},
|
|
is_ui_histogram: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false,
|
|
},
|
|
shouldRefreshWithoutCache: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false,
|
|
},
|
|
histogramQuery: {
|
|
type: String,
|
|
required: false,
|
|
default: "",
|
|
},
|
|
},
|
|
components: {
|
|
AddToDashboard,
|
|
PanelEditor,
|
|
},
|
|
emits: ["handleChartApiError", "searchRequestTraceIdsUpdated"],
|
|
setup(props, { emit }) {
|
|
const dashboardPanelDataPageKey = inject(
|
|
"dashboardPanelDataPageKey",
|
|
"logs",
|
|
);
|
|
const { t } = useI18n();
|
|
const store = useStore();
|
|
const { dashboardPanelData, resetAggregationFunction, validatePanel } =
|
|
useDashboardPanelData(dashboardPanelDataPageKey);
|
|
const resultMetaData = ref(null);
|
|
|
|
const { showErrorNotification } = useNotifications();
|
|
|
|
const { searchObj } = searchState();
|
|
const { buildSearch } = useSearchStream();
|
|
|
|
const {
|
|
visualizeChartData,
|
|
is_ui_histogram,
|
|
shouldRefreshWithoutCache,
|
|
}: any = toRefs(props);
|
|
const chartData = ref(visualizeChartData.value);
|
|
|
|
const showAddToDashboardDialog = ref(false);
|
|
|
|
// PanelEditor ref for accessing exposed methods/properties
|
|
const panelEditorRef = ref<InstanceType<typeof PanelEditor> | null>(null);
|
|
|
|
// Allowed chart types for logs visualization
|
|
const allowedChartTypes = [
|
|
"area",
|
|
"bar",
|
|
"h-bar",
|
|
"line",
|
|
"scatter",
|
|
"table",
|
|
];
|
|
|
|
// Watch for external chart data changes
|
|
watch(
|
|
() => visualizeChartData.value,
|
|
async () => {
|
|
chartData.value = JSON.parse(JSON.stringify(visualizeChartData.value));
|
|
},
|
|
{ deep: true },
|
|
);
|
|
|
|
// Handle chart type change with validation
|
|
const handleChartTypeChange = (newType: string) => {
|
|
// Get the actual logs page query, handling SQL mode
|
|
let logsPageQuery = "";
|
|
|
|
// Handle sql mode - same as in Index.vue
|
|
if (!searchObj.meta.sqlMode) {
|
|
const queryBuild = buildSearch();
|
|
logsPageQuery = queryBuild?.query?.sql ?? "";
|
|
} else {
|
|
logsPageQuery = searchObj.data.query;
|
|
}
|
|
|
|
// Check if query is SELECT * and trying to switch chart type
|
|
if (
|
|
store.state.zoConfig.quick_mode_enabled === true &&
|
|
isSimpleSelectAllQuery(logsPageQuery)
|
|
) {
|
|
showErrorNotification(
|
|
"Select * query is not supported for visualization.",
|
|
);
|
|
// Prevent the change by not updating the type
|
|
return;
|
|
}
|
|
|
|
// If validation passes, proceed with the change
|
|
dashboardPanelData.data.type = newType;
|
|
resetAggregationFunction();
|
|
};
|
|
|
|
const handleChartApiError = (errorMsg: any) => {
|
|
if (typeof errorMsg === "string") {
|
|
const errorList = props.errorData.errors ?? [];
|
|
errorList.splice(0);
|
|
errorList.push(errorMsg);
|
|
} else if (errorMsg?.message) {
|
|
const errorList = props.errorData.errors ?? [];
|
|
errorList.splice(0);
|
|
errorList.push(errorMsg.message);
|
|
props.errorData.value = errorMsg?.message ?? "";
|
|
}
|
|
|
|
emit("handleChartApiError", errorMsg);
|
|
};
|
|
|
|
// Forward search request trace IDs to parent for cancel query functionality
|
|
const handleSearchRequestTraceIdsUpdated = (traceIds: string[]) => {
|
|
emit("searchRequestTraceIdsUpdated", traceIds);
|
|
};
|
|
|
|
// Provide hovered series state for child components
|
|
const hoveredSeriesState = ref({
|
|
hoveredSeriesName: "",
|
|
panelId: -1,
|
|
dataIndex: -1,
|
|
seriesIndex: -1,
|
|
hoveredTime: null,
|
|
setHoveredSeriesName: function (name: string) {
|
|
hoveredSeriesState.value.hoveredSeriesName = name ?? "";
|
|
},
|
|
setIndex: function (
|
|
dataIndex: number,
|
|
seriesIndex: number,
|
|
panelId: any,
|
|
hoveredTime?: any,
|
|
) {
|
|
hoveredSeriesState.value.dataIndex = dataIndex ?? -1;
|
|
hoveredSeriesState.value.seriesIndex = seriesIndex ?? -1;
|
|
hoveredSeriesState.value.panelId = panelId ?? -1;
|
|
hoveredSeriesState.value.hoveredTime = hoveredTime ?? null;
|
|
},
|
|
});
|
|
|
|
provide("hoveredSeriesState", hoveredSeriesState);
|
|
|
|
const addToDashboard = () => {
|
|
// Get result metadata from PanelEditor if available
|
|
const panelResultMetaData = panelEditorRef.value?.metaData?.value;
|
|
|
|
// Check for histogram query - prioritize sources in order of reliability
|
|
if (is_ui_histogram.value === true) {
|
|
// First priority: use the stored histogram query prop (most reliable - persists across state changes)
|
|
if (props.histogramQuery) {
|
|
dashboardPanelData.data.queries[0].query = props.histogramQuery;
|
|
} else if (searchObj.data.queryResults?.converted_histogram_query) {
|
|
// Fallback: check searchObj.data.queryResults
|
|
dashboardPanelData.data.queries[0].query =
|
|
searchObj.data.queryResults.converted_histogram_query;
|
|
} else if (props.searchResponse?.converted_histogram_query) {
|
|
// Fallback: check searchResponse (from logs search API)
|
|
dashboardPanelData.data.queries[0].query =
|
|
props.searchResponse.converted_histogram_query;
|
|
} else if (panelResultMetaData?.[0]?.[0]?.converted_histogram_query) {
|
|
// Fallback: check panelResultMetaData (new format)
|
|
dashboardPanelData.data.queries[0].query =
|
|
panelResultMetaData?.[0]?.[0]?.converted_histogram_query;
|
|
} else if (
|
|
// Fallback: check panelResultMetaData (old format)
|
|
panelResultMetaData?.[0]?.converted_histogram_query &&
|
|
!Array.isArray(panelResultMetaData?.[0])
|
|
) {
|
|
dashboardPanelData.data.queries[0].query =
|
|
panelResultMetaData?.[0]?.converted_histogram_query;
|
|
}
|
|
}
|
|
|
|
const errors: any = [];
|
|
// will push errors in errors array
|
|
validatePanel(errors, true);
|
|
|
|
if (errors.length) {
|
|
// set errors into errorData
|
|
props.errorData.errors = errors;
|
|
showErrorNotification(
|
|
"There are some errors, please fix them and try again",
|
|
);
|
|
return;
|
|
} else {
|
|
showAddToDashboardDialog.value = true;
|
|
}
|
|
};
|
|
|
|
const addPanelToDashboard = () => {
|
|
showAddToDashboardDialog.value = false;
|
|
};
|
|
|
|
onActivated(() => {
|
|
dashboardPanelData.layout.querySplitter = 20;
|
|
|
|
// keep field list closed for visualization
|
|
dashboardPanelData.layout.showFieldList = false;
|
|
dashboardPanelData.layout.splitter = 0;
|
|
});
|
|
|
|
return {
|
|
t,
|
|
dashboardPanelData,
|
|
handleChartApiError,
|
|
handleSearchRequestTraceIdsUpdated,
|
|
resetAggregationFunction,
|
|
store,
|
|
chartData,
|
|
showAddToDashboardDialog,
|
|
addPanelToDashboard,
|
|
addToDashboard,
|
|
is_ui_histogram,
|
|
shouldRefreshWithoutCache,
|
|
hoveredSeriesState,
|
|
resultMetaData,
|
|
isSimpleSelectAllQuery,
|
|
handleChartTypeChange,
|
|
panelEditorRef,
|
|
allowedChartTypes,
|
|
};
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
@import "@/styles/logs/visualizelogs-query.scss";
|
|
</style>
|