[8.3] [Osquery] Fix ECS editor field array value (#132786) (#134054)

* [Osquery] Fix ECS editor field array value (#132786)

(cherry picked from commit 272d9be670)

* revert
This commit is contained in:
Patryk Kopyciński 2022-06-09 19:57:02 +02:00 committed by GitHub
parent 8a40536c61
commit 0b6a03355b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 61 additions and 56 deletions

View file

@ -79,7 +79,7 @@ describe('ALL - Add Integration', () => {
it.skip('should have integration and packs copied when upgrading integration', () => { it.skip('should have integration and packs copied when upgrading integration', () => {
const packageName = 'osquery_manager'; const packageName = 'osquery_manager';
const oldVersion = '1.2.0'; const oldVersion = '1.2.0';
const newVersion = '1.3.0'; const newVersion = '1.3.1';
cy.visit(`app/integrations/detail/${packageName}-${oldVersion}/overview`); cy.visit(`app/integrations/detail/${packageName}-${oldVersion}/overview`);
cy.contains('Add Osquery Manager').click(); cy.contains('Add Osquery Manager').click();

View file

@ -17,7 +17,6 @@ import {
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useMutation } from 'react-query'; import { useMutation } from 'react-query';
import deepMerge from 'deepmerge';
import styled from 'styled-components'; import styled from 'styled-components';
import { pickBy, isEmpty, map } from 'lodash'; import { pickBy, isEmpty, map } from 'lodash';
@ -110,8 +109,12 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
options: { options: {
stripEmptyFields: false, stripEmptyFields: false,
}, },
// eslint-disable-next-line @typescript-eslint/naming-convention serializer: ({
serializer: ({ savedQueryId, ecs_mapping, ...formData }) => savedQueryId,
// eslint-disable-next-line @typescript-eslint/naming-convention
ecs_mapping,
...formData
}) =>
pickBy( pickBy(
{ {
...formData, ...formData,
@ -120,20 +123,6 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
}, },
(value) => !isEmpty(value) (value) => !isEmpty(value)
), ),
defaultValue: deepMerge(
{
agentSelection: {
agents: [],
allAgentsSelected: false,
platformsSelected: [],
policiesSelected: [],
},
query: '',
savedQueryId: null,
ecs_mapping: [],
},
defaultValue ?? {}
),
}); });
const { updateFieldValues, setFieldValue, submit, isSubmitting } = form; const { updateFieldValues, setFieldValue, submit, isSubmitting } = form;
@ -256,6 +245,7 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
/> />
</> </>
)} )}
<UseField path="savedQueryId" component={GhostFormField} />
<UseField <UseField
path="query" path="query"
component={LiveQueryQueryField} component={LiveQueryQueryField}
@ -385,7 +375,6 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
<EuiFlexItem>{queryFieldStepContent}</EuiFlexItem> <EuiFlexItem>{queryFieldStepContent}</EuiFlexItem>
<EuiFlexItem>{resultsStepContent}</EuiFlexItem> <EuiFlexItem>{resultsStepContent}</EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>
<UseField path="savedQueryId" component={GhostFormField} />
</Form> </Form>
{showSavedQueryFlyout ? ( {showSavedQueryFlyout ? (
<SavedQueryFlyout <SavedQueryFlyout

View file

@ -28,6 +28,7 @@ export const liveQueryFormSchema = {
validations: [], validations: [],
}, },
query: { query: {
defaultValue: '',
type: FIELD_TYPES.TEXT, type: FIELD_TYPES.TEXT,
validations: [ validations: [
{ {

View file

@ -143,7 +143,7 @@ const QueriesFieldComponent: React.FC<QueriesFieldProps> = ({
pickBy( pickBy(
{ {
id: newQueryId, id: newQueryId,
interval: newQuery.interval ?? parsedContent.interval, interval: newQuery.interval ?? parsedContent.interval ?? '3600',
query: newQuery.query, query: newQuery.query,
version: newQuery.version ?? parsedContent.version, version: newQuery.version ?? parsedContent.version,
platform: getSupportedPlatforms(newQuery.platform ?? parsedContent.platform), platform: getSupportedPlatforms(newQuery.platform ?? parsedContent.platform),

View file

@ -398,7 +398,7 @@ const OsqueryColumnFieldComponent: React.FC<OsqueryColumnFieldProps> = ({
return ecsKeySchemaOption?.value?.normalization !== 'array'; return ecsKeySchemaOption?.value?.normalization !== 'array';
} }
return true; return !!ecsKey?.length;
}, [typeValue, formData, item.path]); }, [typeValue, formData, item.path]);
const onTypeChange = useCallback( const onTypeChange = useCallback(
@ -637,6 +637,7 @@ export const ECSMappingEditorForm: React.FC<ECSMappingEditorFormProps> = ({
osquerySchemaOptions, osquerySchemaOptions,
editForm: !isLastItem, editForm: !isLastItem,
}, },
readDefaultValueOnForm: !item.isNew,
config: { config: {
valueChangeDebounceTime: 300, valueChangeDebounceTime: 300,
type: FIELD_TYPES.COMBO_BOX, type: FIELD_TYPES.COMBO_BOX,
@ -702,6 +703,7 @@ export const ECSMappingEditorForm: React.FC<ECSMappingEditorFormProps> = ({
component={ECSComboboxField} component={ECSComboboxField}
euiFieldProps={ecsComboBoxEuiFieldProps} euiFieldProps={ecsComboBoxEuiFieldProps}
validationData={validationData} validationData={validationData}
readDefaultValueOnForm={!item.isNew}
// @ts-expect-error update types // @ts-expect-error update types
config={config} config={config}
/> />
@ -1017,7 +1019,9 @@ export const ECSMappingEditorField = React.memo(
if (itemKey) { if (itemKey) {
const serializedFormData = formDataSerializer(); const serializedFormData = formDataSerializer();
const itemValue = const itemValue =
serializedFormData.ecs_mapping && serializedFormData.ecs_mapping[`${itemKey}`]?.field; serializedFormData.ecs_mapping &&
(serializedFormData.ecs_mapping[`${itemKey}`]?.field ||
serializedFormData.ecs_mapping[`${itemKey}`]?.value);
if (itemValue && onAdd.current) { if (itemValue && onAdd.current) {
onAdd.current(); onAdd.current();

View file

@ -6,7 +6,7 @@
*/ */
import { EuiIcon } from '@elastic/eui'; import { EuiIcon } from '@elastic/eui';
import React, { useEffect, useState } from 'react'; import React, { useMemo } from 'react';
import { getPlatformIconModule } from './helpers'; import { getPlatformIconModule } from './helpers';
export interface PlatformIconProps { export interface PlatformIconProps {
@ -14,19 +14,9 @@ export interface PlatformIconProps {
} }
const PlatformIconComponent: React.FC<PlatformIconProps> = ({ platform }) => { const PlatformIconComponent: React.FC<PlatformIconProps> = ({ platform }) => {
const [Icon, setIcon] = useState<React.ReactElement | null>(null); const platformIconModule = useMemo(() => getPlatformIconModule(platform), [platform]);
// FIXME: This is a hack to force the icon to be loaded asynchronously. return <EuiIcon type={platformIconModule} title={platform} size="l" />;
useEffect(() => {
const interval = setInterval(() => {
const platformIconModule = getPlatformIconModule(platform);
setIcon(<EuiIcon type={platformIconModule} title={platform} size="l" />);
}, 0);
return () => clearInterval(interval);
}, [platform, setIcon]);
return Icon;
}; };
export const PlatformIcon = React.memo(PlatformIconComponent); export const PlatformIcon = React.memo(PlatformIconComponent);

View file

@ -30,6 +30,7 @@ import { ALL_OSQUERY_VERSIONS_OPTIONS } from './constants';
import { UsePackQueryFormProps, PackFormData, usePackQueryForm } from './use_pack_query_form'; import { UsePackQueryFormProps, PackFormData, usePackQueryForm } from './use_pack_query_form';
import { SavedQueriesDropdown } from '../../saved_queries/saved_queries_dropdown'; import { SavedQueriesDropdown } from '../../saved_queries/saved_queries_dropdown';
import { ECSMappingEditorField } from './lazy_ecs_mapping_editor_field'; import { ECSMappingEditorField } from './lazy_ecs_mapping_editor_field';
import { useKibana } from '../../common/lib/kibana';
const CommonUseField = getUseField({ component: Field }); const CommonUseField = getUseField({ component: Field });
@ -46,6 +47,7 @@ const QueryFlyoutComponent: React.FC<QueryFlyoutProps> = ({
onSave, onSave,
onClose, onClose,
}) => { }) => {
const permissions = useKibana().services.application.capabilities.osquery;
const [isEditMode] = useState(!!defaultValue); const [isEditMode] = useState(!!defaultValue);
const { form } = usePackQueryForm({ const { form } = usePackQueryForm({
uniqueQueryIds, uniqueQueryIds,
@ -117,7 +119,7 @@ const QueryFlyoutComponent: React.FC<QueryFlyoutProps> = ({
</EuiFlyoutHeader> </EuiFlyoutHeader>
<EuiFlyoutBody> <EuiFlyoutBody>
<Form form={form}> <Form form={form}>
{!isEditMode ? ( {!isEditMode && permissions.readSavedQueries ? (
<> <>
<SavedQueriesDropdown onChange={handleSetQueryValue} /> <SavedQueriesDropdown onChange={handleSetQueryValue} />
<EuiSpacer /> <EuiSpacer />

View file

@ -14,7 +14,7 @@ import { FIELD_TYPES } from '../../shared_imports';
import { import {
createIdFieldValidations, createIdFieldValidations,
intervalFieldValidation, intervalFieldValidations,
queryFieldValidation, queryFieldValidation,
} from './validations'; } from './validations';
@ -46,7 +46,7 @@ export const createFormSchema = (ids: Set<string>) => ({
label: i18n.translate('xpack.osquery.pack.queryFlyoutForm.intervalFieldLabel', { label: i18n.translate('xpack.osquery.pack.queryFlyoutForm.intervalFieldLabel', {
defaultMessage: 'Interval (s)', defaultMessage: 'Interval (s)',
}), }),
validations: [{ validator: intervalFieldValidation }], validations: intervalFieldValidations,
}, },
platform: { platform: {
type: FIELD_TYPES.TEXT, type: FIELD_TYPES.TEXT,

View file

@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { ValidationFunc, fieldValidators } from '../../shared_imports'; import { ValidationConfig, ValidationFunc, fieldValidators } from '../../shared_imports';
export { queryFieldValidation } from '../../common/validations'; export { queryFieldValidation } from '../../common/validations';
const idPattern = /^[a-zA-Z0-9-_]+$/; const idPattern = /^[a-zA-Z0-9-_]+$/;
@ -48,14 +48,30 @@ export const createIdFieldValidations = (ids: Set<string>) => [
createUniqueIdValidation(ids), createUniqueIdValidation(ids),
]; ];
export const intervalFieldValidation: ValidationFunc< export const intervalFieldValidations: Array<
// eslint-disable-next-line @typescript-eslint/no-explicit-any ValidationConfig<
any, // eslint-disable-next-line @typescript-eslint/no-explicit-any
string, any,
number string,
> = fieldValidators.numberGreaterThanField({ number
than: 0, >
message: i18n.translate('xpack.osquery.pack.queryFlyoutForm.invalidIntervalField', { > = [
defaultMessage: 'A positive interval value is required', {
}), validator: fieldValidators.numberGreaterThanField({
}); than: 0,
message: i18n.translate('xpack.osquery.pack.queryFlyoutForm.intervalFieldMinNumberError', {
defaultMessage: 'A positive interval value is required',
}),
}),
},
{
validator: fieldValidators.numberSmallerThanField({
than: 604800,
message: ({ than }) =>
i18n.translate('xpack.osquery.pack.queryFlyoutForm.intervalFieldMaxNumberError', {
defaultMessage: 'An interval value must be lower than {than}',
values: { than },
}),
}),
},
];

View file

@ -12,6 +12,7 @@ export type {
FormData, FormData,
FormHook, FormHook,
FormSchema, FormSchema,
ValidationConfig,
ValidationError, ValidationError,
ValidationFunc, ValidationFunc,
ValidationFuncArg, ValidationFuncArg,

View file

@ -19,14 +19,16 @@ const getInstallation = async (osqueryContext: OsqueryAppContext) =>
export const getInstalledSavedQueriesMap = async (osqueryContext: OsqueryAppContext) => { export const getInstalledSavedQueriesMap = async (osqueryContext: OsqueryAppContext) => {
const installation = await getInstallation(osqueryContext); const installation = await getInstallation(osqueryContext);
if (installation) { if (installation) {
return reduce( return reduce<KibanaAssetReference, Record<string, KibanaAssetReference>>(
installation.installed_kibana, installation.installed_kibana,
// @ts-expect-error not sure why it shouts, but still it's properly typed (acc, item) => {
(acc: Record<string, KibanaAssetReference>, item: KibanaAssetReference) => {
if (item.type === savedQuerySavedObjectType) { if (item.type === savedQuerySavedObjectType) {
return { ...acc, [item.id]: item }; return { ...acc, [item.id]: item };
} }
return acc;
}, },
{} {}
); );

View file

@ -21923,7 +21923,7 @@
"xpack.osquery.pack.queryFlyoutForm.idFieldLabel": "ID", "xpack.osquery.pack.queryFlyoutForm.idFieldLabel": "ID",
"xpack.osquery.pack.queryFlyoutForm.intervalFieldLabel": "Intervalle (s)", "xpack.osquery.pack.queryFlyoutForm.intervalFieldLabel": "Intervalle (s)",
"xpack.osquery.pack.queryFlyoutForm.invalidIdError": "Les caractères doivent être alphanumériques, _ ou -", "xpack.osquery.pack.queryFlyoutForm.invalidIdError": "Les caractères doivent être alphanumériques, _ ou -",
"xpack.osquery.pack.queryFlyoutForm.invalidIntervalField": "Une valeur d'intervalle positive est requise", "xpack.osquery.pack.queryFlyoutForm.intervalFieldMinNumberError": "Une valeur d'intervalle positive est requise",
"xpack.osquery.pack.queryFlyoutForm.mappingEcsFieldLabel": "Champ ECS", "xpack.osquery.pack.queryFlyoutForm.mappingEcsFieldLabel": "Champ ECS",
"xpack.osquery.pack.queryFlyoutForm.mappingValueFieldLabel": "Valeur", "xpack.osquery.pack.queryFlyoutForm.mappingValueFieldLabel": "Valeur",
"xpack.osquery.pack.queryFlyoutForm.osqueryResultFieldRequiredErrorMessage": "Valeur obligatoire.", "xpack.osquery.pack.queryFlyoutForm.osqueryResultFieldRequiredErrorMessage": "Valeur obligatoire.",

View file

@ -22061,7 +22061,7 @@
"xpack.osquery.pack.queryFlyoutForm.idFieldLabel": "ID", "xpack.osquery.pack.queryFlyoutForm.idFieldLabel": "ID",
"xpack.osquery.pack.queryFlyoutForm.intervalFieldLabel": "間隔", "xpack.osquery.pack.queryFlyoutForm.intervalFieldLabel": "間隔",
"xpack.osquery.pack.queryFlyoutForm.invalidIdError": "文字は英数字、_、または-でなければなりません", "xpack.osquery.pack.queryFlyoutForm.invalidIdError": "文字は英数字、_、または-でなければなりません",
"xpack.osquery.pack.queryFlyoutForm.invalidIntervalField": "正の間隔値が必要です", "xpack.osquery.pack.queryFlyoutForm.intervalFieldMinNumberError": "正の間隔値が必要です",
"xpack.osquery.pack.queryFlyoutForm.mappingEcsFieldLabel": "ECSフィールド", "xpack.osquery.pack.queryFlyoutForm.mappingEcsFieldLabel": "ECSフィールド",
"xpack.osquery.pack.queryFlyoutForm.mappingValueFieldLabel": "値", "xpack.osquery.pack.queryFlyoutForm.mappingValueFieldLabel": "値",
"xpack.osquery.pack.queryFlyoutForm.osqueryResultFieldRequiredErrorMessage": "値が必要です。", "xpack.osquery.pack.queryFlyoutForm.osqueryResultFieldRequiredErrorMessage": "値が必要です。",

View file

@ -22092,7 +22092,7 @@
"xpack.osquery.pack.queryFlyoutForm.idFieldLabel": "ID", "xpack.osquery.pack.queryFlyoutForm.idFieldLabel": "ID",
"xpack.osquery.pack.queryFlyoutForm.intervalFieldLabel": "时间间隔 (s)", "xpack.osquery.pack.queryFlyoutForm.intervalFieldLabel": "时间间隔 (s)",
"xpack.osquery.pack.queryFlyoutForm.invalidIdError": "字符必须是数字字母、_ 或 -", "xpack.osquery.pack.queryFlyoutForm.invalidIdError": "字符必须是数字字母、_ 或 -",
"xpack.osquery.pack.queryFlyoutForm.invalidIntervalField": "时间间隔值必须为正数", "xpack.osquery.pack.queryFlyoutForm.intervalFieldMinNumberError": "时间间隔值必须为正数",
"xpack.osquery.pack.queryFlyoutForm.mappingEcsFieldLabel": "ECS 字段", "xpack.osquery.pack.queryFlyoutForm.mappingEcsFieldLabel": "ECS 字段",
"xpack.osquery.pack.queryFlyoutForm.mappingValueFieldLabel": "值", "xpack.osquery.pack.queryFlyoutForm.mappingValueFieldLabel": "值",
"xpack.osquery.pack.queryFlyoutForm.osqueryResultFieldRequiredErrorMessage": "“值”必填。", "xpack.osquery.pack.queryFlyoutForm.osqueryResultFieldRequiredErrorMessage": "“值”必填。",