From 1dcfc1f0b8eb6425f6498d879f5a802249097986 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Mon, 3 Feb 2025 15:07:35 +0300 Subject: [PATCH 01/17] feat: code assistant integration --- package-lock.json | 31 ++- package.json | 1 + .../Tenant/Query/QueryEditor/YqlEditor.tsx | 218 +++++++++++------- src/services/api/codeAssist.ts | 32 +++ src/services/api/index.ts | 3 + src/store/configureStore.ts | 1 + src/store/index.ts | 1 + src/store/reducers/codeAssist/codeAssist.ts | 24 ++ src/types/window.d.ts | 1 + 9 files changed, 224 insertions(+), 88 deletions(-) create mode 100644 src/services/api/codeAssist.ts create mode 100644 src/store/reducers/codeAssist/codeAssist.ts diff --git a/package-lock.json b/package-lock.json index 07afc62fc..c975b2e03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", + "@ydb-platform/monaco-ghost": "^0.1.7", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", @@ -5778,7 +5779,6 @@ "version": "18.3.5", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", - "dev": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -6415,6 +6415,35 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "node_modules/@ydb-platform/monaco-ghost": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.1.7.tgz", + "integrity": "sha512-oIBt1jTvdWtfmGyEanM5oqdkVmcSpoX+VOn9DDHYT8ZseU0alOMKBVYkh6iewhyRh0PmoxY+OyrnJH9e1FgZ+A==", + "license": "Apache-2.0", + "dependencies": { + "uuid": "^9.0.0" + }, + "peerDependencies": { + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "monaco-editor": "^0.52.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + } + }, + "node_modules/@ydb-platform/monaco-ghost/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", diff --git a/package.json b/package.json index 25e590424..3df5fe78e 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", + "@ydb-platform/monaco-ghost": "^0.1.7", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", diff --git a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx index 281fd3111..cf416e83a 100644 --- a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx @@ -1,8 +1,12 @@ +import React from 'react'; + import NiceModal from '@ebay/nice-modal-react'; +import {useMonacoGhost} from '@ydb-platform/monaco-ghost'; import throttle from 'lodash/throttle'; import type Monaco from 'monaco-editor'; import {MonacoEditor} from '../../../../components/MonacoEditor/MonacoEditor'; +import {codeAssistApi} from '../../../../store/reducers/codeAssist/codeAssist'; import { goToNextQuery, goToPreviousQuery, @@ -68,95 +72,135 @@ export function YqlEditor({ window.ydbEditor = undefined; }; - const editorDidMount = (editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco) => { - window.ydbEditor = editor; - const keybindings = getKeyBindings(monaco); - monaco.editor.registerCommand('insertSnippetToEditor', (_asessor, input: string) => { - //suggestController is not properly typed yet in monaco-editor package - const contribution = editor.getContribution('snippetController2'); - if (contribution) { - editor.focus(); - editor.setValue(''); - contribution.insert(input); - } - }); - initResizeHandler(editor); - initUserPrompt(editor, getLastQueryText); - editor.focus(); - editor.addAction({ - id: 'sendQuery', - label: i18n('action.send-query'), - keybindings: [keybindings.sendQuery], - // A precondition for this action. - precondition: undefined, - // A rule to evaluate on top of the precondition in order to dispatch the keybindings. - keybindingContext: undefined, - contextMenuGroupId: CONTEXT_MENU_GROUP_ID, - contextMenuOrder: 1, - // Method that will be executed when the action is triggered. - // @param editor The editor instance is passed in as a convenience - run: () => handleSendQuery(), - }); - - const canSendSelectedText = editor.createContextKey('canSendSelectedText', false); - editor.onDidChangeCursorSelection(({selection, secondarySelections}) => { - const notEmpty = - selection.selectionStartLineNumber !== selection.positionLineNumber || - selection.selectionStartColumn !== selection.positionColumn; - const hasMultipleSelections = secondarySelections.length > 0; - canSendSelectedText.set(notEmpty && !hasMultipleSelections); - }); - editor.addAction({ - id: 'sendSelectedQuery', - label: i18n('action.send-selected-query'), - keybindings: [keybindings.sendSelectedQuery], - precondition: 'canSendSelectedText', - contextMenuGroupId: CONTEXT_MENU_GROUP_ID, - contextMenuOrder: 1, - run: (e) => { - const selection = e.getSelection(); - const model = e.getModel(); - if (selection && model) { - const text = model.getValueInRange({ - startLineNumber: selection.getSelectionStart().lineNumber, - startColumn: selection.getSelectionStart().column, - endLineNumber: selection.getPosition().lineNumber, - endColumn: selection.getPosition().column, - }); - handleSendExecuteClick(text, true); + const [sendCodeAssistPrompt] = codeAssistApi.useLazyGetCodeAssistSuggestionsQuery(); + + const {registerMonacoGhost, dispose} = useMonacoGhost({ + api: { + getCodeAssistSuggestions: async (prompt) => { + try { + return await sendCodeAssistPrompt(prompt).unwrap(); + } catch { + return {items: []}; } }, - }); - - editor.addAction({ - id: 'previous-query', - label: i18n('action.previous-query'), - keybindings: [keybindings.selectPreviousQuery], - contextMenuGroupId: CONTEXT_MENU_GROUP_ID, - contextMenuOrder: 2, - run: () => { - dispatch(goToPreviousQuery()); - }, - }); - editor.addAction({ - id: 'next-query', - label: i18n('action.next-query'), - keybindings: [keybindings.selectNextQuery], - contextMenuGroupId: CONTEXT_MENU_GROUP_ID, - contextMenuOrder: 3, - run: () => { - dispatch(goToNextQuery()); - }, - }); - editor.addAction({ - id: 'save-query', - label: i18n('action.save-query'), - keybindings: [keybindings.saveQuery], - run: () => { - NiceModal.show(SAVE_QUERY_DIALOG); - }, - }); - }; + }, + config: { + language: YQL_LANGUAGE_ID, + }, + }); + + const editorDidMount = React.useCallback( + (editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco) => { + window.ydbEditor = editor; + const keybindings = getKeyBindings(monaco); + monaco.editor.registerCommand('insertSnippetToEditor', (_asessor, input: string) => { + //suggestController is not properly typed yet in monaco-editor package + const contribution = editor.getContribution('snippetController2'); + if (contribution) { + editor.focus(); + editor.setValue(''); + contribution.insert(input); + } + }); + + if (window.api.codeAssist) { + registerMonacoGhost(editor); + } + initResizeHandler(editor); + initUserPrompt(editor, getLastQueryText); + editor.focus(); + editor.addAction({ + id: 'sendQuery', + label: i18n('action.send-query'), + keybindings: [keybindings.sendQuery], + // A precondition for this action. + precondition: undefined, + // A rule to evaluate on top of the precondition in order to dispatch the keybindings. + keybindingContext: undefined, + contextMenuGroupId: CONTEXT_MENU_GROUP_ID, + contextMenuOrder: 1, + // Method that will be executed when the action is triggered. + // @param editor The editor instance is passed in as a convenience + run: () => handleSendQuery(), + }); + + const canSendSelectedText = editor.createContextKey( + 'canSendSelectedText', + false, + ); + editor.onDidChangeCursorSelection(({selection, secondarySelections}) => { + const notEmpty = + selection.selectionStartLineNumber !== selection.positionLineNumber || + selection.selectionStartColumn !== selection.positionColumn; + const hasMultipleSelections = secondarySelections.length > 0; + canSendSelectedText.set(notEmpty && !hasMultipleSelections); + }); + editor.addAction({ + id: 'sendSelectedQuery', + label: i18n('action.send-selected-query'), + keybindings: [keybindings.sendSelectedQuery], + precondition: 'canSendSelectedText', + contextMenuGroupId: CONTEXT_MENU_GROUP_ID, + contextMenuOrder: 1, + run: (e) => { + const selection = e.getSelection(); + const model = e.getModel(); + if (selection && model) { + const text = model.getValueInRange({ + startLineNumber: selection.getSelectionStart().lineNumber, + startColumn: selection.getSelectionStart().column, + endLineNumber: selection.getPosition().lineNumber, + endColumn: selection.getPosition().column, + }); + handleSendExecuteClick(text, true); + } + }, + }); + + editor.addAction({ + id: 'previous-query', + label: i18n('action.previous-query'), + keybindings: [keybindings.selectPreviousQuery], + contextMenuGroupId: CONTEXT_MENU_GROUP_ID, + contextMenuOrder: 2, + run: () => { + dispatch(goToPreviousQuery()); + }, + }); + editor.addAction({ + id: 'next-query', + label: i18n('action.next-query'), + keybindings: [keybindings.selectNextQuery], + contextMenuGroupId: CONTEXT_MENU_GROUP_ID, + contextMenuOrder: 3, + run: () => { + dispatch(goToNextQuery()); + }, + }); + editor.addAction({ + id: 'save-query', + label: i18n('action.save-query'), + keybindings: [keybindings.saveQuery], + run: () => { + NiceModal.show(SAVE_QUERY_DIALOG); + }, + }); + + return () => { + if (window.api.codeAssist) { + dispose(); + } + }; + }, + [ + dispatch, + dispose, + getLastQueryText, + handleSendExecuteClick, + handleSendQuery, + registerMonacoGhost, + ], + ); const onChange = (newValue: string) => { updateErrorsHighlighting(); diff --git a/src/services/api/codeAssist.ts b/src/services/api/codeAssist.ts new file mode 100644 index 000000000..30aaef644 --- /dev/null +++ b/src/services/api/codeAssist.ts @@ -0,0 +1,32 @@ +import type {PromptFile, Suggestions} from '@ydb-platform/monaco-ghost'; + +import {codeAssistBackend as CODE_ASSISTANT_BACKEND} from '../../store'; + +import {BaseYdbAPI} from './base'; +const ideInfo = { + Ide: 'ydb', + IdeVersion: '1', + PluginFamily: 'ydb', + PluginVersion: '0.2', +}; +export class CodeAssistAPI extends BaseYdbAPI { + getPath(path: string) { + return `${CODE_ASSISTANT_BACKEND ?? ''}${path}`; + } + + getCodeAssistSuggestions(data: PromptFile[]) { + return this.post( + this.getPath('/code-assist-suggestion'), + { + Files: data, + ContextCreateType: 1, + IdeInfo: ideInfo, + }, + null, + { + concurrentId: 'code-assist-suggestion', + collectRequest: false, + }, + ); + } +} diff --git a/src/services/api/index.ts b/src/services/api/index.ts index fbd795c2d..edbd82060 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -1,6 +1,7 @@ import type {AxiosRequestConfig} from 'axios'; import {AuthAPI} from './auth'; +import {CodeAssistAPI} from './codeAssist'; import {MetaAPI} from './meta'; import {OperationAPI} from './operation'; import {PDiskAPI} from './pdisk'; @@ -22,11 +23,13 @@ export class YdbEmbeddedAPI { vdisk: VDiskAPI; viewer: ViewerAPI; meta?: MetaAPI; + codeAssist?: CodeAssistAPI; constructor({config, webVersion}: {config: AxiosRequestConfig; webVersion?: boolean}) { this.auth = new AuthAPI({config}); if (webVersion) { this.meta = new MetaAPI({config}); + this.codeAssist = new CodeAssistAPI({config}); } this.operation = new OperationAPI({config}); this.pdisk = new PDiskAPI({config}); diff --git a/src/store/configureStore.ts b/src/store/configureStore.ts index afc32d463..ee45dfec3 100644 --- a/src/store/configureStore.ts +++ b/src/store/configureStore.ts @@ -46,6 +46,7 @@ function _configureStore< export const webVersion = window.web_version; export const customBackend = window.custom_backend; export const metaBackend = window.meta_backend; +export const codeAssistBackend = window.code_assist_backend; const isSingleClusterMode = `${metaBackend}` === 'undefined'; diff --git a/src/store/index.ts b/src/store/index.ts index 38f228f33..dfd8a8919 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -5,6 +5,7 @@ export { configureStore, customBackend, metaBackend, + codeAssistBackend, webVersion, } from './configureStore'; export {rootReducer} from './reducers'; diff --git a/src/store/reducers/codeAssist/codeAssist.ts b/src/store/reducers/codeAssist/codeAssist.ts new file mode 100644 index 000000000..606a50a66 --- /dev/null +++ b/src/store/reducers/codeAssist/codeAssist.ts @@ -0,0 +1,24 @@ +import type {PromptFile, Suggestions} from '@ydb-platform/monaco-ghost'; + +import {api} from '../api'; + +export const codeAssistApi = api.injectEndpoints({ + endpoints: (builder) => ({ + getCodeAssistSuggestions: builder.query({ + queryFn: async (prompt: PromptFile[]) => { + try { + if (window.api.codeAssist) { + const data = await window.api.codeAssist.getCodeAssistSuggestions(prompt); + return {data}; + } else { + throw new Error('Method is not implemented.'); + } + } catch (error) { + return {error}; + } + }, + providesTags: ['All'], + }), + }), + overrideExisting: 'throw', +}); diff --git a/src/types/window.d.ts b/src/types/window.d.ts index a31c06ad2..c6855bc41 100644 --- a/src/types/window.d.ts +++ b/src/types/window.d.ts @@ -37,6 +37,7 @@ interface Window { web_version?: boolean; custom_backend?: string; meta_backend?: string; + code_assist_backend?: string; userSettings?: import('../services/settings').SettingsObject; systemSettings?: import('../services/settings').SettingsObject; From 2531e6036a27a6c54a1eda4da0df78dd907ce958 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Mon, 3 Feb 2025 15:37:28 +0300 Subject: [PATCH 02/17] fix: build --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index c975b2e03..7fdd4c838 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", - "@ydb-platform/monaco-ghost": "^0.1.7", + "@ydb-platform/monaco-ghost": "^0.1.8", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", @@ -6416,9 +6416,9 @@ "dev": true }, "node_modules/@ydb-platform/monaco-ghost": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.1.7.tgz", - "integrity": "sha512-oIBt1jTvdWtfmGyEanM5oqdkVmcSpoX+VOn9DDHYT8ZseU0alOMKBVYkh6iewhyRh0PmoxY+OyrnJH9e1FgZ+A==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.1.8.tgz", + "integrity": "sha512-QIPEYK661WnYOCX+kMkhlR2Lh4FpN6g2a0oh90Mgj6eTqwznxmKzuycrbnZ8gV7ER2seEVFZFMRuSU3XfXJsNg==", "license": "Apache-2.0", "dependencies": { "uuid": "^9.0.0" diff --git a/package.json b/package.json index 3df5fe78e..50ba1cfa4 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", - "@ydb-platform/monaco-ghost": "^0.1.7", + "@ydb-platform/monaco-ghost": "^0.1.8", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", From 66c28e3670dfbef77886136f44a864b5acad1d7f Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Tue, 4 Feb 2025 14:29:43 +0300 Subject: [PATCH 03/17] fix: better code --- package-lock.json | 8 +- package.json | 2 +- .../Tenant/Query/QueryEditor/YqlEditor.tsx | 37 ++++++-- .../Tenant/Query/QueryEditor/helpers.ts | 80 ++++++++++++++++ src/services/api/codeAssist.ts | 53 +++++++++++ src/store/reducers/codeAssist/codeAssist.ts | 95 ++++++++++++++++++- 6 files changed, 259 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7fdd4c838..3a3ae25b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", - "@ydb-platform/monaco-ghost": "^0.1.8", + "@ydb-platform/monaco-ghost": "^0.1.10", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", @@ -6416,9 +6416,9 @@ "dev": true }, "node_modules/@ydb-platform/monaco-ghost": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.1.8.tgz", - "integrity": "sha512-QIPEYK661WnYOCX+kMkhlR2Lh4FpN6g2a0oh90Mgj6eTqwznxmKzuycrbnZ8gV7ER2seEVFZFMRuSU3XfXJsNg==", + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.1.10.tgz", + "integrity": "sha512-yAV6aVXLjCrlhFRpUwGc/6h6Fp1DAIPvl1YgHf1IMryh5qHO+QOUKmTOTD5Ct4XavR3KODYwpgt6n3ezo8Hkew==", "license": "Apache-2.0", "dependencies": { "uuid": "^9.0.0" diff --git a/package.json b/package.json index 50ba1cfa4..8ad371fd6 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", - "@ydb-platform/monaco-ghost": "^0.1.8", + "@ydb-platform/monaco-ghost": "^0.1.10", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", diff --git a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx index cf416e83a..c3842d2b1 100644 --- a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx @@ -6,7 +6,6 @@ import throttle from 'lodash/throttle'; import type Monaco from 'monaco-editor'; import {MonacoEditor} from '../../../../components/MonacoEditor/MonacoEditor'; -import {codeAssistApi} from '../../../../store/reducers/codeAssist/codeAssist'; import { goToNextQuery, goToPreviousQuery, @@ -26,8 +25,9 @@ import {useUpdateErrorsHighlighting} from '../../../../utils/monaco/highlightErr import {QUERY_ACTIONS} from '../../../../utils/query'; import {SAVE_QUERY_DIALOG} from '../SaveQuery/SaveQuery'; import i18n from '../i18n'; +import {useSavedQueries} from '../utils/useSavedQueries'; -import {useEditorOptions} from './helpers'; +import {useCodeAssist, useEditorOptions} from './helpers'; import {getKeyBindings} from './keybindings'; const CONTEXT_MENU_GROUP_ID = 'navigation'; @@ -48,6 +48,7 @@ export function YqlEditor({ const input = useTypedSelector(selectUserInput); const dispatch = useTypedDispatch(); const historyQueries = useTypedSelector(selectQueriesHistory); + const savedQueries = useSavedQueries(); const editorOptions = useEditorOptions(); const updateErrorsHighlighting = useUpdateErrorsHighlighting(); @@ -72,21 +73,24 @@ export function YqlEditor({ window.ydbEditor = undefined; }; - const [sendCodeAssistPrompt] = codeAssistApi.useLazyGetCodeAssistSuggestionsQuery(); + const { + getCodeAssistSuggestions, + onCompletionAccept, + onCompletionDecline, + onCompletionIgnore, + prepareUserQueriesCache, + } = useCodeAssist(); const {registerMonacoGhost, dispose} = useMonacoGhost({ api: { - getCodeAssistSuggestions: async (prompt) => { - try { - return await sendCodeAssistPrompt(prompt).unwrap(); - } catch { - return {items: []}; - } - }, + getCodeAssistSuggestions, }, config: { language: YQL_LANGUAGE_ID, }, + onCompletionAccept, + onCompletionDecline, + onCompletionIgnore, }); const editorDidMount = React.useCallback( @@ -105,6 +109,16 @@ export function YqlEditor({ if (window.api.codeAssist) { registerMonacoGhost(editor); + prepareUserQueriesCache([ + ...historyQueries.map((query, index) => ({ + name: `query${index}.yql`, + text: query.queryText, + })), + ...savedQueries.map((query) => ({ + name: query.name, + text: query.body, + })), + ]); } initResizeHandler(editor); initUserPrompt(editor, getLastQueryText); @@ -198,7 +212,10 @@ export function YqlEditor({ getLastQueryText, handleSendExecuteClick, handleSendQuery, + historyQueries, + prepareUserQueriesCache, registerMonacoGhost, + savedQueries, ], ); diff --git a/src/containers/Tenant/Query/QueryEditor/helpers.ts b/src/containers/Tenant/Query/QueryEditor/helpers.ts index 990ded87b..c289786e1 100644 --- a/src/containers/Tenant/Query/QueryEditor/helpers.ts +++ b/src/containers/Tenant/Query/QueryEditor/helpers.ts @@ -1,7 +1,10 @@ import React from 'react'; +import type {AcceptEvent, DeclineEvent, IgnoreEvent, PromptFile} from '@ydb-platform/monaco-ghost'; import type Monaco from 'monaco-editor'; +import type {TelemetryOpenTabs} from '../../../../services/api/codeAssist'; +import {codeAssistApi} from '../../../../store/reducers/codeAssist/codeAssist'; import {AUTOCOMPLETE_ON_ENTER, ENABLE_AUTOCOMPLETE} from '../../../../utils/constants'; import {useSetting} from '../../../../utils/hooks'; @@ -32,3 +35,80 @@ export function useEditorOptions() { return options; } + +export function useCodeAssist() { + const [sendCodeAssistPrompt] = codeAssistApi.useLazyGetCodeAssistSuggestionsQuery(); + const [acceptSuggestion] = codeAssistApi.useLazyAcceptSuggestionQuery(); + const [discardSuggestion] = codeAssistApi.useLazyDiscardSuggestionQuery(); + const [ignoreSuggestion] = codeAssistApi.useLazyIgnoreSuggestionQuery(); + const [sendUserQueriesData] = codeAssistApi.useLazySendUserQueriesDataQuery(); + + const getCodeAssistSuggestions = React.useCallback( + async (prompt: PromptFile[]) => { + try { + const {items} = await sendCodeAssistPrompt(prompt).unwrap(); + + return {items}; + } catch { + return {items: []}; + } + }, + [sendCodeAssistPrompt], + ); + + const onCompletionAccept = React.useCallback( + async (event: AcceptEvent) => { + try { + return await acceptSuggestion(event).unwrap(); + } catch { + return {items: []}; + } + }, + [acceptSuggestion], + ); + + const onCompletionDecline = React.useCallback( + async (event: DeclineEvent) => { + try { + return await discardSuggestion(event).unwrap(); + } catch { + return {items: []}; + } + }, + [discardSuggestion], + ); + + const onCompletionIgnore = React.useCallback( + async (event: IgnoreEvent) => { + try { + return await ignoreSuggestion(event).unwrap(); + } catch { + return {items: []}; + } + }, + [ignoreSuggestion], + ); + + const prepareUserQueriesCache = React.useCallback( + async (queries: {text: string; name?: string}[]) => { + const preparedData: TelemetryOpenTabs = queries.map((query, index) => ({ + FileName: query.name || `query${index}.yql`, + Text: query.text, + })); + try { + return await sendUserQueriesData(preparedData).unwrap(); + } catch { + return {items: []}; + } + }, + [sendUserQueriesData], + ); + + return { + getCodeAssistSuggestions, + onCompletionAccept, + onCompletionDecline, + onCompletionIgnore, + prepareUserQueriesCache, + }; +} diff --git a/src/services/api/codeAssist.ts b/src/services/api/codeAssist.ts index 30aaef644..c400a667a 100644 --- a/src/services/api/codeAssist.ts +++ b/src/services/api/codeAssist.ts @@ -9,6 +9,40 @@ const ideInfo = { PluginFamily: 'ydb', PluginVersion: '0.2', }; + +interface AcceptSuggestionEvent { + Accepted: { + RequestId: string; + Timestamp: number; + AcceptedText: string; + ConvertedText: string; + }; +} +interface DiscardSuggestionEvent { + Discarded: { + RequestId: string; + Timestamp: number; + DiscardReason: 'OnCancel'; + DiscardedText: string; + CacheHitCount: number; + }; +} +interface IgnoreSuggestionEvent { + Ignored: { + RequestId: string; + Timestamp: number; + IgnoredText: string; + }; +} + +type OpenTab = { + FileName: string; + Text: string; +}; + +export type TelemetryOpenTabs = OpenTab[]; + +export type TelemetryEvent = AcceptSuggestionEvent | DiscardSuggestionEvent | IgnoreSuggestionEvent; export class CodeAssistAPI extends BaseYdbAPI { getPath(path: string) { return `${CODE_ASSISTANT_BACKEND ?? ''}${path}`; @@ -29,4 +63,23 @@ export class CodeAssistAPI extends BaseYdbAPI { }, ); } + + sendCodeAssistTelemetry(data: TelemetryEvent) { + return this.post('/code-assist-telemetry', data, null, { + concurrentId: 'code-assist-telemetry', + collectRequest: true, + }); + } + + sendCodeAssistOpenTabs(data: TelemetryOpenTabs) { + return this.post( + '/code-assist-telemetry', + {OpenTabs: {Tabs: data, IdeInfo: ideInfo}}, + null, + { + concurrentId: 'code-assist-telemetry', + collectRequest: false, + }, + ); + } } diff --git a/src/store/reducers/codeAssist/codeAssist.ts b/src/store/reducers/codeAssist/codeAssist.ts index 606a50a66..13afe87a3 100644 --- a/src/store/reducers/codeAssist/codeAssist.ts +++ b/src/store/reducers/codeAssist/codeAssist.ts @@ -1,5 +1,12 @@ -import type {PromptFile, Suggestions} from '@ydb-platform/monaco-ghost'; +import type { + AcceptEvent, + DeclineEvent, + IgnoreEvent, + PromptFile, + Suggestions, +} from '@ydb-platform/monaco-ghost'; +import type {TelemetryOpenTabs} from '../../../services/api/codeAssist'; import {api} from '../api'; export const codeAssistApi = api.injectEndpoints({ @@ -19,6 +26,92 @@ export const codeAssistApi = api.injectEndpoints({ }, providesTags: ['All'], }), + + acceptSuggestion: builder.query({ + queryFn: async (event: AcceptEvent) => { + try { + if (window.api.codeAssist) { + const data = await window.api.codeAssist.sendCodeAssistTelemetry({ + Accepted: { + AcceptedText: event.acceptedText, + ConvertedText: event.acceptedText, + Timestamp: Date.now(), + RequestId: event.requestId, + }, + }); + return {data}; + } else { + throw new Error('Method is not implemented.'); + } + } catch (error) { + return {error}; + } + }, + providesTags: ['All'], + }), + + discardSuggestion: builder.query({ + queryFn: async (event: DeclineEvent) => { + try { + if (window.api.codeAssist) { + const data = await window.api.codeAssist.sendCodeAssistTelemetry({ + Discarded: { + RequestId: event.requestId, + Timestamp: Date.now(), + DiscardReason: 'OnCancel', + DiscardedText: event.suggestionText, + CacheHitCount: event.hitCount, + }, + }); + return {data}; + } else { + throw new Error('Method is not implemented.'); + } + } catch (error) { + return {error}; + } + }, + providesTags: ['All'], + }), + + ignoreSuggestion: builder.query({ + queryFn: async (event: IgnoreEvent) => { + try { + if (window.api.codeAssist) { + const data = await window.api.codeAssist.sendCodeAssistTelemetry({ + Ignored: { + RequestId: event.requestId, + Timestamp: Date.now(), + IgnoredText: event.suggestionText, + }, + }); + return {data}; + } else { + throw new Error('Method is not implemented.'); + } + } catch (error) { + return {error}; + } + }, + providesTags: ['All'], + }), + + sendUserQueriesData: builder.query({ + queryFn: async (userQueries: TelemetryOpenTabs) => { + try { + if (window.api.codeAssist) { + const data = + await window.api.codeAssist.sendCodeAssistOpenTabs(userQueries); + return {data}; + } else { + throw new Error('Method is not implemented.'); + } + } catch (error) { + return {error}; + } + }, + providesTags: ['All'], + }), }), overrideExisting: 'throw', }); From 1f48c51308e593d15cff13f690b34d3cb7f5e8c5 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Wed, 5 Feb 2025 16:44:44 +0300 Subject: [PATCH 04/17] fix: monaco-ghost --- package-lock.json | 8 +-- package.json | 2 +- .../Tenant/Query/QueryEditor/helpers.ts | 16 ++--- src/services/api/codeAssist.ts | 71 ++++++++++--------- src/store/reducers/codeAssist/codeAssist.ts | 7 +- src/types/api/codeAssist.ts | 57 +++++++++++++++ 6 files changed, 110 insertions(+), 51 deletions(-) create mode 100644 src/types/api/codeAssist.ts diff --git a/package-lock.json b/package-lock.json index 5d5e827ef..39f4d448a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", - "@ydb-platform/monaco-ghost": "^0.1.10", + "@ydb-platform/monaco-ghost": "^0.1.11", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", @@ -6416,9 +6416,9 @@ "dev": true }, "node_modules/@ydb-platform/monaco-ghost": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.1.10.tgz", - "integrity": "sha512-yAV6aVXLjCrlhFRpUwGc/6h6Fp1DAIPvl1YgHf1IMryh5qHO+QOUKmTOTD5Ct4XavR3KODYwpgt6n3ezo8Hkew==", + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.1.11.tgz", + "integrity": "sha512-wz9lUIti9iID3jUcI30fqa1qKB9keDK+49gXQ9I1/AdJdbnqRPjOhYNGBxsLonemDfvUZK7ihPEROekgBT8GOQ==", "license": "Apache-2.0", "dependencies": { "uuid": "^9.0.0" diff --git a/package.json b/package.json index 84f5f303a..a6b2a15b0 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", - "@ydb-platform/monaco-ghost": "^0.1.10", + "@ydb-platform/monaco-ghost": "^0.1.11", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", diff --git a/src/containers/Tenant/Query/QueryEditor/helpers.ts b/src/containers/Tenant/Query/QueryEditor/helpers.ts index c289786e1..df59707c5 100644 --- a/src/containers/Tenant/Query/QueryEditor/helpers.ts +++ b/src/containers/Tenant/Query/QueryEditor/helpers.ts @@ -3,8 +3,8 @@ import React from 'react'; import type {AcceptEvent, DeclineEvent, IgnoreEvent, PromptFile} from '@ydb-platform/monaco-ghost'; import type Monaco from 'monaco-editor'; -import type {TelemetryOpenTabs} from '../../../../services/api/codeAssist'; import {codeAssistApi} from '../../../../store/reducers/codeAssist/codeAssist'; +import type {TelemetryOpenTabs} from '../../../../types/api/codeAssist'; import {AUTOCOMPLETE_ON_ENTER, ENABLE_AUTOCOMPLETE} from '../../../../utils/constants'; import {useSetting} from '../../../../utils/hooks'; @@ -44,11 +44,9 @@ export function useCodeAssist() { const [sendUserQueriesData] = codeAssistApi.useLazySendUserQueriesDataQuery(); const getCodeAssistSuggestions = React.useCallback( - async (prompt: PromptFile[]) => { + async (promptFiles: PromptFile[]) => { try { - const {items} = await sendCodeAssistPrompt(prompt).unwrap(); - - return {items}; + return sendCodeAssistPrompt(promptFiles).unwrap(); } catch { return {items: []}; } @@ -59,7 +57,7 @@ export function useCodeAssist() { const onCompletionAccept = React.useCallback( async (event: AcceptEvent) => { try { - return await acceptSuggestion(event).unwrap(); + return acceptSuggestion(event).unwrap(); } catch { return {items: []}; } @@ -70,7 +68,7 @@ export function useCodeAssist() { const onCompletionDecline = React.useCallback( async (event: DeclineEvent) => { try { - return await discardSuggestion(event).unwrap(); + return discardSuggestion(event).unwrap(); } catch { return {items: []}; } @@ -81,7 +79,7 @@ export function useCodeAssist() { const onCompletionIgnore = React.useCallback( async (event: IgnoreEvent) => { try { - return await ignoreSuggestion(event).unwrap(); + return ignoreSuggestion(event).unwrap(); } catch { return {items: []}; } @@ -96,7 +94,7 @@ export function useCodeAssist() { Text: query.text, })); try { - return await sendUserQueriesData(preparedData).unwrap(); + return sendUserQueriesData(preparedData).unwrap(); } catch { return {items: []}; } diff --git a/src/services/api/codeAssist.ts b/src/services/api/codeAssist.ts index c400a667a..b1b0f5369 100644 --- a/src/services/api/codeAssist.ts +++ b/src/services/api/codeAssist.ts @@ -1,6 +1,12 @@ import type {PromptFile, Suggestions} from '@ydb-platform/monaco-ghost'; import {codeAssistBackend as CODE_ASSISTANT_BACKEND} from '../../store'; +import type { + CodeAssistSuggestionsFiles, + CodeAssistSuggestionsResponse, + TelemetryEvent, + TelemetryOpenTabs, +} from '../../types/api/codeAssist'; import {BaseYdbAPI} from './base'; const ideInfo = { @@ -10,49 +16,41 @@ const ideInfo = { PluginVersion: '0.2', }; -interface AcceptSuggestionEvent { - Accepted: { - RequestId: string; - Timestamp: number; - AcceptedText: string; - ConvertedText: string; - }; -} -interface DiscardSuggestionEvent { - Discarded: { - RequestId: string; - Timestamp: number; - DiscardReason: 'OnCancel'; - DiscardedText: string; - CacheHitCount: number; - }; -} -interface IgnoreSuggestionEvent { - Ignored: { - RequestId: string; - Timestamp: number; - IgnoredText: string; - }; +function prepareCodeAssistPrompt(promptFiles: PromptFile[]): CodeAssistSuggestionsFiles { + return promptFiles.map((file) => ({ + Fragments: file.fragments.map((fragment) => ({ + Text: fragment.text, + Start: { + Ln: fragment.start.lineNumber, + Col: fragment.start.column, + }, + End: { + Ln: fragment.end.lineNumber, + Col: fragment.end.column, + }, + })), + Cursor: { + Ln: file.cursorPostion.lineNumber, + Col: file.cursorPostion.column, + }, + Path: `${file.path}.yql`, + })); } -type OpenTab = { - FileName: string; - Text: string; -}; - -export type TelemetryOpenTabs = OpenTab[]; - -export type TelemetryEvent = AcceptSuggestionEvent | DiscardSuggestionEvent | IgnoreSuggestionEvent; +// In the future code assist api will be provided to ydb-ui component explicitly by consumer service. +// Current solution is temporary and aimed to satisfy internal puproses. export class CodeAssistAPI extends BaseYdbAPI { getPath(path: string) { return `${CODE_ASSISTANT_BACKEND ?? ''}${path}`; } - getCodeAssistSuggestions(data: PromptFile[]) { - return this.post( + async getCodeAssistSuggestions(data: PromptFile[]): Promise { + const request: CodeAssistSuggestionsFiles = prepareCodeAssistPrompt(data); + + const response = await this.post( this.getPath('/code-assist-suggestion'), { - Files: data, + Files: request, ContextCreateType: 1, IdeInfo: ideInfo, }, @@ -62,6 +60,11 @@ export class CodeAssistAPI extends BaseYdbAPI { collectRequest: false, }, ); + + return { + items: response.Suggests.map((suggestion) => suggestion.Text), + requestId: response.RequestId, + }; } sendCodeAssistTelemetry(data: TelemetryEvent) { diff --git a/src/store/reducers/codeAssist/codeAssist.ts b/src/store/reducers/codeAssist/codeAssist.ts index 13afe87a3..df142dccb 100644 --- a/src/store/reducers/codeAssist/codeAssist.ts +++ b/src/store/reducers/codeAssist/codeAssist.ts @@ -6,16 +6,17 @@ import type { Suggestions, } from '@ydb-platform/monaco-ghost'; -import type {TelemetryOpenTabs} from '../../../services/api/codeAssist'; +import type {TelemetryOpenTabs} from '../../../types/api/codeAssist'; import {api} from '../api'; export const codeAssistApi = api.injectEndpoints({ endpoints: (builder) => ({ getCodeAssistSuggestions: builder.query({ - queryFn: async (prompt: PromptFile[]) => { + queryFn: async (promptFiles: PromptFile[]) => { try { if (window.api.codeAssist) { - const data = await window.api.codeAssist.getCodeAssistSuggestions(prompt); + const data = + await window.api.codeAssist.getCodeAssistSuggestions(promptFiles); return {data}; } else { throw new Error('Method is not implemented.'); diff --git a/src/types/api/codeAssist.ts b/src/types/api/codeAssist.ts new file mode 100644 index 000000000..21e2048be --- /dev/null +++ b/src/types/api/codeAssist.ts @@ -0,0 +1,57 @@ +interface AcceptSuggestionEvent { + Accepted: { + RequestId: string; + Timestamp: number; + AcceptedText: string; + ConvertedText: string; + }; +} +interface DiscardSuggestionEvent { + Discarded: { + RequestId: string; + Timestamp: number; + DiscardReason: 'OnCancel'; + DiscardedText: string; + CacheHitCount: number; + }; +} +interface IgnoreSuggestionEvent { + Ignored: { + RequestId: string; + Timestamp: number; + IgnoredText: string; + }; +} + +type OpenTab = { + FileName: string; + Text: string; +}; + +interface Position { + Ln: number; + Col: number; +} + +interface Fragment { + Text: string; + Start: Position; + End: Position; +} + +interface File { + Fragments: Fragment[]; + Cursor: Position; + Path: string; +} + +export type CodeAssistSuggestionsFiles = File[]; + +export type CodeAssistSuggestionsResponse = { + RequestId: string; + Suggests: {Text: string}[]; +}; + +export type TelemetryOpenTabs = OpenTab[]; + +export type TelemetryEvent = AcceptSuggestionEvent | DiscardSuggestionEvent | IgnoreSuggestionEvent; From 9208f491a370750b90e6df1af96167462cca1b6b Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Wed, 5 Feb 2025 16:50:28 +0300 Subject: [PATCH 05/17] fix: copilot review --- src/store/reducers/codeAssist/codeAssist.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/store/reducers/codeAssist/codeAssist.ts b/src/store/reducers/codeAssist/codeAssist.ts index df142dccb..225d26dc3 100644 --- a/src/store/reducers/codeAssist/codeAssist.ts +++ b/src/store/reducers/codeAssist/codeAssist.ts @@ -11,7 +11,7 @@ import {api} from '../api'; export const codeAssistApi = api.injectEndpoints({ endpoints: (builder) => ({ - getCodeAssistSuggestions: builder.query({ + getCodeAssistSuggestions: builder.mutation({ queryFn: async (promptFiles: PromptFile[]) => { try { if (window.api.codeAssist) { @@ -25,10 +25,9 @@ export const codeAssistApi = api.injectEndpoints({ return {error}; } }, - providesTags: ['All'], }), - acceptSuggestion: builder.query({ + acceptSuggestion: builder.mutation({ queryFn: async (event: AcceptEvent) => { try { if (window.api.codeAssist) { @@ -48,10 +47,9 @@ export const codeAssistApi = api.injectEndpoints({ return {error}; } }, - providesTags: ['All'], }), - discardSuggestion: builder.query({ + discardSuggestion: builder.mutation({ queryFn: async (event: DeclineEvent) => { try { if (window.api.codeAssist) { @@ -72,10 +70,9 @@ export const codeAssistApi = api.injectEndpoints({ return {error}; } }, - providesTags: ['All'], }), - ignoreSuggestion: builder.query({ + ignoreSuggestion: builder.mutation({ queryFn: async (event: IgnoreEvent) => { try { if (window.api.codeAssist) { @@ -94,10 +91,9 @@ export const codeAssistApi = api.injectEndpoints({ return {error}; } }, - providesTags: ['All'], }), - sendUserQueriesData: builder.query({ + sendUserQueriesData: builder.mutation({ queryFn: async (userQueries: TelemetryOpenTabs) => { try { if (window.api.codeAssist) { @@ -111,7 +107,6 @@ export const codeAssistApi = api.injectEndpoints({ return {error}; } }, - providesTags: ['All'], }), }), overrideExisting: 'throw', From 34fbdb240663e82c9b353a8bf4d35db7d245461c Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Wed, 5 Feb 2025 16:59:05 +0300 Subject: [PATCH 06/17] fix: typo --- package-lock.json | 8 ++++---- package.json | 2 +- src/services/api/codeAssist.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 39f4d448a..7895ee1bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", - "@ydb-platform/monaco-ghost": "^0.1.11", + "@ydb-platform/monaco-ghost": "^0.1.12", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", @@ -6416,9 +6416,9 @@ "dev": true }, "node_modules/@ydb-platform/monaco-ghost": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.1.11.tgz", - "integrity": "sha512-wz9lUIti9iID3jUcI30fqa1qKB9keDK+49gXQ9I1/AdJdbnqRPjOhYNGBxsLonemDfvUZK7ihPEROekgBT8GOQ==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.1.12.tgz", + "integrity": "sha512-IwAENee1kswl1F/4rD++qWIbenY6QIIHt/XeCF78dWzSoJa34zj5vumoFUuiCAODwe2P5jGaR63bEfJA/h1QxA==", "license": "Apache-2.0", "dependencies": { "uuid": "^9.0.0" diff --git a/package.json b/package.json index a6b2a15b0..ccb9a52df 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", - "@ydb-platform/monaco-ghost": "^0.1.11", + "@ydb-platform/monaco-ghost": "^0.1.12", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", diff --git a/src/services/api/codeAssist.ts b/src/services/api/codeAssist.ts index b1b0f5369..3c9d82110 100644 --- a/src/services/api/codeAssist.ts +++ b/src/services/api/codeAssist.ts @@ -30,8 +30,8 @@ function prepareCodeAssistPrompt(promptFiles: PromptFile[]): CodeAssistSuggestio }, })), Cursor: { - Ln: file.cursorPostion.lineNumber, - Col: file.cursorPostion.column, + Ln: file.cursorPosition.lineNumber, + Col: file.cursorPosition.column, }, Path: `${file.path}.yql`, })); From 5a8b826676799a58b3ff42144e621f3ca4dab6ac Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Wed, 5 Feb 2025 17:02:23 +0300 Subject: [PATCH 07/17] fix: query and mutations --- src/containers/Tenant/Query/QueryEditor/helpers.ts | 8 ++++---- src/store/reducers/codeAssist/codeAssist.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/containers/Tenant/Query/QueryEditor/helpers.ts b/src/containers/Tenant/Query/QueryEditor/helpers.ts index df59707c5..9ded96373 100644 --- a/src/containers/Tenant/Query/QueryEditor/helpers.ts +++ b/src/containers/Tenant/Query/QueryEditor/helpers.ts @@ -38,10 +38,10 @@ export function useEditorOptions() { export function useCodeAssist() { const [sendCodeAssistPrompt] = codeAssistApi.useLazyGetCodeAssistSuggestionsQuery(); - const [acceptSuggestion] = codeAssistApi.useLazyAcceptSuggestionQuery(); - const [discardSuggestion] = codeAssistApi.useLazyDiscardSuggestionQuery(); - const [ignoreSuggestion] = codeAssistApi.useLazyIgnoreSuggestionQuery(); - const [sendUserQueriesData] = codeAssistApi.useLazySendUserQueriesDataQuery(); + const [acceptSuggestion] = codeAssistApi.useAcceptSuggestionMutation(); + const [discardSuggestion] = codeAssistApi.useDiscardSuggestionMutation(); + const [ignoreSuggestion] = codeAssistApi.useIgnoreSuggestionMutation(); + const [sendUserQueriesData] = codeAssistApi.useSendUserQueriesDataMutation(); const getCodeAssistSuggestions = React.useCallback( async (promptFiles: PromptFile[]) => { diff --git a/src/store/reducers/codeAssist/codeAssist.ts b/src/store/reducers/codeAssist/codeAssist.ts index 225d26dc3..7ad4af61b 100644 --- a/src/store/reducers/codeAssist/codeAssist.ts +++ b/src/store/reducers/codeAssist/codeAssist.ts @@ -11,7 +11,7 @@ import {api} from '../api'; export const codeAssistApi = api.injectEndpoints({ endpoints: (builder) => ({ - getCodeAssistSuggestions: builder.mutation({ + getCodeAssistSuggestions: builder.query({ queryFn: async (promptFiles: PromptFile[]) => { try { if (window.api.codeAssist) { From cc3450ac3956ef1b9f7bdf03d732678560d41759 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Wed, 5 Feb 2025 17:18:16 +0300 Subject: [PATCH 08/17] fix: remove useCallback for clear diff --- .../Tenant/Query/QueryEditor/YqlEditor.tsx | 226 ++++++++---------- 1 file changed, 105 insertions(+), 121 deletions(-) diff --git a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx index c3842d2b1..d67034e1a 100644 --- a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx @@ -93,131 +93,115 @@ export function YqlEditor({ onCompletionIgnore, }); - const editorDidMount = React.useCallback( - (editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco) => { - window.ydbEditor = editor; - const keybindings = getKeyBindings(monaco); - monaco.editor.registerCommand('insertSnippetToEditor', (_asessor, input: string) => { - //suggestController is not properly typed yet in monaco-editor package - const contribution = editor.getContribution('snippetController2'); - if (contribution) { - editor.focus(); - editor.setValue(''); - contribution.insert(input); - } - }); - - if (window.api.codeAssist) { - registerMonacoGhost(editor); - prepareUserQueriesCache([ - ...historyQueries.map((query, index) => ({ - name: `query${index}.yql`, - text: query.queryText, - })), - ...savedQueries.map((query) => ({ - name: query.name, - text: query.body, - })), - ]); + const editorDidMount = (editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco) => { + window.ydbEditor = editor; + const keybindings = getKeyBindings(monaco); + monaco.editor.registerCommand('insertSnippetToEditor', (_asessor, input: string) => { + //suggestController is not properly typed yet in monaco-editor package + const contribution = editor.getContribution('snippetController2'); + if (contribution) { + editor.focus(); + editor.setValue(''); + contribution.insert(input); } - initResizeHandler(editor); - initUserPrompt(editor, getLastQueryText); - editor.focus(); - editor.addAction({ - id: 'sendQuery', - label: i18n('action.send-query'), - keybindings: [keybindings.sendQuery], - // A precondition for this action. - precondition: undefined, - // A rule to evaluate on top of the precondition in order to dispatch the keybindings. - keybindingContext: undefined, - contextMenuGroupId: CONTEXT_MENU_GROUP_ID, - contextMenuOrder: 1, - // Method that will be executed when the action is triggered. - // @param editor The editor instance is passed in as a convenience - run: () => handleSendQuery(), - }); - - const canSendSelectedText = editor.createContextKey( - 'canSendSelectedText', - false, - ); - editor.onDidChangeCursorSelection(({selection, secondarySelections}) => { - const notEmpty = - selection.selectionStartLineNumber !== selection.positionLineNumber || - selection.selectionStartColumn !== selection.positionColumn; - const hasMultipleSelections = secondarySelections.length > 0; - canSendSelectedText.set(notEmpty && !hasMultipleSelections); - }); - editor.addAction({ - id: 'sendSelectedQuery', - label: i18n('action.send-selected-query'), - keybindings: [keybindings.sendSelectedQuery], - precondition: 'canSendSelectedText', - contextMenuGroupId: CONTEXT_MENU_GROUP_ID, - contextMenuOrder: 1, - run: (e) => { - const selection = e.getSelection(); - const model = e.getModel(); - if (selection && model) { - const text = model.getValueInRange({ - startLineNumber: selection.getSelectionStart().lineNumber, - startColumn: selection.getSelectionStart().column, - endLineNumber: selection.getPosition().lineNumber, - endColumn: selection.getPosition().column, - }); - handleSendExecuteClick(text, true); - } - }, - }); + }); - editor.addAction({ - id: 'previous-query', - label: i18n('action.previous-query'), - keybindings: [keybindings.selectPreviousQuery], - contextMenuGroupId: CONTEXT_MENU_GROUP_ID, - contextMenuOrder: 2, - run: () => { - dispatch(goToPreviousQuery()); - }, - }); - editor.addAction({ - id: 'next-query', - label: i18n('action.next-query'), - keybindings: [keybindings.selectNextQuery], - contextMenuGroupId: CONTEXT_MENU_GROUP_ID, - contextMenuOrder: 3, - run: () => { - dispatch(goToNextQuery()); - }, - }); - editor.addAction({ - id: 'save-query', - label: i18n('action.save-query'), - keybindings: [keybindings.saveQuery], - run: () => { - NiceModal.show(SAVE_QUERY_DIALOG); - }, - }); + if (window.api.codeAssist) { + registerMonacoGhost(editor); + prepareUserQueriesCache([ + ...historyQueries.map((query, index) => ({ + name: `query${index}.yql`, + text: query.queryText, + })), + ...savedQueries.map((query) => ({ + name: query.name, + text: query.body, + })), + ]); + } + initResizeHandler(editor); + initUserPrompt(editor, getLastQueryText); + editor.focus(); + editor.addAction({ + id: 'sendQuery', + label: i18n('action.send-query'), + keybindings: [keybindings.sendQuery], + // A precondition for this action. + precondition: undefined, + // A rule to evaluate on top of the precondition in order to dispatch the keybindings. + keybindingContext: undefined, + contextMenuGroupId: CONTEXT_MENU_GROUP_ID, + contextMenuOrder: 1, + // Method that will be executed when the action is triggered. + // @param editor The editor instance is passed in as a convenience + run: () => handleSendQuery(), + }); - return () => { - if (window.api.codeAssist) { - dispose(); + const canSendSelectedText = editor.createContextKey('canSendSelectedText', false); + editor.onDidChangeCursorSelection(({selection, secondarySelections}) => { + const notEmpty = + selection.selectionStartLineNumber !== selection.positionLineNumber || + selection.selectionStartColumn !== selection.positionColumn; + const hasMultipleSelections = secondarySelections.length > 0; + canSendSelectedText.set(notEmpty && !hasMultipleSelections); + }); + editor.addAction({ + id: 'sendSelectedQuery', + label: i18n('action.send-selected-query'), + keybindings: [keybindings.sendSelectedQuery], + precondition: 'canSendSelectedText', + contextMenuGroupId: CONTEXT_MENU_GROUP_ID, + contextMenuOrder: 1, + run: (e) => { + const selection = e.getSelection(); + const model = e.getModel(); + if (selection && model) { + const text = model.getValueInRange({ + startLineNumber: selection.getSelectionStart().lineNumber, + startColumn: selection.getSelectionStart().column, + endLineNumber: selection.getPosition().lineNumber, + endColumn: selection.getPosition().column, + }); + handleSendExecuteClick(text, true); } - }; - }, - [ - dispatch, - dispose, - getLastQueryText, - handleSendExecuteClick, - handleSendQuery, - historyQueries, - prepareUserQueriesCache, - registerMonacoGhost, - savedQueries, - ], - ); + }, + }); + + editor.addAction({ + id: 'previous-query', + label: i18n('action.previous-query'), + keybindings: [keybindings.selectPreviousQuery], + contextMenuGroupId: CONTEXT_MENU_GROUP_ID, + contextMenuOrder: 2, + run: () => { + dispatch(goToPreviousQuery()); + }, + }); + editor.addAction({ + id: 'next-query', + label: i18n('action.next-query'), + keybindings: [keybindings.selectNextQuery], + contextMenuGroupId: CONTEXT_MENU_GROUP_ID, + contextMenuOrder: 3, + run: () => { + dispatch(goToNextQuery()); + }, + }); + editor.addAction({ + id: 'save-query', + label: i18n('action.save-query'), + keybindings: [keybindings.saveQuery], + run: () => { + NiceModal.show(SAVE_QUERY_DIALOG); + }, + }); + + return () => { + if (window.api.codeAssist) { + dispose(); + } + }; + }; const onChange = (newValue: string) => { updateErrorsHighlighting(); From 61d3af061794e6371f5335ad6f649a2a4d9b2cda Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Wed, 5 Feb 2025 17:22:01 +0300 Subject: [PATCH 09/17] fix: unused dispose --- src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx index d67034e1a..232dbe9dc 100644 --- a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx @@ -81,7 +81,7 @@ export function YqlEditor({ prepareUserQueriesCache, } = useCodeAssist(); - const {registerMonacoGhost, dispose} = useMonacoGhost({ + const {registerMonacoGhost} = useMonacoGhost({ api: { getCodeAssistSuggestions, }, @@ -195,12 +195,6 @@ export function YqlEditor({ NiceModal.show(SAVE_QUERY_DIALOG); }, }); - - return () => { - if (window.api.codeAssist) { - dispose(); - } - }; }; const onChange = (newValue: string) => { From 8858edbb6ae0ec1395407f5ba4270f882df150cd Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Wed, 5 Feb 2025 17:42:43 +0300 Subject: [PATCH 10/17] fix: clearer code --- package-lock.json | 8 ++--- package.json | 2 +- .../Tenant/Query/QueryEditor/YqlEditor.tsx | 21 ++++-------- .../Tenant/Query/QueryEditor/helpers.ts | 32 +++---------------- src/store/reducers/codeAssist/codeAssist.ts | 4 +-- 5 files changed, 17 insertions(+), 50 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7895ee1bb..84aa781be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", - "@ydb-platform/monaco-ghost": "^0.1.12", + "@ydb-platform/monaco-ghost": "^0.1.13", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", @@ -6416,9 +6416,9 @@ "dev": true }, "node_modules/@ydb-platform/monaco-ghost": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.1.12.tgz", - "integrity": "sha512-IwAENee1kswl1F/4rD++qWIbenY6QIIHt/XeCF78dWzSoJa34zj5vumoFUuiCAODwe2P5jGaR63bEfJA/h1QxA==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.1.13.tgz", + "integrity": "sha512-8RLPBxovO2JSuI+2o6AENsUfiMi/NH3CGtZGQgAbtHyYzAL9LGKjncu8u1VWMMWT1gVdp0+JOYjCXUe3XAEdiQ==", "license": "Apache-2.0", "dependencies": { "uuid": "^9.0.0" diff --git a/package.json b/package.json index ccb9a52df..5aaa3e48e 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", - "@ydb-platform/monaco-ghost": "^0.1.12", + "@ydb-platform/monaco-ghost": "^0.1.13", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", diff --git a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx index 232dbe9dc..3d170ba51 100644 --- a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - import NiceModal from '@ebay/nice-modal-react'; import {useMonacoGhost} from '@ydb-platform/monaco-ghost'; import throttle from 'lodash/throttle'; @@ -73,24 +71,17 @@ export function YqlEditor({ window.ydbEditor = undefined; }; - const { - getCodeAssistSuggestions, - onCompletionAccept, - onCompletionDecline, - onCompletionIgnore, - prepareUserQueriesCache, - } = useCodeAssist(); - + const codeAssist = useCodeAssist(); const {registerMonacoGhost} = useMonacoGhost({ api: { - getCodeAssistSuggestions, + getCodeAssistSuggestions: codeAssist.getCodeAssistSuggestions, }, config: { language: YQL_LANGUAGE_ID, }, - onCompletionAccept, - onCompletionDecline, - onCompletionIgnore, + onCompletionAccept: codeAssist.onCompletionAccept, + onCompletionDecline: codeAssist.onCompletionDecline, + onCompletionIgnore: codeAssist.onCompletionIgnore, }); const editorDidMount = (editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco) => { @@ -108,7 +99,7 @@ export function YqlEditor({ if (window.api.codeAssist) { registerMonacoGhost(editor); - prepareUserQueriesCache([ + codeAssist.prepareUserQueriesCache([ ...historyQueries.map((query, index) => ({ name: `query${index}.yql`, text: query.queryText, diff --git a/src/containers/Tenant/Query/QueryEditor/helpers.ts b/src/containers/Tenant/Query/QueryEditor/helpers.ts index 9ded96373..c4ebb5d23 100644 --- a/src/containers/Tenant/Query/QueryEditor/helpers.ts +++ b/src/containers/Tenant/Query/QueryEditor/helpers.ts @@ -44,46 +44,22 @@ export function useCodeAssist() { const [sendUserQueriesData] = codeAssistApi.useSendUserQueriesDataMutation(); const getCodeAssistSuggestions = React.useCallback( - async (promptFiles: PromptFile[]) => { - try { - return sendCodeAssistPrompt(promptFiles).unwrap(); - } catch { - return {items: []}; - } - }, + async (promptFiles: PromptFile[]) => sendCodeAssistPrompt(promptFiles).unwrap(), [sendCodeAssistPrompt], ); const onCompletionAccept = React.useCallback( - async (event: AcceptEvent) => { - try { - return acceptSuggestion(event).unwrap(); - } catch { - return {items: []}; - } - }, + async (event: AcceptEvent) => acceptSuggestion(event).unwrap(), [acceptSuggestion], ); const onCompletionDecline = React.useCallback( - async (event: DeclineEvent) => { - try { - return discardSuggestion(event).unwrap(); - } catch { - return {items: []}; - } - }, + async (event: DeclineEvent) => discardSuggestion(event).unwrap(), [discardSuggestion], ); const onCompletionIgnore = React.useCallback( - async (event: IgnoreEvent) => { - try { - return ignoreSuggestion(event).unwrap(); - } catch { - return {items: []}; - } - }, + async (event: IgnoreEvent) => ignoreSuggestion(event).unwrap(), [ignoreSuggestion], ); diff --git a/src/store/reducers/codeAssist/codeAssist.ts b/src/store/reducers/codeAssist/codeAssist.ts index 7ad4af61b..10e2a4e64 100644 --- a/src/store/reducers/codeAssist/codeAssist.ts +++ b/src/store/reducers/codeAssist/codeAssist.ts @@ -21,8 +21,8 @@ export const codeAssistApi = api.injectEndpoints({ } else { throw new Error('Method is not implemented.'); } - } catch (error) { - return {error}; + } catch { + return {data: {items: []}}; } }, }), From 5cf295ef0c8f6da918f80a93c5225cdece8fd5ce Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Thu, 6 Feb 2025 12:22:23 +0300 Subject: [PATCH 11/17] fix: better code --- package-lock.json | 8 ++++---- package.json | 2 +- src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx | 8 +++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 84aa781be..fe5fa0968 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", - "@ydb-platform/monaco-ghost": "^0.1.13", + "@ydb-platform/monaco-ghost": "^0.3.0", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", @@ -6416,9 +6416,9 @@ "dev": true }, "node_modules/@ydb-platform/monaco-ghost": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.1.13.tgz", - "integrity": "sha512-8RLPBxovO2JSuI+2o6AENsUfiMi/NH3CGtZGQgAbtHyYzAL9LGKjncu8u1VWMMWT1gVdp0+JOYjCXUe3XAEdiQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.3.0.tgz", + "integrity": "sha512-cX7zmZDNyRIybYJA4QP4Pl/AtE4pCPEShILzyFemR/HqhfI8s94ODb9qvKJoNaP9CPh0Be2ZaSXzhlFRcwmHKA==", "license": "Apache-2.0", "dependencies": { "uuid": "^9.0.0" diff --git a/package.json b/package.json index 5aaa3e48e..f2f2107bb 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", - "@ydb-platform/monaco-ghost": "^0.1.13", + "@ydb-platform/monaco-ghost": "^0.3.0", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", diff --git a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx index 3d170ba51..ae6fb2223 100644 --- a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx @@ -76,12 +76,14 @@ export function YqlEditor({ api: { getCodeAssistSuggestions: codeAssist.getCodeAssistSuggestions, }, + eventHandlers: { + onCompletionAccept: codeAssist.onCompletionAccept, + onCompletionDecline: codeAssist.onCompletionDecline, + onCompletionIgnore: codeAssist.onCompletionIgnore, + }, config: { language: YQL_LANGUAGE_ID, }, - onCompletionAccept: codeAssist.onCompletionAccept, - onCompletionDecline: codeAssist.onCompletionDecline, - onCompletionIgnore: codeAssist.onCompletionIgnore, }); const editorDidMount = (editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco) => { From c07e55c7771e3e33ce544fb5bd9fb72b38acf5c1 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Thu, 6 Feb 2025 13:14:40 +0300 Subject: [PATCH 12/17] fix: tests --- tests/suites/tenant/TenantPage.ts | 22 ++++++++++++++++++++++ tests/suites/tenant/initialLoad.test.ts | 18 ++++++++---------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/tests/suites/tenant/TenantPage.ts b/tests/suites/tenant/TenantPage.ts index de43bdf8f..13d753ca1 100644 --- a/tests/suites/tenant/TenantPage.ts +++ b/tests/suites/tenant/TenantPage.ts @@ -13,12 +13,34 @@ export enum NavigationTabs { export class TenantPage extends PageModel { private navigation: Locator; private radioGroup: Locator; + private diagnosticsContainer: Locator; + private emptyState: Locator; + private emptyStateTitle: Locator; constructor(page: Page) { super(page, tenantPage); this.navigation = page.locator('.ydb-tenant-navigation'); this.radioGroup = this.navigation.locator('.g-radio-button'); + this.diagnosticsContainer = page.locator('.kv-tenant-diagnostics'); + this.emptyState = page.locator('.empty-state'); + this.emptyStateTitle = this.emptyState.locator('.empty-state__title'); + } + + async waitForDiagnosticsToLoad() { + await this.diagnosticsContainer.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT}); + } + + async isDiagnosticsVisible() { + return this.diagnosticsContainer.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT}); + } + + async isEmptyStateVisible() { + return this.emptyState.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT}); + } + + async getEmptyStateTitle(): Promise { + return this.emptyStateTitle.innerText(); } async selectNavigationTab(tabName: NavigationTabs) { diff --git a/tests/suites/tenant/initialLoad.test.ts b/tests/suites/tenant/initialLoad.test.ts index 1203a9860..157d231d8 100644 --- a/tests/suites/tenant/initialLoad.test.ts +++ b/tests/suites/tenant/initialLoad.test.ts @@ -14,10 +14,9 @@ test.describe('Tenant initial load', () => { test('Tenant diagnostics page is visible', async ({page}) => { const tenantPage = new TenantPage(page); await tenantPage.goto(pageQueryParams); + await tenantPage.waitForDiagnosticsToLoad(); - await page.waitForTimeout(2000); - - await expect(page.locator('.kv-tenant-diagnostics')).toBeVisible(); + await expect(await tenantPage.isDiagnosticsVisible()).toBeTruthy(); }); test('Tenant diagnostics page is visible when describe returns no data', async ({page}) => { @@ -27,8 +26,9 @@ test.describe('Tenant initial load', () => { const tenantPage = new TenantPage(page); await tenantPage.goto(pageQueryParams); + await tenantPage.waitForDiagnosticsToLoad(); - await expect(page.locator('.kv-tenant-diagnostics')).toBeVisible(); + await expect(await tenantPage.isDiagnosticsVisible()).toBeTruthy(); }); test('Tenant page shows error message when describe returns 401', async ({page}) => { @@ -39,10 +39,8 @@ test.describe('Tenant initial load', () => { const tenantPage = new TenantPage(page); await tenantPage.goto(pageQueryParams); - await page.waitForTimeout(2000); - - await expect(page.locator('.empty-state')).toBeVisible(); - await expect(page.locator('.empty-state__title')).toHaveText('Access denied'); + await expect(await tenantPage.isEmptyStateVisible()).toBeTruthy(); + await expect(await tenantPage.getEmptyStateTitle()).toBe('Access denied'); }); test('Tenant page shows error message when describe returns 403', async ({page}) => { @@ -53,7 +51,7 @@ test.describe('Tenant initial load', () => { const tenantPage = new TenantPage(page); await tenantPage.goto(pageQueryParams); - await expect(page.locator('.empty-state')).toBeVisible(); - await expect(page.locator('.empty-state__title')).toHaveText('Access denied'); + await expect(await tenantPage.isEmptyStateVisible()).toBeTruthy(); + await expect(await tenantPage.getEmptyStateTitle()).toBe('Access denied'); }); }); From c11d0d3a434877779be92869ab1ddece87009b3c Mon Sep 17 00:00:00 2001 From: astandrik Date: Thu, 6 Feb 2025 13:32:15 +0300 Subject: [PATCH 13/17] fix: tests --- tests/suites/tenant/TenantPage.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/suites/tenant/TenantPage.ts b/tests/suites/tenant/TenantPage.ts index 13d753ca1..9c6580ea4 100644 --- a/tests/suites/tenant/TenantPage.ts +++ b/tests/suites/tenant/TenantPage.ts @@ -29,14 +29,17 @@ export class TenantPage extends PageModel { async waitForDiagnosticsToLoad() { await this.diagnosticsContainer.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT}); + return true; } async isDiagnosticsVisible() { - return this.diagnosticsContainer.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT}); + await this.diagnosticsContainer.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT}); + return true; } async isEmptyStateVisible() { - return this.emptyState.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT}); + await this.emptyState.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT}); + return true; } async getEmptyStateTitle(): Promise { From def6cce39d60cb9a82eb0947e0da55bbf7e65f37 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Fri, 7 Feb 2025 13:13:21 +0300 Subject: [PATCH 14/17] fix: tests --- tests/suites/tenant/TenantPage.ts | 5 ----- tests/suites/tenant/initialLoad.test.ts | 2 -- 2 files changed, 7 deletions(-) diff --git a/tests/suites/tenant/TenantPage.ts b/tests/suites/tenant/TenantPage.ts index 9c6580ea4..1085c9ae2 100644 --- a/tests/suites/tenant/TenantPage.ts +++ b/tests/suites/tenant/TenantPage.ts @@ -27,11 +27,6 @@ export class TenantPage extends PageModel { this.emptyStateTitle = this.emptyState.locator('.empty-state__title'); } - async waitForDiagnosticsToLoad() { - await this.diagnosticsContainer.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT}); - return true; - } - async isDiagnosticsVisible() { await this.diagnosticsContainer.waitFor({state: 'visible', timeout: VISIBILITY_TIMEOUT}); return true; diff --git a/tests/suites/tenant/initialLoad.test.ts b/tests/suites/tenant/initialLoad.test.ts index 157d231d8..d9f741476 100644 --- a/tests/suites/tenant/initialLoad.test.ts +++ b/tests/suites/tenant/initialLoad.test.ts @@ -14,7 +14,6 @@ test.describe('Tenant initial load', () => { test('Tenant diagnostics page is visible', async ({page}) => { const tenantPage = new TenantPage(page); await tenantPage.goto(pageQueryParams); - await tenantPage.waitForDiagnosticsToLoad(); await expect(await tenantPage.isDiagnosticsVisible()).toBeTruthy(); }); @@ -26,7 +25,6 @@ test.describe('Tenant initial load', () => { const tenantPage = new TenantPage(page); await tenantPage.goto(pageQueryParams); - await tenantPage.waitForDiagnosticsToLoad(); await expect(await tenantPage.isDiagnosticsVisible()).toBeTruthy(); }); From 341eb3f66025d427bfd62ea5ba87dde4f98e7766 Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Fri, 7 Feb 2025 13:21:06 +0300 Subject: [PATCH 15/17] fix: add config --- .../Tenant/Query/QueryEditor/YqlEditor.tsx | 4 +--- .../Tenant/Query/QueryEditor/helpers.ts | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx index ae6fb2223..b8d11ae48 100644 --- a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx @@ -81,9 +81,7 @@ export function YqlEditor({ onCompletionDecline: codeAssist.onCompletionDecline, onCompletionIgnore: codeAssist.onCompletionIgnore, }, - config: { - language: YQL_LANGUAGE_ID, - }, + config: codeAssist.config, }); const editorDidMount = (editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco) => { diff --git a/src/containers/Tenant/Query/QueryEditor/helpers.ts b/src/containers/Tenant/Query/QueryEditor/helpers.ts index c4ebb5d23..30abfb3c3 100644 --- a/src/containers/Tenant/Query/QueryEditor/helpers.ts +++ b/src/containers/Tenant/Query/QueryEditor/helpers.ts @@ -7,6 +7,11 @@ import {codeAssistApi} from '../../../../store/reducers/codeAssist/codeAssist'; import type {TelemetryOpenTabs} from '../../../../types/api/codeAssist'; import {AUTOCOMPLETE_ON_ENTER, ENABLE_AUTOCOMPLETE} from '../../../../utils/constants'; import {useSetting} from '../../../../utils/hooks'; +import {YQL_LANGUAGE_ID} from '../../../../utils/monaco/constats'; + +const limitForTab = 10_000; +const limitBeforeCursor = 8_000; +const limitAfterCursor = 1_000; export type EditorOptions = Monaco.editor.IEditorOptions & Monaco.editor.IGlobalEditorOptions; @@ -67,7 +72,7 @@ export function useCodeAssist() { async (queries: {text: string; name?: string}[]) => { const preparedData: TelemetryOpenTabs = queries.map((query, index) => ({ FileName: query.name || `query${index}.yql`, - Text: query.text, + Text: query.text.slice(0, limitForTab), })); try { return sendUserQueriesData(preparedData).unwrap(); @@ -78,7 +83,16 @@ export function useCodeAssist() { [sendUserQueriesData], ); + const config = { + language: YQL_LANGUAGE_ID, + textLimits: { + beforeCursor: limitBeforeCursor, + afterCursor: limitAfterCursor, + }, + }; + return { + config, getCodeAssistSuggestions, onCompletionAccept, onCompletionDecline, From 058264b6561893b50621988eabe36caf39e1429c Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Fri, 7 Feb 2025 13:51:06 +0300 Subject: [PATCH 16/17] fix: move configs to api --- package-lock.json | 8 +- package.json | 2 +- .../Tenant/Query/QueryEditor/YqlEditor.tsx | 4 +- .../Tenant/Query/QueryEditor/helpers.ts | 16 +--- src/services/api/codeAssist.ts | 75 ++++++++++++++----- 5 files changed, 66 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index fe5fa0968..2e0a60b27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", - "@ydb-platform/monaco-ghost": "^0.3.0", + "@ydb-platform/monaco-ghost": "^0.4.0", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", @@ -6416,9 +6416,9 @@ "dev": true }, "node_modules/@ydb-platform/monaco-ghost": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.3.0.tgz", - "integrity": "sha512-cX7zmZDNyRIybYJA4QP4Pl/AtE4pCPEShILzyFemR/HqhfI8s94ODb9qvKJoNaP9CPh0Be2ZaSXzhlFRcwmHKA==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@ydb-platform/monaco-ghost/-/monaco-ghost-0.4.0.tgz", + "integrity": "sha512-yOgfQ7PUIPKmTqpOaKtGLc2gDQAzp3z2jASGLB7IAhC8smdDLoLwtNMz30gKkzU29TnICfp6Eem5fxj6nKqmrQ==", "license": "Apache-2.0", "dependencies": { "uuid": "^9.0.0" diff --git a/package.json b/package.json index f2f2107bb..76bf7de32 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@hookform/resolvers": "^3.10.0", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.20.6", - "@ydb-platform/monaco-ghost": "^0.3.0", + "@ydb-platform/monaco-ghost": "^0.4.0", "axios": "^1.7.9", "axios-retry": "^4.5.0", "colord": "^2.9.3", diff --git a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx index b8d11ae48..ae6fb2223 100644 --- a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx @@ -81,7 +81,9 @@ export function YqlEditor({ onCompletionDecline: codeAssist.onCompletionDecline, onCompletionIgnore: codeAssist.onCompletionIgnore, }, - config: codeAssist.config, + config: { + language: YQL_LANGUAGE_ID, + }, }); const editorDidMount = (editor: Monaco.editor.IStandaloneCodeEditor, monaco: typeof Monaco) => { diff --git a/src/containers/Tenant/Query/QueryEditor/helpers.ts b/src/containers/Tenant/Query/QueryEditor/helpers.ts index 30abfb3c3..c4ebb5d23 100644 --- a/src/containers/Tenant/Query/QueryEditor/helpers.ts +++ b/src/containers/Tenant/Query/QueryEditor/helpers.ts @@ -7,11 +7,6 @@ import {codeAssistApi} from '../../../../store/reducers/codeAssist/codeAssist'; import type {TelemetryOpenTabs} from '../../../../types/api/codeAssist'; import {AUTOCOMPLETE_ON_ENTER, ENABLE_AUTOCOMPLETE} from '../../../../utils/constants'; import {useSetting} from '../../../../utils/hooks'; -import {YQL_LANGUAGE_ID} from '../../../../utils/monaco/constats'; - -const limitForTab = 10_000; -const limitBeforeCursor = 8_000; -const limitAfterCursor = 1_000; export type EditorOptions = Monaco.editor.IEditorOptions & Monaco.editor.IGlobalEditorOptions; @@ -72,7 +67,7 @@ export function useCodeAssist() { async (queries: {text: string; name?: string}[]) => { const preparedData: TelemetryOpenTabs = queries.map((query, index) => ({ FileName: query.name || `query${index}.yql`, - Text: query.text.slice(0, limitForTab), + Text: query.text, })); try { return sendUserQueriesData(preparedData).unwrap(); @@ -83,16 +78,7 @@ export function useCodeAssist() { [sendUserQueriesData], ); - const config = { - language: YQL_LANGUAGE_ID, - textLimits: { - beforeCursor: limitBeforeCursor, - afterCursor: limitAfterCursor, - }, - }; - return { - config, getCodeAssistSuggestions, onCompletionAccept, onCompletionDecline, diff --git a/src/services/api/codeAssist.ts b/src/services/api/codeAssist.ts index 3c9d82110..057d39991 100644 --- a/src/services/api/codeAssist.ts +++ b/src/services/api/codeAssist.ts @@ -16,25 +16,64 @@ const ideInfo = { PluginVersion: '0.2', }; +const limitForTab = 10_000; +const limitBeforeCursor = 8_000; +const limitAfterCursor = 1_000; + +function prepareCodeAssistTabs(tabs: TelemetryOpenTabs): TelemetryOpenTabs { + return tabs.map((tab) => { + const text = tab.Text; + if (text.length > limitForTab) { + return { + ...tab, + Text: text.slice(0, limitForTab), + }; + } + + return tab; + }); +} + function prepareCodeAssistPrompt(promptFiles: PromptFile[]): CodeAssistSuggestionsFiles { - return promptFiles.map((file) => ({ - Fragments: file.fragments.map((fragment) => ({ - Text: fragment.text, - Start: { - Ln: fragment.start.lineNumber, - Col: fragment.start.column, - }, - End: { - Ln: fragment.end.lineNumber, - Col: fragment.end.column, + return promptFiles.map((file) => { + const cursorLine = file.cursorPosition.lineNumber; + const cursorCol = file.cursorPosition.column; + + return { + Fragments: file.fragments.map((fragment) => { + let text = fragment.text; + const isBeforeCursor = + fragment.end.lineNumber < cursorLine || + (fragment.end.lineNumber === cursorLine && fragment.end.column <= cursorCol); + const isAfterCursor = + fragment.start.lineNumber > cursorLine || + (fragment.start.lineNumber === cursorLine && fragment.start.column > cursorCol); + + if (isBeforeCursor) { + text = text.slice(-limitBeforeCursor); + } else if (isAfterCursor) { + text = text.slice(0, limitAfterCursor); + } + + return { + Text: text, + Start: { + Ln: fragment.start.lineNumber, + Col: fragment.start.column, + }, + End: { + Ln: fragment.end.lineNumber, + Col: fragment.end.column, + }, + }; + }), + Cursor: { + Ln: cursorLine, + Col: cursorCol, }, - })), - Cursor: { - Ln: file.cursorPosition.lineNumber, - Col: file.cursorPosition.column, - }, - Path: `${file.path}.yql`, - })); + Path: `${file.path}.yql`, + }; + }); } // In the future code assist api will be provided to ydb-ui component explicitly by consumer service. @@ -77,7 +116,7 @@ export class CodeAssistAPI extends BaseYdbAPI { sendCodeAssistOpenTabs(data: TelemetryOpenTabs) { return this.post( '/code-assist-telemetry', - {OpenTabs: {Tabs: data, IdeInfo: ideInfo}}, + {OpenTabs: {Tabs: prepareCodeAssistTabs(data), IdeInfo: ideInfo}}, null, { concurrentId: 'code-assist-telemetry', From 6b0d4533f1b75a982e846a3fc1db0b72c22a669c Mon Sep 17 00:00:00 2001 From: Anton Standrik Date: Fri, 7 Feb 2025 14:14:25 +0300 Subject: [PATCH 17/17] fix: move comment --- src/services/api/codeAssist.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/services/api/codeAssist.ts b/src/services/api/codeAssist.ts index 057d39991..61f5d5935 100644 --- a/src/services/api/codeAssist.ts +++ b/src/services/api/codeAssist.ts @@ -1,3 +1,8 @@ +// IMPORTANT! +// In the future code assist api will be provided to ydb-ui component explicitly by consumer service. +// Current solution is temporary and aimed to satisfy internal puproses. +// It means this whole file will be moved to customer service + import type {PromptFile, Suggestions} from '@ydb-platform/monaco-ghost'; import {codeAssistBackend as CODE_ASSISTANT_BACKEND} from '../../store'; @@ -76,8 +81,6 @@ function prepareCodeAssistPrompt(promptFiles: PromptFile[]): CodeAssistSuggestio }); } -// In the future code assist api will be provided to ydb-ui component explicitly by consumer service. -// Current solution is temporary and aimed to satisfy internal puproses. export class CodeAssistAPI extends BaseYdbAPI { getPath(path: string) { return `${CODE_ASSISTANT_BACKEND ?? ''}${path}`;