mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Fleet] Adjust add integration flow (#101714)
* Initial commit, very WIP - added link to the integrations UI instead of add integration in fleet - added a new piece of route state that gets passed to integration from fleet when browsing integrations for a policy - when the integration is being added the page will send the user back to the policy overview instead of back to integrations UI * remove unnecessary agent policy clear function * added # to path so that navigation is correctly handled * added logic to read the forward agent policy id * remove inline select integration package from fleet add integration * updated toast notification * using query parameter to pass policy id back to create policy package page * removed policyId from route path * fix type issue * updated the select agent field layout per the designs * simpified item rendering in combobox and fixed combobox z-index issue * added comment * fix types and i18n * updated icon and removed unused i18n * refactor to using styled components for cusomt z-index styling * attempt to fix integration test * added scroll functionality for dealing with fixed footers that might be obstructing content * fix scroll direction! * attempting another scroll algorithm Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
d3144e1439
commit
f70542abe2
15 changed files with 265 additions and 372 deletions
|
@ -27,7 +27,7 @@
|
|||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
export function scrollIntoViewIfNecessary(target, fixedHeaderHeight) {
|
||||
export function scrollIntoViewIfNecessary(target, fixedHeaderHeight, fixedFooterHeight) {
|
||||
var rootScroller = document.scrollingElement || document.documentElement;
|
||||
if (!rootScroller) {
|
||||
throw new Error('Unable to find document.scrollingElement or document.documentElement');
|
||||
|
@ -63,4 +63,11 @@ export function scrollIntoViewIfNecessary(target, fixedHeaderHeight) {
|
|||
if (additionalScrollNecessary > 0) {
|
||||
rootScroller.scrollTop = rootScroller.scrollTop - additionalScrollNecessary;
|
||||
}
|
||||
|
||||
if (fixedFooterHeight) {
|
||||
var bottomOfVisibility = viewportHeight - fixedFooterHeight;
|
||||
if (bottomOfVisibility < boundingRect.bottom) {
|
||||
rootScroller.scrollTop = rootScroller.scrollTop + fixedFooterHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -692,11 +692,22 @@ export class WebElementWrapper {
|
|||
* @nonstandard
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async scrollIntoViewIfNecessary(topOffset?: number): Promise<void> {
|
||||
public async scrollIntoViewIfNecessary(
|
||||
topOffsetOrOptions?: number | { topOffset?: number; bottomOffset?: number }
|
||||
): Promise<void> {
|
||||
let topOffset: undefined | number;
|
||||
let bottomOffset: undefined | number;
|
||||
if (typeof topOffsetOrOptions === 'number') {
|
||||
topOffset = topOffsetOrOptions;
|
||||
} else {
|
||||
topOffset = topOffsetOrOptions?.topOffset;
|
||||
bottomOffset = topOffsetOrOptions?.bottomOffset;
|
||||
}
|
||||
await this.driver.executeScript(
|
||||
scrollIntoViewIfNecessary,
|
||||
this._webElement,
|
||||
topOffset || this.fixedHeaderHeight
|
||||
topOffset || this.fixedHeaderHeight,
|
||||
bottomOffset
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import type { ReactEventHandler } from 'react';
|
||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { useRouteMatch, useHistory } from 'react-router-dom';
|
||||
import { useRouteMatch, useHistory, useLocation } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
@ -49,7 +49,6 @@ import { CreatePackagePolicyPageLayout } from './components';
|
|||
import type { CreatePackagePolicyFrom, PackagePolicyFormState } from './types';
|
||||
import type { PackagePolicyValidationResults } from './services';
|
||||
import { validatePackagePolicy, validationHasErrors } from './services';
|
||||
import { StepSelectPackage } from './step_select_package';
|
||||
import { StepSelectAgentPolicy } from './step_select_agent_policy';
|
||||
import { StepConfigurePackagePolicy } from './step_configure_package';
|
||||
import { StepDefinePackagePolicy } from './step_define_package_policy';
|
||||
|
@ -60,9 +59,15 @@ const StepsWithLessPadding = styled(EuiSteps)`
|
|||
}
|
||||
`;
|
||||
|
||||
const CustomEuiBottomBar = styled(EuiBottomBar)`
|
||||
// Set a relatively _low_ z-index value here to account for EuiComboBox popover that might appear under the bottom bar
|
||||
z-index: 50;
|
||||
`;
|
||||
|
||||
interface AddToPolicyParams {
|
||||
pkgkey: string;
|
||||
integration?: string;
|
||||
policyId?: string;
|
||||
}
|
||||
|
||||
interface AddFromPolicyParams {
|
||||
|
@ -81,10 +86,14 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
const routeState = useIntraAppState<CreatePackagePolicyRouteState>();
|
||||
const from: CreatePackagePolicyFrom = 'policyId' in params ? 'policy' : 'package';
|
||||
|
||||
const { search } = useLocation();
|
||||
const queryParams = useMemo(() => new URLSearchParams(search), [search]);
|
||||
const policyId = useMemo(() => queryParams.get('policyId') ?? undefined, [queryParams]);
|
||||
|
||||
// Agent policy and package info states
|
||||
const [agentPolicy, setAgentPolicy] = useState<AgentPolicy>();
|
||||
const [agentPolicy, setAgentPolicy] = useState<AgentPolicy | undefined>();
|
||||
const [packageInfo, setPackageInfo] = useState<PackageInfo>();
|
||||
const [isLoadingSecondStep, setIsLoadingSecondStep] = useState<boolean>(false);
|
||||
const [isLoadingAgentPolicyStep, setIsLoadingAgentPolicyStep] = useState<boolean>(false);
|
||||
|
||||
// Retrieve agent count
|
||||
const agentPolicyId = agentPolicy?.id;
|
||||
|
@ -286,6 +295,13 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
agentPolicyName: agentPolicy.name,
|
||||
},
|
||||
})
|
||||
: (params as AddToPolicyParams)?.policyId && agentPolicy && agentCount === 0
|
||||
? i18n.translate('xpack.fleet.createPackagePolicy.addAgentNextNotification', {
|
||||
defaultMessage: `The policy has been updated. Add an agent to the '{agentPolicyName}' policy to deploy this policy.`,
|
||||
values: {
|
||||
agentPolicyName: agentPolicy.name,
|
||||
},
|
||||
})
|
||||
: undefined,
|
||||
'data-test-subj': 'packagePolicyCreateSuccessToast',
|
||||
});
|
||||
|
@ -337,32 +353,20 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
<StepSelectAgentPolicy
|
||||
pkgkey={(params as AddToPolicyParams).pkgkey}
|
||||
updatePackageInfo={updatePackageInfo}
|
||||
defaultAgentPolicyId={policyId}
|
||||
agentPolicy={agentPolicy}
|
||||
updateAgentPolicy={updateAgentPolicy}
|
||||
setIsLoadingSecondStep={setIsLoadingSecondStep}
|
||||
setIsLoadingSecondStep={setIsLoadingAgentPolicyStep}
|
||||
/>
|
||||
),
|
||||
[params, updatePackageInfo, agentPolicy, updateAgentPolicy]
|
||||
[params, updatePackageInfo, agentPolicy, updateAgentPolicy, policyId]
|
||||
);
|
||||
|
||||
const ExtensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-create');
|
||||
|
||||
const stepSelectPackage = useMemo(
|
||||
() => (
|
||||
<StepSelectPackage
|
||||
agentPolicyId={(params as AddFromPolicyParams).policyId}
|
||||
updateAgentPolicy={updateAgentPolicy}
|
||||
packageInfo={packageInfo}
|
||||
updatePackageInfo={updatePackageInfo}
|
||||
setIsLoadingSecondStep={setIsLoadingSecondStep}
|
||||
/>
|
||||
),
|
||||
[params, updateAgentPolicy, packageInfo, updatePackageInfo]
|
||||
);
|
||||
|
||||
const stepConfigurePackagePolicy = useMemo(
|
||||
() =>
|
||||
isLoadingSecondStep ? (
|
||||
isLoadingAgentPolicyStep ? (
|
||||
<Loading />
|
||||
) : agentPolicy && packageInfo ? (
|
||||
<>
|
||||
|
@ -399,7 +403,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
<div />
|
||||
),
|
||||
[
|
||||
isLoadingSecondStep,
|
||||
isLoadingAgentPolicyStep,
|
||||
agentPolicy,
|
||||
packageInfo,
|
||||
packagePolicy,
|
||||
|
@ -413,27 +417,20 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
);
|
||||
|
||||
const steps: EuiStepProps[] = [
|
||||
from === 'package'
|
||||
? {
|
||||
title: i18n.translate('xpack.fleet.createPackagePolicy.stepSelectAgentPolicyTitle', {
|
||||
defaultMessage: 'Select an agent policy',
|
||||
}),
|
||||
children: stepSelectAgentPolicy,
|
||||
}
|
||||
: {
|
||||
title: i18n.translate('xpack.fleet.createPackagePolicy.stepSelectPackageTitle', {
|
||||
defaultMessage: 'Select an integration',
|
||||
}),
|
||||
children: stepSelectPackage,
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.fleet.createPackagePolicy.stepConfigurePackagePolicyTitle', {
|
||||
defaultMessage: 'Configure integration',
|
||||
}),
|
||||
status: !packageInfo || !agentPolicy || isLoadingSecondStep ? 'disabled' : undefined,
|
||||
status: !packageInfo || !agentPolicy || isLoadingAgentPolicyStep ? 'disabled' : undefined,
|
||||
'data-test-subj': 'dataCollectionSetupStep',
|
||||
children: stepConfigurePackagePolicy,
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.fleet.createPackagePolicy.stepSelectAgentPolicyTitle', {
|
||||
defaultMessage: 'Apply to agent policy',
|
||||
}),
|
||||
children: stepSelectAgentPolicy,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
@ -459,10 +456,10 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
)}
|
||||
<StepsWithLessPadding steps={steps} />
|
||||
<EuiSpacer size="l" />
|
||||
<EuiBottomBar>
|
||||
<CustomEuiBottomBar data-test-subj="integrationsBottomBar">
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
{!isLoadingSecondStep && agentPolicy && packageInfo && formState === 'INVALID' ? (
|
||||
{!isLoadingAgentPolicyStep && agentPolicy && packageInfo && formState === 'INVALID' ? (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.errorOnSaveText"
|
||||
defaultMessage="Your integration policy has errors. Please fix them before saving."
|
||||
|
@ -504,7 +501,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiBottomBar>
|
||||
</CustomEuiBottomBar>
|
||||
</CreatePackagePolicyPageLayout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -14,9 +14,11 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiComboBox,
|
||||
EuiTextColor,
|
||||
EuiPortal,
|
||||
EuiFormRow,
|
||||
EuiDescribedFormGroup,
|
||||
EuiTitle,
|
||||
EuiText,
|
||||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
|
||||
|
@ -32,38 +34,32 @@ import {
|
|||
} from '../../../hooks';
|
||||
import { CreateAgentPolicyFlyout } from '../list_page/components';
|
||||
|
||||
const AgentPolicyWrapper = styled(EuiFormRow)`
|
||||
const AgentPolicyFormRow = styled(EuiFormRow)`
|
||||
.euiFormRow__label {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
// Custom styling for drop down list items due to:
|
||||
// 1) the max-width and overflow properties is added to prevent long agent policy
|
||||
// names/descriptions from overflowing the flex items
|
||||
// 2) max-width is built from the grow property on the flex items because the value
|
||||
// changes based on if Fleet is enabled/setup or not
|
||||
const AgentPolicyNameColumn = styled(EuiFlexItem)`
|
||||
max-width: ${(props) => `${((props.grow as number) / 9) * 100}%`};
|
||||
overflow: hidden;
|
||||
`;
|
||||
const AgentPolicyDescriptionColumn = styled(EuiFlexItem)`
|
||||
max-width: ${(props) => `${((props.grow as number) / 9) * 100}%`};
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export const StepSelectAgentPolicy: React.FunctionComponent<{
|
||||
pkgkey: string;
|
||||
updatePackageInfo: (packageInfo: PackageInfo | undefined) => void;
|
||||
defaultAgentPolicyId?: string;
|
||||
agentPolicy: AgentPolicy | undefined;
|
||||
updateAgentPolicy: (agentPolicy: AgentPolicy | undefined) => void;
|
||||
setIsLoadingSecondStep: (isLoading: boolean) => void;
|
||||
}> = ({ pkgkey, updatePackageInfo, agentPolicy, updateAgentPolicy, setIsLoadingSecondStep }) => {
|
||||
}> = ({
|
||||
pkgkey,
|
||||
updatePackageInfo,
|
||||
agentPolicy,
|
||||
updateAgentPolicy,
|
||||
setIsLoadingSecondStep,
|
||||
defaultAgentPolicyId,
|
||||
}) => {
|
||||
const { isReady: isFleetReady } = useFleetStatus();
|
||||
|
||||
// Selected agent policy state
|
||||
const [selectedPolicyId, setSelectedPolicyId] = useState<string | undefined>(
|
||||
agentPolicy ? agentPolicy.id : undefined
|
||||
agentPolicy?.id ?? defaultAgentPolicyId
|
||||
);
|
||||
const [selectedAgentPolicyError, setSelectedAgentPolicyError] = useState<Error>();
|
||||
|
||||
|
@ -223,95 +219,91 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{
|
|||
) : null}
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<AgentPolicyWrapper
|
||||
fullWidth={true}
|
||||
label={
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<EuiTitle size="xs">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyLabel"
|
||||
id="xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyFormGroupTitle"
|
||||
defaultMessage="Agent policy"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div>
|
||||
<EuiLink
|
||||
disabled={!hasWriteCapabilites}
|
||||
onClick={() => setIsCreateAgentPolicyFlyoutOpen(true)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.StepSelectPolicy.addButton"
|
||||
defaultMessage="Create agent policy"
|
||||
/>
|
||||
</EuiLink>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
}
|
||||
helpText={
|
||||
isFleetReady && selectedPolicyId ? (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyAgentsDescriptionText"
|
||||
defaultMessage="{count, plural, one {# agent} other {# agents}} are enrolled with the selected agent policy."
|
||||
values={{
|
||||
count: agentPoliciesById[selectedPolicyId]?.agents ?? 0,
|
||||
}}
|
||||
/>
|
||||
) : null
|
||||
description={
|
||||
<EuiText color="subdued" size="s">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyFormGroupDescription"
|
||||
defaultMessage="Agent policies are used to manage a group of integrations across a set of agents"
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
}
|
||||
>
|
||||
<EuiComboBox
|
||||
placeholder={i18n.translate(
|
||||
'xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyPlaceholderText',
|
||||
{
|
||||
defaultMessage: 'Select an agent policy to add this integration to',
|
||||
}
|
||||
)}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
isClearable={false}
|
||||
<AgentPolicyFormRow
|
||||
fullWidth={true}
|
||||
isLoading={isAgentPoliciesLoading || isPackageInfoLoading}
|
||||
options={agentPolicyOptions}
|
||||
renderOption={(option: EuiComboBoxOptionOption<string>) => {
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
<AgentPolicyNameColumn grow={2}>
|
||||
<span className="eui-textTruncate">{option.label}</span>
|
||||
</AgentPolicyNameColumn>
|
||||
<AgentPolicyDescriptionColumn grow={isFleetReady ? 5 : 7}>
|
||||
<EuiTextColor className="eui-textTruncate" color="subdued">
|
||||
{agentPoliciesById[option.value!].description}
|
||||
</EuiTextColor>
|
||||
</AgentPolicyDescriptionColumn>
|
||||
{isFleetReady ? (
|
||||
<EuiFlexItem grow={2} className="eui-textRight">
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyAgentsCountText"
|
||||
defaultMessage="{count, plural, one {# agent} other {# agents}} enrolled"
|
||||
values={{
|
||||
count: agentPoliciesById[option.value!]?.agents ?? 0,
|
||||
}}
|
||||
/>
|
||||
</EuiTextColor>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}}
|
||||
selectedOptions={selectedAgentPolicyOption ? [selectedAgentPolicyOption] : []}
|
||||
onChange={(options) => {
|
||||
const selectedOption = options[0] || undefined;
|
||||
if (selectedOption) {
|
||||
if (selectedOption.value !== selectedPolicyId) {
|
||||
setSelectedPolicyId(selectedOption.value);
|
||||
label={
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyLabel"
|
||||
defaultMessage="Agent policy"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<div>
|
||||
<EuiLink
|
||||
disabled={!hasWriteCapabilites}
|
||||
onClick={() => setIsCreateAgentPolicyFlyoutOpen(true)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.StepSelectPolicy.addButton"
|
||||
defaultMessage="Create agent policy"
|
||||
/>
|
||||
</EuiLink>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
helpText={
|
||||
isFleetReady && selectedPolicyId ? (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyAgentsDescriptionText"
|
||||
defaultMessage="{count, plural, one {# agent is} other {# agents are}} enrolled with the selected agent policy."
|
||||
values={{
|
||||
count: agentPoliciesById[selectedPolicyId]?.agents ?? 0,
|
||||
}}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<EuiComboBox
|
||||
placeholder={i18n.translate(
|
||||
'xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyPlaceholderText',
|
||||
{
|
||||
defaultMessage: 'Select an agent policy to add this integration to',
|
||||
}
|
||||
} else {
|
||||
setSelectedPolicyId(undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</AgentPolicyWrapper>
|
||||
)}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
isClearable={false}
|
||||
fullWidth={true}
|
||||
isLoading={isAgentPoliciesLoading || isPackageInfoLoading}
|
||||
options={agentPolicyOptions}
|
||||
selectedOptions={selectedAgentPolicyOption ? [selectedAgentPolicyOption] : []}
|
||||
onChange={(options) => {
|
||||
const selectedOption = options[0] || undefined;
|
||||
if (selectedOption) {
|
||||
if (selectedOption.value !== selectedPolicyId) {
|
||||
setSelectedPolicyId(selectedOption.value);
|
||||
}
|
||||
} else {
|
||||
setSelectedPolicyId(undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</AgentPolicyFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
</EuiFlexItem>
|
||||
{/* Display selected agent policy error if there is one */}
|
||||
{selectedAgentPolicyError ? (
|
||||
|
|
|
@ -1,198 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState, Fragment } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSelectable, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { Error, PackageIcon } from '../../../components';
|
||||
import type { AgentPolicy, PackageInfo, PackagePolicy, GetPackagesResponse } from '../../../types';
|
||||
import {
|
||||
useGetOneAgentPolicy,
|
||||
useGetPackages,
|
||||
useGetLimitedPackages,
|
||||
sendGetPackageInfoByKey,
|
||||
} from '../../../hooks';
|
||||
import { pkgKeyFromPackageInfo } from '../../../services';
|
||||
|
||||
export const StepSelectPackage: React.FunctionComponent<{
|
||||
agentPolicyId: string;
|
||||
updateAgentPolicy: (agentPolicy: AgentPolicy | undefined) => void;
|
||||
packageInfo?: PackageInfo;
|
||||
updatePackageInfo: (packageInfo: PackageInfo | undefined) => void;
|
||||
setIsLoadingSecondStep: (isLoading: boolean) => void;
|
||||
}> = ({
|
||||
agentPolicyId,
|
||||
updateAgentPolicy,
|
||||
packageInfo,
|
||||
updatePackageInfo,
|
||||
setIsLoadingSecondStep,
|
||||
}) => {
|
||||
// Selected package state
|
||||
const [selectedPkgKey, setSelectedPkgKey] = useState<string | undefined>(
|
||||
packageInfo ? pkgKeyFromPackageInfo(packageInfo) : undefined
|
||||
);
|
||||
const [selectedPkgError, setSelectedPkgError] = useState<Error>();
|
||||
|
||||
// Fetch agent policy info
|
||||
const {
|
||||
data: agentPolicyData,
|
||||
error: agentPolicyError,
|
||||
isLoading: isAgentPoliciesLoading,
|
||||
} = useGetOneAgentPolicy(agentPolicyId);
|
||||
|
||||
// Fetch packages info
|
||||
// Filter out limited packages already part of selected agent policy
|
||||
const [packages, setPackages] = useState<GetPackagesResponse['response']>([]);
|
||||
const {
|
||||
data: packagesData,
|
||||
error: packagesError,
|
||||
isLoading: isPackagesLoading,
|
||||
} = useGetPackages();
|
||||
const {
|
||||
data: limitedPackagesData,
|
||||
isLoading: isLimitedPackagesLoading,
|
||||
} = useGetLimitedPackages();
|
||||
useEffect(() => {
|
||||
if (packagesData?.response && limitedPackagesData?.response && agentPolicyData?.item) {
|
||||
const allPackages = packagesData.response;
|
||||
const limitedPackages = limitedPackagesData.response;
|
||||
const usedLimitedPackages = (agentPolicyData.item.package_policies as PackagePolicy[])
|
||||
.map((packagePolicy) => packagePolicy.package?.name || '')
|
||||
.filter((pkgName) => limitedPackages.includes(pkgName));
|
||||
setPackages(allPackages.filter((pkg) => !usedLimitedPackages.includes(pkg.name)));
|
||||
}
|
||||
}, [packagesData, limitedPackagesData, agentPolicyData]);
|
||||
|
||||
// Update parent agent policy state
|
||||
useEffect(() => {
|
||||
if (agentPolicyData && agentPolicyData.item) {
|
||||
updateAgentPolicy(agentPolicyData.item);
|
||||
}
|
||||
}, [agentPolicyData, updateAgentPolicy]);
|
||||
|
||||
// Update parent selected package state
|
||||
useEffect(() => {
|
||||
const fetchPackageInfo = async () => {
|
||||
if (selectedPkgKey) {
|
||||
setIsLoadingSecondStep(true);
|
||||
const { data, error } = await sendGetPackageInfoByKey(selectedPkgKey);
|
||||
if (error) {
|
||||
setSelectedPkgError(error);
|
||||
updatePackageInfo(undefined);
|
||||
} else if (data && data.response) {
|
||||
setSelectedPkgError(undefined);
|
||||
updatePackageInfo(data.response);
|
||||
}
|
||||
setIsLoadingSecondStep(false);
|
||||
} else {
|
||||
setSelectedPkgError(undefined);
|
||||
updatePackageInfo(undefined);
|
||||
}
|
||||
};
|
||||
if (!packageInfo || selectedPkgKey !== pkgKeyFromPackageInfo(packageInfo)) {
|
||||
fetchPackageInfo();
|
||||
}
|
||||
}, [selectedPkgKey, packageInfo, updatePackageInfo, setIsLoadingSecondStep]);
|
||||
|
||||
// Display agent policy error if there is one
|
||||
if (agentPolicyError) {
|
||||
return (
|
||||
<Error
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepSelectPackage.errorLoadingPolicyTitle"
|
||||
defaultMessage="Error loading agent policy information"
|
||||
/>
|
||||
}
|
||||
error={agentPolicyError}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Display packages list error if there is one
|
||||
if (packagesError) {
|
||||
return (
|
||||
<Error
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepSelectPackage.errorLoadingPackagesTitle"
|
||||
defaultMessage="Error loading integrations"
|
||||
/>
|
||||
}
|
||||
error={packagesError}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<EuiSelectable
|
||||
searchable
|
||||
allowExclusions={false}
|
||||
singleSelection={true}
|
||||
isLoading={isPackagesLoading || isLimitedPackagesLoading || isAgentPoliciesLoading}
|
||||
options={packages.map(({ title, name, version, icons }) => {
|
||||
const pkgkey = `${name}-${version}`;
|
||||
return {
|
||||
label: title || name,
|
||||
key: pkgkey,
|
||||
prepend: <PackageIcon packageName={name} version={version} icons={icons} size="m" />,
|
||||
checked: selectedPkgKey === pkgkey ? 'on' : undefined,
|
||||
};
|
||||
})}
|
||||
listProps={{
|
||||
bordered: true,
|
||||
}}
|
||||
searchProps={{
|
||||
placeholder: i18n.translate(
|
||||
'xpack.fleet.createPackagePolicy.stepSelectPackage.filterPackagesInputPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Search for integrations',
|
||||
}
|
||||
),
|
||||
}}
|
||||
height={240}
|
||||
onChange={(options) => {
|
||||
const selectedOption = options.find((option) => option.checked === 'on');
|
||||
if (selectedOption) {
|
||||
if (selectedOption.key !== selectedPkgKey) {
|
||||
setSelectedPkgKey(selectedOption.key);
|
||||
}
|
||||
} else {
|
||||
setSelectedPkgKey(undefined);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{(list, search) => (
|
||||
<Fragment>
|
||||
{search}
|
||||
<EuiSpacer size="m" />
|
||||
{list}
|
||||
</Fragment>
|
||||
)}
|
||||
</EuiSelectable>
|
||||
</EuiFlexItem>
|
||||
{/* Display selected package error if there is one */}
|
||||
{selectedPkgError ? (
|
||||
<EuiFlexItem>
|
||||
<Error
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.stepSelectPackage.errorLoadingSelectedPackageTitle"
|
||||
defaultMessage="Error loading selected integration"
|
||||
/>
|
||||
}
|
||||
error={selectedPkgError}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -18,9 +18,11 @@ import {
|
|||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { INTEGRATIONS_PLUGIN_ID } from '../../../../../../../../common';
|
||||
import { pagePathGetters } from '../../../../../../../constants';
|
||||
import type { AgentPolicy, PackagePolicy } from '../../../../../types';
|
||||
import { PackageIcon, PackagePolicyActionsMenu } from '../../../../../components';
|
||||
import { useCapabilities, useLink } from '../../../../../hooks';
|
||||
import { useCapabilities, useStartServices } from '../../../../../hooks';
|
||||
|
||||
interface InMemoryPackagePolicy extends PackagePolicy {
|
||||
packageName?: string;
|
||||
|
@ -49,7 +51,7 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({
|
|||
agentPolicy,
|
||||
...rest
|
||||
}) => {
|
||||
const { getHref } = useLink();
|
||||
const { application } = useStartServices();
|
||||
const hasWriteCapabilities = useCapabilities().write;
|
||||
|
||||
// With the package policies provided on input, generate the list of package policies
|
||||
|
@ -194,8 +196,13 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({
|
|||
<EuiButton
|
||||
key="addPackagePolicyButton"
|
||||
isDisabled={!hasWriteCapabilities}
|
||||
iconType="plusInCircle"
|
||||
href={getHref('add_integration_from_policy', { policyId: agentPolicy.id })}
|
||||
iconType="refresh"
|
||||
onClick={() => {
|
||||
application.navigateToApp(INTEGRATIONS_PLUGIN_ID, {
|
||||
path: `#${pagePathGetters.integrations_all()[1]}`,
|
||||
state: { forAgentPolicyId: agentPolicy.id },
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.policyDetails.addPackagePolicyButtonText"
|
||||
|
|
|
@ -29,6 +29,7 @@ import type { FleetConfigType, FleetStartServices } from '../../plugin';
|
|||
import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common';
|
||||
|
||||
import { AgentPolicyContextProvider } from './hooks';
|
||||
import { INTEGRATIONS_ROUTING_PATHS } from './constants';
|
||||
|
||||
import { Error, Loading } from './components';
|
||||
|
@ -201,9 +202,11 @@ export const IntegrationsAppContext: React.FC<{
|
|||
<FleetStatusProvider>
|
||||
<IntraAppStateProvider kibanaScopedHistory={history}>
|
||||
<Router history={routerHistoryInstance}>
|
||||
<PackageInstallProvider notifications={startServices.notifications}>
|
||||
{children}
|
||||
</PackageInstallProvider>
|
||||
<AgentPolicyContextProvider>
|
||||
<PackageInstallProvider notifications={startServices.notifications}>
|
||||
{children}
|
||||
</PackageInstallProvider>
|
||||
</AgentPolicyContextProvider>
|
||||
</Router>
|
||||
</IntraAppStateProvider>
|
||||
</FleetStatusProvider>
|
||||
|
|
|
@ -10,3 +10,4 @@ export { useBreadcrumbs } from './use_breadcrumbs';
|
|||
export { useLinks } from './use_links';
|
||||
export * from './use_local_search';
|
||||
export * from './use_package_install';
|
||||
export * from './use_agent_policy_context';
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { FunctionComponent } from 'react';
|
||||
import React, { createContext, useContext, useRef, useCallback } from 'react';
|
||||
|
||||
import type { IntegrationsAppBrowseRouteState } from '../../../types';
|
||||
import { useIntraAppState } from '../../../hooks';
|
||||
|
||||
interface AgentPolicyContextValue {
|
||||
getId(): string | undefined;
|
||||
}
|
||||
|
||||
const AgentPolicyContext = createContext<AgentPolicyContextValue>({ getId: () => undefined });
|
||||
|
||||
export const AgentPolicyContextProvider: FunctionComponent = ({ children }) => {
|
||||
const maybeState = useIntraAppState<undefined | IntegrationsAppBrowseRouteState>();
|
||||
const ref = useRef<undefined | string>(maybeState?.forAgentPolicyId);
|
||||
|
||||
const getId = useCallback(() => {
|
||||
return ref.current;
|
||||
}, []);
|
||||
return <AgentPolicyContext.Provider value={{ getId }}>{children}</AgentPolicyContext.Provider>;
|
||||
};
|
||||
|
||||
export const useAgentPolicyContext = () => {
|
||||
const ctx = useContext(AgentPolicyContext);
|
||||
if (!ctx) {
|
||||
throw new Error('useAgentPolicyContext can only be used inside of AgentPolicyContextProvider');
|
||||
}
|
||||
return ctx;
|
||||
};
|
|
@ -37,7 +37,12 @@ import {
|
|||
INTEGRATIONS_ROUTING_PATHS,
|
||||
pagePathGetters,
|
||||
} from '../../../../constants';
|
||||
import { useCapabilities, useGetPackageInfoByKey, useLink } from '../../../../hooks';
|
||||
import {
|
||||
useCapabilities,
|
||||
useGetPackageInfoByKey,
|
||||
useLink,
|
||||
useAgentPolicyContext,
|
||||
} from '../../../../hooks';
|
||||
import { pkgKeyFromPackageInfo } from '../../../../services';
|
||||
import type {
|
||||
CreatePackagePolicyRouteState,
|
||||
|
@ -79,6 +84,7 @@ function Breadcrumbs({ packageTitle }: { packageTitle: string }) {
|
|||
}
|
||||
|
||||
export function Detail() {
|
||||
const { getId: getAgentPolicyId } = useAgentPolicyContext();
|
||||
const { pkgkey, panel } = useParams<DetailParams>();
|
||||
const { getHref } = useLink();
|
||||
const hasWriteCapabilites = useCapabilities().write;
|
||||
|
@ -87,6 +93,7 @@ export function Detail() {
|
|||
const queryParams = useMemo(() => new URLSearchParams(search), [search]);
|
||||
const integration = useMemo(() => queryParams.get('integration'), [queryParams]);
|
||||
const services = useStartServices();
|
||||
const agentPolicyIdFromContext = getAgentPolicyId();
|
||||
|
||||
// Package info state
|
||||
const [packageInfo, setPackageInfo] = useState<PackageInfo | null>(null);
|
||||
|
@ -211,24 +218,42 @@ export function Detail() {
|
|||
search,
|
||||
hash,
|
||||
});
|
||||
const redirectToPath: CreatePackagePolicyRouteState['onSaveNavigateTo'] &
|
||||
CreatePackagePolicyRouteState['onCancelNavigateTo'] = [
|
||||
INTEGRATIONS_PLUGIN_ID,
|
||||
{
|
||||
path: currentPath,
|
||||
},
|
||||
];
|
||||
|
||||
const path = pagePathGetters.add_integration_to_policy({
|
||||
pkgkey,
|
||||
...(integration ? { integration } : {}),
|
||||
...(agentPolicyIdFromContext ? { agentPolicyId: agentPolicyIdFromContext } : {}),
|
||||
})[1];
|
||||
|
||||
let redirectToPath: CreatePackagePolicyRouteState['onSaveNavigateTo'] &
|
||||
CreatePackagePolicyRouteState['onCancelNavigateTo'];
|
||||
|
||||
if (agentPolicyIdFromContext) {
|
||||
redirectToPath = [
|
||||
PLUGIN_ID,
|
||||
{
|
||||
path: `#${
|
||||
pagePathGetters.policy_details({
|
||||
policyId: agentPolicyIdFromContext,
|
||||
})[1]
|
||||
}`,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
redirectToPath = [
|
||||
INTEGRATIONS_PLUGIN_ID,
|
||||
{
|
||||
path: currentPath,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const redirectBackRouteState: CreatePackagePolicyRouteState = {
|
||||
onSaveNavigateTo: redirectToPath,
|
||||
onCancelNavigateTo: redirectToPath,
|
||||
onCancelUrl: currentPath,
|
||||
};
|
||||
|
||||
const path = pagePathGetters.add_integration_to_policy({
|
||||
pkgkey,
|
||||
...(integration ? { integration } : {}),
|
||||
})[1];
|
||||
|
||||
services.application.navigateToApp(PLUGIN_ID, {
|
||||
// Necessary because of Fleet's HashRouter. Can be changed when
|
||||
// https://github.com/elastic/kibana/issues/96134 is resolved
|
||||
|
@ -236,7 +261,16 @@ export function Detail() {
|
|||
state: redirectBackRouteState,
|
||||
});
|
||||
},
|
||||
[history, hash, pathname, search, pkgkey, integration, services.application]
|
||||
[
|
||||
history,
|
||||
hash,
|
||||
pathname,
|
||||
search,
|
||||
pkgkey,
|
||||
integration,
|
||||
services.application,
|
||||
agentPolicyIdFromContext,
|
||||
]
|
||||
);
|
||||
|
||||
const headerRightContent = useMemo(
|
||||
|
@ -284,6 +318,9 @@ export function Detail() {
|
|||
href={getHref('add_integration_to_policy', {
|
||||
pkgkey,
|
||||
...(integration ? { integration } : {}),
|
||||
...(agentPolicyIdFromContext
|
||||
? { agentPolicyId: agentPolicyIdFromContext }
|
||||
: {}),
|
||||
})}
|
||||
onClick={handleAddIntegrationPolicyClick}
|
||||
data-test-subj="addIntegrationPolicyButton"
|
||||
|
@ -325,6 +362,7 @@ export function Detail() {
|
|||
packageInstallStatus,
|
||||
pkgkey,
|
||||
updateAvailable,
|
||||
agentPolicyIdFromContext,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -111,9 +111,10 @@ export const pagePathGetters: {
|
|||
FLEET_BASE_PATH,
|
||||
`/policies/${policyId}/add-integration`,
|
||||
],
|
||||
add_integration_to_policy: ({ pkgkey, integration }) => [
|
||||
add_integration_to_policy: ({ pkgkey, integration, agentPolicyId }) => [
|
||||
FLEET_BASE_PATH,
|
||||
`/integrations/${pkgkey}/add-integration${integration ? `/${integration}` : ''}`,
|
||||
// prettier-ignore
|
||||
`/integrations/${pkgkey}/add-integration${integration ? `/${integration}` : ''}${agentPolicyId ? `?policyId=${agentPolicyId}` : ''}`,
|
||||
],
|
||||
edit_integration: ({ policyId, packagePolicyId }) => [
|
||||
FLEET_BASE_PATH,
|
||||
|
|
|
@ -39,6 +39,11 @@ export interface AgentDetailsReassignPolicyAction {
|
|||
onDoneNavigateTo?: Parameters<ApplicationStart['navigateToApp']>;
|
||||
}
|
||||
|
||||
export interface IntegrationsAppBrowseRouteState {
|
||||
/** The agent policy that we are browsing integrations for */
|
||||
forAgentPolicyId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* All possible Route states.
|
||||
*/
|
||||
|
|
|
@ -8996,11 +8996,6 @@
|
|||
"xpack.fleet.createPackagePolicy.stepConfigure.toggleAdvancedOptionsButtonText": "高度なオプション",
|
||||
"xpack.fleet.createPackagePolicy.stepConfigurePackagePolicyTitle": "統合の構成",
|
||||
"xpack.fleet.createPackagePolicy.stepSelectAgentPolicyTitle": "エージェントポリシーを選択",
|
||||
"xpack.fleet.createPackagePolicy.stepSelectPackage.errorLoadingPackagesTitle": "統合の読み込みエラー",
|
||||
"xpack.fleet.createPackagePolicy.stepSelectPackage.errorLoadingPolicyTitle": "エージェントポリシー情報の読み込みエラー",
|
||||
"xpack.fleet.createPackagePolicy.stepSelectPackage.errorLoadingSelectedPackageTitle": "選択した統合の読み込みエラー",
|
||||
"xpack.fleet.createPackagePolicy.stepSelectPackage.filterPackagesInputPlaceholder": "統合を検索",
|
||||
"xpack.fleet.createPackagePolicy.stepSelectPackageTitle": "統合を選択",
|
||||
"xpack.fleet.createPackagePolicy.StepSelectPolicy.addButton": "エージェントポリシーを作成",
|
||||
"xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyLabel": "エージェントポリシー",
|
||||
"xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyPlaceholderText": "この統合を追加するエージェントポリシーを選択",
|
||||
|
|
|
@ -9077,13 +9077,7 @@
|
|||
"xpack.fleet.createPackagePolicy.stepConfigure.toggleAdvancedOptionsButtonText": "高级选项",
|
||||
"xpack.fleet.createPackagePolicy.stepConfigurePackagePolicyTitle": "配置集成",
|
||||
"xpack.fleet.createPackagePolicy.stepSelectAgentPolicyTitle": "选择代理策略",
|
||||
"xpack.fleet.createPackagePolicy.stepSelectPackage.errorLoadingPackagesTitle": "加载集成时出错",
|
||||
"xpack.fleet.createPackagePolicy.stepSelectPackage.errorLoadingPolicyTitle": "加载代理策略信息时出错",
|
||||
"xpack.fleet.createPackagePolicy.stepSelectPackage.errorLoadingSelectedPackageTitle": "加载选定集成时出错",
|
||||
"xpack.fleet.createPackagePolicy.stepSelectPackage.filterPackagesInputPlaceholder": "搜索集成",
|
||||
"xpack.fleet.createPackagePolicy.stepSelectPackageTitle": "选择集成",
|
||||
"xpack.fleet.createPackagePolicy.StepSelectPolicy.addButton": "创建代理策略",
|
||||
"xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyAgentsCountText": "{count, plural, other {# 个代理}}已注册",
|
||||
"xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyAgentsDescriptionText": "{count, plural, other {# 个代理}}已注册到选定代理策略。",
|
||||
"xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyLabel": "代理策略",
|
||||
"xpack.fleet.createPackagePolicy.StepSelectPolicy.agentPolicyPlaceholderText": "选择要将此集成添加到的代理策略",
|
||||
|
|
|
@ -16,6 +16,8 @@ export function SyntheticsIntegrationPageProvider({
|
|||
const testSubjects = getService('testSubjects');
|
||||
const comboBox = getService('comboBox');
|
||||
|
||||
const fixedFooterHeight = 72; // Size of EuiBottomBar more or less
|
||||
|
||||
return {
|
||||
/**
|
||||
* Navigates to the Synthetics Integration page
|
||||
|
@ -85,6 +87,7 @@ export function SyntheticsIntegrationPageProvider({
|
|||
*/
|
||||
async fillTextInputByTestSubj(testSubj: string, value: string) {
|
||||
const field = await testSubjects.find(testSubj, 5000);
|
||||
await field.scrollIntoViewIfNecessary({ bottomOffset: fixedFooterHeight });
|
||||
await field.click();
|
||||
await field.clearValue();
|
||||
await field.type(value);
|
||||
|
@ -96,6 +99,7 @@ export function SyntheticsIntegrationPageProvider({
|
|||
* @params {value} the value of the input
|
||||
*/
|
||||
async fillTextInput(field: WebElementWrapper, value: string) {
|
||||
await field.scrollIntoViewIfNecessary({ bottomOffset: fixedFooterHeight });
|
||||
await field.click();
|
||||
await field.clearValue();
|
||||
await field.type(value);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue