mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* Match add integration page with designs * Clean up package config layout code * Match edit integration config page with designs * Fix typing and i18n issues * Add back data test subj * Add loading UI for second step; code clean up * Fix limited packages incorrect response * Add ability to create agent config when selecting config to add integration to * Add error count to input-level panel; memoize children components * Add error count next to all advanced options toggles * Move general form error to bottom bar * #69750 Auto-expand inputs with required & empty (invalid) vars * #68019 Enforce unique package config names, per agent config * Fix typing * Fix i18n * Fix reloading when new agent config _wasn't_ created * Memoize edit integration and fix fields not collapsing on edit * Really fix types # Conflicts: # x-pack/plugins/translations/translations/ja-JP.json # x-pack/plugins/translations/translations/zh-CN.json
This commit is contained in:
parent
e2d7e2cbdd
commit
81b4f8204d
24 changed files with 1606 additions and 1020 deletions
|
@ -3,7 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
|
@ -27,130 +27,148 @@ export const CreatePackageConfigPageLayout: React.FunctionComponent<{
|
|||
agentConfig?: AgentConfig;
|
||||
packageInfo?: PackageInfo;
|
||||
'data-test-subj'?: string;
|
||||
}> = ({
|
||||
from,
|
||||
cancelUrl,
|
||||
onCancel,
|
||||
agentConfig,
|
||||
packageInfo,
|
||||
children,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}) => {
|
||||
const leftColumn = (
|
||||
<EuiFlexGroup direction="column" gutterSize="s" alignItems="flexStart">
|
||||
<EuiFlexItem>
|
||||
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType="arrowLeft"
|
||||
flush="left"
|
||||
href={cancelUrl}
|
||||
onClick={onCancel}
|
||||
data-test-subj={`${dataTestSubj}_cancelBackLink`}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.cancelLinkText"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
}> = memo(
|
||||
({
|
||||
from,
|
||||
cancelUrl,
|
||||
onCancel,
|
||||
agentConfig,
|
||||
packageInfo,
|
||||
children,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}) => {
|
||||
const pageTitle = useMemo(() => {
|
||||
if ((from === 'package' || from === 'edit') && packageInfo) {
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<PackageIcon
|
||||
packageName={packageInfo?.name || ''}
|
||||
version={packageInfo?.version || ''}
|
||||
icons={packageInfo?.icons}
|
||||
size="xl"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<h1>
|
||||
{from === 'edit' ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editPackageConfig.pageTitleWithPackageName"
|
||||
defaultMessage="Edit {packageName} integration"
|
||||
values={{
|
||||
packageName: packageInfo.title,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.pageTitleWithPackageName"
|
||||
defaultMessage="Add {packageName} integration"
|
||||
values={{
|
||||
packageName: packageInfo.title,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</h1>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return from === 'edit' ? (
|
||||
<EuiText>
|
||||
<h1>
|
||||
{from === 'edit' ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editPackageConfig.pageTitle"
|
||||
defaultMessage="Edit integration"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.pageTitle"
|
||||
defaultMessage="Add integration"
|
||||
/>
|
||||
)}
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editPackageConfig.pageTitle"
|
||||
defaultMessage="Edit integration"
|
||||
/>
|
||||
</h1>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText color="subdued" size="s">
|
||||
{from === 'edit' ? (
|
||||
) : (
|
||||
<EuiText>
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editPackageConfig.pageDescription"
|
||||
defaultMessage="Follow the instructions below to edit this integration."
|
||||
id="xpack.ingestManager.createPackageConfig.pageTitle"
|
||||
defaultMessage="Add integration"
|
||||
/>
|
||||
) : from === 'config' ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.pageDescriptionfromConfig"
|
||||
defaultMessage="Follow the instructions below to add an integration to this agent configuration."
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.pageDescriptionfromPackage"
|
||||
defaultMessage="Follow the instructions below to add this integration to an agent configuration."
|
||||
/>
|
||||
)}
|
||||
</h1>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
const rightColumn = (
|
||||
<EuiFlexGroup justifyContent="flexEnd" direction={'row'} gutterSize="xl">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSpacer size="s" />
|
||||
{agentConfig && (from === 'config' || from === 'edit') ? (
|
||||
<EuiDescriptionList style={{ textAlign: 'right' }} textStyle="reverse">
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.agentConfigurationNameLabel"
|
||||
defaultMessage="Configuration"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
{agentConfig?.name || '-'}
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiDescriptionList>
|
||||
) : null}
|
||||
{packageInfo && from === 'package' ? (
|
||||
<EuiDescriptionList style={{ textAlign: 'right' }} textStyle="reverse">
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.packageNameLabel"
|
||||
defaultMessage="Integration"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
<EuiFlexGroup justifyContent="flexEnd" alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<PackageIcon
|
||||
packageName={packageInfo?.name || ''}
|
||||
version={packageInfo?.version || ''}
|
||||
icons={packageInfo?.icons}
|
||||
size="m"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{packageInfo?.title || packageInfo?.name || '-'}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiDescriptionList>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
);
|
||||
}, [from, packageInfo]);
|
||||
|
||||
const maxWidth = 770;
|
||||
return (
|
||||
<WithHeaderLayout
|
||||
restrictHeaderWidth={maxWidth}
|
||||
restrictWidth={maxWidth}
|
||||
leftColumn={leftColumn}
|
||||
rightColumn={rightColumn}
|
||||
rightColumnGrow={false}
|
||||
data-test-subj={dataTestSubj}
|
||||
>
|
||||
{children}
|
||||
</WithHeaderLayout>
|
||||
);
|
||||
};
|
||||
const pageDescription = useMemo(() => {
|
||||
return from === 'edit' ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editPackageConfig.pageDescription"
|
||||
defaultMessage="Modify integration settings and deploy changes to the selected agent configuration."
|
||||
/>
|
||||
) : from === 'config' ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.pageDescriptionfromConfig"
|
||||
defaultMessage="Configure an integration for the selected agent configuration."
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.pageDescriptionfromPackage"
|
||||
defaultMessage="Follow the instructions below to add this integration to an agent configuraiton."
|
||||
/>
|
||||
);
|
||||
}, [from]);
|
||||
|
||||
const leftColumn = (
|
||||
<EuiFlexGroup direction="column" gutterSize="s" alignItems="flexStart">
|
||||
<EuiFlexItem>
|
||||
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType="arrowLeft"
|
||||
flush="left"
|
||||
href={cancelUrl}
|
||||
onClick={onCancel}
|
||||
data-test-subj={`${dataTestSubj}_cancelBackLink`}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.cancelLinkText"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>{pageTitle}</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText color="subdued" size="s">
|
||||
{pageDescription}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
||||
const rightColumn =
|
||||
agentConfig && (from === 'config' || from === 'edit') ? (
|
||||
<EuiDescriptionList className="eui-textRight" textStyle="reverse">
|
||||
<EuiDescriptionListTitle>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.agentConfigurationNameLabel"
|
||||
defaultMessage="Agent configuration"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>{agentConfig?.name || '-'}</EuiDescriptionListDescription>
|
||||
</EuiDescriptionList>
|
||||
) : undefined;
|
||||
|
||||
const maxWidth = 770;
|
||||
return (
|
||||
<WithHeaderLayout
|
||||
restrictHeaderWidth={maxWidth}
|
||||
restrictWidth={maxWidth}
|
||||
leftColumn={leftColumn}
|
||||
rightColumn={rightColumn}
|
||||
rightColumnGrow={false}
|
||||
data-test-subj={dataTestSubj}
|
||||
>
|
||||
{children}
|
||||
</WithHeaderLayout>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -3,17 +3,15 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { useState, Fragment } from 'react';
|
||||
import React, { useState, Fragment, memo, useMemo } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiFlexGrid,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiTextColor,
|
||||
EuiSpacer,
|
||||
EuiButtonEmpty,
|
||||
EuiTitle,
|
||||
EuiIconTip,
|
||||
} from '@elastic/eui';
|
||||
import { PackageConfigInput, RegistryVarsEntry } from '../../../../types';
|
||||
import {
|
||||
|
@ -29,150 +27,157 @@ export const PackageConfigInputConfig: React.FunctionComponent<{
|
|||
updatePackageConfigInput: (updatedInput: Partial<PackageConfigInput>) => void;
|
||||
inputVarsValidationResults: PackageConfigConfigValidationResults;
|
||||
forceShowErrors?: boolean;
|
||||
}> = ({
|
||||
packageInputVars,
|
||||
packageConfigInput,
|
||||
updatePackageConfigInput,
|
||||
inputVarsValidationResults,
|
||||
forceShowErrors,
|
||||
}) => {
|
||||
// Showing advanced options toggle state
|
||||
const [isShowingAdvanced, setIsShowingAdvanced] = useState<boolean>(false);
|
||||
}> = memo(
|
||||
({
|
||||
packageInputVars,
|
||||
packageConfigInput,
|
||||
updatePackageConfigInput,
|
||||
inputVarsValidationResults,
|
||||
forceShowErrors,
|
||||
}) => {
|
||||
// Showing advanced options toggle state
|
||||
const [isShowingAdvanced, setIsShowingAdvanced] = useState<boolean>(false);
|
||||
|
||||
// Errors state
|
||||
const hasErrors = forceShowErrors && validationHasErrors(inputVarsValidationResults);
|
||||
// Errors state
|
||||
const hasErrors = forceShowErrors && validationHasErrors(inputVarsValidationResults);
|
||||
|
||||
const requiredVars: RegistryVarsEntry[] = [];
|
||||
const advancedVars: RegistryVarsEntry[] = [];
|
||||
const requiredVars: RegistryVarsEntry[] = [];
|
||||
const advancedVars: RegistryVarsEntry[] = [];
|
||||
|
||||
if (packageInputVars) {
|
||||
packageInputVars.forEach((varDef) => {
|
||||
if (isAdvancedVar(varDef)) {
|
||||
advancedVars.push(varDef);
|
||||
} else {
|
||||
requiredVars.push(varDef);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (packageInputVars) {
|
||||
packageInputVars.forEach((varDef) => {
|
||||
if (isAdvancedVar(varDef)) {
|
||||
advancedVars.push(varDef);
|
||||
} else {
|
||||
requiredVars.push(varDef);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="flexStart">
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiTitle size="s">
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<h4>
|
||||
<EuiTextColor color={hasErrors ? 'danger' : 'default'}>
|
||||
const advancedVarsWithErrorsCount: number = useMemo(
|
||||
() =>
|
||||
advancedVars.filter(
|
||||
({ name: varName }) => inputVarsValidationResults.vars?.[varName]?.length
|
||||
).length,
|
||||
[advancedVars, inputVarsValidationResults.vars]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGrid columns={2}>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="none" alignItems="flexStart">
|
||||
<EuiFlexItem grow={1} />
|
||||
<EuiFlexItem grow={5}>
|
||||
<EuiText>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.inputSettingsTitle"
|
||||
defaultMessage="Settings"
|
||||
/>
|
||||
</EuiTextColor>
|
||||
</h4>
|
||||
</h4>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText color="subdued" size="s">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.inputSettingsDescription"
|
||||
defaultMessage="The following settings are applicable to all inputs below."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
{hasErrors ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.inputConfigErrorsTooltip"
|
||||
defaultMessage="Fix configuration errors"
|
||||
/>
|
||||
}
|
||||
position="right"
|
||||
type="alert"
|
||||
iconProps={{ color: 'danger' }}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
{requiredVars.map((varDef) => {
|
||||
const { name: varName, type: varType } = varDef;
|
||||
const value = packageConfigInput.vars![varName].value;
|
||||
return (
|
||||
<EuiFlexItem key={varName}>
|
||||
<PackageConfigInputVarField
|
||||
varDef={varDef}
|
||||
value={value}
|
||||
onChange={(newValue: any) => {
|
||||
updatePackageConfigInput({
|
||||
vars: {
|
||||
...packageConfigInput.vars,
|
||||
[varName]: {
|
||||
type: varType,
|
||||
value: newValue,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
errors={inputVarsValidationResults.vars![varName]}
|
||||
forceShowErrors={forceShowErrors}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
{advancedVars.length ? (
|
||||
<Fragment>
|
||||
<EuiFlexItem>
|
||||
{/* Wrapper div to prevent button from going full width */}
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType={isShowingAdvanced ? 'arrowDown' : 'arrowRight'}
|
||||
onClick={() => setIsShowingAdvanced(!isShowingAdvanced)}
|
||||
flush="left"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.toggleAdvancedOptionsButtonText"
|
||||
defaultMessage="Advanced options"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
{!isShowingAdvanced && hasErrors && advancedVarsWithErrorsCount ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="danger" size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.errorCountText"
|
||||
defaultMessage="{count, plural, one {# error} other {# errors}}"
|
||||
values={{ count: advancedVarsWithErrorsCount }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{isShowingAdvanced
|
||||
? advancedVars.map((varDef) => {
|
||||
const { name: varName, type: varType } = varDef;
|
||||
const value = packageConfigInput.vars![varName].value;
|
||||
return (
|
||||
<EuiFlexItem key={varName}>
|
||||
<PackageConfigInputVarField
|
||||
varDef={varDef}
|
||||
value={value}
|
||||
onChange={(newValue: any) => {
|
||||
updatePackageConfigInput({
|
||||
vars: {
|
||||
...packageConfigInput.vars,
|
||||
[varName]: {
|
||||
type: varType,
|
||||
value: newValue,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
errors={inputVarsValidationResults.vars![varName]}
|
||||
forceShowErrors={forceShowErrors}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</Fragment>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText color="subdued" size="s">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.inputSettingsDescription"
|
||||
defaultMessage="The following settings are applicable to all streams."
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
{requiredVars.map((varDef) => {
|
||||
const { name: varName, type: varType } = varDef;
|
||||
const value = packageConfigInput.vars![varName].value;
|
||||
return (
|
||||
<EuiFlexItem key={varName}>
|
||||
<PackageConfigInputVarField
|
||||
varDef={varDef}
|
||||
value={value}
|
||||
onChange={(newValue: any) => {
|
||||
updatePackageConfigInput({
|
||||
vars: {
|
||||
...packageConfigInput.vars,
|
||||
[varName]: {
|
||||
type: varType,
|
||||
value: newValue,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
errors={inputVarsValidationResults.vars![varName]}
|
||||
forceShowErrors={forceShowErrors}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
{advancedVars.length ? (
|
||||
<Fragment>
|
||||
<EuiFlexItem>
|
||||
{/* Wrapper div to prevent button from going full width */}
|
||||
<div>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType={isShowingAdvanced ? 'arrowDown' : 'arrowRight'}
|
||||
onClick={() => setIsShowingAdvanced(!isShowingAdvanced)}
|
||||
flush="left"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.toggleAdvancedOptionsButtonText"
|
||||
defaultMessage="Advanced options"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
{isShowingAdvanced
|
||||
? advancedVars.map((varDef) => {
|
||||
const { name: varName, type: varType } = varDef;
|
||||
const value = packageConfigInput.vars![varName].value;
|
||||
return (
|
||||
<EuiFlexItem key={varName}>
|
||||
<PackageConfigInputVarField
|
||||
varDef={varDef}
|
||||
value={value}
|
||||
onChange={(newValue: any) => {
|
||||
updatePackageConfigInput({
|
||||
vars: {
|
||||
...packageConfigInput.vars,
|
||||
[varName]: {
|
||||
type: varType,
|
||||
value: newValue,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
errors={inputVarsValidationResults.vars![varName]}
|
||||
forceShowErrors={forceShowErrors}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</Fragment>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -3,21 +3,18 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { useState, Fragment } from 'react';
|
||||
import React, { useState, Fragment, memo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiPanel,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSwitch,
|
||||
EuiText,
|
||||
EuiTextColor,
|
||||
EuiButtonIcon,
|
||||
EuiHorizontalRule,
|
||||
EuiSpacer,
|
||||
EuiIconTip,
|
||||
} from '@elastic/eui';
|
||||
import {
|
||||
PackageConfigInput,
|
||||
|
@ -25,16 +22,44 @@ import {
|
|||
RegistryInput,
|
||||
RegistryStream,
|
||||
} from '../../../../types';
|
||||
import { PackageConfigInputValidationResults, validationHasErrors } from '../services';
|
||||
import {
|
||||
PackageConfigInputValidationResults,
|
||||
hasInvalidButRequiredVar,
|
||||
countValidationErrors,
|
||||
} from '../services';
|
||||
import { PackageConfigInputConfig } from './package_config_input_config';
|
||||
import { PackageConfigInputStreamConfig } from './package_config_input_stream';
|
||||
|
||||
const FlushHorizontalRule = styled(EuiHorizontalRule)`
|
||||
margin-left: -${(props) => props.theme.eui.paddingSizes.m};
|
||||
margin-right: -${(props) => props.theme.eui.paddingSizes.m};
|
||||
width: auto;
|
||||
const ShortenedHorizontalRule = styled(EuiHorizontalRule)`
|
||||
&&& {
|
||||
width: ${(11 / 12) * 100}%;
|
||||
margin-left: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
const shouldShowStreamsByDefault = (
|
||||
packageInput: RegistryInput,
|
||||
packageInputStreams: Array<RegistryStream & { dataset: { name: string } }>,
|
||||
packageConfigInput: PackageConfigInput
|
||||
): boolean => {
|
||||
return (
|
||||
packageConfigInput.enabled &&
|
||||
(hasInvalidButRequiredVar(packageInput.vars, packageConfigInput.vars) ||
|
||||
Boolean(
|
||||
packageInputStreams.find(
|
||||
(stream) =>
|
||||
stream.enabled &&
|
||||
hasInvalidButRequiredVar(
|
||||
stream.vars,
|
||||
packageConfigInput.streams.find(
|
||||
(pkgStream) => stream.dataset.name === pkgStream.dataset.name
|
||||
)?.vars
|
||||
)
|
||||
)
|
||||
))
|
||||
);
|
||||
};
|
||||
|
||||
export const PackageConfigInputPanel: React.FunctionComponent<{
|
||||
packageInput: RegistryInput;
|
||||
packageInputStreams: Array<RegistryStream & { dataset: { name: string } }>;
|
||||
|
@ -42,148 +67,136 @@ export const PackageConfigInputPanel: React.FunctionComponent<{
|
|||
updatePackageConfigInput: (updatedInput: Partial<PackageConfigInput>) => void;
|
||||
inputValidationResults: PackageConfigInputValidationResults;
|
||||
forceShowErrors?: boolean;
|
||||
}> = ({
|
||||
packageInput,
|
||||
packageInputStreams,
|
||||
packageConfigInput,
|
||||
updatePackageConfigInput,
|
||||
inputValidationResults,
|
||||
forceShowErrors,
|
||||
}) => {
|
||||
// Showing streams toggle state
|
||||
const [isShowingStreams, setIsShowingStreams] = useState<boolean>(false);
|
||||
}> = memo(
|
||||
({
|
||||
packageInput,
|
||||
packageInputStreams,
|
||||
packageConfigInput,
|
||||
updatePackageConfigInput,
|
||||
inputValidationResults,
|
||||
forceShowErrors,
|
||||
}) => {
|
||||
// Showing streams toggle state
|
||||
const [isShowingStreams, setIsShowingStreams] = useState<boolean>(
|
||||
shouldShowStreamsByDefault(packageInput, packageInputStreams, packageConfigInput)
|
||||
);
|
||||
|
||||
// Errors state
|
||||
const hasErrors = forceShowErrors && validationHasErrors(inputValidationResults);
|
||||
// Errors state
|
||||
const errorCount = countValidationErrors(inputValidationResults);
|
||||
const hasErrors = forceShowErrors && errorCount;
|
||||
|
||||
return (
|
||||
<EuiPanel>
|
||||
{/* Header / input-level toggle */}
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
label={
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
const inputStreams = packageInputStreams
|
||||
.map((packageInputStream) => {
|
||||
return {
|
||||
packageInputStream,
|
||||
packageConfigInputStream: packageConfigInput.streams.find(
|
||||
(stream) => stream.dataset.name === packageInputStream.dataset.name
|
||||
),
|
||||
};
|
||||
})
|
||||
.filter((stream) => Boolean(stream.packageConfigInputStream));
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Header / input-level toggle */}
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiSwitch
|
||||
label={
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<h4>{packageInput.title || packageInput.type}</h4>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
checked={packageConfigInput.enabled}
|
||||
onChange={(e) => {
|
||||
const enabled = e.target.checked;
|
||||
updatePackageConfigInput({
|
||||
enabled,
|
||||
streams: packageConfigInput.streams.map((stream) => ({
|
||||
...stream,
|
||||
enabled,
|
||||
})),
|
||||
});
|
||||
if (!enabled && isShowingStreams) {
|
||||
setIsShowingStreams(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
{hasErrors ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText>
|
||||
<h4>
|
||||
<EuiTextColor color={hasErrors ? 'danger' : 'default'}>
|
||||
{packageInput.title || packageInput.type}
|
||||
</EuiTextColor>
|
||||
</h4>
|
||||
<EuiText color="danger" size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.errorCountText"
|
||||
defaultMessage="{count, plural, one {# error} other {# errors}}"
|
||||
values={{ count: errorCount }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
{hasErrors ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.inputLevelErrorsTooltip"
|
||||
defaultMessage="Fix configuration errors"
|
||||
/>
|
||||
}
|
||||
position="right"
|
||||
type="alert"
|
||||
iconProps={{ color: 'danger' }}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
checked={packageConfigInput.enabled}
|
||||
onChange={(e) => {
|
||||
const enabled = e.target.checked;
|
||||
updatePackageConfigInput({
|
||||
enabled,
|
||||
streams: packageConfigInput.streams.map((stream) => ({
|
||||
...stream,
|
||||
enabled,
|
||||
})),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.streamsEnabledCountText"
|
||||
defaultMessage="{count} / {total, plural, one {# stream} other {# streams}} enabled"
|
||||
values={{
|
||||
count: (
|
||||
<EuiTextColor color="default">
|
||||
<strong>
|
||||
{packageConfigInput.streams.filter((stream) => stream.enabled).length}
|
||||
</strong>
|
||||
</EuiTextColor>
|
||||
),
|
||||
total: packageInputStreams.length,
|
||||
}}
|
||||
) : null}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
iconType={isShowingStreams ? 'arrowUp' : 'arrowDown'}
|
||||
onClick={() => setIsShowingStreams(!isShowingStreams)}
|
||||
color={hasErrors ? 'danger' : 'text'}
|
||||
aria-label={
|
||||
isShowingStreams
|
||||
? i18n.translate(
|
||||
'xpack.ingestManager.createPackageConfig.stepConfigure.hideStreamsAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Hide {type} inputs',
|
||||
values: {
|
||||
type: packageInput.type,
|
||||
},
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.ingestManager.createPackageConfig.stepConfigure.showStreamsAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Show {type} inputs',
|
||||
values: {
|
||||
type: packageInput.type,
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
iconType={isShowingStreams ? 'arrowUp' : 'arrowDown'}
|
||||
onClick={() => setIsShowingStreams(!isShowingStreams)}
|
||||
color="text"
|
||||
aria-label={
|
||||
isShowingStreams
|
||||
? i18n.translate(
|
||||
'xpack.ingestManager.createPackageConfig.stepConfigure.hideStreamsAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Hide {type} streams',
|
||||
values: {
|
||||
type: packageInput.type,
|
||||
},
|
||||
}
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.ingestManager.createPackageConfig.stepConfigure.showStreamsAriaLabel',
|
||||
{
|
||||
defaultMessage: 'Show {type} streams',
|
||||
values: {
|
||||
type: packageInput.type,
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
{/* Header rule break */}
|
||||
{isShowingStreams ? <FlushHorizontalRule margin="m" /> : null}
|
||||
{/* Header rule break */}
|
||||
{isShowingStreams ? <EuiSpacer size="l" /> : null}
|
||||
|
||||
{/* Input level configuration */}
|
||||
{isShowingStreams && packageInput.vars && packageInput.vars.length ? (
|
||||
<Fragment>
|
||||
<PackageConfigInputConfig
|
||||
packageInputVars={packageInput.vars}
|
||||
packageConfigInput={packageConfigInput}
|
||||
updatePackageConfigInput={updatePackageConfigInput}
|
||||
inputVarsValidationResults={{ vars: inputValidationResults.vars }}
|
||||
forceShowErrors={forceShowErrors}
|
||||
/>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
</Fragment>
|
||||
) : null}
|
||||
{/* Input level configuration */}
|
||||
{isShowingStreams && packageInput.vars && packageInput.vars.length ? (
|
||||
<Fragment>
|
||||
<PackageConfigInputConfig
|
||||
packageInputVars={packageInput.vars}
|
||||
packageConfigInput={packageConfigInput}
|
||||
updatePackageConfigInput={updatePackageConfigInput}
|
||||
inputVarsValidationResults={{ vars: inputValidationResults.vars }}
|
||||
forceShowErrors={forceShowErrors}
|
||||
/>
|
||||
<ShortenedHorizontalRule margin="m" />
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
||||
{/* Per-stream configuration */}
|
||||
{isShowingStreams ? (
|
||||
<EuiFlexGroup direction="column">
|
||||
{packageInputStreams.map((packageInputStream) => {
|
||||
const packageConfigInputStream = packageConfigInput.streams.find(
|
||||
(stream) => stream.dataset.name === packageInputStream.dataset.name
|
||||
);
|
||||
return packageConfigInputStream ? (
|
||||
<EuiFlexItem key={packageInputStream.dataset.name}>
|
||||
{/* Per-stream configuration */}
|
||||
{isShowingStreams ? (
|
||||
<EuiFlexGroup direction="column">
|
||||
{inputStreams.map(({ packageInputStream, packageConfigInputStream }, index) => (
|
||||
<EuiFlexItem key={index}>
|
||||
<PackageConfigInputStreamConfig
|
||||
packageInputStream={packageInputStream}
|
||||
packageConfigInputStream={packageConfigInputStream}
|
||||
packageConfigInputStream={packageConfigInputStream!}
|
||||
updatePackageConfigInputStream={(
|
||||
updatedStream: Partial<PackageConfigInputStream>
|
||||
) => {
|
||||
|
@ -213,17 +226,21 @@ export const PackageConfigInputPanel: React.FunctionComponent<{
|
|||
updatePackageConfigInput(updatedInput);
|
||||
}}
|
||||
inputStreamValidationResults={
|
||||
inputValidationResults.streams![packageConfigInputStream.id]
|
||||
inputValidationResults.streams![packageConfigInputStream!.id]
|
||||
}
|
||||
forceShowErrors={forceShowErrors}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiHorizontalRule margin="none" />
|
||||
{index !== inputStreams.length - 1 ? (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<ShortenedHorizontalRule margin="none" />
|
||||
</>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
) : null;
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
) : null}
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -3,18 +3,17 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { useState, Fragment } from 'react';
|
||||
import React, { useState, Fragment, memo, useMemo } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiFlexGrid,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSwitch,
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
EuiButtonEmpty,
|
||||
EuiTextColor,
|
||||
EuiIconTip,
|
||||
} from '@elastic/eui';
|
||||
import { PackageConfigInputStream, RegistryStream, RegistryVarsEntry } from '../../../../types';
|
||||
import {
|
||||
|
@ -30,153 +29,157 @@ export const PackageConfigInputStreamConfig: React.FunctionComponent<{
|
|||
updatePackageConfigInputStream: (updatedStream: Partial<PackageConfigInputStream>) => void;
|
||||
inputStreamValidationResults: PackageConfigConfigValidationResults;
|
||||
forceShowErrors?: boolean;
|
||||
}> = ({
|
||||
packageInputStream,
|
||||
packageConfigInputStream,
|
||||
updatePackageConfigInputStream,
|
||||
inputStreamValidationResults,
|
||||
forceShowErrors,
|
||||
}) => {
|
||||
// Showing advanced options toggle state
|
||||
const [isShowingAdvanced, setIsShowingAdvanced] = useState<boolean>(false);
|
||||
}> = memo(
|
||||
({
|
||||
packageInputStream,
|
||||
packageConfigInputStream,
|
||||
updatePackageConfigInputStream,
|
||||
inputStreamValidationResults,
|
||||
forceShowErrors,
|
||||
}) => {
|
||||
// Showing advanced options toggle state
|
||||
const [isShowingAdvanced, setIsShowingAdvanced] = useState<boolean>();
|
||||
|
||||
// Errors state
|
||||
const hasErrors = forceShowErrors && validationHasErrors(inputStreamValidationResults);
|
||||
// Errors state
|
||||
const hasErrors = forceShowErrors && validationHasErrors(inputStreamValidationResults);
|
||||
|
||||
const requiredVars: RegistryVarsEntry[] = [];
|
||||
const advancedVars: RegistryVarsEntry[] = [];
|
||||
const requiredVars: RegistryVarsEntry[] = [];
|
||||
const advancedVars: RegistryVarsEntry[] = [];
|
||||
|
||||
if (packageInputStream.vars && packageInputStream.vars.length) {
|
||||
packageInputStream.vars.forEach((varDef) => {
|
||||
if (isAdvancedVar(varDef)) {
|
||||
advancedVars.push(varDef);
|
||||
} else {
|
||||
requiredVars.push(varDef);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (packageInputStream.vars && packageInputStream.vars.length) {
|
||||
packageInputStream.vars.forEach((varDef) => {
|
||||
if (isAdvancedVar(varDef)) {
|
||||
advancedVars.push(varDef);
|
||||
} else {
|
||||
requiredVars.push(varDef);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiSwitch
|
||||
label={
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTextColor color={hasErrors ? 'danger' : 'default'}>
|
||||
{packageInputStream.title}
|
||||
</EuiTextColor>
|
||||
</EuiFlexItem>
|
||||
{hasErrors ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.streamLevelErrorsTooltip"
|
||||
defaultMessage="Fix configuration errors"
|
||||
/>
|
||||
}
|
||||
position="right"
|
||||
type="alert"
|
||||
iconProps={{ color: 'danger' }}
|
||||
const advancedVarsWithErrorsCount: number = useMemo(
|
||||
() =>
|
||||
advancedVars.filter(
|
||||
({ name: varName }) => inputStreamValidationResults.vars?.[varName]?.length
|
||||
).length,
|
||||
[advancedVars, inputStreamValidationResults.vars]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGrid columns={2}>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="none" alignItems="flexStart">
|
||||
<EuiFlexItem grow={1} />
|
||||
<EuiFlexItem grow={5}>
|
||||
<EuiSwitch
|
||||
label={packageInputStream.title}
|
||||
checked={packageConfigInputStream.enabled}
|
||||
onChange={(e) => {
|
||||
const enabled = e.target.checked;
|
||||
updatePackageConfigInputStream({
|
||||
enabled,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{packageInputStream.description ? (
|
||||
<Fragment>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s" color="subdued">
|
||||
<ReactMarkdown source={packageInputStream.description} />
|
||||
</EuiText>
|
||||
</Fragment>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
{requiredVars.map((varDef) => {
|
||||
const { name: varName, type: varType } = varDef;
|
||||
const value = packageConfigInputStream.vars![varName].value;
|
||||
return (
|
||||
<EuiFlexItem key={varName}>
|
||||
<PackageConfigInputVarField
|
||||
varDef={varDef}
|
||||
value={value}
|
||||
onChange={(newValue: any) => {
|
||||
updatePackageConfigInputStream({
|
||||
vars: {
|
||||
...packageConfigInputStream.vars,
|
||||
[varName]: {
|
||||
type: varType,
|
||||
value: newValue,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
errors={inputStreamValidationResults.vars![varName]}
|
||||
forceShowErrors={forceShowErrors}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
checked={packageConfigInputStream.enabled}
|
||||
onChange={(e) => {
|
||||
const enabled = e.target.checked;
|
||||
updatePackageConfigInputStream({
|
||||
enabled,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{packageInputStream.description ? (
|
||||
<Fragment>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s" color="subdued">
|
||||
<ReactMarkdown source={packageInputStream.description} />
|
||||
</EuiText>
|
||||
</Fragment>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
{requiredVars.map((varDef) => {
|
||||
const { name: varName, type: varType } = varDef;
|
||||
const value = packageConfigInputStream.vars![varName].value;
|
||||
return (
|
||||
<EuiFlexItem key={varName}>
|
||||
<PackageConfigInputVarField
|
||||
varDef={varDef}
|
||||
value={value}
|
||||
onChange={(newValue: any) => {
|
||||
updatePackageConfigInputStream({
|
||||
vars: {
|
||||
...packageConfigInputStream.vars,
|
||||
[varName]: {
|
||||
type: varType,
|
||||
value: newValue,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
errors={inputStreamValidationResults.vars![varName]}
|
||||
forceShowErrors={forceShowErrors}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
{advancedVars.length ? (
|
||||
<Fragment>
|
||||
<EuiFlexItem>
|
||||
{/* Wrapper div to prevent button from going full width */}
|
||||
<div>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType={isShowingAdvanced ? 'arrowDown' : 'arrowRight'}
|
||||
onClick={() => setIsShowingAdvanced(!isShowingAdvanced)}
|
||||
flush="left"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.toggleAdvancedOptionsButtonText"
|
||||
defaultMessage="Advanced options"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
{isShowingAdvanced
|
||||
? advancedVars.map((varDef) => {
|
||||
const { name: varName, type: varType } = varDef;
|
||||
const value = packageConfigInputStream.vars![varName].value;
|
||||
return (
|
||||
<EuiFlexItem key={varName}>
|
||||
<PackageConfigInputVarField
|
||||
varDef={varDef}
|
||||
value={value}
|
||||
onChange={(newValue: any) => {
|
||||
updatePackageConfigInputStream({
|
||||
vars: {
|
||||
...packageConfigInputStream.vars,
|
||||
[varName]: {
|
||||
type: varType,
|
||||
value: newValue,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
errors={inputStreamValidationResults.vars![varName]}
|
||||
forceShowErrors={forceShowErrors}
|
||||
);
|
||||
})}
|
||||
{advancedVars.length ? (
|
||||
<Fragment>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType={isShowingAdvanced ? 'arrowDown' : 'arrowRight'}
|
||||
onClick={() => setIsShowingAdvanced(!isShowingAdvanced)}
|
||||
flush="left"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.toggleAdvancedOptionsButtonText"
|
||||
defaultMessage="Advanced options"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
{!isShowingAdvanced && hasErrors && advancedVarsWithErrorsCount ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="danger" size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.errorCountText"
|
||||
defaultMessage="{count, plural, one {# error} other {# errors}}"
|
||||
values={{ count: advancedVarsWithErrorsCount }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</Fragment>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{isShowingAdvanced
|
||||
? advancedVars.map((varDef) => {
|
||||
const { name: varName, type: varType } = varDef;
|
||||
const value = packageConfigInputStream.vars![varName].value;
|
||||
return (
|
||||
<EuiFlexItem key={varName}>
|
||||
<PackageConfigInputVarField
|
||||
varDef={varDef}
|
||||
value={value}
|
||||
onChange={(newValue: any) => {
|
||||
updatePackageConfigInputStream({
|
||||
vars: {
|
||||
...packageConfigInputStream.vars,
|
||||
[varName]: {
|
||||
type: varType,
|
||||
value: newValue,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
errors={inputStreamValidationResults.vars![varName]}
|
||||
forceShowErrors={forceShowErrors}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
</Fragment>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, memo, useMemo } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiFormRow, EuiFieldText, EuiComboBox, EuiText, EuiCodeEditor } from '@elastic/eui';
|
||||
|
@ -18,13 +18,13 @@ export const PackageConfigInputVarField: React.FunctionComponent<{
|
|||
onChange: (newValue: any) => void;
|
||||
errors?: string[] | null;
|
||||
forceShowErrors?: boolean;
|
||||
}> = ({ varDef, value, onChange, errors: varErrors, forceShowErrors }) => {
|
||||
}> = memo(({ varDef, value, onChange, errors: varErrors, forceShowErrors }) => {
|
||||
const [isDirty, setIsDirty] = useState<boolean>(false);
|
||||
const { multi, required, type, title, name, description } = varDef;
|
||||
const isInvalid = (isDirty || forceShowErrors) && !!varErrors;
|
||||
const errors = isInvalid ? varErrors : null;
|
||||
|
||||
const renderField = () => {
|
||||
const field = useMemo(() => {
|
||||
if (multi) {
|
||||
return (
|
||||
<EuiComboBox
|
||||
|
@ -67,7 +67,7 @@ export const PackageConfigInputVarField: React.FunctionComponent<{
|
|||
onBlur={() => setIsDirty(true)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}, [isInvalid, multi, onChange, type, value]);
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
|
@ -86,7 +86,7 @@ export const PackageConfigInputVarField: React.FunctionComponent<{
|
|||
}
|
||||
helpText={<ReactMarkdown source={description} />}
|
||||
>
|
||||
{renderField()}
|
||||
{field}
|
||||
</EuiFormRow>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
import React, { useState, useEffect, useMemo, useCallback, ReactEventHandler } from 'react';
|
||||
import { useRouteMatch, useHistory } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
|
@ -31,6 +32,7 @@ import {
|
|||
useConfig,
|
||||
sendGetAgentStatus,
|
||||
} from '../../../hooks';
|
||||
import { Loading } from '../../../components';
|
||||
import { ConfirmDeployConfigModal } from '../components';
|
||||
import { CreatePackageConfigPageLayout } from './components';
|
||||
import { CreatePackageConfigFrom, PackageConfigFormState } from './types';
|
||||
|
@ -45,6 +47,12 @@ import { StepConfigurePackage } from './step_configure_package';
|
|||
import { StepDefinePackageConfig } from './step_define_package_config';
|
||||
import { useIntraAppState } from '../../../hooks/use_intra_app_state';
|
||||
|
||||
const StepsWithLessPadding = styled(EuiSteps)`
|
||||
.euiStep__content {
|
||||
padding-bottom: ${(props) => props.theme.eui.paddingSizes.m};
|
||||
}
|
||||
`;
|
||||
|
||||
export const CreatePackageConfigPage: React.FunctionComponent = () => {
|
||||
const {
|
||||
notifications,
|
||||
|
@ -75,6 +83,7 @@ export const CreatePackageConfigPage: React.FunctionComponent = () => {
|
|||
// Agent config and package info states
|
||||
const [agentConfig, setAgentConfig] = useState<AgentConfig>();
|
||||
const [packageInfo, setPackageInfo] = useState<PackageInfo>();
|
||||
const [isLoadingSecondStep, setIsLoadingSecondStep] = useState<boolean>(false);
|
||||
|
||||
const agentConfigId = agentConfig?.id;
|
||||
// Retrieve agent count
|
||||
|
@ -151,40 +160,47 @@ export const CreatePackageConfigPage: React.FunctionComponent = () => {
|
|||
|
||||
const hasErrors = validationResults ? validationHasErrors(validationResults) : false;
|
||||
|
||||
// Update package config validation
|
||||
const updatePackageConfigValidation = useCallback(
|
||||
(newPackageConfig?: NewPackageConfig) => {
|
||||
if (packageInfo) {
|
||||
const newValidationResult = validatePackageConfig(
|
||||
newPackageConfig || packageConfig,
|
||||
packageInfo
|
||||
);
|
||||
setValidationResults(newValidationResult);
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug('Package config validation results', newValidationResult);
|
||||
|
||||
return newValidationResult;
|
||||
}
|
||||
},
|
||||
[packageConfig, packageInfo]
|
||||
);
|
||||
|
||||
// Update package config method
|
||||
const updatePackageConfig = (updatedFields: Partial<NewPackageConfig>) => {
|
||||
const newPackageConfig = {
|
||||
...packageConfig,
|
||||
...updatedFields,
|
||||
};
|
||||
setPackageConfig(newPackageConfig);
|
||||
const updatePackageConfig = useCallback(
|
||||
(updatedFields: Partial<NewPackageConfig>) => {
|
||||
const newPackageConfig = {
|
||||
...packageConfig,
|
||||
...updatedFields,
|
||||
};
|
||||
setPackageConfig(newPackageConfig);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug('Package config updated', newPackageConfig);
|
||||
const newValidationResults = updatePackageConfigValidation(newPackageConfig);
|
||||
const hasPackage = newPackageConfig.package;
|
||||
const hasValidationErrors = newValidationResults
|
||||
? validationHasErrors(newValidationResults)
|
||||
: false;
|
||||
const hasAgentConfig = newPackageConfig.config_id && newPackageConfig.config_id !== '';
|
||||
if (hasPackage && hasAgentConfig && !hasValidationErrors) {
|
||||
setFormState('VALID');
|
||||
}
|
||||
};
|
||||
|
||||
const updatePackageConfigValidation = (newPackageConfig?: NewPackageConfig) => {
|
||||
if (packageInfo) {
|
||||
const newValidationResult = validatePackageConfig(
|
||||
newPackageConfig || packageConfig,
|
||||
packageInfo
|
||||
);
|
||||
setValidationResults(newValidationResult);
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug('Package config validation results', newValidationResult);
|
||||
|
||||
return newValidationResult;
|
||||
}
|
||||
};
|
||||
console.debug('Package config updated', newPackageConfig);
|
||||
const newValidationResults = updatePackageConfigValidation(newPackageConfig);
|
||||
const hasPackage = newPackageConfig.package;
|
||||
const hasValidationErrors = newValidationResults
|
||||
? validationHasErrors(newValidationResults)
|
||||
: false;
|
||||
const hasAgentConfig = newPackageConfig.config_id && newPackageConfig.config_id !== '';
|
||||
if (hasPackage && hasAgentConfig && !hasValidationErrors) {
|
||||
setFormState('VALID');
|
||||
}
|
||||
},
|
||||
[packageConfig, updatePackageConfigValidation]
|
||||
);
|
||||
|
||||
// Cancel path
|
||||
const cancelUrl = useMemo(() => {
|
||||
|
@ -276,6 +292,7 @@ export const CreatePackageConfigPage: React.FunctionComponent = () => {
|
|||
updatePackageInfo={updatePackageInfo}
|
||||
agentConfig={agentConfig}
|
||||
updateAgentConfig={updateAgentConfig}
|
||||
setIsLoadingSecondStep={setIsLoadingSecondStep}
|
||||
/>
|
||||
),
|
||||
[pkgkey, updatePackageInfo, agentConfig, updateAgentConfig]
|
||||
|
@ -288,11 +305,47 @@ export const CreatePackageConfigPage: React.FunctionComponent = () => {
|
|||
updateAgentConfig={updateAgentConfig}
|
||||
packageInfo={packageInfo}
|
||||
updatePackageInfo={updatePackageInfo}
|
||||
setIsLoadingSecondStep={setIsLoadingSecondStep}
|
||||
/>
|
||||
),
|
||||
[configId, updateAgentConfig, packageInfo, updatePackageInfo]
|
||||
);
|
||||
|
||||
const stepConfigurePackage = useMemo(
|
||||
() =>
|
||||
isLoadingSecondStep ? (
|
||||
<Loading />
|
||||
) : agentConfig && packageInfo ? (
|
||||
<>
|
||||
<StepDefinePackageConfig
|
||||
agentConfig={agentConfig}
|
||||
packageInfo={packageInfo}
|
||||
packageConfig={packageConfig}
|
||||
updatePackageConfig={updatePackageConfig}
|
||||
validationResults={validationResults!}
|
||||
/>
|
||||
<StepConfigurePackage
|
||||
packageInfo={packageInfo}
|
||||
packageConfig={packageConfig}
|
||||
updatePackageConfig={updatePackageConfig}
|
||||
validationResults={validationResults!}
|
||||
submitAttempted={formState === 'INVALID'}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div />
|
||||
),
|
||||
[
|
||||
agentConfig,
|
||||
formState,
|
||||
isLoadingSecondStep,
|
||||
packageConfig,
|
||||
packageInfo,
|
||||
updatePackageConfig,
|
||||
validationResults,
|
||||
]
|
||||
);
|
||||
|
||||
const steps: EuiStepProps[] = [
|
||||
from === 'package'
|
||||
? {
|
||||
|
@ -312,42 +365,14 @@ export const CreatePackageConfigPage: React.FunctionComponent = () => {
|
|||
},
|
||||
{
|
||||
title: i18n.translate(
|
||||
'xpack.ingestManager.createPackageConfig.stepDefinePackageConfigTitle',
|
||||
'xpack.ingestManager.createPackageConfig.stepConfigurePackageConfigTitle',
|
||||
{
|
||||
defaultMessage: 'Configure integration',
|
||||
}
|
||||
),
|
||||
status: !packageInfo || !agentConfig ? 'disabled' : undefined,
|
||||
children:
|
||||
agentConfig && packageInfo ? (
|
||||
<StepDefinePackageConfig
|
||||
agentConfig={agentConfig}
|
||||
packageInfo={packageInfo}
|
||||
packageConfig={packageConfig}
|
||||
updatePackageConfig={updatePackageConfig}
|
||||
validationResults={validationResults!}
|
||||
/>
|
||||
) : null,
|
||||
},
|
||||
{
|
||||
title: i18n.translate(
|
||||
'xpack.ingestManager.createPackageConfig.stepConfigurePackageConfigTitle',
|
||||
{
|
||||
defaultMessage: 'Select the data you want to collect',
|
||||
}
|
||||
),
|
||||
status: !packageInfo || !agentConfig ? 'disabled' : undefined,
|
||||
status: !packageInfo || !agentConfig || isLoadingSecondStep ? 'disabled' : undefined,
|
||||
'data-test-subj': 'dataCollectionSetupStep',
|
||||
children:
|
||||
agentConfig && packageInfo ? (
|
||||
<StepConfigurePackage
|
||||
packageInfo={packageInfo}
|
||||
packageConfig={packageConfig}
|
||||
updatePackageConfig={updatePackageConfig}
|
||||
validationResults={validationResults!}
|
||||
submitAttempted={formState === 'INVALID'}
|
||||
/>
|
||||
) : null,
|
||||
children: stepConfigurePackage,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -371,7 +396,7 @@ export const CreatePackageConfigPage: React.FunctionComponent = () => {
|
|||
: agentConfig && (
|
||||
<ConfigurationBreadcrumb configName={agentConfig.name} configId={agentConfig.id} />
|
||||
)}
|
||||
<EuiSteps steps={steps} />
|
||||
<StepsWithLessPadding steps={steps} />
|
||||
<EuiSpacer size="l" />
|
||||
{/* TODO #64541 - Remove classes */}
|
||||
<EuiBottomBar
|
||||
|
@ -383,36 +408,48 @@ export const CreatePackageConfigPage: React.FunctionComponent = () => {
|
|||
: undefined
|
||||
}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd">
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
|
||||
<EuiButtonEmpty
|
||||
color="ghost"
|
||||
href={cancelUrl}
|
||||
onClick={cancelClickHandler}
|
||||
data-test-subj="createPackageConfigCancelButton"
|
||||
>
|
||||
{!isLoadingSecondStep && agentConfig && packageInfo && formState === 'INVALID' ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.cancelButton"
|
||||
defaultMessage="Cancel"
|
||||
id="xpack.ingestManager.createPackageConfig.errorOnSaveText"
|
||||
defaultMessage="Your integration configuration has errors. Please fix them before saving."
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={onSubmit}
|
||||
isLoading={formState === 'LOADING'}
|
||||
disabled={formState !== 'VALID'}
|
||||
iconType="save"
|
||||
color="primary"
|
||||
fill
|
||||
data-test-subj="createPackageConfigSaveButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.saveButton"
|
||||
defaultMessage="Save integration"
|
||||
/>
|
||||
</EuiButton>
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
|
||||
<EuiButtonEmpty
|
||||
color="ghost"
|
||||
href={cancelUrl}
|
||||
onClick={cancelClickHandler}
|
||||
data-test-subj="createPackageConfigCancelButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.cancelButton"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={onSubmit}
|
||||
isLoading={formState === 'LOADING'}
|
||||
disabled={formState !== 'VALID'}
|
||||
iconType="save"
|
||||
color="primary"
|
||||
fill
|
||||
data-test-subj="createPackageConfigSaveButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.saveButton"
|
||||
defaultMessage="Save integration"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiBottomBar>
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { hasInvalidButRequiredVar } from './has_invalid_but_required_var';
|
||||
|
||||
describe('Ingest Manager - hasInvalidButRequiredVar', () => {
|
||||
it('returns true for invalid & required vars', () => {
|
||||
expect(
|
||||
hasInvalidButRequiredVar(
|
||||
[
|
||||
{
|
||||
name: 'mock_var',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
{}
|
||||
)
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
hasInvalidButRequiredVar(
|
||||
[
|
||||
{
|
||||
name: 'mock_var',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
{
|
||||
mock_var: {
|
||||
value: undefined,
|
||||
},
|
||||
}
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for valid & required vars', () => {
|
||||
expect(
|
||||
hasInvalidButRequiredVar(
|
||||
[
|
||||
{
|
||||
name: 'mock_var',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
{
|
||||
mock_var: {
|
||||
value: 'foo',
|
||||
},
|
||||
}
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false for optional vars', () => {
|
||||
expect(
|
||||
hasInvalidButRequiredVar(
|
||||
[
|
||||
{
|
||||
name: 'mock_var',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
{
|
||||
mock_var: {
|
||||
value: 'foo',
|
||||
},
|
||||
}
|
||||
)
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
hasInvalidButRequiredVar(
|
||||
[
|
||||
{
|
||||
name: 'mock_var',
|
||||
type: 'text',
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
{
|
||||
mock_var: {
|
||||
value: undefined,
|
||||
},
|
||||
}
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { PackageConfigConfigRecord, RegistryVarsEntry } from '../../../../types';
|
||||
import { validatePackageConfigConfig } from './';
|
||||
|
||||
export const hasInvalidButRequiredVar = (
|
||||
registryVars?: RegistryVarsEntry[],
|
||||
packageConfigVars?: PackageConfigConfigRecord
|
||||
): boolean => {
|
||||
return (
|
||||
(registryVars && !packageConfigVars) ||
|
||||
Boolean(
|
||||
registryVars &&
|
||||
registryVars.find(
|
||||
(registryVar) =>
|
||||
registryVar.required &&
|
||||
(!packageConfigVars ||
|
||||
!packageConfigVars[registryVar.name] ||
|
||||
validatePackageConfigConfig(packageConfigVars[registryVar.name], registryVar)?.length)
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
|
@ -4,10 +4,13 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
export { isAdvancedVar } from './is_advanced_var';
|
||||
export { hasInvalidButRequiredVar } from './has_invalid_but_required_var';
|
||||
export {
|
||||
PackageConfigValidationResults,
|
||||
PackageConfigConfigValidationResults,
|
||||
PackageConfigInputValidationResults,
|
||||
validatePackageConfig,
|
||||
validatePackageConfigConfig,
|
||||
validationHasErrors,
|
||||
countValidationErrors,
|
||||
} from './validate_package_config';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { RegistryVarsEntry } from '../../../../types';
|
||||
|
||||
export const isAdvancedVar = (varDef: RegistryVarsEntry): boolean => {
|
||||
if (varDef.show_user || (varDef.required && !varDef.default)) {
|
||||
if (varDef.show_user || (varDef.required && varDef.default === undefined)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -171,7 +171,7 @@ export const validatePackageConfig = (
|
|||
return validationResults;
|
||||
};
|
||||
|
||||
const validatePackageConfigConfig = (
|
||||
export const validatePackageConfigConfig = (
|
||||
configEntry: PackageConfigConfigRecordEntry,
|
||||
varDef: RegistryVarsEntry
|
||||
): string[] | null => {
|
||||
|
@ -237,13 +237,22 @@ const validatePackageConfigConfig = (
|
|||
return errors.length ? errors : null;
|
||||
};
|
||||
|
||||
export const countValidationErrors = (
|
||||
validationResults:
|
||||
| PackageConfigValidationResults
|
||||
| PackageConfigInputValidationResults
|
||||
| PackageConfigConfigValidationResults
|
||||
): number => {
|
||||
const flattenedValidation = getFlattenedObject(validationResults);
|
||||
const errors = Object.values(flattenedValidation).filter((value) => Boolean(value)) || [];
|
||||
return errors.length;
|
||||
};
|
||||
|
||||
export const validationHasErrors = (
|
||||
validationResults:
|
||||
| PackageConfigValidationResults
|
||||
| PackageConfigInputValidationResults
|
||||
| PackageConfigConfigValidationResults
|
||||
) => {
|
||||
const flattenedValidation = getFlattenedObject(validationResults);
|
||||
|
||||
return !!Object.entries(flattenedValidation).find(([, value]) => !!value);
|
||||
): boolean => {
|
||||
return countValidationErrors(validationResults) > 0;
|
||||
};
|
||||
|
|
|
@ -4,12 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiPanel, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiCallOut } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { PackageInfo, RegistryStream, NewPackageConfig, PackageConfigInput } from '../../../types';
|
||||
import { Loading } from '../../../components';
|
||||
import { PackageConfigValidationResults, validationHasErrors } from './services';
|
||||
import { PackageConfigValidationResults } from './services';
|
||||
import { PackageConfigInputPanel, CustomPackageConfig } from './components';
|
||||
import { CreatePackageConfigFrom } from './types';
|
||||
|
||||
|
@ -52,8 +50,6 @@ export const StepConfigurePackage: React.FunctionComponent<{
|
|||
validationResults,
|
||||
submitAttempted,
|
||||
}) => {
|
||||
const hasErrors = validationResults ? validationHasErrors(validationResults) : false;
|
||||
|
||||
// Configure inputs (and their streams)
|
||||
// Assume packages only export one config template for now
|
||||
const renderConfigureInputs = () =>
|
||||
|
@ -61,76 +57,50 @@ export const StepConfigurePackage: React.FunctionComponent<{
|
|||
packageInfo.config_templates[0] &&
|
||||
packageInfo.config_templates[0].inputs &&
|
||||
packageInfo.config_templates[0].inputs.length ? (
|
||||
<EuiFlexGroup direction="column">
|
||||
{packageInfo.config_templates[0].inputs.map((packageInput) => {
|
||||
const packageConfigInput = packageConfig.inputs.find(
|
||||
(input) => input.type === packageInput.type
|
||||
);
|
||||
const packageInputStreams = findStreamsForInputType(packageInput.type, packageInfo);
|
||||
return packageConfigInput ? (
|
||||
<EuiFlexItem key={packageInput.type}>
|
||||
<PackageConfigInputPanel
|
||||
packageInput={packageInput}
|
||||
packageInputStreams={packageInputStreams}
|
||||
packageConfigInput={packageConfigInput}
|
||||
updatePackageConfigInput={(updatedInput: Partial<PackageConfigInput>) => {
|
||||
const indexOfUpdatedInput = packageConfig.inputs.findIndex(
|
||||
(input) => input.type === packageInput.type
|
||||
);
|
||||
const newInputs = [...packageConfig.inputs];
|
||||
newInputs[indexOfUpdatedInput] = {
|
||||
...newInputs[indexOfUpdatedInput],
|
||||
...updatedInput,
|
||||
};
|
||||
updatePackageConfig({
|
||||
inputs: newInputs,
|
||||
});
|
||||
}}
|
||||
inputValidationResults={validationResults!.inputs![packageConfigInput.type]}
|
||||
forceShowErrors={submitAttempted}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null;
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
<>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
{packageInfo.config_templates[0].inputs.map((packageInput) => {
|
||||
const packageConfigInput = packageConfig.inputs.find(
|
||||
(input) => input.type === packageInput.type
|
||||
);
|
||||
const packageInputStreams = findStreamsForInputType(packageInput.type, packageInfo);
|
||||
return packageConfigInput ? (
|
||||
<EuiFlexItem key={packageInput.type}>
|
||||
<PackageConfigInputPanel
|
||||
packageInput={packageInput}
|
||||
packageInputStreams={packageInputStreams}
|
||||
packageConfigInput={packageConfigInput}
|
||||
updatePackageConfigInput={(updatedInput: Partial<PackageConfigInput>) => {
|
||||
const indexOfUpdatedInput = packageConfig.inputs.findIndex(
|
||||
(input) => input.type === packageInput.type
|
||||
);
|
||||
const newInputs = [...packageConfig.inputs];
|
||||
newInputs[indexOfUpdatedInput] = {
|
||||
...newInputs[indexOfUpdatedInput],
|
||||
...updatedInput,
|
||||
};
|
||||
updatePackageConfig({
|
||||
inputs: newInputs,
|
||||
});
|
||||
}}
|
||||
inputValidationResults={validationResults!.inputs![packageConfigInput.type]}
|
||||
forceShowErrors={submitAttempted}
|
||||
/>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
</EuiFlexItem>
|
||||
) : null;
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
) : (
|
||||
<EuiPanel>
|
||||
<CustomPackageConfig
|
||||
from={from}
|
||||
packageName={packageInfo.name}
|
||||
packageConfig={packageConfig}
|
||||
packageConfigId={packageConfigId}
|
||||
/>
|
||||
</EuiPanel>
|
||||
<CustomPackageConfig
|
||||
from={from}
|
||||
packageName={packageInfo.name}
|
||||
packageConfig={packageConfig}
|
||||
packageConfigId={packageConfigId}
|
||||
/>
|
||||
);
|
||||
|
||||
return validationResults ? (
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
<EuiFlexItem>{renderConfigureInputs()}</EuiFlexItem>
|
||||
{hasErrors && submitAttempted ? (
|
||||
<EuiFlexItem>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.ingestManager.createPackageConfig.stepConfigure.validationErrorTitle',
|
||||
{
|
||||
defaultMessage: 'Your integration configuration has errors',
|
||||
}
|
||||
)}
|
||||
color="danger"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.validationErrorText"
|
||||
defaultMessage="Please fix the above errors before continuing"
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m" />
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<Loading />
|
||||
);
|
||||
return validationResults ? renderConfigureInputs() : <Loading />;
|
||||
};
|
||||
|
|
|
@ -3,17 +3,18 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { useEffect, useState, Fragment } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiFlexGrid,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiFieldText,
|
||||
EuiButtonEmpty,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiComboBox,
|
||||
EuiDescribedFormGroup,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
import { AgentConfig, PackageInfo, PackageConfig, NewPackageConfig } from '../../../types';
|
||||
import { packageToPackageConfigInputs } from '../../../services';
|
||||
|
@ -28,7 +29,7 @@ export const StepDefinePackageConfig: React.FunctionComponent<{
|
|||
validationResults: PackageConfigValidationResults;
|
||||
}> = ({ agentConfig, packageInfo, packageConfig, updatePackageConfig, validationResults }) => {
|
||||
// Form show/hide states
|
||||
const [isShowingAdvancedDefine, setIsShowingAdvancedDefine] = useState<boolean>(false);
|
||||
const [isShowingAdvanced, setIsShowingAdvanced] = useState<boolean>(false);
|
||||
|
||||
// Update package config's package and config info
|
||||
useEffect(() => {
|
||||
|
@ -74,111 +75,140 @@ export const StepDefinePackageConfig: React.FunctionComponent<{
|
|||
]);
|
||||
|
||||
return validationResults ? (
|
||||
<>
|
||||
<EuiFlexGrid columns={2}>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
isInvalid={!!validationResults.name}
|
||||
error={validationResults.name}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.packageConfigNameInputLabel"
|
||||
defaultMessage="Integration name"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={packageConfig.name}
|
||||
onChange={(e) =>
|
||||
updatePackageConfig({
|
||||
name: e.target.value,
|
||||
})
|
||||
}
|
||||
data-test-subj="packageConfigNameInput"
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.integrationSettingsSectionTitle"
|
||||
defaultMessage="Integration settings"
|
||||
/>
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.integrationSettingsSectionDescription"
|
||||
defaultMessage="Choose a name and description to help identify how this integration will be used."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<>
|
||||
{/* Name */}
|
||||
<EuiFormRow
|
||||
isInvalid={!!validationResults.name}
|
||||
error={validationResults.name}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.packageConfigNameInputLabel"
|
||||
defaultMessage="Integration name"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.packageConfigDescriptionInputLabel"
|
||||
defaultMessage="Description"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={packageConfig.name}
|
||||
onChange={(e) =>
|
||||
updatePackageConfig({
|
||||
name: e.target.value,
|
||||
})
|
||||
}
|
||||
labelAppend={
|
||||
<EuiText size="xs" color="subdued">
|
||||
data-test-subj="packageConfigNameInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
{/* Description */}
|
||||
<EuiFormRow
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.packageConfigDescriptionInputLabel"
|
||||
defaultMessage="Description"
|
||||
/>
|
||||
}
|
||||
labelAppend={
|
||||
<EuiText size="xs" color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.inputVarFieldOptionalLabel"
|
||||
defaultMessage="Optional"
|
||||
/>
|
||||
</EuiText>
|
||||
}
|
||||
isInvalid={!!validationResults.description}
|
||||
error={validationResults.description}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={packageConfig.description}
|
||||
onChange={(e) =>
|
||||
updatePackageConfig({
|
||||
description: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
{/* Advanced options toggle */}
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
iconType={isShowingAdvanced ? 'arrowDown' : 'arrowRight'}
|
||||
onClick={() => setIsShowingAdvanced(!isShowingAdvanced)}
|
||||
flush="left"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.advancedOptionsToggleLinkText"
|
||||
defaultMessage="Advanced options"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
{!isShowingAdvanced && !!validationResults.namespace ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText color="danger" size="s">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.inputVarFieldOptionalLabel"
|
||||
defaultMessage="Optional"
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.errorCountText"
|
||||
defaultMessage="{count, plural, one {# error} other {# errors}}"
|
||||
values={{ count: 1 }}
|
||||
/>
|
||||
</EuiText>
|
||||
}
|
||||
isInvalid={!!validationResults.description}
|
||||
error={validationResults.description}
|
||||
>
|
||||
<EuiFieldText
|
||||
value={packageConfig.description}
|
||||
onChange={(e) =>
|
||||
updatePackageConfig({
|
||||
description: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButtonEmpty
|
||||
flush="left"
|
||||
size="xs"
|
||||
iconType={isShowingAdvancedDefine ? 'arrowUp' : 'arrowDown'}
|
||||
onClick={() => setIsShowingAdvancedDefine(!isShowingAdvancedDefine)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.advancedOptionsToggleLinkText"
|
||||
defaultMessage="Advanced options"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
{/* Todo: Populate list of existing namespaces */}
|
||||
{isShowingAdvancedDefine || !!validationResults.namespace ? (
|
||||
<Fragment>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGrid columns={2}>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
isInvalid={!!validationResults.namespace}
|
||||
error={validationResults.namespace}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.packageConfigNamespaceInputLabel"
|
||||
defaultMessage="Namespace"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiComboBox
|
||||
noSuggestions
|
||||
singleSelection={true}
|
||||
selectedOptions={
|
||||
packageConfig.namespace ? [{ label: packageConfig.namespace }] : []
|
||||
}
|
||||
onCreateOption={(newNamespace: string) => {
|
||||
updatePackageConfig({
|
||||
namespace: newNamespace,
|
||||
});
|
||||
}}
|
||||
onChange={(newNamespaces: Array<{ label: string }>) => {
|
||||
updatePackageConfig({
|
||||
namespace: newNamespaces.length ? newNamespaces[0].label : '',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGrid>
|
||||
</Fragment>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
|
||||
{/* Advanced options content */}
|
||||
{/* Todo: Populate list of existing namespaces */}
|
||||
{isShowingAdvanced ? (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFormRow
|
||||
isInvalid={!!validationResults.namespace}
|
||||
error={validationResults.namespace}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.stepConfigure.packageConfigNamespaceInputLabel"
|
||||
defaultMessage="Namespace"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiComboBox
|
||||
noSuggestions
|
||||
singleSelection={true}
|
||||
selectedOptions={
|
||||
packageConfig.namespace ? [{ label: packageConfig.namespace }] : []
|
||||
}
|
||||
onCreateOption={(newNamespace: string) => {
|
||||
updatePackageConfig({
|
||||
namespace: newNamespace,
|
||||
});
|
||||
}}
|
||||
onChange={(newNamespaces: Array<{ label: string }>) => {
|
||||
updatePackageConfig({
|
||||
namespace: newNamespaces.length ? newNamespaces[0].label : '',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
</EuiDescribedFormGroup>
|
||||
) : (
|
||||
<Loading />
|
||||
);
|
||||
|
|
|
@ -6,29 +6,50 @@
|
|||
import React, { useEffect, useState, Fragment } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSelectable, EuiSpacer, EuiTextColor } from '@elastic/eui';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSelectable,
|
||||
EuiSpacer,
|
||||
EuiTextColor,
|
||||
EuiPortal,
|
||||
EuiButtonEmpty,
|
||||
} from '@elastic/eui';
|
||||
import { Error } from '../../../components';
|
||||
import { AgentConfig, PackageInfo, GetAgentConfigsResponseItem } from '../../../types';
|
||||
import { isPackageLimited, doesAgentConfigAlreadyIncludePackage } from '../../../services';
|
||||
import { useGetPackageInfoByKey, useGetAgentConfigs, sendGetOneAgentConfig } from '../../../hooks';
|
||||
import {
|
||||
useGetPackageInfoByKey,
|
||||
useGetAgentConfigs,
|
||||
sendGetOneAgentConfig,
|
||||
useCapabilities,
|
||||
} from '../../../hooks';
|
||||
import { CreateAgentConfigFlyout } from '../list_page/components';
|
||||
|
||||
export const StepSelectConfig: React.FunctionComponent<{
|
||||
pkgkey: string;
|
||||
updatePackageInfo: (packageInfo: PackageInfo | undefined) => void;
|
||||
agentConfig: AgentConfig | undefined;
|
||||
updateAgentConfig: (config: AgentConfig | undefined) => void;
|
||||
}> = ({ pkgkey, updatePackageInfo, agentConfig, updateAgentConfig }) => {
|
||||
setIsLoadingSecondStep: (isLoading: boolean) => void;
|
||||
}> = ({ pkgkey, updatePackageInfo, agentConfig, updateAgentConfig, setIsLoadingSecondStep }) => {
|
||||
// Selected config state
|
||||
const [selectedConfigId, setSelectedConfigId] = useState<string | undefined>(
|
||||
agentConfig ? agentConfig.id : undefined
|
||||
);
|
||||
const [selectedConfigError, setSelectedConfigError] = useState<Error>();
|
||||
|
||||
// Create new config flyout state
|
||||
const hasWriteCapabilites = useCapabilities().write;
|
||||
const [isCreateAgentConfigFlyoutOpen, setIsCreateAgentConfigFlyoutOpen] = useState<boolean>(
|
||||
false
|
||||
);
|
||||
|
||||
// Fetch package info
|
||||
const {
|
||||
data: packageInfoData,
|
||||
error: packageInfoError,
|
||||
isLoading: packageInfoLoading,
|
||||
isLoading: isPackageInfoLoading,
|
||||
} = useGetPackageInfoByKey(pkgkey);
|
||||
const isLimitedPackage = (packageInfoData && isPackageLimited(packageInfoData.response)) || false;
|
||||
|
||||
|
@ -37,6 +58,7 @@ export const StepSelectConfig: React.FunctionComponent<{
|
|||
data: agentConfigsData,
|
||||
error: agentConfigsError,
|
||||
isLoading: isAgentConfigsLoading,
|
||||
sendRequest: refreshAgentConfigs,
|
||||
} = useGetAgentConfigs({
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
|
@ -64,6 +86,7 @@ export const StepSelectConfig: React.FunctionComponent<{
|
|||
useEffect(() => {
|
||||
const fetchAgentConfigInfo = async () => {
|
||||
if (selectedConfigId) {
|
||||
setIsLoadingSecondStep(true);
|
||||
const { data, error } = await sendGetOneAgentConfig(selectedConfigId);
|
||||
if (error) {
|
||||
setSelectedConfigError(error);
|
||||
|
@ -76,11 +99,12 @@ export const StepSelectConfig: React.FunctionComponent<{
|
|||
setSelectedConfigError(undefined);
|
||||
updateAgentConfig(undefined);
|
||||
}
|
||||
setIsLoadingSecondStep(false);
|
||||
};
|
||||
if (!agentConfig || selectedConfigId !== agentConfig.id) {
|
||||
fetchAgentConfigInfo();
|
||||
}
|
||||
}, [selectedConfigId, agentConfig, updateAgentConfig]);
|
||||
}, [selectedConfigId, agentConfig, updateAgentConfig, setIsLoadingSecondStep]);
|
||||
|
||||
// Display package error if there is one
|
||||
if (packageInfoError) {
|
||||
|
@ -113,92 +137,125 @@ export const StepSelectConfig: React.FunctionComponent<{
|
|||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiSelectable
|
||||
searchable
|
||||
allowExclusions={false}
|
||||
singleSelection={true}
|
||||
isLoading={isAgentConfigsLoading || packageInfoLoading}
|
||||
options={agentConfigs.map((agentConf) => {
|
||||
const alreadyHasLimitedPackage =
|
||||
(isLimitedPackage &&
|
||||
packageInfoData &&
|
||||
doesAgentConfigAlreadyIncludePackage(agentConf, packageInfoData.response.name)) ||
|
||||
false;
|
||||
return {
|
||||
label: agentConf.name,
|
||||
key: agentConf.id,
|
||||
checked: selectedConfigId === agentConf.id ? 'on' : undefined,
|
||||
disabled: alreadyHasLimitedPackage,
|
||||
'data-test-subj': 'agentConfigItem',
|
||||
};
|
||||
})}
|
||||
renderOption={(option) => (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>{option.label}</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTextColor color="subdued">
|
||||
{agentConfigsById[option.key!].description}
|
||||
</EuiTextColor>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.StepSelectConfig.agentConfigAgentsCountText"
|
||||
defaultMessage="{count, plural, one {# agent} other {# agents}}"
|
||||
values={{
|
||||
count: agentConfigsById[option.key!].agents || 0,
|
||||
}}
|
||||
/>
|
||||
</EuiTextColor>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
listProps={{
|
||||
bordered: true,
|
||||
}}
|
||||
searchProps={{
|
||||
placeholder: i18n.translate(
|
||||
'xpack.ingestManager.createPackageConfig.StepSelectConfig.filterAgentConfigsInputPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Search for agent configurations',
|
||||
<>
|
||||
{isCreateAgentConfigFlyoutOpen ? (
|
||||
<EuiPortal>
|
||||
<CreateAgentConfigFlyout
|
||||
onClose={(newAgentConfig?: AgentConfig) => {
|
||||
setIsCreateAgentConfigFlyoutOpen(false);
|
||||
if (newAgentConfig) {
|
||||
refreshAgentConfigs();
|
||||
setSelectedConfigId(newAgentConfig.id);
|
||||
}
|
||||
),
|
||||
}}
|
||||
height={240}
|
||||
onChange={(options) => {
|
||||
const selectedOption = options.find((option) => option.checked === 'on');
|
||||
if (selectedOption) {
|
||||
setSelectedConfigId(selectedOption.key);
|
||||
} else {
|
||||
setSelectedConfigId(undefined);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{(list, search) => (
|
||||
<Fragment>
|
||||
{search}
|
||||
<EuiSpacer size="m" />
|
||||
{list}
|
||||
</Fragment>
|
||||
)}
|
||||
</EuiSelectable>
|
||||
</EuiFlexItem>
|
||||
{/* Display selected agent config error if there is one */}
|
||||
{selectedConfigError ? (
|
||||
<EuiFlexItem>
|
||||
<Error
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingSelectedAgentConfigTitle"
|
||||
defaultMessage="Error loading selected agent config"
|
||||
/>
|
||||
}
|
||||
error={selectedConfigError}
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiPortal>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiSelectable
|
||||
searchable
|
||||
allowExclusions={false}
|
||||
singleSelection={true}
|
||||
isLoading={isAgentConfigsLoading || isPackageInfoLoading}
|
||||
options={agentConfigs.map((agentConf) => {
|
||||
const alreadyHasLimitedPackage =
|
||||
(isLimitedPackage &&
|
||||
packageInfoData &&
|
||||
doesAgentConfigAlreadyIncludePackage(agentConf, packageInfoData.response.name)) ||
|
||||
false;
|
||||
return {
|
||||
label: agentConf.name,
|
||||
key: agentConf.id,
|
||||
checked: selectedConfigId === agentConf.id ? 'on' : undefined,
|
||||
disabled: alreadyHasLimitedPackage,
|
||||
'data-test-subj': 'agentConfigItem',
|
||||
};
|
||||
})}
|
||||
renderOption={(option) => (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>{option.label}</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTextColor color="subdued">
|
||||
{agentConfigsById[option.key!].description}
|
||||
</EuiTextColor>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.StepSelectConfig.agentConfigAgentsCountText"
|
||||
defaultMessage="{count, plural, one {# agent} other {# agents}}"
|
||||
values={{
|
||||
count: agentConfigsById[option.key!].agents || 0,
|
||||
}}
|
||||
/>
|
||||
</EuiTextColor>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
listProps={{
|
||||
bordered: true,
|
||||
}}
|
||||
searchProps={{
|
||||
placeholder: i18n.translate(
|
||||
'xpack.ingestManager.createPackageConfig.StepSelectConfig.filterAgentConfigsInputPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Search for agent configurations',
|
||||
}
|
||||
),
|
||||
}}
|
||||
height={180}
|
||||
onChange={(options) => {
|
||||
const selectedOption = options.find((option) => option.checked === 'on');
|
||||
if (selectedOption) {
|
||||
if (selectedOption.key !== selectedConfigId) {
|
||||
setSelectedConfigId(selectedOption.key);
|
||||
}
|
||||
} else {
|
||||
setSelectedConfigId(undefined);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{(list, search) => (
|
||||
<Fragment>
|
||||
{search}
|
||||
<EuiSpacer size="m" />
|
||||
{list}
|
||||
</Fragment>
|
||||
)}
|
||||
</EuiSelectable>
|
||||
</EuiFlexItem>
|
||||
{/* Display selected agent config error if there is one */}
|
||||
{selectedConfigError ? (
|
||||
<EuiFlexItem>
|
||||
<Error
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingSelectedAgentConfigTitle"
|
||||
defaultMessage="Error loading selected agent config"
|
||||
/>
|
||||
}
|
||||
error={selectedConfigError}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
<EuiFlexItem>
|
||||
<div>
|
||||
<EuiButtonEmpty
|
||||
iconType="plusInCircle"
|
||||
isDisabled={!hasWriteCapabilites}
|
||||
onClick={() => setIsCreateAgentConfigFlyoutOpen(true)}
|
||||
flush="left"
|
||||
size="s"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createPackageConfig.StepSelectConfig.addButton"
|
||||
defaultMessage="New agent configuration"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -22,7 +22,14 @@ export const StepSelectPackage: React.FunctionComponent<{
|
|||
updateAgentConfig: (config: AgentConfig | undefined) => void;
|
||||
packageInfo?: PackageInfo;
|
||||
updatePackageInfo: (packageInfo: PackageInfo | undefined) => void;
|
||||
}> = ({ agentConfigId, updateAgentConfig, packageInfo, updatePackageInfo }) => {
|
||||
setIsLoadingSecondStep: (isLoading: boolean) => void;
|
||||
}> = ({
|
||||
agentConfigId,
|
||||
updateAgentConfig,
|
||||
packageInfo,
|
||||
updatePackageInfo,
|
||||
setIsLoadingSecondStep,
|
||||
}) => {
|
||||
// Selected package state
|
||||
const [selectedPkgKey, setSelectedPkgKey] = useState<string | undefined>(
|
||||
packageInfo ? `${packageInfo.name}-${packageInfo.version}` : undefined
|
||||
|
@ -30,7 +37,11 @@ export const StepSelectPackage: React.FunctionComponent<{
|
|||
const [selectedPkgError, setSelectedPkgError] = useState<Error>();
|
||||
|
||||
// Fetch agent config info
|
||||
const { data: agentConfigData, error: agentConfigError } = useGetOneAgentConfig(agentConfigId);
|
||||
const {
|
||||
data: agentConfigData,
|
||||
error: agentConfigError,
|
||||
isLoading: isAgentConfigsLoading,
|
||||
} = useGetOneAgentConfig(agentConfigId);
|
||||
|
||||
// Fetch packages info
|
||||
// Filter out limited packages already part of selected agent config
|
||||
|
@ -66,6 +77,7 @@ export const StepSelectPackage: React.FunctionComponent<{
|
|||
useEffect(() => {
|
||||
const fetchPackageInfo = async () => {
|
||||
if (selectedPkgKey) {
|
||||
setIsLoadingSecondStep(true);
|
||||
const { data, error } = await sendGetPackageInfoByKey(selectedPkgKey);
|
||||
if (error) {
|
||||
setSelectedPkgError(error);
|
||||
|
@ -74,6 +86,7 @@ export const StepSelectPackage: React.FunctionComponent<{
|
|||
setSelectedPkgError(undefined);
|
||||
updatePackageInfo(data.response);
|
||||
}
|
||||
setIsLoadingSecondStep(false);
|
||||
} else {
|
||||
setSelectedPkgError(undefined);
|
||||
updatePackageInfo(undefined);
|
||||
|
@ -82,7 +95,7 @@ export const StepSelectPackage: React.FunctionComponent<{
|
|||
if (!packageInfo || selectedPkgKey !== `${packageInfo.name}-${packageInfo.version}`) {
|
||||
fetchPackageInfo();
|
||||
}
|
||||
}, [selectedPkgKey, packageInfo, updatePackageInfo]);
|
||||
}, [selectedPkgKey, packageInfo, updatePackageInfo, setIsLoadingSecondStep]);
|
||||
|
||||
// Display agent config error if there is one
|
||||
if (agentConfigError) {
|
||||
|
@ -121,7 +134,7 @@ export const StepSelectPackage: React.FunctionComponent<{
|
|||
searchable
|
||||
allowExclusions={false}
|
||||
singleSelection={true}
|
||||
isLoading={isPackagesLoading || isLimitedPackagesLoading}
|
||||
isLoading={isPackagesLoading || isLimitedPackagesLoading || isAgentConfigsLoading}
|
||||
options={packages.map(({ title, name, version, icons }) => {
|
||||
const pkgkey = `${name}-${version}`;
|
||||
return {
|
||||
|
@ -154,7 +167,9 @@ export const StepSelectPackage: React.FunctionComponent<{
|
|||
onChange={(options) => {
|
||||
const selectedOption = options.find((option) => option.checked === 'on');
|
||||
if (selectedOption) {
|
||||
setSelectedPkgKey(selectedOption.key);
|
||||
if (selectedOption.key !== selectedPkgKey) {
|
||||
setSelectedPkgKey(selectedOption.key);
|
||||
}
|
||||
} else {
|
||||
setSelectedPkgKey(undefined);
|
||||
}
|
||||
|
|
|
@ -3,14 +3,13 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useRouteMatch, useHistory } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiButton,
|
||||
EuiSteps,
|
||||
EuiBottomBar,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
|
@ -160,38 +159,45 @@ export const EditPackageConfigPage: React.FunctionComponent = () => {
|
|||
const [validationResults, setValidationResults] = useState<PackageConfigValidationResults>();
|
||||
const hasErrors = validationResults ? validationHasErrors(validationResults) : false;
|
||||
|
||||
// Update package config validation
|
||||
const updatePackageConfigValidation = useCallback(
|
||||
(newPackageConfig?: UpdatePackageConfig) => {
|
||||
if (packageInfo) {
|
||||
const newValidationResult = validatePackageConfig(
|
||||
newPackageConfig || packageConfig,
|
||||
packageInfo
|
||||
);
|
||||
setValidationResults(newValidationResult);
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug('Package config validation results', newValidationResult);
|
||||
|
||||
return newValidationResult;
|
||||
}
|
||||
},
|
||||
[packageConfig, packageInfo]
|
||||
);
|
||||
|
||||
// Update package config method
|
||||
const updatePackageConfig = (updatedFields: Partial<UpdatePackageConfig>) => {
|
||||
const newPackageConfig = {
|
||||
...packageConfig,
|
||||
...updatedFields,
|
||||
};
|
||||
setPackageConfig(newPackageConfig);
|
||||
const updatePackageConfig = useCallback(
|
||||
(updatedFields: Partial<UpdatePackageConfig>) => {
|
||||
const newPackageConfig = {
|
||||
...packageConfig,
|
||||
...updatedFields,
|
||||
};
|
||||
setPackageConfig(newPackageConfig);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug('Package config updated', newPackageConfig);
|
||||
const newValidationResults = updatePackageConfigValidation(newPackageConfig);
|
||||
const hasValidationErrors = newValidationResults
|
||||
? validationHasErrors(newValidationResults)
|
||||
: false;
|
||||
if (!hasValidationErrors) {
|
||||
setFormState('VALID');
|
||||
}
|
||||
};
|
||||
|
||||
const updatePackageConfigValidation = (newPackageConfig?: UpdatePackageConfig) => {
|
||||
if (packageInfo) {
|
||||
const newValidationResult = validatePackageConfig(
|
||||
newPackageConfig || packageConfig,
|
||||
packageInfo
|
||||
);
|
||||
setValidationResults(newValidationResult);
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug('Package config validation results', newValidationResult);
|
||||
|
||||
return newValidationResult;
|
||||
}
|
||||
};
|
||||
console.debug('Package config updated', newPackageConfig);
|
||||
const newValidationResults = updatePackageConfigValidation(newPackageConfig);
|
||||
const hasValidationErrors = newValidationResults
|
||||
? validationHasErrors(newValidationResults)
|
||||
: false;
|
||||
if (!hasValidationErrors) {
|
||||
setFormState('VALID');
|
||||
}
|
||||
},
|
||||
[packageConfig, updatePackageConfigValidation]
|
||||
);
|
||||
|
||||
// Cancel url
|
||||
const cancelUrl = getHref('configuration_details', { configId });
|
||||
|
@ -271,6 +277,40 @@ export const EditPackageConfigPage: React.FunctionComponent = () => {
|
|||
packageInfo,
|
||||
};
|
||||
|
||||
const configurePackage = useMemo(
|
||||
() =>
|
||||
agentConfig && packageInfo ? (
|
||||
<>
|
||||
<StepDefinePackageConfig
|
||||
agentConfig={agentConfig}
|
||||
packageInfo={packageInfo}
|
||||
packageConfig={packageConfig}
|
||||
updatePackageConfig={updatePackageConfig}
|
||||
validationResults={validationResults!}
|
||||
/>
|
||||
|
||||
<StepConfigurePackage
|
||||
from={'edit'}
|
||||
packageInfo={packageInfo}
|
||||
packageConfig={packageConfig}
|
||||
packageConfigId={packageConfigId}
|
||||
updatePackageConfig={updatePackageConfig}
|
||||
validationResults={validationResults!}
|
||||
submitAttempted={formState === 'INVALID'}
|
||||
/>
|
||||
</>
|
||||
) : null,
|
||||
[
|
||||
agentConfig,
|
||||
formState,
|
||||
packageConfig,
|
||||
packageConfigId,
|
||||
packageInfo,
|
||||
updatePackageConfig,
|
||||
validationResults,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<CreatePackageConfigPageLayout {...layoutProps} data-test-subj="editPackageConfig">
|
||||
{isLoadingData ? (
|
||||
|
@ -301,46 +341,7 @@ export const EditPackageConfigPage: React.FunctionComponent = () => {
|
|||
onCancel={() => setFormState('VALID')}
|
||||
/>
|
||||
)}
|
||||
<EuiSteps
|
||||
steps={[
|
||||
{
|
||||
title: i18n.translate(
|
||||
'xpack.ingestManager.editPackageConfig.stepDefinePackageConfigTitle',
|
||||
{
|
||||
defaultMessage: 'Define your integration',
|
||||
}
|
||||
),
|
||||
children: (
|
||||
<StepDefinePackageConfig
|
||||
agentConfig={agentConfig}
|
||||
packageInfo={packageInfo}
|
||||
packageConfig={packageConfig}
|
||||
updatePackageConfig={updatePackageConfig}
|
||||
validationResults={validationResults!}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.translate(
|
||||
'xpack.ingestManager.editPackageConfig.stepConfigurePackageConfigTitle',
|
||||
{
|
||||
defaultMessage: 'Select the data you want to collect',
|
||||
}
|
||||
),
|
||||
children: (
|
||||
<StepConfigurePackage
|
||||
from={'edit'}
|
||||
packageInfo={packageInfo}
|
||||
packageConfig={packageConfig}
|
||||
packageConfigId={packageConfigId}
|
||||
updatePackageConfig={updatePackageConfig}
|
||||
validationResults={validationResults!}
|
||||
submitAttempted={formState === 'INVALID'}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{configurePackage}
|
||||
<EuiSpacer size="l" />
|
||||
{/* TODO #64541 - Remove classes */}
|
||||
<EuiBottomBar
|
||||
|
@ -352,29 +353,41 @@ export const EditPackageConfigPage: React.FunctionComponent = () => {
|
|||
: undefined
|
||||
}
|
||||
>
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd">
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty color="ghost" href={cancelUrl}>
|
||||
{agentConfig && packageInfo && formState === 'INVALID' ? (
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editPackageConfig.cancelButton"
|
||||
defaultMessage="Cancel"
|
||||
id="xpack.ingestManager.createPackageConfig.errorOnSaveText"
|
||||
defaultMessage="Your integration configuration has errors. Please fix them before saving."
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
) : null}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={onSubmit}
|
||||
isLoading={formState === 'LOADING'}
|
||||
disabled={formState !== 'VALID'}
|
||||
iconType="save"
|
||||
color="primary"
|
||||
fill
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editPackageConfig.saveButton"
|
||||
defaultMessage="Save integration"
|
||||
/>
|
||||
</EuiButton>
|
||||
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty color="ghost" href={cancelUrl}>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editPackageConfig.cancelButton"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={onSubmit}
|
||||
isLoading={formState === 'LOADING'}
|
||||
disabled={formState !== 'VALID'}
|
||||
iconType="save"
|
||||
color="primary"
|
||||
fill
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.editPackageConfig.saveButton"
|
||||
defaultMessage="Save integration"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiBottomBar>
|
||||
|
|
|
@ -18,12 +18,12 @@ import {
|
|||
EuiButton,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { NewAgentConfig } from '../../../../types';
|
||||
import { NewAgentConfig, AgentConfig } from '../../../../types';
|
||||
import { useCapabilities, useCore, sendCreateAgentConfig } from '../../../../hooks';
|
||||
import { AgentConfigForm, agentConfigFormValidation } from '../../components';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
onClose: (createdAgentConfig?: AgentConfig) => void;
|
||||
}
|
||||
|
||||
export const CreateAgentConfigFlyout: React.FunctionComponent<Props> = ({ onClose }) => {
|
||||
|
@ -86,7 +86,7 @@ export const CreateAgentConfigFlyout: React.FunctionComponent<Props> = ({ onClos
|
|||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty iconType="cross" onClick={onClose} flush="left">
|
||||
<EuiButtonEmpty iconType="cross" onClick={() => onClose()} flush="left">
|
||||
<FormattedMessage
|
||||
id="xpack.ingestManager.createAgentConfig.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
|
@ -113,7 +113,7 @@ export const CreateAgentConfigFlyout: React.FunctionComponent<Props> = ({ onClos
|
|||
}
|
||||
)
|
||||
);
|
||||
onClose();
|
||||
onClose(data.item);
|
||||
} else {
|
||||
notifications.toasts.addDanger(
|
||||
error
|
||||
|
|
|
@ -18,6 +18,7 @@ export {
|
|||
UpdatePackageConfig,
|
||||
PackageConfigInput,
|
||||
PackageConfigInputStream,
|
||||
PackageConfigConfigRecord,
|
||||
PackageConfigConfigRecordEntry,
|
||||
Output,
|
||||
DataStream,
|
||||
|
|
|
@ -44,6 +44,20 @@ class PackageConfigService {
|
|||
packageConfig: NewPackageConfig,
|
||||
options?: { id?: string; user?: AuthenticatedUser }
|
||||
): Promise<PackageConfig> {
|
||||
// Check that its agent config does not have a package config with the same name
|
||||
const parentAgentConfig = await agentConfigService.get(soClient, packageConfig.config_id);
|
||||
if (!parentAgentConfig) {
|
||||
throw new Error('Agent config not found');
|
||||
} else {
|
||||
if (
|
||||
(parentAgentConfig.package_configs as PackageConfig[]).find(
|
||||
(siblingPackageConfig) => siblingPackageConfig.name === packageConfig.name
|
||||
)
|
||||
) {
|
||||
throw new Error('There is already a package with the same name on this agent config');
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the associated package is installed
|
||||
if (packageConfig.package?.name) {
|
||||
const [, pkgInfo] = await Promise.all([
|
||||
|
@ -225,6 +239,21 @@ class PackageConfigService {
|
|||
throw new Error('Package config not found');
|
||||
}
|
||||
|
||||
// Check that its agent config does not have a package config with the same name
|
||||
const parentAgentConfig = await agentConfigService.get(soClient, packageConfig.config_id);
|
||||
if (!parentAgentConfig) {
|
||||
throw new Error('Agent config not found');
|
||||
} else {
|
||||
if (
|
||||
(parentAgentConfig.package_configs as PackageConfig[]).find(
|
||||
(siblingPackageConfig) =>
|
||||
siblingPackageConfig.id !== id && siblingPackageConfig.name === packageConfig.name
|
||||
)
|
||||
) {
|
||||
throw new Error('There is already a package with the same name on this agent config');
|
||||
}
|
||||
}
|
||||
|
||||
await soClient.update<PackageConfigSOAttributes>(
|
||||
SAVED_OBJECT_TYPE,
|
||||
id,
|
||||
|
|
|
@ -8187,6 +8187,42 @@
|
|||
"xpack.ingestManager.createAgentConfig.flyoutTitleDescription": "エージェント構成は、エージェントのグループ全体にわたる設定を管理する目的で使用されます。エージェント構成にデータソースを追加すると、エージェントで収集するデータを指定できます。エージェント構成の編集時には、フリートを使用して、指定したエージェントのグループに更新をデプロイできます。",
|
||||
"xpack.ingestManager.createAgentConfig.submitButtonLabel": "エージェント構成を作成",
|
||||
"xpack.ingestManager.createAgentConfig.successNotificationTitle": "エージェント構成「{name}」を作成しました",
|
||||
"xpack.ingestManager.createPackageConfig.addedNotificationMessage": "フリートは'{agentConfigName}'構成で使用されているすべてのエージェントに更新をデプロイします。",
|
||||
"xpack.ingestManager.createPackageConfig.addedNotificationTitle": "正常に'{packageConfigName}'を追加しました",
|
||||
"xpack.ingestManager.createPackageConfig.agentConfigurationNameLabel": "構成",
|
||||
"xpack.ingestManager.createPackageConfig.cancelButton": "キャンセル",
|
||||
"xpack.ingestManager.createPackageConfig.cancelLinkText": "キャンセル",
|
||||
"xpack.ingestManager.createPackageConfig.pageDescriptionfromConfig": "次の手順に従い、統合をこのエージェント構成に追加します。",
|
||||
"xpack.ingestManager.createPackageConfig.pageDescriptionfromPackage": "次の手順に従い、この統合をエージェント構成に追加します。",
|
||||
"xpack.ingestManager.createPackageConfig.pageTitle": "データソースを追加",
|
||||
"xpack.ingestManager.createPackageConfig.saveButton": "データソースを保存",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigurePackageConfigTitle": "収集するデータを選択",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.advancedOptionsToggleLinkText": "高度なオプション",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.packageConfigDescriptionInputLabel": "説明",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.packageConfigNameInputLabel": "データソース名",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.packageConfigNamespaceInputLabel": "名前空間",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.hideStreamsAriaLabel": "{type} ストリームを隠す",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.inputSettingsDescription": "次の設定はすべてのストリームに適用されます。",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.inputSettingsTitle": "設定",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.inputVarFieldOptionalLabel": "オプション",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.noConfigOptionsMessage": "構成するものがありません",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.showStreamsAriaLabel": "{type} ストリームを表示",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.toggleAdvancedOptionsButtonText": "高度なオプション",
|
||||
"xpack.ingestManager.createPackageConfig.stepSelectAgentConfigTitle": "エージェント構成を選択する",
|
||||
"xpack.ingestManager.createPackageConfig.StepSelectConfig.agentConfigAgentsCountText": "{count, plural, one {# エージェント} other {# エージェント}}",
|
||||
"xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingAgentConfigsTitle": "エージェント構成の読み込みエラー",
|
||||
"xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingPackageTitle": "パッケージ情報の読み込みエラー",
|
||||
"xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingSelectedAgentConfigTitle": "選択したエージェント構成の読み込みエラー",
|
||||
"xpack.ingestManager.createPackageConfig.StepSelectConfig.filterAgentConfigsInputPlaceholder": "エージェント構成の検索",
|
||||
"xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingConfigTitle": "エージェント構成情報の読み込みエラー",
|
||||
"xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingPackagesTitle": "統合の読み込みエラー",
|
||||
"xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingSelectedPackageTitle": "選択した統合の読み込みエラー",
|
||||
"xpack.ingestManager.createPackageConfig.stepSelectPackage.filterPackagesInputPlaceholder": "統合を検索",
|
||||
"xpack.ingestManager.createPackageConfig.stepSelectPackageTitle": "統合を選択",
|
||||
"xpack.ingestManager.packageConfigValidation.invalidArrayErrorMessage": "無効なフォーマット",
|
||||
"xpack.ingestManager.packageConfigValidation.invalidYamlFormatErrorMessage": "YAML形式が無効です",
|
||||
"xpack.ingestManager.packageConfigValidation.nameRequiredErrorMessage": "名前が必要です",
|
||||
"xpack.ingestManager.packageConfigValidation.requiredErrorMessage": "{fieldName}が必要です",
|
||||
"xpack.ingestManager.dataStreamList.actionsColumnTitle": "アクション",
|
||||
"xpack.ingestManager.dataStreamList.datasetColumnTitle": "データセット",
|
||||
"xpack.ingestManager.dataStreamList.integrationColumnTitle": "統合",
|
||||
|
@ -8224,6 +8260,14 @@
|
|||
"xpack.ingestManager.editAgentConfig.savingButtonText": "保存中…",
|
||||
"xpack.ingestManager.editAgentConfig.successNotificationTitle": "正常に'{name}'設定を更新しました",
|
||||
"xpack.ingestManager.editAgentConfig.unsavedChangesText": "保存されていない変更があります",
|
||||
"xpack.ingestManager.editPackageConfig.cancelButton": "キャンセル",
|
||||
"xpack.ingestManager.editPackageConfig.errorLoadingDataMessage": "このデータソース情報の読み込みエラーが発生しました",
|
||||
"xpack.ingestManager.editPackageConfig.errorLoadingDataTitle": "データの読み込み中にエラーが発生",
|
||||
"xpack.ingestManager.editPackageConfig.pageDescription": "次の手順に従い、このデータソースを編集します。",
|
||||
"xpack.ingestManager.editPackageConfig.pageTitle": "データソースを編集",
|
||||
"xpack.ingestManager.editPackageConfig.saveButton": "データソースを保存",
|
||||
"xpack.ingestManager.editPackageConfig.updatedNotificationMessage": "フリートは'{agentConfigName}'構成で使用されているすべてのエージェントに更新をデプロイします。",
|
||||
"xpack.ingestManager.editPackageConfig.updatedNotificationTitle": "正常に'{packageConfigName}'を更新しました",
|
||||
"xpack.ingestManager.enrollemntAPIKeyList.emptyMessage": "登録トークンが見つかりません。",
|
||||
"xpack.ingestManager.enrollemntAPIKeyList.loadingTokensMessage": "登録トークンを読み込んでいます...",
|
||||
"xpack.ingestManager.enrollmentInstructions.copyButton": "コマンドをコピー",
|
||||
|
|
|
@ -8191,6 +8191,42 @@
|
|||
"xpack.ingestManager.createAgentConfig.flyoutTitleDescription": "代理配置用于管理整个代理组的设置。您可以将数据源添加到代理配置以指定代理收集的数据。编辑代理配置时,可以使用 Fleet 将更新部署到指定的代理组。",
|
||||
"xpack.ingestManager.createAgentConfig.submitButtonLabel": "创建代理配置",
|
||||
"xpack.ingestManager.createAgentConfig.successNotificationTitle": "代理配置“{name}”已创建",
|
||||
"xpack.ingestManager.createPackageConfig.addedNotificationMessage": "Fleet 会将更新部署到所有使用配置“{agentConfigName}”的代理",
|
||||
"xpack.ingestManager.createPackageConfig.addedNotificationTitle": "已成功添加“{packageConfigName}”",
|
||||
"xpack.ingestManager.createPackageConfig.agentConfigurationNameLabel": "配置",
|
||||
"xpack.ingestManager.createPackageConfig.cancelButton": "取消",
|
||||
"xpack.ingestManager.createPackageConfig.cancelLinkText": "取消",
|
||||
"xpack.ingestManager.createPackageConfig.pageDescriptionfromConfig": "按照下面的说明将集成添加此代理配置。",
|
||||
"xpack.ingestManager.createPackageConfig.pageDescriptionfromPackage": "按照下面的说明将此集成添加代理配置。",
|
||||
"xpack.ingestManager.createPackageConfig.pageTitle": "添加数据源",
|
||||
"xpack.ingestManager.createPackageConfig.saveButton": "保存数据源",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigurePackageConfigTitle": "选择要收集的数据",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.advancedOptionsToggleLinkText": "高级选项",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.packageConfigDescriptionInputLabel": "描述",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.packageConfigNameInputLabel": "数据源名称",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.packageConfigNamespaceInputLabel": "命名空间",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.hideStreamsAriaLabel": "隐藏 {type} 流",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.inputSettingsDescription": "以下设置适用于所有流。",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.inputSettingsTitle": "设置",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.inputVarFieldOptionalLabel": "可选",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.noConfigOptionsMessage": "没有可配置的内容",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.showStreamsAriaLabel": "显示 {type} 流",
|
||||
"xpack.ingestManager.createPackageConfig.stepConfigure.toggleAdvancedOptionsButtonText": "高级选项",
|
||||
"xpack.ingestManager.createPackageConfig.stepSelectAgentConfigTitle": "选择代理配置",
|
||||
"xpack.ingestManager.createPackageConfig.StepSelectConfig.agentConfigAgentsCountText": "{count, plural, one {# 个代理} other {# 个代理}}",
|
||||
"xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingAgentConfigsTitle": "加载代理配置时出错",
|
||||
"xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingPackageTitle": "加载软件包信息时出错",
|
||||
"xpack.ingestManager.createPackageConfig.StepSelectConfig.errorLoadingSelectedAgentConfigTitle": "加载选定代理配置时出错",
|
||||
"xpack.ingestManager.createPackageConfig.StepSelectConfig.filterAgentConfigsInputPlaceholder": "搜索代理配置",
|
||||
"xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingConfigTitle": "加载代理配置信息时出错",
|
||||
"xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingPackagesTitle": "加载集成时出错",
|
||||
"xpack.ingestManager.createPackageConfig.stepSelectPackage.errorLoadingSelectedPackageTitle": "加载选定集成时出错",
|
||||
"xpack.ingestManager.createPackageConfig.stepSelectPackage.filterPackagesInputPlaceholder": "搜索集成",
|
||||
"xpack.ingestManager.createPackageConfig.stepSelectPackageTitle": "选择集成",
|
||||
"xpack.ingestManager.packageConfigValidation.invalidArrayErrorMessage": "格式无效",
|
||||
"xpack.ingestManager.packageConfigValidation.invalidYamlFormatErrorMessage": "YAML 格式无效",
|
||||
"xpack.ingestManager.packageConfigValidation.nameRequiredErrorMessage": "“名称”必填",
|
||||
"xpack.ingestManager.packageConfigValidation.requiredErrorMessage": "“{fieldName}”必填",
|
||||
"xpack.ingestManager.dataStreamList.actionsColumnTitle": "操作",
|
||||
"xpack.ingestManager.dataStreamList.datasetColumnTitle": "数据集",
|
||||
"xpack.ingestManager.dataStreamList.integrationColumnTitle": "集成",
|
||||
|
@ -8228,6 +8264,14 @@
|
|||
"xpack.ingestManager.editAgentConfig.savingButtonText": "正在保存……",
|
||||
"xpack.ingestManager.editAgentConfig.successNotificationTitle": "已成功更新“{name}”设置",
|
||||
"xpack.ingestManager.editAgentConfig.unsavedChangesText": "您有未保存更改",
|
||||
"xpack.ingestManager.editPackageConfig.cancelButton": "取消",
|
||||
"xpack.ingestManager.editPackageConfig.errorLoadingDataMessage": "加载此数据源信息时出错",
|
||||
"xpack.ingestManager.editPackageConfig.errorLoadingDataTitle": "加载数据时出错",
|
||||
"xpack.ingestManager.editPackageConfig.pageDescription": "按照下面的说明编辑此数据源。",
|
||||
"xpack.ingestManager.editPackageConfig.pageTitle": "编辑数据源",
|
||||
"xpack.ingestManager.editPackageConfig.saveButton": "保存数据源",
|
||||
"xpack.ingestManager.editPackageConfig.updatedNotificationMessage": "Fleet 会将更新部署到所有使用配置“{agentConfigName}”的代理",
|
||||
"xpack.ingestManager.editPackageConfig.updatedNotificationTitle": "已成功更新“{packageConfigName}”",
|
||||
"xpack.ingestManager.enrollemntAPIKeyList.emptyMessage": "未找到任何注册令牌。",
|
||||
"xpack.ingestManager.enrollemntAPIKeyList.loadingTokensMessage": "正在加载注册令牌......",
|
||||
"xpack.ingestManager.enrollmentInstructions.copyButton": "复制命令",
|
||||
|
|
|
@ -17,5 +17,6 @@ export default function ({ loadTestFile }) {
|
|||
|
||||
// Package configs
|
||||
loadTestFile(require.resolve('./package_config/create'));
|
||||
loadTestFile(require.resolve('./package_config/update'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -126,5 +126,48 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
warnAndSkipTest(this, log);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return a 500 if there is another package config with the same name', async function () {
|
||||
if (server.enabled) {
|
||||
await supertest
|
||||
.post(`/api/ingest_manager/package_configs`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'same-name-test-1',
|
||||
description: '',
|
||||
namespace: 'default',
|
||||
config_id: agentConfigId,
|
||||
enabled: true,
|
||||
output_id: '',
|
||||
inputs: [],
|
||||
package: {
|
||||
name: 'filetest',
|
||||
title: 'For File Tests',
|
||||
version: '0.1.0',
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
await supertest
|
||||
.post(`/api/ingest_manager/package_configs`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'same-name-test-1',
|
||||
description: '',
|
||||
namespace: 'default',
|
||||
config_id: agentConfigId,
|
||||
enabled: true,
|
||||
output_id: '',
|
||||
inputs: [],
|
||||
package: {
|
||||
name: 'filetest',
|
||||
title: 'For File Tests',
|
||||
version: '0.1.0',
|
||||
},
|
||||
})
|
||||
.expect(500);
|
||||
} else {
|
||||
warnAndSkipTest(this, log);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
|
||||
import { warnAndSkipTest } from '../../helpers';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const log = getService('log');
|
||||
const supertest = getService('supertest');
|
||||
const dockerServers = getService('dockerServers');
|
||||
|
||||
const server = dockerServers.get('registry');
|
||||
// use function () {} and not () => {} here
|
||||
// because `this` has to point to the Mocha context
|
||||
// see https://mochajs.org/#arrow-functions
|
||||
|
||||
describe('Package Config - update', async function () {
|
||||
let agentConfigId: string;
|
||||
let packageConfigId: string;
|
||||
let packageConfigId2: string;
|
||||
|
||||
before(async function () {
|
||||
const { body: agentConfigResponse } = await supertest
|
||||
.post(`/api/ingest_manager/agent_configs`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'Test config',
|
||||
namespace: 'default',
|
||||
});
|
||||
agentConfigId = agentConfigResponse.item.id;
|
||||
|
||||
const { body: packageConfigResponse } = await supertest
|
||||
.post(`/api/ingest_manager/package_configs`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'filetest-1',
|
||||
description: '',
|
||||
namespace: 'default',
|
||||
config_id: agentConfigId,
|
||||
enabled: true,
|
||||
output_id: '',
|
||||
inputs: [],
|
||||
package: {
|
||||
name: 'filetest',
|
||||
title: 'For File Tests',
|
||||
version: '0.1.0',
|
||||
},
|
||||
});
|
||||
packageConfigId = packageConfigResponse.item.id;
|
||||
|
||||
const { body: packageConfigResponse2 } = await supertest
|
||||
.post(`/api/ingest_manager/package_configs`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'filetest-2',
|
||||
description: '',
|
||||
namespace: 'default',
|
||||
config_id: agentConfigId,
|
||||
enabled: true,
|
||||
output_id: '',
|
||||
inputs: [],
|
||||
package: {
|
||||
name: 'filetest',
|
||||
title: 'For File Tests',
|
||||
version: '0.1.0',
|
||||
},
|
||||
});
|
||||
packageConfigId2 = packageConfigResponse2.item.id;
|
||||
});
|
||||
|
||||
it('should work with valid values', async function () {
|
||||
if (server.enabled) {
|
||||
const { body: apiResponse } = await supertest
|
||||
.put(`/api/ingest_manager/package_configs/${packageConfigId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'filetest-1',
|
||||
description: '',
|
||||
namespace: 'updated_namespace',
|
||||
config_id: agentConfigId,
|
||||
enabled: true,
|
||||
output_id: '',
|
||||
inputs: [],
|
||||
package: {
|
||||
name: 'filetest',
|
||||
title: 'For File Tests',
|
||||
version: '0.1.0',
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.success).to.be(true);
|
||||
} else {
|
||||
warnAndSkipTest(this, log);
|
||||
}
|
||||
});
|
||||
|
||||
it('should return a 500 if there is another package config with the same name', async function () {
|
||||
if (server.enabled) {
|
||||
await supertest
|
||||
.put(`/api/ingest_manager/package_configs/${packageConfigId2}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'filetest-1',
|
||||
description: '',
|
||||
namespace: 'updated_namespace',
|
||||
config_id: agentConfigId,
|
||||
enabled: true,
|
||||
output_id: '',
|
||||
inputs: [],
|
||||
package: {
|
||||
name: 'filetest',
|
||||
title: 'For File Tests',
|
||||
version: '0.1.0',
|
||||
},
|
||||
})
|
||||
.expect(500);
|
||||
} else {
|
||||
warnAndSkipTest(this, log);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue