Fix: Issue related to SQL query viewer (#21627)

(cherry picked from commit 5fc2c90e5b8c927c8d1aef57f064b5e7ab1754a1)
This commit is contained in:
Shailesh Parmar 2025-06-06 21:33:06 +05:30 committed by OpenMetadata Release Bot
parent 2cd35486ec
commit 25ef5a6f4c
10 changed files with 78 additions and 6 deletions

View File

@ -543,6 +543,7 @@ const ProfilerSettingsModal: React.FC<ProfilerSettingsModalProps> = ({
className="custom-query-editor query-editor-h-200 custom-code-mirror-theme"
data-testid="profiler-setting-sql-editor"
mode={{ name: CSMode.SQL }}
refreshEditor={visible}
value={state?.sqlQuery ?? ''}
onChange={handleCodeMirrorChange}
/>

View File

@ -20,6 +20,7 @@ export type Mode = {
export interface SchemaEditorProps {
value?: string;
refreshEditor?: boolean;
className?: string;
mode?: Mode;
readOnly?: boolean;

View File

@ -95,4 +95,44 @@ describe('SchemaEditor component test', () => {
expect(mockOnChange).toHaveBeenCalled();
});
it('Should call refreshEditor', async () => {
jest.useFakeTimers();
const mockEditor = {
refresh: jest.fn(),
};
jest.spyOn(React, 'useRef').mockReturnValue({
current: mockEditor,
});
render(<SchemaEditor {...mockProps} refreshEditor />);
// Fast-forward timers to trigger the refresh
jest.advanceTimersByTime(50);
expect(mockEditor.refresh).toHaveBeenCalled();
jest.useRealTimers();
});
it('Should not call refresh if refreshEditor is false', async () => {
jest.useFakeTimers();
const mockEditor = {
refresh: jest.fn(),
};
jest.spyOn(React, 'useRef').mockReturnValue({
current: mockEditor,
});
render(<SchemaEditor {...mockProps} refreshEditor={false} />);
// Fast-forward timers to trigger the refresh
jest.advanceTimersByTime(50);
expect(mockEditor.refresh).not.toHaveBeenCalled();
jest.useRealTimers();
});
});

View File

@ -26,7 +26,7 @@ import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/python/python';
import 'codemirror/mode/sql/sql';
import { isUndefined } from 'lodash';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { Controlled as CodeMirror } from 'react-codemirror2';
import { useTranslation } from 'react-i18next';
import { ReactComponent as CopyIcon } from '../../../assets/svg/icon-copy.svg';
@ -49,6 +49,7 @@ const SchemaEditor = ({
showCopyButton = true,
onChange,
onFocus,
refreshEditor,
}: SchemaEditorProps) => {
const { t } = useTranslation();
const defaultOptions = {
@ -69,6 +70,8 @@ const SchemaEditor = ({
const [internalValue, setInternalValue] = useState<string>(
getSchemaEditorValue(value)
);
// Store the CodeMirror editor instance
const editorInstance = useRef<Editor | null>(null);
const { onCopyToClipBoard, hasCopied } = useClipboard(internalValue);
const handleEditorInputBeforeChange = (
@ -92,6 +95,18 @@ const SchemaEditor = ({
setInternalValue(getSchemaEditorValue(value));
}, [value]);
useEffect(() => {
if (refreshEditor) {
// CodeMirror can't measure its container if hidden (e.g., in an inactive tab with display: none).
// When the tab becomes visible, the browser may not have finished layout/reflow when this runs.
// Delaying refresh by 50ms ensures the editor is visible and DOM is ready for CodeMirror to re-render.
// This is a common workaround for editors inside tabbed interfaces.
setTimeout(() => {
editorInstance.current?.refresh();
}, 50);
}
}, [refreshEditor]);
return (
<div
className={classNames('relative', className)}
@ -114,6 +129,9 @@ const SchemaEditor = ({
<CodeMirror
className={editorClass}
editorDidMount={(editor) => {
editorInstance.current = editor;
}}
options={defaultOptions}
value={internalValue}
onBeforeChange={handleEditorInputBeforeChange}

View File

@ -309,6 +309,7 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
),
queryViewerTab: (
<QueryViewer
isActive={activeTab === EntityTabs.CONFIG}
sqlQuery={JSON.stringify(topicDetails.topicConfig)}
title={t('label.config')}
/>

View File

@ -24,9 +24,11 @@ import './query-viewer.style.less';
const QueryViewer = ({
title,
sqlQuery,
isActive,
}: {
title?: React.ReactNode;
sqlQuery: string;
isActive?: boolean;
}) => {
const { t } = useTranslation();
@ -69,6 +71,7 @@ const QueryViewer = ({
)}
mode={{ name: CSMode.SQL }}
options={{ readOnly: true }}
refreshEditor={isActive}
showCopyButton={false}
value={sqlQuery}
/>

View File

@ -53,7 +53,4 @@
.CodeMirror-linenumber {
color: @text-color;
}
.CodeMirror-sizer {
margin-left: 40px !important;
}
}

View File

@ -99,12 +99,13 @@ export const getDashboardDataModelDetailPageTabs = ({
children: (
<Card>
<SchemaEditor
editorClass="custom-code-mirror-theme full-screen-editor-height"
editorClass="custom-query-editor custom-code-mirror-theme full-screen-editor-height"
mode={{ name: CSMode.SQL }}
options={{
styleActiveLine: false,
readOnly: true,
}}
refreshEditor={activeTab === EntityTabs.SQL}
value={dataModelData?.sql}
/>
</Card>

View File

@ -151,6 +151,7 @@ export const getSearchIndexDetailsTabs = ({
key: EntityTabs.SEARCH_INDEX_SETTINGS,
children: (
<QueryViewer
isActive={activeTab === EntityTabs.SEARCH_INDEX_SETTINGS}
sqlQuery={JSON.stringify(searchIndexDetails?.searchIndexSettings)}
title={t('label.search-index-setting-plural')}
/>

View File

@ -966,6 +966,7 @@ export const getTableDetailPageBaseTabs = ({
key: EntityTabs.DBT,
children: (
<QueryViewer
isActive={activeTab === EntityTabs.DBT}
sqlQuery={
get(tableDetails, 'dataModel.sql', '') ??
get(tableDetails, 'dataModel.rawSql', '')
@ -1000,7 +1001,15 @@ export const getTableDetailPageBaseTabs = ({
),
isHidden: isUndefined(tableDetails?.schemaDefinition),
key: EntityTabs.VIEW_DEFINITION,
children: <QueryViewer sqlQuery={tableDetails?.schemaDefinition ?? ''} />,
children: (
<QueryViewer
isActive={[
EntityTabs.VIEW_DEFINITION,
EntityTabs.SCHEMA_DEFINITION,
].includes(activeTab)}
sqlQuery={tableDetails?.schemaDefinition ?? ''}
/>
),
},
{
label: (