mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-15 12:25:58 +08:00
feat: support for attribute key suggestions and example queries in logs explorer query builder (#5608)
* feat: qb-suggestions base setup * chore: make the dropdown a little similar to the designs * chore: move out example queries from og and add to renderer * chore: added the handlers for example queries * chore: hide the example queries as soon as the user starts typing * feat: handle changes for cancel query * chore: remove stupid concept of option group * chore: show only first 3 items and add option to show all filters * chore: minor css changes and remove transitions * feat: integrate suggestions api and control re-renders * feat: added keyboard shortcuts for the dropdown * fix: design cleanups and touchups * fix: build issues and tests * chore: extra safety check for base64 and fix tests * fix: qs doesn't handle padding in base64 strings, added client logic * chore: some code comments * chore: some code comments * chore: increase the height of the bar when key is set * chore: address minor designs * chore: update the keyboard shortcut to cmd+/ * feat: correct the option render for logs for tooltip * chore: search bar to not loose focus on btn click * fix: update the spacing and icon for search bar * chore: address review comments
This commit is contained in:
parent
1308f0f15f
commit
65280cf4e1
63
frontend/src/api/queryBuilder/getAttributeSuggestions.ts
Normal file
63
frontend/src/api/queryBuilder/getAttributeSuggestions.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { ApiV3Instance } from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
|
import { baseAutoCompleteIdKeysOrder } from 'constants/queryBuilder';
|
||||||
|
import { encode } from 'js-base64';
|
||||||
|
import { createIdFromObjectFields } from 'lib/createIdFromObjectFields';
|
||||||
|
import createQueryParams from 'lib/createQueryParams';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import {
|
||||||
|
IGetAttributeSuggestionsPayload,
|
||||||
|
IGetAttributeSuggestionsSuccessResponse,
|
||||||
|
} from 'types/api/queryBuilder/getAttributeSuggestions';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
|
||||||
|
export const getAttributeSuggestions = async ({
|
||||||
|
searchText,
|
||||||
|
dataSource,
|
||||||
|
filters,
|
||||||
|
}: IGetAttributeSuggestionsPayload): Promise<
|
||||||
|
SuccessResponse<IGetAttributeSuggestionsSuccessResponse> | ErrorResponse
|
||||||
|
> => {
|
||||||
|
try {
|
||||||
|
let base64EncodedFiltersString;
|
||||||
|
try {
|
||||||
|
// the replace function is to remove the padding at the end of base64 encoded string which is auto added to make it a multiple of 4
|
||||||
|
// why ? because the current working of qs doesn't work well with padding
|
||||||
|
base64EncodedFiltersString = encode(JSON.stringify(filters)).replace(
|
||||||
|
/=+$/,
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// default base64 encoded string for empty filters object
|
||||||
|
base64EncodedFiltersString = 'eyJpdGVtcyI6W10sIm9wIjoiQU5EIn0';
|
||||||
|
}
|
||||||
|
const response: AxiosResponse<{
|
||||||
|
data: IGetAttributeSuggestionsSuccessResponse;
|
||||||
|
}> = await ApiV3Instance.get(
|
||||||
|
`/filter_suggestions?${createQueryParams({
|
||||||
|
searchText,
|
||||||
|
dataSource,
|
||||||
|
existingFilter: base64EncodedFiltersString,
|
||||||
|
})}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const payload: BaseAutocompleteData[] =
|
||||||
|
response.data.data.attributes?.map(({ id: _, ...item }) => ({
|
||||||
|
...item,
|
||||||
|
id: createIdFromObjectFields(item, baseAutoCompleteIdKeysOrder),
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.statusText,
|
||||||
|
payload: {
|
||||||
|
attributes: payload,
|
||||||
|
example_queries: response.data.data.example_queries,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return ErrorResponseHandler(e as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
@ -52,7 +52,7 @@ export const selectValueDivider = '__';
|
|||||||
|
|
||||||
export const baseAutoCompleteIdKeysOrder: (keyof Omit<
|
export const baseAutoCompleteIdKeysOrder: (keyof Omit<
|
||||||
BaseAutocompleteData,
|
BaseAutocompleteData,
|
||||||
'id' | 'isJSON'
|
'id' | 'isJSON' | 'isIndexed'
|
||||||
>)[] = ['key', 'dataType', 'type', 'isColumn'];
|
>)[] = ['key', 'dataType', 'type', 'isColumn'];
|
||||||
|
|
||||||
export const autocompleteType: Record<AutocompleteType, AutocompleteType> = {
|
export const autocompleteType: Record<AutocompleteType, AutocompleteType> = {
|
||||||
@ -71,6 +71,7 @@ export const alphabet: string[] = alpha.map((str) => String.fromCharCode(str));
|
|||||||
export enum QueryBuilderKeys {
|
export enum QueryBuilderKeys {
|
||||||
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
|
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
|
||||||
GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS',
|
GET_AGGREGATE_KEYS = 'GET_AGGREGATE_KEYS',
|
||||||
|
GET_ATTRIBUTE_SUGGESTIONS = 'GET_ATTRIBUTE_SUGGESTIONS',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mapOfOperators = {
|
export const mapOfOperators = {
|
||||||
|
@ -4,6 +4,7 @@ const userOS = getUserOperatingSystem();
|
|||||||
export const LogsExplorerShortcuts = {
|
export const LogsExplorerShortcuts = {
|
||||||
StageAndRunQuery: 'enter+meta',
|
StageAndRunQuery: 'enter+meta',
|
||||||
FocusTheSearchBar: 's',
|
FocusTheSearchBar: 's',
|
||||||
|
ShowAllFilters: '/+meta',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LogsExplorerShortcutsName = {
|
export const LogsExplorerShortcutsName = {
|
||||||
@ -11,9 +12,11 @@ export const LogsExplorerShortcutsName = {
|
|||||||
userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'
|
userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'
|
||||||
}+enter`,
|
}+enter`,
|
||||||
FocusTheSearchBar: 's',
|
FocusTheSearchBar: 's',
|
||||||
|
ShowAllFilters: `${userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'}+/`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LogsExplorerShortcutsDescription = {
|
export const LogsExplorerShortcutsDescription = {
|
||||||
StageAndRunQuery: 'Stage and Run the current query',
|
StageAndRunQuery: 'Stage and Run the current query',
|
||||||
FocusTheSearchBar: 'Shift the focus to the last query filter bar',
|
FocusTheSearchBar: 'Shift the focus to the last query filter bar',
|
||||||
|
ShowAllFilters: 'Toggle all filters in the filters dropdown',
|
||||||
};
|
};
|
||||||
|
@ -48,7 +48,15 @@ import {
|
|||||||
} from 'lodash-es';
|
} from 'lodash-es';
|
||||||
import { Sliders } from 'lucide-react';
|
import { Sliders } from 'lucide-react';
|
||||||
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
|
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
|
||||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import {
|
||||||
|
memo,
|
||||||
|
MutableRefObject,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@ -72,9 +80,15 @@ import { v4 } from 'uuid';
|
|||||||
function LogsExplorerViews({
|
function LogsExplorerViews({
|
||||||
selectedView,
|
selectedView,
|
||||||
showFrequencyChart,
|
showFrequencyChart,
|
||||||
|
setIsLoadingQueries,
|
||||||
|
listQueryKeyRef,
|
||||||
|
chartQueryKeyRef,
|
||||||
}: {
|
}: {
|
||||||
selectedView: SELECTED_VIEWS;
|
selectedView: SELECTED_VIEWS;
|
||||||
showFrequencyChart: boolean;
|
showFrequencyChart: boolean;
|
||||||
|
setIsLoadingQueries: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
listQueryKeyRef: MutableRefObject<any>;
|
||||||
|
chartQueryKeyRef: MutableRefObject<any>;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@ -214,6 +228,9 @@ function LogsExplorerViews({
|
|||||||
{
|
{
|
||||||
enabled: !!listChartQuery && panelType === PANEL_TYPES.LIST,
|
enabled: !!listChartQuery && panelType === PANEL_TYPES.LIST,
|
||||||
},
|
},
|
||||||
|
{},
|
||||||
|
undefined,
|
||||||
|
chartQueryKeyRef,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data, isLoading, isFetching, isError } = useGetExplorerQueryRange(
|
const { data, isLoading, isFetching, isError } = useGetExplorerQueryRange(
|
||||||
@ -232,6 +249,8 @@ function LogsExplorerViews({
|
|||||||
end: timeRange.end,
|
end: timeRange.end,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
undefined,
|
||||||
|
listQueryKeyRef,
|
||||||
);
|
);
|
||||||
|
|
||||||
const getRequestData = useCallback(
|
const getRequestData = useCallback(
|
||||||
@ -569,6 +588,25 @@ function LogsExplorerViews({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
isLoading ||
|
||||||
|
isFetching ||
|
||||||
|
isLoadingListChartData ||
|
||||||
|
isFetchingListChartData
|
||||||
|
) {
|
||||||
|
setIsLoadingQueries(true);
|
||||||
|
} else {
|
||||||
|
setIsLoadingQueries(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
isLoading,
|
||||||
|
isFetching,
|
||||||
|
isFetchingListChartData,
|
||||||
|
isLoadingListChartData,
|
||||||
|
setIsLoadingQueries,
|
||||||
|
]);
|
||||||
|
|
||||||
const flattenLogData = useMemo(
|
const flattenLogData = useMemo(
|
||||||
() =>
|
() =>
|
||||||
logs.map((log) => {
|
logs.map((log) => {
|
||||||
|
@ -79,6 +79,9 @@ const renderer = (): RenderResult =>
|
|||||||
<LogsExplorerViews
|
<LogsExplorerViews
|
||||||
selectedView={SELECTED_VIEWS.SEARCH}
|
selectedView={SELECTED_VIEWS.SEARCH}
|
||||||
showFrequencyChart
|
showFrequencyChart
|
||||||
|
setIsLoadingQueries={(): void => {}}
|
||||||
|
listQueryKeyRef={{ current: {} }}
|
||||||
|
chartQueryKeyRef={{ current: {} }}
|
||||||
/>
|
/>
|
||||||
</VirtuosoMockContext.Provider>
|
</VirtuosoMockContext.Provider>
|
||||||
</QueryBuilderProvider>
|
</QueryBuilderProvider>
|
||||||
|
@ -18,6 +18,7 @@ export const defaultTraceSelectedColumns = [
|
|||||||
isColumn: true,
|
isColumn: true,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
id: 'serviceName--string--tag--true',
|
id: 'serviceName--string--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
@ -26,6 +27,7 @@ export const defaultTraceSelectedColumns = [
|
|||||||
isColumn: true,
|
isColumn: true,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
id: 'name--string--tag--true',
|
id: 'name--string--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'durationNano',
|
key: 'durationNano',
|
||||||
@ -34,6 +36,7 @@ export const defaultTraceSelectedColumns = [
|
|||||||
isColumn: true,
|
isColumn: true,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
id: 'durationNano--float64--tag--true',
|
id: 'durationNano--float64--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'httpMethod',
|
key: 'httpMethod',
|
||||||
@ -42,6 +45,7 @@ export const defaultTraceSelectedColumns = [
|
|||||||
isColumn: true,
|
isColumn: true,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
id: 'httpMethod--string--tag--true',
|
id: 'httpMethod--string--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'responseStatusCode',
|
key: 'responseStatusCode',
|
||||||
@ -50,5 +54,6 @@ export const defaultTraceSelectedColumns = [
|
|||||||
isColumn: true,
|
isColumn: true,
|
||||||
isJSON: false,
|
isJSON: false,
|
||||||
id: 'responseStatusCode--string--tag--true',
|
id: 'responseStatusCode--string--tag--true',
|
||||||
|
isIndexed: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -3,18 +3,27 @@ import './ToolbarActions.styles.scss';
|
|||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import { LogsExplorerShortcuts } from 'constants/shortcuts/logsExplorerShortcuts';
|
import { LogsExplorerShortcuts } from 'constants/shortcuts/logsExplorerShortcuts';
|
||||||
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||||
import { Play } from 'lucide-react';
|
import { Play, X } from 'lucide-react';
|
||||||
import { useEffect } from 'react';
|
import { MutableRefObject, useEffect } from 'react';
|
||||||
|
import { useQueryClient } from 'react-query';
|
||||||
|
|
||||||
interface RightToolbarActionsProps {
|
interface RightToolbarActionsProps {
|
||||||
onStageRunQuery: () => void;
|
onStageRunQuery: () => void;
|
||||||
|
isLoadingQueries?: boolean;
|
||||||
|
listQueryKeyRef?: MutableRefObject<any>;
|
||||||
|
chartQueryKeyRef?: MutableRefObject<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RightToolbarActions({
|
export default function RightToolbarActions({
|
||||||
onStageRunQuery,
|
onStageRunQuery,
|
||||||
|
isLoadingQueries,
|
||||||
|
listQueryKeyRef,
|
||||||
|
chartQueryKeyRef,
|
||||||
}: RightToolbarActionsProps): JSX.Element {
|
}: RightToolbarActionsProps): JSX.Element {
|
||||||
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
registerShortcut(LogsExplorerShortcuts.StageAndRunQuery, onStageRunQuery);
|
registerShortcut(LogsExplorerShortcuts.StageAndRunQuery, onStageRunQuery);
|
||||||
|
|
||||||
@ -25,14 +34,41 @@ export default function RightToolbarActions({
|
|||||||
}, [onStageRunQuery]);
|
}, [onStageRunQuery]);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
{isLoadingQueries ? (
|
||||||
|
<div className="loading-container">
|
||||||
|
<Button className="loading-btn" loading={isLoadingQueries} />
|
||||||
|
<Button
|
||||||
|
icon={<X size={14} />}
|
||||||
|
className="cancel-run"
|
||||||
|
onClick={(): void => {
|
||||||
|
if (listQueryKeyRef?.current) {
|
||||||
|
queryClient.cancelQueries(listQueryKeyRef.current);
|
||||||
|
}
|
||||||
|
if (chartQueryKeyRef?.current) {
|
||||||
|
queryClient.cancelQueries(chartQueryKeyRef.current);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel Run
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
className="right-toolbar"
|
className="right-toolbar"
|
||||||
|
disabled={isLoadingQueries}
|
||||||
onClick={onStageRunQuery}
|
onClick={onStageRunQuery}
|
||||||
icon={<Play size={14} />}
|
icon={<Play size={14} />}
|
||||||
>
|
>
|
||||||
Stage & Run Query
|
Stage & Run Query
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RightToolbarActions.defaultProps = {
|
||||||
|
isLoadingQueries: false,
|
||||||
|
listQueryKeyRef: null,
|
||||||
|
chartQueryKeyRef: null,
|
||||||
|
};
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
.left-toolbar-query-actions {
|
.left-toolbar-query-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
border: 1px solid var(--bg-slate-400, #1d212d);
|
border: 1px solid var(--bg-slate-400);
|
||||||
background: var(--bg-ink-300, #16181d);
|
background: var(--bg-ink-300);
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
.prom-ql-icon {
|
.prom-ql-icon {
|
||||||
@ -24,7 +24,7 @@
|
|||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
|
||||||
&.active-tab {
|
&.active-tab {
|
||||||
background-color: #1d212d;
|
background-color: var(--bg-slate-400);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
@ -33,7 +33,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.action-btn + .action-btn {
|
.action-btn + .action-btn {
|
||||||
border-left: 1px solid var(--bg-slate-400, #1d212d);
|
border-left: 1px solid var(--bg-slate-400);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,6 +51,50 @@
|
|||||||
background-color: var(--bg-robin-600);
|
background-color: var(--bg-robin-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.right-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.loading-btn {
|
||||||
|
display: flex;
|
||||||
|
width: 32px;
|
||||||
|
height: 33px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--bg-slate-300);
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-run {
|
||||||
|
display: flex;
|
||||||
|
height: 33px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex: 1 0 0;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--bg-cherry-500);
|
||||||
|
border-color: none;
|
||||||
|
}
|
||||||
|
.cancel-run:hover {
|
||||||
|
background-color: #ff7875 !important;
|
||||||
|
color: var(--bg-vanilla-100) !important;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.lightMode {
|
.lightMode {
|
||||||
.left-toolbar {
|
.left-toolbar {
|
||||||
.left-toolbar-query-actions {
|
.left-toolbar-query-actions {
|
||||||
@ -68,4 +112,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.loading-container {
|
||||||
|
.loading-btn {
|
||||||
|
background: var(--bg-vanilla-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-run {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-run:hover {
|
||||||
|
background-color: #ff7875;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
|
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
|
||||||
|
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||||
|
|
||||||
import LeftToolbarActions from '../LeftToolbarActions';
|
import LeftToolbarActions from '../LeftToolbarActions';
|
||||||
import RightToolbarActions from '../RightToolbarActions';
|
import RightToolbarActions from '../RightToolbarActions';
|
||||||
@ -94,7 +95,9 @@ describe('ToolbarActions', () => {
|
|||||||
it('RightToolbarActions - render correctly with props', async () => {
|
it('RightToolbarActions - render correctly with props', async () => {
|
||||||
const onStageRunQuery = jest.fn();
|
const onStageRunQuery = jest.fn();
|
||||||
const { queryByText } = render(
|
const { queryByText } = render(
|
||||||
|
<MockQueryClientProvider>
|
||||||
<RightToolbarActions onStageRunQuery={onStageRunQuery} />,
|
<RightToolbarActions onStageRunQuery={onStageRunQuery} />,
|
||||||
|
</MockQueryClientProvider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const stageNRunBtn = queryByText('Stage & Run Query');
|
const stageNRunBtn = queryByText('Stage & Run Query');
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||||
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||||
|
import './QueryBuilderSearch.styles.scss';
|
||||||
|
|
||||||
|
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
function ExampleQueriesRendererForLogs({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
handleAddTag,
|
||||||
|
}: ExampleQueriesRendererForLogsProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="example-query-container"
|
||||||
|
onClick={(): void => {
|
||||||
|
handleAddTag(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="example-query">{label}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExampleQueriesRendererForLogsProps {
|
||||||
|
label: string;
|
||||||
|
value: TagFilter;
|
||||||
|
handleAddTag: (value: TagFilter) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExampleQueriesRendererForLogs;
|
@ -0,0 +1,77 @@
|
|||||||
|
import './QueryBuilderSearch.styles.scss';
|
||||||
|
|
||||||
|
import { Color } from '@signozhq/design-tokens';
|
||||||
|
import { Tooltip, Typography } from 'antd';
|
||||||
|
import cx from 'classnames';
|
||||||
|
import { Zap } from 'lucide-react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { getOptionType } from './utils';
|
||||||
|
|
||||||
|
function OptionRendererForLogs({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
dataType,
|
||||||
|
isIndexed,
|
||||||
|
setDynamicPlaceholder,
|
||||||
|
}: OptionRendererProps): JSX.Element {
|
||||||
|
const [truncated, setTruncated] = useState<boolean>(false);
|
||||||
|
const optionType = getOptionType(label);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className="option"
|
||||||
|
onMouseEnter={(): void => setDynamicPlaceholder(value)}
|
||||||
|
onFocus={(): void => setDynamicPlaceholder(value)}
|
||||||
|
>
|
||||||
|
{optionType ? (
|
||||||
|
<Tooltip title={truncated ? `${value}` : ''} placement="topLeft">
|
||||||
|
<div className="logs-options-select">
|
||||||
|
<section className="left-section">
|
||||||
|
{isIndexed ? (
|
||||||
|
<Zap size={12} fill={Color.BG_AMBER_500} />
|
||||||
|
) : (
|
||||||
|
<div className="dot" />
|
||||||
|
)}
|
||||||
|
<Typography.Text
|
||||||
|
className="text value"
|
||||||
|
ellipsis={{ onEllipsis: (ellipsis): void => setTruncated(ellipsis) }}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</Typography.Text>
|
||||||
|
</section>
|
||||||
|
<section className="right-section">
|
||||||
|
<div className="text tags data-type-tag">{dataType}</div>
|
||||||
|
<div className={cx('text tags option-type-tag', optionType)}>
|
||||||
|
<div className="dot" />
|
||||||
|
{optionType}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<Tooltip title={truncated ? `${label}` : ''} placement="topLeft">
|
||||||
|
<div className="without-option-type">
|
||||||
|
<div className="dot" />
|
||||||
|
<Typography.Text
|
||||||
|
className="text"
|
||||||
|
ellipsis={{ onEllipsis: (ellipsis): void => setTruncated(ellipsis) }}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OptionRendererProps {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
dataType: string;
|
||||||
|
isIndexed: boolean;
|
||||||
|
setDynamicPlaceholder: React.Dispatch<React.SetStateAction<string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OptionRendererForLogs;
|
@ -11,6 +11,290 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logs-popup {
|
||||||
|
&.hide-scroll {
|
||||||
|
.rc-virtual-list-holder {
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-explorer-popup {
|
||||||
|
padding: 0px;
|
||||||
|
.ant-select-item-group {
|
||||||
|
padding: 12px 14px 8px 14px;
|
||||||
|
color: var(--bg-slate-50);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 11px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 18px; /* 163.636% */
|
||||||
|
letter-spacing: 0.88px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-all-filter-props {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 13px;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.left-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px; /* 142.857% */
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text:hover {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
.keyboard-shortcut-slash {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 2.286px;
|
||||||
|
border-top: 1.143px solid var(--bg-ink-200);
|
||||||
|
border-right: 1.143px solid var(--bg-ink-200);
|
||||||
|
border-bottom: 2.286px solid var(--bg-ink-200);
|
||||||
|
border-left: 1.143px solid var(--bg-ink-200);
|
||||||
|
background: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-all-filter-props:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.04) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-queries {
|
||||||
|
cursor: default;
|
||||||
|
.heading {
|
||||||
|
padding: 12px 14px 8px 14px;
|
||||||
|
color: var(--bg-slate-50);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 11px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 18px; /* 163.636% */
|
||||||
|
letter-spacing: 0.88px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 0px 12px 12px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.example-query {
|
||||||
|
display: flex;
|
||||||
|
padding: 4px 8px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--bg-ink-200);
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: normal;
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-query:hover {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-item-option-grouped {
|
||||||
|
padding-inline-start: 0px;
|
||||||
|
padding: 7px 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-shortcuts {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 0px 0px 4px 4px;
|
||||||
|
border: 1px solid var(--bg-slate-400);
|
||||||
|
background: var(--bg-ink-300);
|
||||||
|
padding: 11px 16px;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
.icons {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 2.286px;
|
||||||
|
border-top: 1.143px solid var(--Ink-200, #23262e);
|
||||||
|
border-right: 1.143px solid var(--Ink-200, #23262e);
|
||||||
|
border-bottom: 2.286px solid var(--Ink-200, #23262e);
|
||||||
|
border-left: 1.143px solid var(--Ink-200, #23262e);
|
||||||
|
background: var(--Ink-400, #121317);
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-text {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px; /* 142.857% */
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigate {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-right: 12px;
|
||||||
|
gap: 4px;
|
||||||
|
border-right: 1px solid #1d212d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-query {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 12px;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.without-option-type {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
.dot {
|
||||||
|
height: 5px;
|
||||||
|
width: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--bg-slate-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-options-select {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
font-family: Inter;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px; /* 142.857% */
|
||||||
|
letter-spacing: -0.07px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags {
|
||||||
|
display: flex;
|
||||||
|
height: 20px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
height: 5px;
|
||||||
|
width: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 90%;
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
background-color: var(--bg-slate-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
.data-type-tag {
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-type-tag {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0px 6px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
border-radius: 50px;
|
||||||
|
background: rgba(189, 153, 121, 0.1);
|
||||||
|
color: var(--bg-sienna-400);
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
background-color: var(--bg-sienna-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource {
|
||||||
|
border-radius: 50px;
|
||||||
|
background: rgba(245, 108, 135, 0.1);
|
||||||
|
color: var(--bg-sakura-400);
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
background-color: var(--bg-sakura-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-item-option-active {
|
||||||
|
.logs-options-select {
|
||||||
|
.left-section {
|
||||||
|
.value {
|
||||||
|
color: var(--bg-vanilla-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.lightMode {
|
.lightMode {
|
||||||
.query-builder-search {
|
.query-builder-search {
|
||||||
.ant-select-dropdown {
|
.ant-select-dropdown {
|
||||||
@ -21,4 +305,108 @@
|
|||||||
background-color: var(--bg-vanilla-200) !important;
|
background-color: var(--bg-vanilla-200) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.logs-explorer-popup {
|
||||||
|
.ant-select-item-group {
|
||||||
|
color: var(--bg-slate-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-all-filter-props {
|
||||||
|
.content {
|
||||||
|
.left-section {
|
||||||
|
.text {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text:hover {
|
||||||
|
color: var(--bg-slate-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.right-section {
|
||||||
|
.keyboard-shortcut-slash {
|
||||||
|
border-top: 1.143px solid var(--bg-ink-200);
|
||||||
|
border-right: 1.143px solid var(--bg-ink-200);
|
||||||
|
border-bottom: 2.286px solid var(--bg-ink-200);
|
||||||
|
border-left: 1.143px solid var(--bg-ink-200);
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-all-filter-props:hover {
|
||||||
|
background: var(--bg-vanilla-200) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-queries {
|
||||||
|
.heading {
|
||||||
|
color: var(--bg-slate-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.query-container {
|
||||||
|
.example-query-container {
|
||||||
|
.example-query {
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-query:hover {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-shortcuts {
|
||||||
|
border: 1px solid var(--bg-vanilla-400);
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
|
||||||
|
.icons {
|
||||||
|
border-top: 1.143px solid var(--Ink-200, #23262e);
|
||||||
|
border-right: 1.143px solid var(--Ink-200, #23262e);
|
||||||
|
border-bottom: 2.286px solid var(--Ink-200, #23262e);
|
||||||
|
border-left: 1.143px solid var(--Ink-200, #23262e);
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.keyboard-text {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigate {
|
||||||
|
border-right: 1px solid #1d212d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-options-select {
|
||||||
|
.text {
|
||||||
|
color: var(--bg-ink-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-section {
|
||||||
|
.data-type-tag {
|
||||||
|
background: var(--bg-vanilla-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
background: rgba(189, 153, 121, 0.1);
|
||||||
|
color: var(--bg-sienna-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource {
|
||||||
|
background: rgba(245, 108, 135, 0.1);
|
||||||
|
color: var(--bg-sakura-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-item-option-active {
|
||||||
|
.logs-options-select {
|
||||||
|
.left-section {
|
||||||
|
.value {
|
||||||
|
color: var(--bg-ink-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
|
/* eslint-disable react/no-unstable-nested-components */
|
||||||
import './QueryBuilderSearch.styles.scss';
|
import './QueryBuilderSearch.styles.scss';
|
||||||
|
|
||||||
import { Select, Spin, Tag, Tooltip } from 'antd';
|
import { Button, Select, Spin, Tag, Tooltip, Typography } from 'antd';
|
||||||
|
import cx from 'classnames';
|
||||||
import { OPERATORS } from 'constants/queryBuilder';
|
import { OPERATORS } from 'constants/queryBuilder';
|
||||||
|
import ROUTES from 'constants/routes';
|
||||||
import { LogsExplorerShortcuts } from 'constants/shortcuts/logsExplorerShortcuts';
|
import { LogsExplorerShortcuts } from 'constants/shortcuts/logsExplorerShortcuts';
|
||||||
import { getDataTypes } from 'container/LogDetailedView/utils';
|
import { getDataTypes } from 'container/LogDetailedView/utils';
|
||||||
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||||
@ -11,7 +14,17 @@ import {
|
|||||||
} from 'hooks/queryBuilder/useAutoComplete';
|
} from 'hooks/queryBuilder/useAutoComplete';
|
||||||
import { useFetchKeysAndValues } from 'hooks/queryBuilder/useFetchKeysAndValues';
|
import { useFetchKeysAndValues } from 'hooks/queryBuilder/useFetchKeysAndValues';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual, isUndefined } from 'lodash-es';
|
||||||
|
import {
|
||||||
|
ArrowDown,
|
||||||
|
ArrowUp,
|
||||||
|
ChevronDown,
|
||||||
|
ChevronUp,
|
||||||
|
Command,
|
||||||
|
CornerDownLeft,
|
||||||
|
Filter,
|
||||||
|
Slash,
|
||||||
|
} from 'lucide-react';
|
||||||
import type { BaseSelectRef } from 'rc-select';
|
import type { BaseSelectRef } from 'rc-select';
|
||||||
import {
|
import {
|
||||||
KeyboardEvent,
|
KeyboardEvent,
|
||||||
@ -23,6 +36,7 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
BaseAutocompleteData,
|
BaseAutocompleteData,
|
||||||
DataTypes,
|
DataTypes,
|
||||||
@ -32,14 +46,18 @@ import {
|
|||||||
TagFilter,
|
TagFilter,
|
||||||
} from 'types/api/queryBuilder/queryBuilderData';
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
import { getUserOperatingSystem, UserOperatingSystem } from 'utils/getUserOS';
|
||||||
import { popupContainer } from 'utils/selectPopupContainer';
|
import { popupContainer } from 'utils/selectPopupContainer';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import { selectStyle } from './config';
|
import { selectStyle } from './config';
|
||||||
import { PLACEHOLDER } from './constant';
|
import { PLACEHOLDER } from './constant';
|
||||||
|
import ExampleQueriesRendererForLogs from './ExampleQueriesRendererForLogs';
|
||||||
import OptionRenderer from './OptionRenderer';
|
import OptionRenderer from './OptionRenderer';
|
||||||
|
import OptionRendererForLogs from './OptionRendererForLogs';
|
||||||
import { StyledCheckOutlined, TypographyText } from './style';
|
import { StyledCheckOutlined, TypographyText } from './style';
|
||||||
import {
|
import {
|
||||||
|
convertExampleQueriesToOptions,
|
||||||
getOperatorValue,
|
getOperatorValue,
|
||||||
getRemovePrefixFromKey,
|
getRemovePrefixFromKey,
|
||||||
getTagToken,
|
getTagToken,
|
||||||
@ -55,6 +73,10 @@ function QueryBuilderSearch({
|
|||||||
placeholder,
|
placeholder,
|
||||||
suffixIcon,
|
suffixIcon,
|
||||||
}: QueryBuilderSearchProps): JSX.Element {
|
}: QueryBuilderSearchProps): JSX.Element {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const isLogsExplorerPage = useMemo(() => pathname === ROUTES.LOGS_EXPLORER, [
|
||||||
|
pathname,
|
||||||
|
]);
|
||||||
const {
|
const {
|
||||||
updateTag,
|
updateTag,
|
||||||
handleClearTag,
|
handleClearTag,
|
||||||
@ -69,14 +91,20 @@ function QueryBuilderSearch({
|
|||||||
isFetching,
|
isFetching,
|
||||||
setSearchKey,
|
setSearchKey,
|
||||||
searchKey,
|
searchKey,
|
||||||
} = useAutoComplete(query, whereClauseConfig);
|
key,
|
||||||
|
exampleQueries,
|
||||||
|
} = useAutoComplete(query, whereClauseConfig, isLogsExplorerPage);
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||||
|
const [showAllFilters, setShowAllFilters] = useState<boolean>(false);
|
||||||
|
const [dynamicPlacholder, setDynamicPlaceholder] = useState<string>(
|
||||||
|
placeholder || '',
|
||||||
|
);
|
||||||
const selectRef = useRef<BaseSelectRef>(null);
|
const selectRef = useRef<BaseSelectRef>(null);
|
||||||
const { sourceKeys, handleRemoveSourceKey } = useFetchKeysAndValues(
|
const { sourceKeys, handleRemoveSourceKey } = useFetchKeysAndValues(
|
||||||
searchValue,
|
searchValue,
|
||||||
query,
|
query,
|
||||||
searchKey,
|
searchKey,
|
||||||
|
isLogsExplorerPage,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
||||||
@ -140,6 +168,12 @@ function QueryBuilderSearch({
|
|||||||
handleRunQuery();
|
handleRunQuery();
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((event.ctrlKey || event.metaKey) && event.key === '/') {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
setShowAllFilters((prev) => !prev);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeselect = useCallback(
|
const handleDeselect = useCallback(
|
||||||
@ -229,6 +263,28 @@ function QueryBuilderSearch({
|
|||||||
deregisterShortcut(LogsExplorerShortcuts.FocusTheSearchBar);
|
deregisterShortcut(LogsExplorerShortcuts.FocusTheSearchBar);
|
||||||
}, [deregisterShortcut, isLastQuery, registerShortcut]);
|
}, [deregisterShortcut, isLastQuery, registerShortcut]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setDynamicPlaceholder(placeholder || '');
|
||||||
|
}
|
||||||
|
}, [isOpen, placeholder]);
|
||||||
|
|
||||||
|
const userOs = getUserOperatingSystem();
|
||||||
|
|
||||||
|
// conditional changes here to use a seperate component to render the example queries based on the option group label
|
||||||
|
const customRendererForLogsExplorer = options.map((option) => (
|
||||||
|
<Select.Option key={option.label} value={option.value}>
|
||||||
|
<OptionRendererForLogs
|
||||||
|
label={option.label}
|
||||||
|
value={option.value}
|
||||||
|
dataType={option.dataType || ''}
|
||||||
|
isIndexed={option.isIndexed || false}
|
||||||
|
setDynamicPlaceholder={setDynamicPlaceholder}
|
||||||
|
/>
|
||||||
|
{option.selected && <StyledCheckOutlined />}
|
||||||
|
</Select.Option>
|
||||||
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -238,7 +294,9 @@ function QueryBuilderSearch({
|
|||||||
<Select
|
<Select
|
||||||
ref={selectRef}
|
ref={selectRef}
|
||||||
getPopupContainer={popupContainer}
|
getPopupContainer={popupContainer}
|
||||||
virtual
|
transitionName=""
|
||||||
|
choiceTransitionName=""
|
||||||
|
virtual={false}
|
||||||
showSearch
|
showSearch
|
||||||
tagRender={onTagRender}
|
tagRender={onTagRender}
|
||||||
filterOption={false}
|
filterOption={false}
|
||||||
@ -246,10 +304,14 @@ function QueryBuilderSearch({
|
|||||||
onDropdownVisibleChange={setIsOpen}
|
onDropdownVisibleChange={setIsOpen}
|
||||||
autoClearSearchValue={false}
|
autoClearSearchValue={false}
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder={placeholder}
|
placeholder={dynamicPlacholder}
|
||||||
value={queryTags}
|
value={queryTags}
|
||||||
searchValue={searchValue}
|
searchValue={searchValue}
|
||||||
className={className}
|
className={cx(
|
||||||
|
className,
|
||||||
|
isLogsExplorerPage ? 'logs-popup' : '',
|
||||||
|
!showAllFilters && options.length > 3 && !key ? 'hide-scroll' : '',
|
||||||
|
)}
|
||||||
rootClassName="query-builder-search"
|
rootClassName="query-builder-search"
|
||||||
disabled={isMetricsDataSource && !query.aggregateAttribute.key}
|
disabled={isMetricsDataSource && !query.aggregateAttribute.key}
|
||||||
style={selectStyle}
|
style={selectStyle}
|
||||||
@ -259,11 +321,90 @@ function QueryBuilderSearch({
|
|||||||
onDeselect={handleDeselect}
|
onDeselect={handleDeselect}
|
||||||
onInputKeyDown={onInputKeyDownHandler}
|
onInputKeyDown={onInputKeyDownHandler}
|
||||||
notFoundContent={isFetching ? <Spin size="small" /> : null}
|
notFoundContent={isFetching ? <Spin size="small" /> : null}
|
||||||
suffixIcon={suffixIcon}
|
suffixIcon={
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
!isUndefined(suffixIcon) ? (
|
||||||
|
suffixIcon
|
||||||
|
) : isOpen ? (
|
||||||
|
<ChevronUp size={14} />
|
||||||
|
) : (
|
||||||
|
<ChevronDown size={14} />
|
||||||
|
)
|
||||||
|
}
|
||||||
showAction={['focus']}
|
showAction={['focus']}
|
||||||
onBlur={handleOnBlur}
|
onBlur={handleOnBlur}
|
||||||
|
popupClassName={isLogsExplorerPage ? 'logs-explorer-popup' : ''}
|
||||||
|
dropdownRender={(menu): ReactElement => (
|
||||||
|
<div>
|
||||||
|
{!searchKey && isLogsExplorerPage && (
|
||||||
|
<div className="ant-select-item-group ">Suggested Filters</div>
|
||||||
|
)}
|
||||||
|
{menu}
|
||||||
|
{isLogsExplorerPage && (
|
||||||
|
<div>
|
||||||
|
{!searchKey && tags.length === 0 && (
|
||||||
|
<div className="example-queries">
|
||||||
|
<div className="heading"> Example Queries </div>
|
||||||
|
<div className="query-container">
|
||||||
|
{convertExampleQueriesToOptions(exampleQueries).map((query) => (
|
||||||
|
<ExampleQueriesRendererForLogs
|
||||||
|
key={query.label}
|
||||||
|
label={query.label}
|
||||||
|
value={query.value}
|
||||||
|
handleAddTag={onChange}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!key && !isFetching && !showAllFilters && options.length > 3 && (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
className="show-all-filter-props"
|
||||||
|
onClick={(): void => {
|
||||||
|
setShowAllFilters(true);
|
||||||
|
// when clicking on the button the search bar looses the focus
|
||||||
|
selectRef?.current?.focus();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{options.map((option) => (
|
<div className="content">
|
||||||
|
<section className="left-section">
|
||||||
|
<Filter size={14} />
|
||||||
|
<Typography.Text className="text">
|
||||||
|
Show all filters properties
|
||||||
|
</Typography.Text>
|
||||||
|
</section>
|
||||||
|
<section className="right-section">
|
||||||
|
{userOs === UserOperatingSystem.MACOS ? (
|
||||||
|
<Command size={14} className="keyboard-shortcut-slash" />
|
||||||
|
) : (
|
||||||
|
<ChevronUp size={14} className="keyboard-shortcut-slash" />
|
||||||
|
)}
|
||||||
|
+
|
||||||
|
<Slash size={14} className="keyboard-shortcut-slash" />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<div className="keyboard-shortcuts">
|
||||||
|
<section className="navigate">
|
||||||
|
<ArrowDown size={10} className="icons" />
|
||||||
|
<ArrowUp size={10} className="icons" />
|
||||||
|
<span className="keyboard-text">to navigate</span>
|
||||||
|
</section>
|
||||||
|
<section className="update-query">
|
||||||
|
<CornerDownLeft size={10} className="icons" />
|
||||||
|
<span className="keyboard-text">to update query</span>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{isLogsExplorerPage
|
||||||
|
? customRendererForLogsExplorer
|
||||||
|
: options.map((option) => (
|
||||||
<Select.Option key={option.label} value={option.value}>
|
<Select.Option key={option.label} value={option.value}>
|
||||||
<OptionRenderer
|
<OptionRenderer
|
||||||
label={option.label}
|
label={option.label}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { OPERATORS } from 'constants/queryBuilder';
|
import { OPERATORS } from 'constants/queryBuilder';
|
||||||
import { MetricsType } from 'container/MetricsApplication/constant';
|
import { MetricsType } from 'container/MetricsApplication/constant';
|
||||||
|
import { queryFilterTags } from 'hooks/queryBuilder/useTag';
|
||||||
import { parse } from 'papaparse';
|
import { parse } from 'papaparse';
|
||||||
|
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
import { orderByValueDelimiter } from '../OrderByFilter/utils';
|
import { orderByValueDelimiter } from '../OrderByFilter/utils';
|
||||||
|
|
||||||
@ -162,3 +164,17 @@ export function getOptionType(label: string): MetricsType | undefined {
|
|||||||
|
|
||||||
return optionType;
|
return optionType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param exampleQueries the example queries based on recommendation engine
|
||||||
|
* @returns the data formatted to the Option[]
|
||||||
|
*/
|
||||||
|
export function convertExampleQueriesToOptions(
|
||||||
|
exampleQueries: TagFilter[],
|
||||||
|
): { label: string; value: TagFilter }[] {
|
||||||
|
return exampleQueries.map((query) => ({
|
||||||
|
value: query,
|
||||||
|
label: queryFilterTags(query).join(' , '),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
@ -15,4 +15,5 @@ export type Option = {
|
|||||||
label: string;
|
label: string;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
dataType?: string;
|
dataType?: string;
|
||||||
|
isIndexed?: boolean;
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,10 @@ import {
|
|||||||
import { Option } from 'container/QueryBuilder/type';
|
import { Option } from 'container/QueryBuilder/type';
|
||||||
import { parse } from 'papaparse';
|
import { parse } from 'papaparse';
|
||||||
import { KeyboardEvent, useCallback, useState } from 'react';
|
import { KeyboardEvent, useCallback, useState } from 'react';
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import {
|
||||||
|
IBuilderQuery,
|
||||||
|
TagFilter,
|
||||||
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
import { useFetchKeysAndValues } from './useFetchKeysAndValues';
|
import { useFetchKeysAndValues } from './useFetchKeysAndValues';
|
||||||
import { useOptions, WHERE_CLAUSE_CUSTOM_SUFFIX } from './useOptions';
|
import { useOptions, WHERE_CLAUSE_CUSTOM_SUFFIX } from './useOptions';
|
||||||
@ -24,14 +27,16 @@ export type WhereClauseConfig = {
|
|||||||
export const useAutoComplete = (
|
export const useAutoComplete = (
|
||||||
query: IBuilderQuery,
|
query: IBuilderQuery,
|
||||||
whereClauseConfig?: WhereClauseConfig,
|
whereClauseConfig?: WhereClauseConfig,
|
||||||
|
shouldUseSuggestions?: boolean,
|
||||||
): IAutoComplete => {
|
): IAutoComplete => {
|
||||||
const [searchValue, setSearchValue] = useState<string>('');
|
const [searchValue, setSearchValue] = useState<string>('');
|
||||||
const [searchKey, setSearchKey] = useState<string>('');
|
const [searchKey, setSearchKey] = useState<string>('');
|
||||||
|
|
||||||
const { keys, results, isFetching } = useFetchKeysAndValues(
|
const { keys, results, isFetching, exampleQueries } = useFetchKeysAndValues(
|
||||||
searchValue,
|
searchValue,
|
||||||
query,
|
query,
|
||||||
searchKey,
|
searchKey,
|
||||||
|
shouldUseSuggestions,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [key, operator, result] = useSetCurrentKeyAndOperator(searchValue, keys);
|
const [key, operator, result] = useSetCurrentKeyAndOperator(searchValue, keys);
|
||||||
@ -144,6 +149,8 @@ export const useAutoComplete = (
|
|||||||
isFetching,
|
isFetching,
|
||||||
setSearchKey,
|
setSearchKey,
|
||||||
searchKey,
|
searchKey,
|
||||||
|
key,
|
||||||
|
exampleQueries,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -161,4 +168,6 @@ interface IAutoComplete {
|
|||||||
isFetching: boolean;
|
isFetching: boolean;
|
||||||
setSearchKey: (value: string) => void;
|
setSearchKey: (value: string) => void;
|
||||||
searchKey: string;
|
searchKey: string;
|
||||||
|
key: string;
|
||||||
|
exampleQueries: TagFilter[];
|
||||||
}
|
}
|
||||||
|
@ -6,17 +6,21 @@ import {
|
|||||||
isInNInOperator,
|
isInNInOperator,
|
||||||
} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
} from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
||||||
import useDebounceValue from 'hooks/useDebounce';
|
import useDebounceValue from 'hooks/useDebounce';
|
||||||
import { isEqual, uniqWith } from 'lodash-es';
|
import { cloneDeep, isEqual, uniqWith, unset } from 'lodash-es';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { useDebounce } from 'react-use';
|
import { useDebounce } from 'react-use';
|
||||||
import {
|
import {
|
||||||
BaseAutocompleteData,
|
BaseAutocompleteData,
|
||||||
DataTypes,
|
DataTypes,
|
||||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
import {
|
||||||
|
IBuilderQuery,
|
||||||
|
TagFilter,
|
||||||
|
} from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import { useGetAggregateKeys } from './useGetAggregateKeys';
|
import { useGetAggregateKeys } from './useGetAggregateKeys';
|
||||||
|
import { useGetAttributeSuggestions } from './useGetAttributeSuggestions';
|
||||||
|
|
||||||
type IuseFetchKeysAndValues = {
|
type IuseFetchKeysAndValues = {
|
||||||
keys: BaseAutocompleteData[];
|
keys: BaseAutocompleteData[];
|
||||||
@ -24,6 +28,7 @@ type IuseFetchKeysAndValues = {
|
|||||||
isFetching: boolean;
|
isFetching: boolean;
|
||||||
sourceKeys: BaseAutocompleteData[];
|
sourceKeys: BaseAutocompleteData[];
|
||||||
handleRemoveSourceKey: (newSourceKey: string) => void;
|
handleRemoveSourceKey: (newSourceKey: string) => void;
|
||||||
|
exampleQueries: TagFilter[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,8 +42,10 @@ export const useFetchKeysAndValues = (
|
|||||||
searchValue: string,
|
searchValue: string,
|
||||||
query: IBuilderQuery,
|
query: IBuilderQuery,
|
||||||
searchKey: string,
|
searchKey: string,
|
||||||
|
shouldUseSuggestions?: boolean,
|
||||||
): IuseFetchKeysAndValues => {
|
): IuseFetchKeysAndValues => {
|
||||||
const [keys, setKeys] = useState<BaseAutocompleteData[]>([]);
|
const [keys, setKeys] = useState<BaseAutocompleteData[]>([]);
|
||||||
|
const [exampleQueries, setExampleQueries] = useState<TagFilter[]>([]);
|
||||||
const [sourceKeys, setSourceKeys] = useState<BaseAutocompleteData[]>([]);
|
const [sourceKeys, setSourceKeys] = useState<BaseAutocompleteData[]>([]);
|
||||||
const [results, setResults] = useState<string[]>([]);
|
const [results, setResults] = useState<string[]>([]);
|
||||||
const [isAggregateFetching, setAggregateFetching] = useState<boolean>(false);
|
const [isAggregateFetching, setAggregateFetching] = useState<boolean>(false);
|
||||||
@ -60,6 +67,28 @@ export const useFetchKeysAndValues = (
|
|||||||
|
|
||||||
const searchParams = useDebounceValue(memoizedSearchParams, DEBOUNCE_DELAY);
|
const searchParams = useDebounceValue(memoizedSearchParams, DEBOUNCE_DELAY);
|
||||||
|
|
||||||
|
const queryFiltersWithoutId = useMemo(
|
||||||
|
() => ({
|
||||||
|
...query.filters,
|
||||||
|
items: query.filters.items.map((item) => {
|
||||||
|
const filterWithoutId = cloneDeep(item);
|
||||||
|
unset(filterWithoutId, 'id');
|
||||||
|
return filterWithoutId;
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
[query.filters],
|
||||||
|
);
|
||||||
|
|
||||||
|
const memoizedSuggestionsParams = useMemo(
|
||||||
|
() => [searchKey, query.dataSource, queryFiltersWithoutId],
|
||||||
|
[query.dataSource, queryFiltersWithoutId, searchKey],
|
||||||
|
);
|
||||||
|
|
||||||
|
const suggestionsParams = useDebounceValue(
|
||||||
|
memoizedSuggestionsParams,
|
||||||
|
DEBOUNCE_DELAY,
|
||||||
|
);
|
||||||
|
|
||||||
const isQueryEnabled = useMemo(
|
const isQueryEnabled = useMemo(
|
||||||
() =>
|
() =>
|
||||||
query.dataSource === DataSource.METRICS
|
query.dataSource === DataSource.METRICS
|
||||||
@ -82,7 +111,26 @@ export const useFetchKeysAndValues = (
|
|||||||
aggregateAttribute: query.aggregateAttribute.key,
|
aggregateAttribute: query.aggregateAttribute.key,
|
||||||
tagType: query.aggregateAttribute.type ?? null,
|
tagType: query.aggregateAttribute.type ?? null,
|
||||||
},
|
},
|
||||||
{ queryKey: [searchParams], enabled: isQueryEnabled },
|
{
|
||||||
|
queryKey: [searchParams],
|
||||||
|
enabled: isQueryEnabled && !shouldUseSuggestions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: suggestionsData,
|
||||||
|
isFetching: isFetchingSuggestions,
|
||||||
|
status: fetchingSuggestionsStatus,
|
||||||
|
} = useGetAttributeSuggestions(
|
||||||
|
{
|
||||||
|
searchText: searchKey,
|
||||||
|
dataSource: query.dataSource,
|
||||||
|
filters: query.filters,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryKey: [suggestionsParams],
|
||||||
|
enabled: isQueryEnabled && shouldUseSuggestions,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -162,11 +210,41 @@ export const useFetchKeysAndValues = (
|
|||||||
}
|
}
|
||||||
}, [data?.payload?.attributeKeys, status]);
|
}, [data?.payload?.attributeKeys, status]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
fetchingSuggestionsStatus === 'success' &&
|
||||||
|
suggestionsData?.payload?.attributes
|
||||||
|
) {
|
||||||
|
setKeys(suggestionsData.payload.attributes);
|
||||||
|
setSourceKeys((prevState) =>
|
||||||
|
uniqWith(
|
||||||
|
[...(suggestionsData.payload.attributes ?? []), ...prevState],
|
||||||
|
isEqual,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setKeys([]);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
fetchingSuggestionsStatus === 'success' &&
|
||||||
|
suggestionsData?.payload?.example_queries
|
||||||
|
) {
|
||||||
|
setExampleQueries(suggestionsData.payload.example_queries);
|
||||||
|
} else {
|
||||||
|
setExampleQueries([]);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
suggestionsData?.payload?.attributes,
|
||||||
|
fetchingSuggestionsStatus,
|
||||||
|
suggestionsData?.payload?.example_queries,
|
||||||
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
keys,
|
keys,
|
||||||
results,
|
results,
|
||||||
isFetching: isFetching || isAggregateFetching,
|
isFetching: isFetching || isAggregateFetching || isFetchingSuggestions,
|
||||||
sourceKeys,
|
sourceKeys,
|
||||||
handleRemoveSourceKey,
|
handleRemoveSourceKey,
|
||||||
|
exampleQueries,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
import { getAttributeSuggestions } from 'api/queryBuilder/getAttributeSuggestions';
|
||||||
|
import { QueryBuilderKeys } from 'constants/queryBuilder';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import {
|
||||||
|
IGetAttributeSuggestionsPayload,
|
||||||
|
IGetAttributeSuggestionsSuccessResponse,
|
||||||
|
} from 'types/api/queryBuilder/getAttributeSuggestions';
|
||||||
|
|
||||||
|
type UseGetAttributeSuggestions = (
|
||||||
|
requestData: IGetAttributeSuggestionsPayload,
|
||||||
|
options?: UseQueryOptions<
|
||||||
|
SuccessResponse<IGetAttributeSuggestionsSuccessResponse> | ErrorResponse
|
||||||
|
>,
|
||||||
|
) => UseQueryResult<
|
||||||
|
SuccessResponse<IGetAttributeSuggestionsSuccessResponse> | ErrorResponse
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const useGetAttributeSuggestions: UseGetAttributeSuggestions = (
|
||||||
|
requestData,
|
||||||
|
options,
|
||||||
|
) => {
|
||||||
|
const queryKey = useMemo(() => {
|
||||||
|
if (options?.queryKey && Array.isArray(options.queryKey)) {
|
||||||
|
return [QueryBuilderKeys.GET_ATTRIBUTE_SUGGESTIONS, ...options.queryKey];
|
||||||
|
}
|
||||||
|
return [QueryBuilderKeys.GET_ATTRIBUTE_SUGGESTIONS, requestData];
|
||||||
|
}, [options?.queryKey, requestData]);
|
||||||
|
|
||||||
|
return useQuery<
|
||||||
|
SuccessResponse<IGetAttributeSuggestionsSuccessResponse> | ErrorResponse
|
||||||
|
>({
|
||||||
|
queryKey,
|
||||||
|
queryFn: () => getAttributeSuggestions(requestData),
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
import { useMemo } from 'react';
|
import { MutableRefObject, useMemo } from 'react';
|
||||||
import { UseQueryOptions, UseQueryResult } from 'react-query';
|
import { UseQueryOptions, UseQueryResult } from 'react-query';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@ -19,6 +19,7 @@ export const useGetExplorerQueryRange = (
|
|||||||
options?: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>,
|
options?: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>,
|
||||||
params?: Record<string, unknown>,
|
params?: Record<string, unknown>,
|
||||||
isDependentOnQB = true,
|
isDependentOnQB = true,
|
||||||
|
keyRef?: MutableRefObject<any>,
|
||||||
): UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error> => {
|
): UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error> => {
|
||||||
const { isEnabledQuery } = useQueryBuilder();
|
const { isEnabledQuery } = useQueryBuilder();
|
||||||
const { selectedTime: globalSelectedInterval, minTime, maxTime } = useSelector<
|
const { selectedTime: globalSelectedInterval, minTime, maxTime } = useSelector<
|
||||||
@ -40,6 +41,11 @@ export const useGetExplorerQueryRange = (
|
|||||||
return isEnabledQuery;
|
return isEnabledQuery;
|
||||||
}, [options, isEnabledQuery, isDependentOnQB]);
|
}, [options, isEnabledQuery, isDependentOnQB]);
|
||||||
|
|
||||||
|
if (keyRef) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
keyRef.current = [key, globalSelectedInterval, requestData, minTime, maxTime];
|
||||||
|
}
|
||||||
|
|
||||||
return useGetQueryRange(
|
return useGetQueryRange(
|
||||||
{
|
{
|
||||||
graphType: panelType || PANEL_TYPES.LIST,
|
graphType: panelType || PANEL_TYPES.LIST,
|
||||||
|
@ -45,6 +45,7 @@ export const useOptions = (
|
|||||||
label: `${getLabel(item)}`,
|
label: `${getLabel(item)}`,
|
||||||
value: item.key,
|
value: item.key,
|
||||||
dataType: item.dataType,
|
dataType: item.dataType,
|
||||||
|
isIndexed: item?.isIndexed,
|
||||||
})),
|
})),
|
||||||
[getLabel],
|
[getLabel],
|
||||||
);
|
);
|
||||||
|
@ -9,7 +9,7 @@ import RightToolbarActions from 'container/QueryBuilder/components/ToolbarAction
|
|||||||
import Toolbar from 'container/Toolbar/Toolbar';
|
import Toolbar from 'container/Toolbar/Toolbar';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import { WrapperStyled } from './styles';
|
import { WrapperStyled } from './styles';
|
||||||
@ -23,6 +23,12 @@ function LogsExplorer(): JSX.Element {
|
|||||||
|
|
||||||
const { handleRunQuery, currentQuery } = useQueryBuilder();
|
const { handleRunQuery, currentQuery } = useQueryBuilder();
|
||||||
|
|
||||||
|
const listQueryKeyRef = useRef<any>();
|
||||||
|
|
||||||
|
const chartQueryKeyRef = useRef<any>();
|
||||||
|
|
||||||
|
const [isLoadingQueries, setIsLoadingQueries] = useState<boolean>(false);
|
||||||
|
|
||||||
const handleToggleShowFrequencyChart = (): void => {
|
const handleToggleShowFrequencyChart = (): void => {
|
||||||
setShowFrequencyChart(!showFrequencyChart);
|
setShowFrequencyChart(!showFrequencyChart);
|
||||||
};
|
};
|
||||||
@ -82,7 +88,14 @@ function LogsExplorer(): JSX.Element {
|
|||||||
showFrequencyChart={showFrequencyChart}
|
showFrequencyChart={showFrequencyChart}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
rightActions={<RightToolbarActions onStageRunQuery={handleRunQuery} />}
|
rightActions={
|
||||||
|
<RightToolbarActions
|
||||||
|
onStageRunQuery={handleRunQuery}
|
||||||
|
listQueryKeyRef={listQueryKeyRef}
|
||||||
|
chartQueryKeyRef={chartQueryKeyRef}
|
||||||
|
isLoadingQueries={isLoadingQueries}
|
||||||
|
/>
|
||||||
|
}
|
||||||
showOldCTA
|
showOldCTA
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -97,6 +110,9 @@ function LogsExplorer(): JSX.Element {
|
|||||||
<LogsExplorerViews
|
<LogsExplorerViews
|
||||||
selectedView={selectedView}
|
selectedView={selectedView}
|
||||||
showFrequencyChart={showFrequencyChart}
|
showFrequencyChart={showFrequencyChart}
|
||||||
|
listQueryKeyRef={listQueryKeyRef}
|
||||||
|
chartQueryKeyRef={chartQueryKeyRef}
|
||||||
|
setIsLoadingQueries={setIsLoadingQueries}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
import { DataSource } from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
import { BaseAutocompleteData } from './queryAutocompleteResponse';
|
||||||
|
import { TagFilter } from './queryBuilderData';
|
||||||
|
|
||||||
|
export interface IGetAttributeSuggestionsPayload {
|
||||||
|
dataSource: DataSource;
|
||||||
|
searchText: string;
|
||||||
|
filters: TagFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGetAttributeSuggestionsSuccessResponse {
|
||||||
|
attributes: BaseAutocompleteData[];
|
||||||
|
example_queries: TagFilter[];
|
||||||
|
}
|
@ -21,6 +21,7 @@ export interface BaseAutocompleteData {
|
|||||||
key: string;
|
key: string;
|
||||||
type: AutocompleteType | string | null;
|
type: AutocompleteType | string | null;
|
||||||
isJSON?: boolean;
|
isJSON?: boolean;
|
||||||
|
isIndexed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IQueryAutocompleteResponse {
|
export interface IQueryAutocompleteResponse {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user