[8.6] [Osquery] - Enable resizing of Osquery query editor (#145231) (#146386)

# Backport

This will backport the following commits from `main` to `8.6`:
- [[Osquery] - Enable resizing of Osquery query editor
(#145231)](https://github.com/elastic/kibana/pull/145231)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Tomasz
Ciecierski","email":"tomasz.ciecierski@elastic.co"},"sourceCommit":{"committedDate":"2022-11-28T13:01:28Z","message":"[Osquery]
- Enable resizing of Osquery query editor
(#145231)","sha":"b300aa55fb421a3b0c98d24faa1895f28ccd67f2","branchLabelMapping":{"^v8.7.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","Team:Asset
Management","Feature:Osquery","v8.6.0","v8.7.0"],"number":145231,"url":"https://github.com/elastic/kibana/pull/145231","mergeCommit":{"message":"[Osquery]
- Enable resizing of Osquery query editor
(#145231)","sha":"b300aa55fb421a3b0c98d24faa1895f28ccd67f2"}},"sourceBranch":"main","suggestedTargetBranches":["8.6"],"targetPullRequestStates":[{"branch":"8.6","label":"v8.6.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.7.0","labelRegex":"^v8.7.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/145231","number":145231,"mergeCommit":{"message":"[Osquery]
- Enable resizing of Osquery query editor
(#145231)","sha":"b300aa55fb421a3b0c98d24faa1895f28ccd67f2"}}]}]
BACKPORT-->

Co-authored-by: Tomasz Ciecierski <tomasz.ciecierski@elastic.co>
This commit is contained in:
Kibana Machine 2022-11-28 10:23:12 -05:00 committed by GitHub
parent e1a755107c
commit 12f1b6179c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 202 additions and 48 deletions

View file

@ -20,6 +20,7 @@ import {
import React, { useState, useCallback, useMemo } from 'react';
import { useHistory } from 'react-router-dom';
import { removeMultilines } from '../../common/utils/build_query/remove_multilines';
import { useAllLiveQueries } from './use_all_live_queries';
import type { SearchHit } from '../../common/search_strategy';
import { Direction } from '../../common/search_strategy';
@ -90,9 +91,13 @@ const ActionsTableComponent = () => {
);
}
const query = item._source.queries[0].query;
const singleLine = removeMultilines(query);
const content = singleLine.length > 90 ? `${singleLine?.substring(0, 90)}...` : singleLine;
return (
<EuiCodeBlock language="sql" fontSize="s" paddingSize="none" transparentBackground>
{item._source.queries[0].query}
{content}
</EuiCodeBlock>
);
}, []);
@ -196,6 +201,7 @@ const ActionsTableComponent = () => {
defaultMessage: 'Query',
}),
truncateText: true,
width: '60%',
render: renderQueryColumn,
},
{

View file

@ -42,7 +42,7 @@ export const WithHeaderLayout: React.FC<WithHeaderLayoutProps> = ({
>
<EuiPageBody>
<ContentWrapper>
<EuiSpacer size="m" />
<EuiSpacer size="l" />
{children}
</ContentWrapper>
</EuiPageBody>

View file

@ -5,8 +5,10 @@
* 2.0.
*/
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useCallback, useRef } from 'react';
import useDebounce from 'react-use/lib/useDebounce';
import styled from 'styled-components';
import { EuiResizeObserver } from '@elastic/eui';
import type { EuiCodeEditorProps } from '../shared_imports';
import { EuiCodeEditor } from '../shared_imports';
@ -29,30 +31,76 @@ interface OsqueryEditorProps {
commands?: EuiCodeEditorProps['commands'];
}
const ResizeWrapper = styled.div`
overflow: auto;
resize: vertical;
min-height: 100px;
`;
const MIN_HEIGHT = 100;
const OsqueryEditorComponent: React.FC<OsqueryEditorProps> = ({
defaultValue,
onChange,
commands,
}) => {
const [editorValue, setEditorValue] = useState(defaultValue ?? '');
const [height, setHeight] = useState(MIN_HEIGHT);
const editorRef = useRef<{ renderer: { layerConfig: { maxHeight: number; minHeight: number } } }>(
{
renderer: { layerConfig: { maxHeight: 100, minHeight: 100 } },
}
);
useDebounce(() => onChange(editorValue), 500, [editorValue]);
useDebounce(
() => {
onChange(editorValue);
const config = editorRef.current?.renderer.layerConfig;
if (config.maxHeight > config.minHeight) {
setHeight(config.maxHeight);
}
},
500,
[editorValue]
);
useEffect(() => setEditorValue(defaultValue), [defaultValue]);
const resizeEditor = useCallback((editorInstance) => {
editorRef.current.renderer = editorInstance.renderer;
setTimeout(() => {
const { maxHeight } = editorInstance.renderer.layerConfig;
if (maxHeight > MIN_HEIGHT) {
setHeight(maxHeight);
}
}, 0);
}, []);
const onResize = useCallback((dimensions) => {
setHeight(dimensions.height);
}, []);
return (
<EuiCodeEditor
value={editorValue}
mode="osquery"
onChange={setEditorValue}
theme="tomorrow"
name="osquery_editor"
setOptions={EDITOR_SET_OPTIONS}
editorProps={EDITOR_PROPS}
height="100px"
width="100%"
commands={commands}
/>
<EuiResizeObserver onResize={onResize}>
{(resizeRef) => (
<ResizeWrapper ref={resizeRef}>
<EuiCodeEditor
value={editorValue}
mode="osquery"
onChange={setEditorValue}
theme="tomorrow"
name="osquery_editor"
setOptions={EDITOR_SET_OPTIONS}
editorProps={EDITOR_PROPS}
onLoad={resizeEditor}
height={height + 'px'}
width="100%"
commands={commands}
/>
</ResizeWrapper>
)}
</EuiResizeObserver>
);
};

View file

@ -286,10 +286,12 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
}
}, [queryType, cleanupLiveQuery, resetField, setValue, clearErrors, defaultValue]);
const groupStyles = useMemo(() => ({ gap: 16 }), []);
return (
<>
<FormProvider {...hooksForm}>
<EuiFlexGroup direction="column">
<EuiFlexGroup direction="column" css={groupStyles}>
{queryField && (
<QueryPackSelectable
queryType={queryType}

View file

@ -14,7 +14,6 @@ import { useController, useFormContext } from 'react-hook-form';
import { i18n } from '@kbn/i18n';
import { OsqueryEditor } from '../../editor';
import { useKibana } from '../../common/lib/kibana';
import { MAX_QUERY_LENGTH } from '../../packs/queries/validations';
import { ECSMappingEditorField } from '../../packs/queries/lazy_ecs_mapping_editor_field';
import type { SavedQueriesDropdownProps } from '../../saved_queries/saved_queries_dropdown';
import { SavedQueriesDropdown } from '../../saved_queries/saved_queries_dropdown';
@ -57,13 +56,6 @@ const LiveQueryQueryFieldComponent: React.FC<LiveQueryQueryFieldProps> = ({
}),
value: queryType !== 'pack',
},
maxLength: {
message: i18n.translate('xpack.osquery.liveQuery.queryForm.largeQueryError', {
defaultMessage: 'Query is too large (max {maxLength} characters)',
values: { maxLength: MAX_QUERY_LENGTH },
}),
value: MAX_QUERY_LENGTH,
},
},
defaultValue: '',
});

View file

@ -23,6 +23,7 @@ import {
import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
import type { ECSMapping } from '@kbn/osquery-io-ts-types';
import { QueryDetailsFlyout } from './query_details_flyout';
import { PackResultsHeader } from './pack_results_header';
import { Direction } from '../../../common/search_strategy';
import { removeMultilines } from '../../../common/utils/build_query/remove_multilines';
@ -43,6 +44,10 @@ const TruncateTooltipText = styled.div`
}
`;
const StyledEuiFlexItem = styled(EuiFlexItem)`
cursor: pointer;
`;
const EMPTY_ARRAY: PackQueryStatusItem[] = [];
// @ts-expect-error TS2769
@ -51,13 +56,16 @@ const StyledEuiBasicTable = styled(EuiBasicTable)`
padding: 0;
border: 1px solid #d3dae6;
}
div.euiDataGrid__virtualized::-webkit-scrollbar {
display: none;
}
.euiDataGrid > div {
.euiDataGrid__scrollOverlay {
box-shadow: none;
}
border-left: 0px;
border-right: 0px;
}
@ -143,6 +151,19 @@ const PackQueriesStatusTableComponent: React.FC<PackQueriesStatusTableProps> = (
expirationDate,
showResultsHeader,
}) => {
const [queryDetailsFlyoutOpen, setQueryDetailsFlyoutOpen] = useState<{
id: string;
query: string;
} | null>(null);
const handleQueryFlyoutOpen = useCallback(
(item) => () => {
setQueryDetailsFlyoutOpen(item);
},
[]
);
const handleQueryFlyoutClose = useCallback(() => setQueryDetailsFlyoutOpen(null), []);
const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<Record<string, unknown>>({});
const renderIDColumn = useCallback(
(id: string) => (
@ -155,18 +176,21 @@ const PackQueriesStatusTableComponent: React.FC<PackQueriesStatusTableProps> = (
[]
);
const renderQueryColumn = useCallback((query: string, item) => {
const singleLine = removeMultilines(query);
const content = singleLine.length > 55 ? `${singleLine.substring(0, 55)}...` : singleLine;
const renderQueryColumn = useCallback(
(query: string, item) => {
const singleLine = removeMultilines(query);
const content = singleLine.length > 55 ? `${singleLine.substring(0, 55)}...` : singleLine;
return (
<EuiToolTip title={item.id} content={<EuiFlexItem>{query}</EuiFlexItem>}>
<EuiCodeBlock language="sql" fontSize="s" paddingSize="none" transparentBackground>
{content}
</EuiCodeBlock>
</EuiToolTip>
);
}, []);
return (
<StyledEuiFlexItem onClick={handleQueryFlyoutOpen(item)}>
<EuiCodeBlock language="sql" fontSize="s" paddingSize="none" transparentBackground>
{content}
</EuiCodeBlock>
</StyledEuiFlexItem>
);
},
[handleQueryFlyoutOpen]
);
const renderDocsColumn = useCallback(
(item: PackQueryStatusItem) => (
@ -269,11 +293,22 @@ const PackQueriesStatusTableComponent: React.FC<PackQueriesStatusTableProps> = (
/>
),
},
{
render: (item: { action_id: string }) => (
<EuiButtonIcon iconType={'expand'} onClick={handleQueryFlyoutOpen(item)} />
),
},
];
return resultActions.map((action) => action.render(row));
},
[actionId, agentIds, renderDiscoverResultsAction, renderLensResultsAction]
[
actionId,
agentIds,
handleQueryFlyoutOpen,
renderDiscoverResultsAction,
renderLensResultsAction,
]
);
const columns = useMemo(
() => [
@ -381,6 +416,9 @@ const PackQueriesStatusTableComponent: React.FC<PackQueriesStatusTableProps> = (
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
isExpandable
/>
{queryDetailsFlyoutOpen ? (
<QueryDetailsFlyout onClose={handleQueryFlyoutClose} action={queryDetailsFlyoutOpen} />
) : null}
</>
);
};

View file

@ -0,0 +1,79 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
EuiFlyout,
EuiTitle,
EuiFlyoutBody,
EuiFlyoutHeader,
EuiPortal,
EuiFlexItem,
EuiCodeBlock,
EuiSpacer,
} from '@elastic/eui';
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
interface QueryDetailsFlyoutProps {
action: {
id: string;
query: string;
};
onClose: () => void;
}
const additionalZIndexStyle = { style: 'z-index: 6000' };
const QueryDetailsFlyoutComponent: React.FC<QueryDetailsFlyoutProps> = ({ action, onClose }) => (
<EuiPortal>
<EuiFlyout
size="m"
ownFocus
onClose={onClose}
aria-labelledby="flyoutTitle"
maskProps={additionalZIndexStyle} // For an edge case to display above the alerts flyout
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s">
<h2 id="flyoutTitle">
<FormattedMessage
id="xpack.osquery.liveQueryActions.details.title"
defaultMessage="Query Details"
/>
</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiFlexItem grow={false}>
<strong>
<FormattedMessage id="xpack.osquery.liveQueryActions.details.id" defaultMessage="Id" />
</strong>
<EuiSpacer size="xs" />
<EuiCodeBlock fontSize="m" paddingSize="s" isCopyable={true}>
{action.id}
</EuiCodeBlock>
</EuiFlexItem>
<EuiSpacer size="m" />
<EuiFlexItem grow={false}>
<strong>
<FormattedMessage
id="xpack.osquery.liveQueryActions.details.query"
defaultMessage="Query"
/>
</strong>
<EuiSpacer size="xs" />
<EuiCodeBlock language="sql" fontSize="m" paddingSize="s" isCopyable={true}>
{action.query}
</EuiCodeBlock>
</EuiFlexItem>
<EuiSpacer size="m" />
</EuiFlyoutBody>
</EuiFlyout>
</EuiPortal>
);
export const QueryDetailsFlyout = React.memo(QueryDetailsFlyoutComponent);

View file

@ -12,7 +12,6 @@ import styled from 'styled-components';
import { useController } from 'react-hook-form';
import { i18n } from '@kbn/i18n';
import { MAX_QUERY_LENGTH } from '../../packs/queries/validations';
import { OsquerySchemaLink } from '../../components/osquery_schema_link';
import { OsqueryEditor } from '../../editor';
@ -43,13 +42,6 @@ const CodeEditorFieldComponent: React.FC<CodeEditorFieldProps> = ({
}),
value: true,
},
maxLength: {
message: i18n.translate('xpack.osquery.liveQuery.queryForm.largeQueryError', {
defaultMessage: 'Query is too large (max {maxLength} characters)',
values: { maxLength: MAX_QUERY_LENGTH },
}),
value: MAX_QUERY_LENGTH,
},
},
defaultValue: '',
});

View file

@ -23769,7 +23769,6 @@
"xpack.osquery.fleetIntegration.osqueryConfig.packConfigFilesErrorMessage": "Les fichiers de configuration de pack ne sont pas pris en charge. Les packs suivants doivent être supprimés : {packNames}.",
"xpack.osquery.fleetIntegration.osqueryConfig.restrictedOptionsErrorMessage": "Les options Osquery suivantes ne sont pas prises en charge et doivent être supprimées : {restrictedFlags}.",
"xpack.osquery.liveQuery.permissionDeniedPromptBody": "Pour pouvoir consulter les résultats de requête, demandez à votre administrateur de mettre à jour votre rôle utilisateur de sorte à disposer des privilèges de {read} pour les index {logs}.",
"xpack.osquery.liveQuery.queryForm.largeQueryError": "La recherche est trop volumineuse ({maxLength} caractères maxi)",
"xpack.osquery.newPack.successToastMessageText": "Le pack \"{packName}\" a bien été créé.",
"xpack.osquery.newSavedQuery.successToastMessageText": "Enregistrement réussi de la recherche \"{savedQueryId}\"",
"xpack.osquery.pack.queriesTable.deleteActionAriaLabel": "Supprimer {queryName}",

View file

@ -23747,7 +23747,6 @@
"xpack.osquery.fleetIntegration.osqueryConfig.packConfigFilesErrorMessage": "パック構成ファイルはサポートされていません。これらのパックを削除する必要があります:{packNames}。",
"xpack.osquery.fleetIntegration.osqueryConfig.restrictedOptionsErrorMessage": "次のosqueryオプションはサポートされていないため、削除する必要があります{restrictedFlags}。",
"xpack.osquery.liveQuery.permissionDeniedPromptBody": "クエリ結果を表示するには、ユーザーロールを更新して、{logs}インデックスに対する{read}権限を付与するように、管理者に依頼してください。",
"xpack.osquery.liveQuery.queryForm.largeQueryError": "クエリが大きすぎます(最大{maxLength}文字)",
"xpack.osquery.newPack.successToastMessageText": "\"{packName}\"パックが正常に作成されました",
"xpack.osquery.newSavedQuery.successToastMessageText": "\"{savedQueryId}\"クエリが正常に保存されました",
"xpack.osquery.pack.queriesTable.deleteActionAriaLabel": "{queryName}を削除",

View file

@ -23778,7 +23778,6 @@
"xpack.osquery.fleetIntegration.osqueryConfig.packConfigFilesErrorMessage": "不支持包配置文件。必须移除这些包:{packNames}。",
"xpack.osquery.fleetIntegration.osqueryConfig.restrictedOptionsErrorMessage": "不支持以下 osquery 选项,必须将其移除:{restrictedFlags}。",
"xpack.osquery.liveQuery.permissionDeniedPromptBody": "要查看查询结果,请要求管理员将您的角色更新为具有 {logs} 索引的索引 {read} 权限。",
"xpack.osquery.liveQuery.queryForm.largeQueryError": "查询过大(最多 {maxLength} 个字符)",
"xpack.osquery.newPack.successToastMessageText": "已成功创建“{packName}”包",
"xpack.osquery.newSavedQuery.successToastMessageText": "已成功保存“{savedQueryId}”查询",
"xpack.osquery.pack.queriesTable.deleteActionAriaLabel": "删除 {queryName}",