Skip to content

Commit

Permalink
chore(Queries): telemetry [YTFRONT-4612]
Browse files Browse the repository at this point in the history
  • Loading branch information
SimbiozizV committed Feb 4, 2025
1 parent a0c94d0 commit ced7ff9
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 46 deletions.
4 changes: 3 additions & 1 deletion packages/ui/src/server/components/layout-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ export async function getLayoutConfig(req: Request, params: Params): Promise<App
allowUserColumnPresets: isUserColumnPresetsEnabled(req),
odinPageEnabled: Boolean(odinBaseUrl),
allowTabletErrorsAPI: Boolean(tabletErrorsBaseUrl),
neuralNetwork: Boolean(ServerFactory.createNeuralNetworkApi(uiVersion.neuralNetwork)),
neuralNetwork: Boolean(
ServerFactory.createNeuralNetworkApi(uiSettings.neuralNetworkConfig),
),
},
pluginsOptions: {
yandexMetrika: {
Expand Down
36 changes: 26 additions & 10 deletions packages/ui/src/server/controllers/neuralNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,36 @@ import {Request, Response} from '@gravity-ui/expresskit';
import ServerFactory from '../ServerFactory';
import {ErrorWithCode, sendApiError} from '../utils';

export const getQuerySuggestions = async (req: Request, res: Response) => {
try {
const nNetwork = ServerFactory.createNeuralNetworkApi(
req.ctx.config.uiSettings.neuralNetworkConfig,
);
const getNeuralNetwork = (req: Request) => {
const nNetwork = ServerFactory.createNeuralNetworkApi(
req.ctx.config.uiSettings.neuralNetworkConfig,
);

if (!nNetwork) {
throw new ErrorWithCode(500, 'Neural network is not configured');
}
if (!nNetwork) {
throw new ErrorWithCode(500, 'Neural network is not configured');
}

const suggestions = await nNetwork.getQuerySuggestions(req);
res.status(200).json({items: suggestions});
return nNetwork;
};

export const getQuerySuggestions = async (req: Request, res: Response) => {
try {
const nNetwork = getNeuralNetwork(req);
const data = await nNetwork.getQuerySuggestions(req);
res.status(200).json(data);
} catch (e) {
req.ctx.logError('Query suggestions error', e);
sendApiError(res, e);
}
};

export const sendTelemetry = async (req: Request, res: Response) => {
try {
const nNetwork = getNeuralNetwork(req);
await nNetwork.sendTelemetry(req);
res.status(200).json({success: true});
} catch (e) {
req.ctx.logError('Telemetry error', e);
sendApiError(res, e);
}
};
3 changes: 2 additions & 1 deletion packages/ui/src/server/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
removeToken,
} from './controllers/vcs';
import {ytTabletErrorsApi} from './controllers/yt-tablet-errors-api';
import {getQuerySuggestions} from './controllers/neuralNetwork';
import {getQuerySuggestions, sendTelemetry} from './controllers/neuralNetwork';

const HOME_INDEX_TARGET: AppRouteDescription = {handler: homeIndexFactory(), ui: true};

Expand Down Expand Up @@ -64,6 +64,7 @@ const routes: AppRoutes = {
'GET /api/vcs/tokens-availability': {handler: getVcsTokensAvailability},

'GET /api/neural-network/query-suggestions': {handler: getQuerySuggestions},
'POST /api/neural-network/send-telemetry': {handler: sendTelemetry},

'POST /api/yt/:ytAuthCluster/change-password': {handler: handleChangePassword, ui: true},
'POST /api/remote-copy': {handler: handleRemoteCopy},
Expand Down
30 changes: 29 additions & 1 deletion packages/ui/src/shared/neuralNetwork.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
import {Request} from '@gravity-ui/expresskit';

export interface NeuralNetworkApi {
getQuerySuggestions(req: Request): Promise<string[]>;
getQuerySuggestions(req: Request): Promise<{items: string[]; requestId: string}>;
sendTelemetry(req: Request): Promise<void>;
}

export type TelemetryData = {
requestId: string;
timestamp: number;
};

export type AcceptedTelemetryData = TelemetryData & {
type: 'accepted';
acceptedText: string;
convertedText: string;
};

export type DiscardedTelemetryData = TelemetryData & {
type: 'discarded';
reason: 'OnCancel';
discardedText: string;
};

export type IgnoredTelemetryData = TelemetryData & {
type: 'ignored';
ignoredText: string;
};

export type NeuralNetworkTelemetryData =
| AcceptedTelemetryData
| DiscardedTelemetryData
| IgnoredTelemetryData;
Original file line number Diff line number Diff line change
@@ -1,32 +1,19 @@
import {CancellationToken, Position, editor, languages} from 'monaco-editor';
import {getRangeToInsertSuggestion} from './getRangeToInsertSuggestion';
import axios from 'axios';
import {QueryEngine} from '../../../pages/query-tracker/module/engines';
import debounce_ from 'lodash/debounce';
import {getWindowStore} from '../../../store/window-store';
import {getConfigData} from '../../../config/ui-settings';
import {getNeuralNetworkSuggestions} from '../neuralNetwork/api';
import {
setRequestId,
setSuggestions,
} from '../../../pages/query-tracker/module/neuralNetwork/neuralNetworkSlice';
import {selectNeuralNetworkContextId} from '../../../pages/query-tracker/module/neuralNetwork/selectors';
import {AcceptedTelemetryData} from '../../../../shared/neuralNetwork';

const getSuggestions = async (data: {
contextId: string;
query: string;
line: number;
column: number;
engine: QueryEngine;
}) => {
try {
const response = await axios.get<{items: string[]}>(
'/api/neural-network/query-suggestions',
{
params: data,
},
);
return response.data.items;
} catch (e) {
return [];
}
};

const debouncedGetSuggestions = debounce_(getSuggestions, 200);
const debouncedGetSuggestions = debounce_(getNeuralNetworkSuggestions, 200);
const store = getWindowStore();

export const createInlineSuggestions =
(engine: QueryEngine) =>
Expand All @@ -36,7 +23,7 @@ export const createInlineSuggestions =
_context: languages.InlineCompletionContext,
_token: CancellationToken,
): Promise<{items: languages.InlineCompletion[]}> => {
const state = getWindowStore().getState();
const contextId = selectNeuralNetworkContextId(store.getState());
const hasNeuralNetwork = getConfigData().neuralNetwork;

if (!hasNeuralNetwork) {
Expand All @@ -45,25 +32,43 @@ export const createInlineSuggestions =
};
}

const suggestions = await debouncedGetSuggestions({
contextId: state.queryTracker.query.draft.annotations?.contextId || '',
const response = await debouncedGetSuggestions({
contextId,
query: model.getValue(),
line: monacoCursorPosition.lineNumber,
column: monacoCursorPosition.column,
engine,
});

if (!suggestions) {
if (!response) {
return {
items: [],
};
}

store.dispatch(setSuggestions(response.items));
store.dispatch(setRequestId(response.requestId));

const range = getRangeToInsertSuggestion(model, monacoCursorPosition);
return {
items: suggestions.map((item) => ({
insertText: item,
range,
})),
items: response.items.map((item) => {
const data: AcceptedTelemetryData = {
requestId: response.requestId,
timestamp: Date.now(),
type: 'accepted',
acceptedText: item,
convertedText: item,
};

return {
insertText: item,
range,
command: {
id: 'neuralNetworkTelemetry',
title: 'string',
arguments: [data],
},
};
}),
};
};
29 changes: 29 additions & 0 deletions packages/ui/src/ui/libs/monaco-yql-languages/neuralNetwork/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {QueryEngine} from '../../../pages/query-tracker/module/engines';
import axios from 'axios';
import {NeuralNetworkTelemetryData} from '../../../../shared/neuralNetwork';

export type QuerySuggestionProps = {
contextId: string;
query: string;
line: number;
column: number;
engine: QueryEngine;
};
const BASE_PATH = '/api/neural-network';

export const getNeuralNetworkSuggestions = async (data: QuerySuggestionProps) => {
const response = await axios.get<{items: string[]; requestId: string}>(
`${BASE_PATH}/query-suggestions`,
{
params: data,
},
);
return response.data;
};

export const sendNeuralNetworkTelemetry = async (data: NeuralNetworkTelemetryData) => {
const response = await axios.post(`${BASE_PATH}/send-telemetry`, {
telemetry: data,
});
return response.data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {useCallback, useEffect} from 'react';
import * as monaco from 'monaco-editor';
import {
selectNeuralNetworkRequestId,
selectNeuralNetworkSuggestions,
} from '../../../pages/query-tracker/module/neuralNetwork/selectors';
import {sendNeuralNetworkTelemetry} from './api';
import {useSelector} from 'react-redux';
import {DiscardedTelemetryData} from '../../../../shared/neuralNetwork';

export const useMonacoNeuralNetwork = (editor?: monaco.editor.IStandaloneCodeEditor) => {
const requestId = useSelector(selectNeuralNetworkRequestId);
const suggestions = useSelector(selectNeuralNetworkSuggestions);

const handleCancelTelemetry = useCallback(
(e: KeyboardEvent) => {
if (e.key === 'Escape' && editor) {
const data: DiscardedTelemetryData = {
requestId,
timestamp: Date.now(),
type: 'discarded',
reason: 'OnCancel',
discardedText: suggestions[0],
};
editor.trigger(undefined, 'neuralNetworkTelemetry', data);
}
},
[editor, requestId, suggestions],
);

useEffect(() => {
monaco.editor.registerCommand('neuralNetworkTelemetry', async (_accessor, ...args) => {
if (args.length > 0) {
await sendNeuralNetworkTelemetry(args[0]);
}
});
}, []);

useEffect(() => {
document.addEventListener('keydown', handleCancelTelemetry, true);

return () => {
document.removeEventListener('keydown', handleCancelTelemetry, true);
};
}, [handleCancelTelemetry, editor]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {Button, Flex, Icon, Loader} from '@gravity-ui/uikit';
import playIcon from '../../../assets/img/svg/play.svg';
import {useDispatch, useSelector} from 'react-redux';
import {
getQuery,
getQueryEditorErrors,
getQueryEngine,
getQueryId,
getQueryText,
isQueryExecuted,
isQueryLoading,
Expand Down Expand Up @@ -41,6 +41,7 @@ import {WaitForFont} from '../../../containers/WaitForFont/WaitForFont';
import {getHashLineNumber} from './helpers/getHashLineNumber';
import {makeHighlightedLineDecorator} from './helpers/makeHighlightedLineDecorator';
import {getDecorationsWithoutHighlight} from './helpers/getDecorationsWithoutHighlight';
import {useMonacoNeuralNetwork} from '../../../libs/monaco-yql-languages/neuralNetwork/useMonacoNeuralNetwork';

const b = block('query-container');

Expand All @@ -65,7 +66,7 @@ const QueryEditorView = React.memo(function QueryEditorView({
const [changed, setChanged] = useState(false);
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
const {setEditor} = useMonaco();
const activeQuery = useSelector(getQuery);
const id = useSelector(getQueryId);
const text = useSelector(getQueryText);
const engine = useSelector(getQueryEngine);
const editorErrors = useSelector(getQueryEditorErrors);
Expand All @@ -76,6 +77,7 @@ const QueryEditorView = React.memo(function QueryEditorView({
undefined,
);
const model = editorRef.current?.getModel();
useMonacoNeuralNetwork(editorRef.current);

const runQueryCallback = useCallback(() => {
dispatch(runQuery(onStartQuery));
Expand All @@ -84,7 +86,7 @@ const QueryEditorView = React.memo(function QueryEditorView({
useEffect(() => {
editorRef.current?.focus();
editorRef.current?.setScrollTop(0);
}, [activeQuery?.id]);
}, [id]);

useEffect(() => {
if (editorRef.current) {
Expand Down

0 comments on commit ced7ff9

Please sign in to comment.