diff --git a/web/src/components/CodeQueryEditor.vue b/web/src/components/CodeQueryEditor.vue index 846007445e..538837f081 100644 --- a/web/src/components/CodeQueryEditor.vue +++ b/web/src/components/CodeQueryEditor.vue @@ -37,7 +37,10 @@ along with this program. If not, see . AI - {{ disableAiReason || t(nlpMode ? 'search.nlpModeEnabled' : 'search.nlpModeLabel') }} + {{ + disableAiReason || + t(nlpMode ? "search.nlpModeEnabled" : "search.nlpModeLabel") + }} @@ -135,10 +138,21 @@ export default defineComponent({ }, disableAiReason: { type: String, - default: '', + default: "", }, }, - emits: ["update-query", "run-query", "update:query", "focus", "blur", "nlpModeDetected", "generation-start", "generation-end", "generation-success", "toggle-nlp-mode"], + emits: [ + "update-query", + "run-query", + "update:query", + "focus", + "blur", + "nlpModeDetected", + "generation-start", + "generation-end", + "generation-success", + "toggle-nlp-mode", + ], setup(props, { emit }) { const store = useStore(); const { t } = useI18n(); @@ -147,10 +161,16 @@ export default defineComponent({ // editor object is used to interact with the monaco editor instance let editorObj: any = null; const { searchObj } = searchState(); - const { detectNaturalLanguage, generateSQL, transformToSQL, isGenerating, streamingResponse } = useNLQuery(); + const { + detectNaturalLanguage, + generateSQL, + transformToSQL, + isGenerating, + streamingResponse, + } = useNLQuery(); let provider: Ref = ref(null); - const currentEditorText = ref(''); + const currentEditorText = ref(""); // These will be initialized when Monaco loads let CompletionKind: any = null; @@ -364,25 +384,31 @@ export default defineComponent({ currentEditorText.value = text; const isNL = detectNaturalLanguage(text, props.language); - console.log('[NL2Q-Detection]', { + console.log("[NL2Q-Detection]", { text: text.substring(0, 50), language: props.language, isNaturalLanguage: isNL, - currentNlpMode: props.nlpMode + currentNlpMode: props.nlpMode, }); // ONLY emit events if NOT already in NLP mode (auto-detection feature) // If already in NLP mode (user toggled it), don't change anything if (!props.nlpMode) { if (isNL) { - console.log('[NL2Q-Detection] Natural language detected, emitting nlpModeDetected: true'); + console.log( + "[NL2Q-Detection] Natural language detected, emitting nlpModeDetected: true", + ); emit("nlpModeDetected", true); } else { - console.log('[NL2Q-Detection] Query syntax detected, emitting nlpModeDetected: false'); + console.log( + "[NL2Q-Detection] Query syntax detected, emitting nlpModeDetected: false", + ); emit("nlpModeDetected", false); } } else { - console.log('[NL2Q-Detection] Already in NLP mode, not emitting auto-detection events'); + console.log( + "[NL2Q-Detection] Already in NLP mode, not emitting auto-detection events", + ); } }, 500); @@ -391,85 +417,114 @@ export default defineComponent({ * Calls AI to generate query based on current language (SQL, PromQL, VRL, JavaScript) * @param customText - Optional custom text to use instead of editor content */ - const handleGenerateSQL = async (customText?: string, abortSignal?: AbortSignal, sessionId?: string) => { + const handleGenerateSQL = async ( + customText?: string, + abortSignal?: AbortSignal, + sessionId?: string, + ) => { const currentText = customText || currentEditorText.value; if (!currentText.trim()) return; - const currentLanguage = props.language?.toLowerCase() || 'sql'; - console.log('[NL2Q-UI] Starting query generation for language:', currentLanguage, 'text:', currentText); + const currentLanguage = props.language?.toLowerCase() || "sql"; + console.log( + "[NL2Q-UI] Starting query generation for language:", + currentLanguage, + "text:", + currentText, + ); try { // Get organization ID from store - const orgId = store.state.selectedOrganization?.identifier || 'default'; - console.log('[NL2Q-UI] Organization ID:', orgId); + const orgId = store.state.selectedOrganization?.identifier || "default"; + console.log("[NL2Q-UI] Organization ID:", orgId); // Create language-appropriate prompt - let promptPrefix = ''; + let promptPrefix = ""; switch (currentLanguage) { - case 'promql': - promptPrefix = 'Generate PromQL query'; + case "promql": + promptPrefix = "Generate PromQL query"; break; - case 'vrl': - promptPrefix = 'Generate VRL function'; + case "vrl": + promptPrefix = "Generate VRL function"; break; - case 'javascript': - promptPrefix = 'Generate JavaScript function'; + case "javascript": + promptPrefix = "Generate JavaScript function"; break; - case 'sql': + case "sql": default: - promptPrefix = 'Generate SQL query'; + promptPrefix = "Generate SQL query"; break; } const prompt = `${promptPrefix} : ${currentText}`; - console.log('[NL2Q-UI] Generated prompt:', prompt); + console.log("[NL2Q-UI] Generated prompt:", prompt); // Generate query from natural language - console.log('[NL2Q-UI] Calling generateSQL...'); - const generatedSQL = await generateSQL(prompt, orgId, abortSignal, sessionId); - console.log('[NL2Q-UI] generateSQL returned:', { + console.log("[NL2Q-UI] Calling generateSQL..."); + const generatedSQL = await generateSQL( + prompt, + orgId, + abortSignal, + sessionId, + ); + console.log("[NL2Q-UI] generateSQL returned:", { value: generatedSQL, type: typeof generatedSQL, isNull: generatedSQL === null, - isEmpty: generatedSQL === '', - isFalsy: !generatedSQL + isEmpty: generatedSQL === "", + isFalsy: !generatedSQL, }); - if (!generatedSQL || generatedSQL.trim() === '') { + if (!generatedSQL || generatedSQL.trim() === "") { // Show error notification - console.log('[NL2Q-UI] Showing error notification - query generation failed or empty'); - showErrorNotification(t('search.nlQueryGenerationFailed')); - throw new Error('Query generation failed'); + console.log( + "[NL2Q-UI] Showing error notification - query generation failed or empty", + ); + showErrorNotification(t("search.nlQueryGenerationFailed")); + throw new Error("Query generation failed"); } // Check if this is a special action completion (dashboard/alert) - if (generatedSQL.startsWith('✓ DASHBOARD_CREATED:')) { - console.log('[NL2Q-UI] Dashboard created successfully'); - const responseText = generatedSQL.replace('✓ DASHBOARD_CREATED:', '').trim(); - emit("generation-success", { type: 'dashboard', message: responseText }); + if (generatedSQL.startsWith("✓ DASHBOARD_CREATED:")) { + console.log("[NL2Q-UI] Dashboard created successfully"); + const responseText = generatedSQL + .replace("✓ DASHBOARD_CREATED:", "") + .trim(); + emit("generation-success", { + type: "dashboard", + message: responseText, + }); // Don't emit nlpModeDetected - keep user in current mode return; // Success without SQL } - if (generatedSQL.startsWith('✓ ALERT_CREATED:')) { - console.log('[NL2Q-UI] Alert created successfully'); - const responseText = generatedSQL.replace('✓ ALERT_CREATED:', '').trim(); - emit("generation-success", { type: 'alert', message: responseText }); + if (generatedSQL.startsWith("✓ ALERT_CREATED:")) { + console.log("[NL2Q-UI] Alert created successfully"); + const responseText = generatedSQL + .replace("✓ ALERT_CREATED:", "") + .trim(); + emit("generation-success", { type: "alert", message: responseText }); // Don't emit nlpModeDetected - keep user in current mode return; // Success without SQL } - if (generatedSQL.startsWith('✓ ACTION_COMPLETED:')) { - console.log('[NL2Q-UI] Action completed successfully'); - const responseText = generatedSQL.replace('✓ ACTION_COMPLETED:', '').trim(); - emit("generation-success", { type: 'action', message: responseText }); + if (generatedSQL.startsWith("✓ ACTION_COMPLETED:")) { + console.log("[NL2Q-UI] Action completed successfully"); + const responseText = generatedSQL + .replace("✓ ACTION_COMPLETED:", "") + .trim(); + emit("generation-success", { type: "action", message: responseText }); // Don't emit nlpModeDetected - keep user in current mode return; // Success without SQL } // Normal query generation - transform and update editor with language-specific comments - const transformedText = transformToSQL(currentText, generatedSQL, props.language); - console.log('[NL2Q-UI] Transformed text:', transformedText); + const transformedText = transformToSQL( + currentText, + generatedSQL, + props.language, + ); + console.log("[NL2Q-UI] Transformed text:", transformedText); // Update editor value setValue(transformedText); @@ -480,16 +535,17 @@ export default defineComponent({ // Turn off NLP mode after generating SQL (we're now in SQL mode) emit("nlpModeDetected", false); - console.log('[NL2Q-UI] Emitted nlpModeDetected: false to turn off NLP mode'); + console.log( + "[NL2Q-UI] Emitted nlpModeDetected: false to turn off NLP mode", + ); // Emit SQL generation success - emit("generation-success", { type: 'sql', message: generatedSQL }); - - console.log('[NL2Q-UI] SQL generation completed successfully'); + emit("generation-success", { type: "sql", message: generatedSQL }); + console.log("[NL2Q-UI] SQL generation completed successfully"); } catch (error) { - console.error('[NL2Q-UI] Exception during SQL generation:', error); - showErrorNotification(t('search.nlQueryGenerationFailed')); + console.error("[NL2Q-UI] Exception during SQL generation:", error); + showErrorNotification(t("search.nlQueryGenerationFailed")); throw error; // Re-throw so SearchBar can handle it } }; @@ -571,7 +627,7 @@ export default defineComponent({ if (editorElement && editorElement?.hasChildNodes()) { if (editorObj) { // Update editor value if different from current - const currentValue = editorObj.getValue(); + const currentValue = editorObj?.getValue(); if (currentValue !== props.query?.trim()) { editorObj.setValue(props.query?.trim() || ""); } @@ -591,7 +647,7 @@ export default defineComponent({ } // New props have actual data - safe to recreate - editorElement.innerHTML = ''; + editorElement.innerHTML = ""; // Fall through to create new editor below } } @@ -630,12 +686,12 @@ export default defineComponent({ editorObj.onDidChangeModelContent( debounce((e: any) => { - const newValue = editorObj.getValue()?.trim(); + const newValue = editorObj?.getValue()?.trim(); emit("update-query", newValue, e); emit("update:query", newValue, e); // Check for natural language after user stops typing (debounced) - checkForNaturalLanguage(newValue); + if (newValue) checkForNaturalLanguage(newValue); }, props.debounceTime), ); @@ -651,7 +707,7 @@ export default defineComponent({ editorObj.onDidFocusEditorWidget(() => { emit("focus"); - // added hack to handle case where ctrl+enter / cmd+enter stops working after + // added hack to handle case where ctrl+enter / cmd+enter stops working after // user click on the result row and open sidebase or opensidebar from schedule search // This is because the editor loses focus and the context key "ctrlenter" is not active anymore, so we need to re-add the command on focus editorObj.addCommand( @@ -665,9 +721,9 @@ export default defineComponent({ }); editorObj.onDidBlurEditorWidget(() => { - const model = editorObj.getModel(); - const value = model.getValue(); - const trimmedValue = value.trim(); + const model = editorObj?.getModel(); + const value = model?.getValue(); + const trimmedValue = value?.trim(); // Only apply trim if there are actually tailing and leading spaces to trim if (value !== trimmedValue) { @@ -713,38 +769,26 @@ export default defineComponent({ provider.value?.dispose(); if (props.language === "sql") { - await import( - "monaco-editor/esm/vs/basic-languages/sql/sql.contribution.js" - ); + await import("monaco-editor/esm/vs/basic-languages/sql/sql.contribution.js"); } if (props.language === "json") { - await import( - "monaco-editor/esm/vs/language/json/monaco.contribution.js" - ); + await import("monaco-editor/esm/vs/language/json/monaco.contribution.js"); } if (props.language === "html") { - await import( - "monaco-editor/esm/vs/language/html/monaco.contribution.js" - ); + await import("monaco-editor/esm/vs/language/html/monaco.contribution.js"); } if (props.language === "markdown") { - await import( - "monaco-editor/esm/vs/basic-languages/markdown/markdown.contribution.js" - ); + await import("monaco-editor/esm/vs/basic-languages/markdown/markdown.contribution.js"); } if (props.language === "python") { - await import( - "monaco-editor/esm/vs/basic-languages/python/python.contribution.js" - ); + await import("monaco-editor/esm/vs/basic-languages/python/python.contribution.js"); } if (props.language === "javascript") { - await import( - "monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js" - ); + await import("monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js"); } setupEditor(); @@ -810,16 +854,17 @@ export default defineComponent({ (newQuery, oldQuery) => { if (!editorObj) return; - const currentValue = editorObj.getValue(); - const newValue = props.query || ''; + const currentValue = editorObj?.getValue(); + const newValue = props.query || ""; const hasFocus = editorObj.hasWidgetFocus(); // Only update if: // 1. Editor doesn't have focus (external update), OR // 2. It's readonly AND values are actually different // 3. Compare trimmed values to avoid cursor jumps from trailing spaces - const shouldUpdate = (props.readOnly || !hasFocus) - && currentValue?.trim() !== newValue?.trim(); + const shouldUpdate = + (props.readOnly || !hasFocus) && + currentValue?.trim() !== newValue?.trim(); if (shouldUpdate) { editorObj.getModel()?.setValue(newValue); @@ -1017,13 +1062,13 @@ export default defineComponent({ // Watch isGenerating and emit events to parent watch(isGenerating, (newValue) => { - console.log('[CodeQueryEditor] isGenerating changed to:', newValue); + console.log("[CodeQueryEditor] isGenerating changed to:", newValue); if (newValue) { - console.log('[CodeQueryEditor] Emitting generation-start'); - emit('generation-start'); + console.log("[CodeQueryEditor] Emitting generation-start"); + emit("generation-start"); } else { - console.log('[CodeQueryEditor] Emitting generation-end'); - emit('generation-end'); + console.log("[CodeQueryEditor] Emitting generation-end"); + emit("generation-end"); } }); @@ -1036,7 +1081,7 @@ export default defineComponent({ // Toggle NLP mode const toggleNlpMode = () => { - emit('toggle-nlp-mode'); + emit("toggle-nlp-mode"); }; return { @@ -1137,7 +1182,7 @@ export default defineComponent({ bottom: 0.5rem; right: 0.5rem; z-index: 100; - background: linear-gradient(135deg, #8B5CF6 0%, #EC4899 100%) !important; + background: linear-gradient(135deg, #8b5cf6 0%, #ec4899 100%) !important; color: white !important; box-shadow: 0 0.25rem 0.9375rem 0 rgba(139, 92, 246, 0.3) !important; transition: all 0.3s ease !important; @@ -1145,7 +1190,7 @@ export default defineComponent({ } .generate-sql-button:hover:not(.disabled):not([disabled]):not(:disabled) { - background: linear-gradient(135deg, #7C3AED 0%, #DB2777 100%) !important; + background: linear-gradient(135deg, #7c3aed 0%, #db2777 100%) !important; box-shadow: 0 0.375rem 1.25rem 0 rgba(139, 92, 246, 0.4) !important; transform: translateY(-0.0625rem) !important; } @@ -1177,7 +1222,7 @@ export default defineComponent({ max-height: 31.25rem; background: var(--o2-card-bg); border-radius: 0.5rem; /* 8px - matches O2 message border-radius */ - border: 2px solid #8B5CF6; /* O2 AI Assistant purple border */ + border: 2px solid #8b5cf6; /* O2 AI Assistant purple border */ padding: 0.75rem; /* 12px - matches O2 message padding */ z-index: 99; overflow: hidden; @@ -1195,7 +1240,9 @@ export default defineComponent({ /* Streaming preview content - O2 AI Assistant text-block style */ .streaming-preview-content { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", + Arial, sans-serif; font-size: 0.875rem; line-height: 1.6; /* Better readability for text content */ color: var(--o2-text-primary); @@ -1215,7 +1262,7 @@ export default defineComponent({ /* Animated dots - ellipsis animation using pseudo-element */ .animated-dots::after { - content: ''; + content: ""; animation: ellipsis 1.5s infinite; display: inline-block; width: 1.5em; @@ -1229,23 +1276,24 @@ export default defineComponent({ @keyframes ellipsis { 0% { - content: ''; + content: ""; } 25% { - content: '.'; + content: "."; } 50% { - content: '..'; + content: ".."; } - 75%, 100% { - content: '...'; + 75%, + 100% { + content: "..."; } } /* Code blocks within streaming preview */ .streaming-preview-content :deep(pre), .streaming-preview-content :deep(code) { - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Courier New', monospace; + font-family: "Monaco", "Menlo", "Ubuntu Mono", "Courier New", monospace; white-space: pre-wrap; word-break: break-word; overflow-wrap: break-word; @@ -1301,7 +1349,8 @@ export default defineComponent({ } @keyframes typing-cursor { - 0%, 100% { + 0%, + 100% { opacity: 1; } 50% { @@ -1325,7 +1374,6 @@ export default defineComponent({ } --vscode-focusBorder: transparent !important; } - } .highlight-error { diff --git a/web/src/components/rum/VideoPlayer.vue b/web/src/components/rum/VideoPlayer.vue index aa5864ce5a..df374ee6cc 100644 --- a/web/src/components/rum/VideoPlayer.vue +++ b/web/src/components/rum/VideoPlayer.vue @@ -424,10 +424,13 @@ const setupSession = async () => { }; const updatePlayerState = () => { + if (!player?.value) return; const playerMeta = player.value?.getMetaData(); - playerState.value.startTime = playerMeta.startTime; - playerState.value.endTime = playerMeta.endTime; - playerState.value.totalTime = playerMeta.totalTime; + + if (!playerMeta) return; + playerState.value.startTime = playerMeta?.startTime; + playerState.value.endTime = playerMeta?.endTime; + playerState.value.totalTime = playerMeta?.totalTime; playerState.value.duration = formatTimeDifference( playerState.value.totalTime, ); diff --git a/web/src/components/rum/performance/ApiDashboard.vue b/web/src/components/rum/performance/ApiDashboard.vue index d62cfa505d..b59b944633 100644 --- a/web/src/components/rum/performance/ApiDashboard.vue +++ b/web/src/components/rum/performance/ApiDashboard.vue @@ -131,14 +131,6 @@ export default defineComponent({ await nextTick(); await nextTick(); // emit window resize event to trigger the layout - if (apiDashboardChartsRef.value) { - apiDashboardChartsRef.value.layoutUpdate(); - - // Dashboards gets overlapped as we have used keep alive - // Its an internal bug of vue-grid-layout - // So adding settimeout of 1 sec to fix the issue - apiDashboardChartsRef.value.layoutUpdate(); - } window.dispatchEvent(new Event("resize")); }; diff --git a/web/src/components/rum/performance/ErrorsDashboard.vue b/web/src/components/rum/performance/ErrorsDashboard.vue index d708ce9bff..26e91ccd13 100644 --- a/web/src/components/rum/performance/ErrorsDashboard.vue +++ b/web/src/components/rum/performance/ErrorsDashboard.vue @@ -116,14 +116,6 @@ export default defineComponent({ await nextTick(); await nextTick(); // emit window resize event to trigger the layout - if (errorRenderDashboardChartsRef.value) { - errorRenderDashboardChartsRef.value.layoutUpdate(); - - // Dashboards gets overlapped as we have used keep alive - // Its an internal bug of vue-grid-layout - // So adding settimeout of 1 sec to fix the issue - errorRenderDashboardChartsRef.value.layoutUpdate(); - } window.dispatchEvent(new Event("resize")); }; diff --git a/web/src/components/rum/performance/WebVitalsDashboard.vue b/web/src/components/rum/performance/WebVitalsDashboard.vue index 1433f78fa3..18fee4bed9 100644 --- a/web/src/components/rum/performance/WebVitalsDashboard.vue +++ b/web/src/components/rum/performance/WebVitalsDashboard.vue @@ -152,15 +152,6 @@ export default defineComponent({ await nextTick(); await nextTick(); // emit window resize event to trigger the layout - if (webVitalsChartsRef.value) { - webVitalsChartsRef.value.layoutUpdate(); - - // Dashboards gets overlapped as we have used keep alive - // Its an internal bug of vue-grid-layout - // So adding settimeout of 1 sec to fix the issue - - webVitalsChartsRef.value.layoutUpdate(); - } window.dispatchEvent(new Event("resize")); }; diff --git a/web/src/main.ts b/web/src/main.ts index d1ac9bd0eb..8378beb995 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -112,7 +112,12 @@ const getConfig = async () => { const errorStack = event.error?.stack || ""; // List of error patterns to ignore - const ignoredErrorPatterns = [/ResizeObserver loop/i]; + const ignoredErrorPatterns = [ + /ResizeObserver loop/i, + /RS SDK/i, + /reo.dev/i, + /Cannot set properties of null \(setting 'innerHTML'\)/, + ]; // Check if error matches any ignored pattern const shouldIgnore = ignoredErrorPatterns.some( diff --git a/web/src/plugins/traces/TraceDetails.vue b/web/src/plugins/traces/TraceDetails.vue index 495fc447b4..de05400122 100644 --- a/web/src/plugins/traces/TraceDetails.vue +++ b/web/src/plugins/traces/TraceDetails.vue @@ -874,7 +874,7 @@ export default defineComponent({ const timeRange: any = ref({ start: 0, end: 0 }); const store = useStore(); const traceServiceMap: any = ref({}); - const { getStreams } = useStreams(); + const { getStreams, getStream } = useStreams(); const spanDimensions = { height: 30, barHeight: 8, @@ -1528,6 +1528,16 @@ export default defineComponent({ return []; } + // Verify _oo_trace_id field exists in _rumdata stream schema + const rumStream = await getStream("_rumdata", "logs", true); + const hasTraceIdField = rumStream?.schema?.some( + (field: any) => field.name === "_oo_trace_id", + ); + + if (!hasTraceIdField) { + return []; + } + const req = { query: { sql: `SELECT * FROM "_rumdata" WHERE _oo_trace_id = '${traceId}' ORDER BY ${store.state.zoConfig.timestamp_column} ASC`,