fix: pipeline query node improvements (#6188)

1.  validate and close  text instead of save button 
2. when user selects stream name query wipes out if any previous query
is there
3.add the click event on whole expand bar 
4. after opening the sidebar clicking on the SQL editor moves the focus
away from the SQL editor
5. remove the pipeline view on the row and introduce a eye icon in
actions section
6. when user clicks on edit the schema should auto populate respective
to the query
This commit is contained in:
Sai Nikhil
2025-03-10 12:29:26 +05:30
committed by GitHub
parent 50a2c29492
commit 72aaa8d67a
11 changed files with 153 additions and 72 deletions
@@ -90,7 +90,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<QTablePagination
:scope="scope"
:pageTitle="t('alert_destinations.header')"
:pageTitle="t('pipeline_destinations.header')"
:position="'top'"
:resultTotal="resultTotal"
:perPageOptions="perPageOptions"
@@ -79,7 +79,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<div class="field_label ellipsis">
{{ props.row.name }}
</div>
<div class="field_overlay">
<div v-if="!hideAddSearchTerm" class="field_overlay">
<q-btn
:data-test="`log-search-index-list-filter-${props.row.name}-field-btn`"
:icon="outlinedAdd"
@@ -90,6 +90,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
round
/>
</div>
<div v-if="!hideCopyValue" style="background-color: #E8E8E8;" class="field_overlay">
<q-btn
:data-test="`log-search-index-list-filter-${props.row.name}-copy-btn`"
icon="content_copy"
size="10px"
class="q-mr-sm"
@click.stop="copyContentValue(props.row.name)"
round
flat
dense
/>
</div>
</div>
</template>
<q-card>
@@ -192,7 +204,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"
>
<q-btn
class="q-ml-sm"
class="q-ml-md"
size="8px"
title="Copy Value"
round
@@ -285,6 +297,10 @@ export default defineComponent({
hideCopyValue:{
type: Boolean,
default: true,
},
hideAddSearchTerm:{
type: Boolean,
default: false,
}
},
emits: ["event-emitted"],
@@ -28,6 +28,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:filter="filterQuery"
:filter-method="filterData"
style="width: 100%"
dense
>
<template #no-data>
<NoData />
@@ -7,7 +7,7 @@
<div class="tw-flex tw-items-center">
<q-icon
name="keyboard_arrow_up"
@click="expanded = !expanded"
@click.stop="expanded = !expanded"
class="tw-mr-1 tw-cursor-pointer tw-transition-all"
:class="[
store.state.theme === 'dark'
@@ -18,6 +18,7 @@
size="20px"
/>
<div
@click="expanded = !expanded"
class="tw-text-[14px] tw-font-bold"
:class="[
store.state.theme === 'dark'
@@ -28,6 +28,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:filter="filterQuery"
:filter-method="filterData"
style="width: 100%"
dense
>
<template #no-data>
<NoData />
+22 -1
View File
@@ -37,9 +37,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:disableVrlFunction="true"
:isValidSqlQuery="isValidSqlQuery"
:disableQueryTypeSelection="true"
:expandedLogs="expandedLogs"
v-model:trigger="streamRoute.trigger_condition"
v-model:sql="streamRoute.query_condition.sql"
v-model:promql="streamRoute.query_condition.promql"
v-model:promql_condition="streamRoute.query_condition.promql_condition"
v-model:query_type="streamRoute.query_condition.type"
v-model:aggregation="streamRoute.query_condition.aggregation"
v-model:stream_type="streamRoute.stream_type"
@@ -50,6 +53,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
@delete:node="openDeleteDialog"
@update:fullscreen="updateFullscreenMode"
@update:stream_type="updateStreamType"
@expandLog="toggleExpandLog"
class="q-mt-sm"
/>
</div>
@@ -80,11 +84,12 @@ import { useRouter } from "vue-router";
import useStreams from "@/composables/useStreams";
import ConfirmDialog from "@/components/ConfirmDialog.vue";
import { useQuasar } from "quasar";
import ScheduledPipeline from "@/components/pipeline/NodeForm/ScheduledPipeline.vue";
import useQuery from "@/composables/useQuery";
import searchService from "@/services/search";
import useDragAndDrop from "@/plugins/pipelines/useDnD";
import ScheduledPipeline from "@/components/pipeline/NodeForm/ScheduledPipeline.vue";
const VariablesInput = defineAsyncComponent(
() => import("@/components/alerts/VariablesInput.vue"),
);
@@ -103,6 +108,11 @@ interface StreamRoute {
query_condition: {
sql: string;
promql: string | null;
promql_condition: {
column: string;
operator: string;
value: any;
} | null;
type: string;
aggregation: {
group_by: string[];
@@ -161,6 +171,7 @@ const filteredColumns: any = ref([]);
const isValidSqlQuery = ref(true);
const expandedLogs = ref([]);
const validateSqlQueryPromise = ref<Promise<unknown>>();
const scheduledPipelineRef = ref<any>(null);
@@ -212,6 +223,11 @@ const getDefaultStreamRoute: any = () => {
sql: "",
type: "sql",
aggregation: null,
promql_condition: {
column: "",
operator: "",
value: "",
},
},
trigger_condition: {
period: 15,
@@ -232,6 +248,7 @@ const getDefaultStreamRoute: any = () => {
};
onMounted(() => {
if (pipelineObj.isEditNode) {
streamRoute.value = pipelineObj.currentSelectedNodeData
?.data as StreamRoute;
@@ -495,6 +512,10 @@ const updateQueryType = (val: string) => {
streamRoute.value.query_condition.sql = "";
}
};
const toggleExpandLog = (index: number) => {
expandedLogs.value = [];
};
</script>
<style scoped>
@@ -110,12 +110,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
>
<template #before>
<!-- fieldlist section -->
<span @click.stop="expandState.buildQuery = !expandState.buildQuery">
<FullViewContainer
name="query"
v-model:is-expanded="expandState.buildQuery"
label="Build Query"
class="tw-mt-1"
/>
</span>
<div class="q-pt-sm" v-show="expandState.buildQuery" >
<div >
@@ -175,12 +177,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
}"
:hideIncludeExlcude="true"
:hideCopyValue="false"
:hideAddSearchTerm="true"
/>
</div>
</template>
<template #after>
<span @click.stop="expandState.setVariables = !expandState.setVariables">
<!-- set variables part -->
<FullViewContainer
name="query"
@@ -188,6 +193,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
label="Set Variables"
class="tw-mt-1"
/>
</span>
<div v-show="expandState.setVariables" class="flex justify-between q-pl-sm full-height" style="overflow-y: auto !important;">
<div>
<div
@@ -888,43 +894,53 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<div class="full-width" style="height: calc(100vh - 140px) !important;" >
<div class="query-editor-container scheduled-pipelines">
<span @click.stop="expandState.query = !expandState.query">
<FullViewContainer
name="query"
v-model:is-expanded="expandState.query"
:label="tab === 'sql' ? 'Sql Query' : 'PromQL Query'"
class="tw-mt-1"
/>
</span>
<query-editor
v-show="expandState.query"
data-test="scheduled-pipeline-sql-editor"
ref="pipelineEditorRef"
editor-id="pipeline-query-editor"
class="monaco-editor"
v-model:query="query"
:class="query == '' && queryEditorPlaceholderFlag ? 'empty-query' : ''"
@update:query="updateQueryValue"
@focus="queryEditorPlaceholderFlag = false"
@blur="onBlurQueryEditor"
style="height: calc(100vh - 190px) !important;"
v-show="expandState.query"
data-test="scheduled-pipeline-sql-editor"
ref="pipelineEditorRef"
editor-id="pipeline-query-editor"
:debounceTime="300"
class="monaco-editor"
v-model:query="query"
:class="query == '' && queryEditorPlaceholderFlag ? 'empty-query' : ''"
@update:query="updateQueryValue"
@focus="focusQueryEditor"
@blur="onBlurQueryEditor"
style="height: calc(100vh - 190px) !important;"
/>
</div>
<div>
<span @click.stop="expandState.output = !expandState.output">
<FullViewContainer
name="output"
v-model:is-expanded="expandState.output"
label="Output"
class="tw-mt-1"
/>
</span>
<TenstackTable
v-if="rows.length > 0 && tab == 'sql'"
style="height: calc(100vh - 190px) !important;"
v-show="expandState.output"
v-show="expandState.output && rows.length > 0 && tab == 'sql'"
ref="searchTableRef"
:columns="getColumns"
:rows="rows"
:jsonpreviewStreamName="selectedStreamName"
:expandedRows="expandedLogs"
@expand-row="expandLog"
@copy="copyLogToClipboard"
/>
<div v-if="loading" style="height: calc(100vh - 190px) !important;" class="flex justify-center items-center" >
<q-spinner-hourglass color="primary" size="lg" />
@@ -980,7 +996,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/>
<q-btn
data-test="stream-routing-query-save-btn"
:label="t('alerts.save')"
:label="'Validate and Close'"
class="text-bold no-border q-ml-md"
color="secondary"
padding="sm xl"
@@ -1032,6 +1048,7 @@ import {
defineAsyncComponent,
nextTick,
onMounted,
onBeforeMount,
} from "vue";
import FieldsInput from "@/components/alerts/FieldsInput.vue";
import { useI18n } from "vue-i18n";
@@ -1050,7 +1067,7 @@ import {
} from "@/utils/zincutils";
import useQuery from "@/composables/useQuery";
import searchService from "@/services/search";
import { useQuasar } from "quasar";
import { useQuasar, copyToClipboard } from "quasar";
import cronParser from "cron-parser";
import useDragAndDrop from "@/plugins/pipelines/useDnD";
import IndexList from "@/plugins/logs/IndexList.vue";
@@ -1120,6 +1137,7 @@ const emits = defineEmits([
const { pipelineObj } = useDragAndDrop();
const { searchObj } = useLogs();
const { getStream, getStreams } = useStreams ();
let parser: any;
const selectedStreamName = ref("");
@@ -1193,6 +1211,7 @@ const functionEditorPlaceholderFlag = ref(true);
const queryEditorPlaceholderFlag = ref(true);
const pipelineEditorRef : any = ref(null);
const expandedLogs = ref<any[]>([]);
const cursorPosition = ref(-1);
const splitterModel = ref(30);
const step = ref(1);
@@ -1231,38 +1250,35 @@ watch(()=> splitterModel.value , (val)=>{
})
watch(()=> selectedStreamName.value, (val)=>{
console.log(val, "selectedStreamName");
searchObj.data.stream.pipelineQueryStream = [val];
})
onBeforeMount(async ()=>{
await importSqlParser();
})
onMounted(async ()=>{
getStreamList();
await getStreamList();
setTimeout(() => {
if(tab.value === 'sql' && query.value != ""){
const parsedQuery = parser?.parse(query.value);
selectedStreamName.value = parsedQuery.ast.from[0].table;
getStreamFields();
}
}, 200);
})
const standardOutput = ref(`
{
"data": {
"results": [
{
"event": {
"_timestamp": 1735128523652186,
"job": "test",
"level": "info",
"log": "test message for openobserve",
},
},
{
"event": {
"log": "test message for openobserve",
},
"message": "Error in event",
},
],
},
}
`)
const importSqlParser = async () => {
const useSqlParser: any = await import("@/composables/useParser");
const { sqlParser }: any = useSqlParser.default();
parser = await sqlParser();
};
const filteredTimezone: any = ref([]);
const expandState =ref( {
@@ -1376,7 +1392,7 @@ const currentTimezone =
const browserTimezone = ref(currentTimezone);
const streamTypes = ["logs", "metrics", "traces"];
const rows = ref([ ])
const rows = ref([])
// @ts-ignore
let timezoneOptions = Intl.supportedValuesOf("timeZone").map((tz: any) => {
@@ -1636,8 +1652,6 @@ const filterFunctionOptions = (val: string, update: any) => {
const onBlurQueryEditor = () => {
queryEditorPlaceholderFlag.value = true;
// emits("validate-sql");
};
const validateInputs = (notify: boolean = true) => {
@@ -1756,10 +1770,10 @@ const getStreamFields = () => {
});
})
.finally(() => {
if(tab.value === 'sql'){
if(tab.value === 'sql' && query.value == ""){
query.value = `SELECT * FROM "${selectedStreamName.value}"`;
}
else if (tab.value === 'promql'){
else if (tab.value === 'promql' && query.value == ""){
query.value = `${selectedStreamName.value}{}`;
}
expandState.value.query = true;
@@ -1856,7 +1870,6 @@ const handleSidebarEvent = (event: string, value: any) => {
if(cursorPosition.value != -1){
cursorPosition.value += valueToInsert.length;
}
console.log("newQuery Cursor Position", newQuery, cursorPosition.value);
// Set the new value
pipelineEditorRef.value.setValue(newQuery);
@@ -1907,16 +1920,7 @@ const runQuery = async () => {
}
else if(tab.value == 'promql'){
previewPromqlQueryRef.value.refreshData();
// searchService.metrics_query_range({
// org_identifier: store.state.selectedOrganization.identifier,
// query: query.value,
// start_time: dateTime.value.startTime,
// end_time: dateTime.value.endTime,
// }).then((res: any) => {
// console.log(res, 'res')
// }).catch((err: any) => {
// console.log(err, 'err')
// })
}
}
@@ -1936,6 +1940,26 @@ watch(() => q.fullscreen.isActive, (val) => {
emits('update:fullscreen', val);
});
const focusQueryEditor = () => {
queryEditorPlaceholderFlag.value = false;
}
const expandLog = (index: any) => {
if (expandedLogs.value.includes(index))
expandedLogs.value = expandedLogs.value.filter((item) => item != index);
else expandedLogs.value.push(index);
}
const copyLogToClipboard = (log: any, copyAsJson: boolean = true) => {
const copyData = copyAsJson ? JSON.stringify(log) : log;
copyToClipboard(copyData).then(() =>
q.notify({
type: "positive",
message: "Content Copied Successfully!",
timeout: 1000,
}),
);
};
defineExpose({
tab,
validateInputs,
@@ -1946,7 +1970,6 @@ defineExpose({
collapseFieldList,
collapseFields,
expandState,
standardOutput,
streamFields,
getStreamFields,
selectedStreamName,
@@ -1962,6 +1985,10 @@ defineExpose({
sideBarSplitterModel,
previewPromqlQueryRef,
cursorPosition,
focusQueryEditor,
expandedLogs,
copyLogToClipboard,
copyToClipboard,
});
</script>
@@ -2028,6 +2055,11 @@ defineExpose({
filter: none !important;
}
}
.empty-query .monaco-editor-background {
background-image: url("@/assets/images/common/query-editor.png");
background-repeat: no-repeat;
background-size: 115px;
}
}
.field-list-collapse-btn {
z-index: 11;
@@ -81,7 +81,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<q-separator class="q-mb-md" />
<div class="flex q-mt-sm">
<NodeSidebar :nodeTypes="nodeTypes" />
<NodeSidebar v-show="!pipelineObj.dialog.show || pipelineObj.dialog.name != 'query'" :nodeTypes="nodeTypes" />
</div>
</div>
<div
@@ -89,6 +89,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
ref="chartContainerRef"
class="relative-position pipeline-chart-container o2vf_node"
:class="store.state.theme === 'dark' ? '' : 'bg-grey-2'"
v-show="!pipelineObj.dialog.show || pipelineObj.dialog.name != 'query'"
>
<PipelineFlow />
</div>
+16 -4
View File
@@ -38,9 +38,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
style="cursor: pointer"
@click="triggerExpand(props)"
>
<q-tooltip position="bottom">
<PipelineView :pipeline="props.row" />
</q-tooltip>
<q-td v-if="activeTab == 'scheduled' " >
<q-btn
@@ -98,6 +95,21 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
:title="t('alerts.delete')"
@click.stop="openDeleteDialog(props.row)"
></q-btn>
<q-btn
:data-test="`pipeline-list-${props.row.name}-view-pipeline`"
:icon="outlinedVisibility"
class="q-ml-xs"
padding="sm"
unelevated
size="sm"
round
flat
:title="t('alerts.view')"
>
<q-tooltip position="bottom">
<PipelineView :pipeline="props.row" />
</q-tooltip>
</q-btn>
</template>
</q-td>
@@ -224,7 +236,7 @@ import { useQuasar, type QTableProps } from "quasar";
import type { QTableColumn } from 'quasar';
import NoData from "../shared/grid/NoData.vue";
import { outlinedDelete , outlinedPause , outlinedPlayArrow } from "@quasar/extras/material-icons-outlined";
import { outlinedDelete , outlinedPause , outlinedPlayArrow, outlinedVisibility } from "@quasar/extras/material-icons-outlined";
import QTablePagination from "@/components/shared/grid/Pagination.vue";
import ConfirmDialog from "@/components/ConfirmDialog.vue";
import useDragAndDrop from "@/plugins/pipelines/useDnD";
+2 -1
View File
@@ -916,7 +916,8 @@
"lastRunAt":"Last Run At",
"lastSuccessfulAt":"Last Successfull At",
"status":"Status",
"numOfEvents": "Number of Events"
"numOfEvents": "Number of Events",
"view":"View"
},
"alert_templates": {
"header": "Templates",
-5
View File
@@ -57,9 +57,6 @@ const hanldeMouseOver = () => {
const onFunctionClick = (data,event,id) =>{
console.log(data,"data")
console.log(id,"id")
console.log(event,"event")
pipelineObj.userSelectedNode = data;
const dataToOpen = {
label: "Function",
@@ -77,7 +74,6 @@ const onFunctionClick = (data,event,id) =>{
const onConditionClick = (data,event,id) =>{
data.label = id;
console.log(data,"data")
pipelineObj.userSelectedNode = data;
const dataToOpen = {
@@ -149,7 +145,6 @@ const editNode = (id) => {
const fullNode = pipelineObj.currentSelectedPipeline.nodes.find(
(node) => node.id === id
);
console.log(fullNode,'full node')
pipelineObj.isEditNode = true;
pipelineObj.currentSelectedNodeData = fullNode;
pipelineObj.currentSelectedNodeID = id;