refactor(sqllab): nonblocking switch query editor (#29108)

This commit is contained in:
JUST.in DO IT
2024-06-12 15:07:34 -07:00
committed by GitHub
parent a88979631e
commit 31afb62e95
14 changed files with 188 additions and 101 deletions

View File

@@ -58,6 +58,7 @@ const unsavedSqlLabState = {
},
editorTabLastUpdatedAt,
};
beforeAll(() => {
jest.useFakeTimers();
});
@@ -66,6 +67,16 @@ afterAll(() => {
jest.useRealTimers();
});
const updateActiveEditorTabState = `glob:*/tabstateview/*/activate`;
beforeEach(() => {
fetchMock.post(updateActiveEditorTabState, {});
});
afterEach(() => {
fetchMock.reset();
});
test('sync the unsaved editor tab state when there are new changes since the last update', async () => {
const updateEditorTabState = `glob:*/tabstateview/${defaultQueryEditor.id}`;
fetchMock.put(updateEditorTabState, 200);
@@ -108,6 +119,33 @@ test('sync the unsaved NEW editor state when there are new in local storage', as
fetchMock.restore();
});
test('sync the active editor id when there are updates in tab history', async () => {
expect(fetchMock.calls(updateActiveEditorTabState)).toHaveLength(0);
render(<EditorAutoSync />, {
useRedux: true,
initialState: {
...initialState,
sqlLab: {
...initialState.sqlLab,
lastUpdatedActiveTab: 'old-tab-id',
queryEditors: [
...initialState.sqlLab.queryEditors,
{
id: 'rnd-new-id12',
name: 'new tab name',
inLocalStorage: false,
},
],
tabHistory: ['old-tab-id', 'rnd-new-id12'],
},
},
});
await waitFor(() => jest.advanceTimersByTime(INTERVAL));
expect(fetchMock.calls(updateActiveEditorTabState)).toHaveLength(1);
await waitFor(() => jest.advanceTimersByTime(INTERVAL));
expect(fetchMock.calls(updateActiveEditorTabState)).toHaveLength(1);
});
test('skip syncing the unsaved editor tab state when the updates are already synced', async () => {
const updateEditorTabState = `glob:*/tabstateview/${defaultQueryEditor.id}`;
fetchMock.put(updateEditorTabState, 200);

View File

@@ -26,11 +26,15 @@ import {
QueryEditor,
UnsavedQueryEditor,
} from 'src/SqlLab/types';
import { useUpdateSqlEditorTabMutation } from 'src/hooks/apiResources/sqlEditorTabs';
import {
useUpdateCurrentSqlEditorTabMutation,
useUpdateSqlEditorTabMutation,
} from 'src/hooks/apiResources/sqlEditorTabs';
import { useDebounceValue } from 'src/hooks/useDebounceValue';
import {
syncQueryEditor,
setEditorTabLastUpdate,
setLastUpdatedActiveTab,
} from 'src/SqlLab/actions/sqlLab';
import useEffectEvent from 'src/hooks/useEffectEvent';
@@ -71,7 +75,15 @@ const EditorAutoSync: FC = () => {
);
const dispatch = useDispatch();
const lastSavedTimestampRef = useRef<number>(editorTabLastUpdatedAt);
const currentQueryEditorId = useSelector<SqlLabRootState, string>(
({ sqlLab }) => sqlLab.tabHistory.slice(-1)[0] || '',
);
const lastUpdatedActiveTab = useSelector<SqlLabRootState, string>(
({ sqlLab }) => sqlLab.lastUpdatedActiveTab,
);
const [updateSqlEditor, { error }] = useUpdateSqlEditorTabMutation();
const [updateCurrentSqlEditor] = useUpdateCurrentSqlEditorTabMutation();
const debouncedUnsavedQueryEditor = useDebounceValue(
unsavedQueryEditor,
@@ -94,21 +106,36 @@ const EditorAutoSync: FC = () => {
).find(({ inLocalStorage }) => Boolean(inLocalStorage)),
);
const syncCurrentQueryEditor = useEffectEvent(() => {
if (
currentQueryEditorId &&
currentQueryEditorId !== lastUpdatedActiveTab &&
!queryEditors.find(({ id }) => id === currentQueryEditorId)
?.inLocalStorage
) {
updateCurrentSqlEditor(currentQueryEditorId).then(() => {
dispatch(setLastUpdatedActiveTab(currentQueryEditorId));
});
}
});
useEffect(() => {
let timer: NodeJS.Timeout;
let saveTimer: NodeJS.Timeout;
function saveUnsavedQueryEditor() {
const firstUnsavedQueryEditor = getUnsavedNewQueryEditor();
if (firstUnsavedQueryEditor) {
dispatch(syncQueryEditor(firstUnsavedQueryEditor));
}
timer = setTimeout(saveUnsavedQueryEditor, INTERVAL);
saveTimer = setTimeout(saveUnsavedQueryEditor, INTERVAL);
}
timer = setTimeout(saveUnsavedQueryEditor, INTERVAL);
const syncTimer = setInterval(syncCurrentQueryEditor, INTERVAL);
saveTimer = setTimeout(saveUnsavedQueryEditor, INTERVAL);
return () => {
clearTimeout(timer);
clearTimeout(saveTimer);
clearInterval(syncTimer);
};
}, [getUnsavedNewQueryEditor, dispatch]);
}, [getUnsavedNewQueryEditor, syncCurrentQueryEditor, dispatch]);
useEffect(() => {
const unsaved = getUnsavedItems(debouncedUnsavedQueryEditor);

View File

@@ -411,7 +411,8 @@ describe('SqlEditor', () => {
await waitFor(() =>
expect(fetchMock.calls('glob:*/tabstateview/*').length).toBe(1),
);
expect(fetchMock.calls(switchTabApi).length).toBe(1);
// it will be called from EditorAutoSync
expect(fetchMock.calls(switchTabApi).length).toBe(0);
});
});
});

View File

@@ -79,7 +79,7 @@ import {
setActiveSouthPaneTab,
updateSavedQuery,
formatQuery,
switchQueryEditor,
fetchQueryEditor,
} from 'src/SqlLab/actions/sqlLab';
import {
STATE_TYPE_MAP,
@@ -506,7 +506,7 @@ const SqlEditor: FC<Props> = ({
const loadQueryEditor = useEffectEvent(() => {
if (shouldLoadQueryEditor) {
dispatch(switchQueryEditor(queryEditor, displayLimit));
dispatch(fetchQueryEditor(queryEditor, displayLimit));
}
});

View File

@@ -181,10 +181,7 @@ class TabbedSqlEditors extends PureComponent<TabbedSqlEditorsProps> {
if (!queryEditor) {
return;
}
this.props.actions.switchQueryEditor(
queryEditor,
this.props.displayLimit,
);
this.props.actions.setActiveQueryEditor(queryEditor);
}
}