Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] Modal to allow user to force install an unverified package #136108

Merged
merged 34 commits into from
Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8290933
show unverified modal on asset install
hop-dev Jul 6, 2022
1314902
fix modal not closing
hop-dev Jul 8, 2022
5988daf
Move modal to common components
hop-dev Jul 8, 2022
ee2d37a
modify types for package policy creation
hop-dev Jul 8, 2022
28171a0
add missing export
hop-dev Jul 8, 2022
7979c92
package policy modal
hop-dev Jul 8, 2022
4b86fe1
add package info to unverified modal
hop-dev Jul 8, 2022
ceb381c
return package info as part of validation error
hop-dev Jul 8, 2022
9a67818
API: skip archive in create package policy
hop-dev Jul 8, 2022
37380e1
show package details in modal
hop-dev Jul 8, 2022
7c03097
pass force parameter on package policy creation
hop-dev Jul 8, 2022
8df9298
fix: ability to force install package when creating package policy
hop-dev Jul 8, 2022
f579885
recert: use error details for package verification moda
hop-dev Jul 11, 2022
58824b8
do not ignore verification errors when creating agent policy
hop-dev Jul 8, 2022
35b86fc
remove old type additions
hop-dev Jul 12, 2022
7f28b75
add modal to multi page layout
hop-dev Jul 12, 2022
858f2e9
remove refs to FleetPackageErrorResponse
hop-dev Jul 12, 2022
314fb14
fix: Error types
hop-dev Jul 12, 2022
2174f02
add storybook file
hop-dev Jul 12, 2022
12967d2
change import to fix test
hop-dev Jul 12, 2022
47b00b1
ensure package is installed before getting package info
hop-dev Jul 12, 2022
68485a4
Update x-pack/plugins/fleet/public/components/unverified_package_moda…
hop-dev Jul 14, 2022
950ec9a
Update x-pack/plugins/fleet/public/components/unverified_package_moda…
hop-dev Jul 14, 2022
e9011e1
make statusCode non optional
hop-dev Jul 14, 2022
48d1531
use overlay service for confirm modal
hop-dev Jul 14, 2022
c0e52cf
move confirm force install to its own hook
hop-dev Jul 14, 2022
2513523
rename to ConfirmForceInstallModal
hop-dev Jul 14, 2022
f070d39
fix error validation types
hop-dev Jul 14, 2022
3685850
return optionallyForceInstall to prevent uncaught rejection
hop-dev Jul 14, 2022
6e2bfc7
fix edit pkg policy unit tests
hop-dev Jul 14, 2022
f56b3dd
fix agent list tests
hop-dev Jul 20, 2022
7944899
fix package card tests
hop-dev Jul 20, 2022
b7c998d
use useForceConfirmForceInstall for add integration pages
hop-dev Jul 20, 2022
0ec8235
PR Feedback
hop-dev Jul 20, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export interface GetOnePackagePolicyResponse {
}

export interface CreatePackagePolicyRequest {
body: NewPackagePolicy;
body: NewPackagePolicy & { force?: boolean };
Copy link
Contributor Author

@hop-dev hop-dev Jul 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

force is not a new parameter, its just the typing didn't have it

}

export interface CreatePackagePolicyResponse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import { safeLoad } from 'js-yaml';

import { i18n } from '@kbn/i18n';

import { useConfirmForceInstall } from '../../../../../../../integrations/hooks';

import { isVerificationError } from '../../../../../../../../services';

import type { MultiPageStepLayoutProps } from '../../types';
import type { PackagePolicyFormState } from '../../../types';
import type { NewPackagePolicy } from '../../../../../../types';
Expand Down Expand Up @@ -83,7 +87,7 @@ export const AddIntegrationPageStep: React.FC<MultiPageStepLayoutProps> = (props
const { notifications } = useStartServices();
const [formState, setFormState] = useState<PackagePolicyFormState>('VALID');
const [validationResults, setValidationResults] = useState<PackagePolicyValidationResults>();

const confirmForceInstall = useConfirmForceInstall();
const [packagePolicy, setPackagePolicy] = useState<NewPackagePolicy>({
name: '',
description: '',
Expand Down Expand Up @@ -136,32 +140,59 @@ export const AddIntegrationPageStep: React.FC<MultiPageStepLayoutProps> = (props
);

// Save package policy
const savePackagePolicy = async (pkgPolicy: NewPackagePolicy) => {
const savePackagePolicy = async ({
newPackagePolicy,
force,
}: {
newPackagePolicy: NewPackagePolicy;
force?: boolean;
}) => {
setFormState('LOADING');
const result = await sendCreatePackagePolicy(pkgPolicy);
const result = await sendCreatePackagePolicy({ ...newPackagePolicy, force });
setFormState('SUBMITTED');
return result;
};

const onSubmit = useCallback(async () => {
const hasErrors = validationResults ? validationHasErrors(validationResults) : false;
const onSubmit = useCallback(
async ({ force = false }: { force?: boolean } = {}) => {
const hasErrors = validationResults ? validationHasErrors(validationResults) : false;

if (formState === 'VALID' && hasErrors) {
setFormState('INVALID');
return;
}
setFormState('LOADING');
if (formState === 'VALID' && hasErrors) {
setFormState('INVALID');
return;
}
setFormState('LOADING');

const { error } = await savePackagePolicy(packagePolicy);
if (error) {
notifications.toasts.addError(error, {
title: 'Error',
});
setFormState('VALID');
} else {
onNext();
}
}, [validationResults, formState, packagePolicy, notifications.toasts, onNext]);
const { error } = await savePackagePolicy({ newPackagePolicy: packagePolicy, force });
if (error) {
if (isVerificationError(error)) {
const forceInstall = await confirmForceInstall(packageInfo);

if (forceInstall) {
onSubmit({ force: true });
} else {
setFormState('VALID');
}
return;
}
notifications.toasts.addError(error, {
title: 'Error',
});
setFormState('VALID');
} else {
onNext();
}
},
[
validationResults,
formState,
packagePolicy,
notifications.toasts,
confirmForceInstall,
packageInfo,
onNext,
]
);

useEffect(() => {
const getBasePolicy = async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import type { EuiStepProps } from '@elastic/eui/src/components/steps/step';
import { safeLoad } from 'js-yaml';

import { useCancelAddPackagePolicy, useOnSaveNavigate } from '../hooks';
import type { CreatePackagePolicyRequest } from '../../../../../../../common';

import { dataTypes, FLEET_SYSTEM_PACKAGE, splitPkgKey } from '../../../../../../../common';
import { useConfirmForceInstall } from '../../../../../integrations/hooks';
import type {
AgentPolicy,
NewAgentPolicy,
Expand All @@ -44,7 +47,7 @@ import { Loading, Error, ExtensionWrapper } from '../../../../components';
import { agentPolicyFormValidation, ConfirmDeployAgentPolicyModal } from '../../components';
import { useUIExtension } from '../../../../hooks';
import type { PackagePolicyEditExtensionComponentProps } from '../../../../types';
import { pkgKeyFromPackageInfo } from '../../../../services';
import { pkgKeyFromPackageInfo, isVerificationError } from '../../../../services';

import type {
PackagePolicyFormState,
Expand Down Expand Up @@ -152,6 +155,9 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({

const navigateAddAgentHelp = (policy?: PackagePolicy) =>
onSaveNavigate(policy, ['showAddAgentHelp']);

const confirmForceInstall = useConfirmForceInstall();

// Validation state
const [validationResults, setValidationResults] = useState<PackagePolicyValidationResults>();
const [hasAgentPolicyError, setHasAgentPolicyError] = useState<boolean>(false);
Expand Down Expand Up @@ -297,7 +303,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({

// Save package policy
const savePackagePolicy = useCallback(
async (pkgPolicy: NewPackagePolicy) => {
async (pkgPolicy: CreatePackagePolicyRequest['body']) => {
setFormState('LOADING');
const result = await sendCreatePackagePolicy(pkgPolicy);
setFormState(agentCount ? 'SUBMITTED' : 'SUBMITTED_NO_AGENTS');
Expand All @@ -306,101 +312,119 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
[agentCount]
);

const createAgentPolicy = useCallback(async (): Promise<string | undefined> => {
let policyId;
setFormState('LOADING');
// do not create agent policy with system integration if package policy already is for system package
const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE;
const resp = await sendCreateAgentPolicy(newAgentPolicy, {
withSysMonitoring: withSysMonitoring && !packagePolicyIsSystem,
});
if (resp.error) {
setFormState('VALID');
throw resp.error;
}
if (resp.data) {
policyId = resp.data.item.id;
setAgentPolicy(resp.data.item);

updatePackagePolicy({ policy_id: policyId });
}
return policyId;
}, [newAgentPolicy, updatePackagePolicy, withSysMonitoring, packagePolicy]);
const createAgentPolicy = useCallback(
async ({ force }: { force?: boolean } = {}): Promise<string | undefined> => {
let policyId;
setFormState('LOADING');
// do not create agent policy with system integration if package policy already is for system package
const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE;
const resp = await sendCreateAgentPolicy(newAgentPolicy, {
withSysMonitoring: withSysMonitoring && !packagePolicyIsSystem,
});
if (resp.error) {
setFormState('VALID');
throw resp.error;
}
if (resp.data) {
policyId = resp.data.item.id;
setAgentPolicy(resp.data.item);
setSelectedPolicyTab(SelectedPolicyTab.EXISTING);
updatePackagePolicy({ policy_id: policyId });
}
return policyId;
},
[newAgentPolicy, updatePackagePolicy, withSysMonitoring, packagePolicy]
);

const onSubmit = useCallback(async () => {
if (formState === 'VALID' && hasErrors) {
setFormState('INVALID');
return;
}
if (agentCount !== 0 && formState !== 'CONFIRM') {
setFormState('CONFIRM');
return;
}
let policyId;
if (selectedPolicyTab === SelectedPolicyTab.NEW) {
try {
policyId = await createAgentPolicy();
} catch (e) {
notifications.toasts.addError(e, {
title: i18n.translate('xpack.fleet.createAgentPolicy.errorNotificationTitle', {
defaultMessage: 'Unable to create agent policy',
}),
});
const onSubmit = useCallback(
async ({ force }: { force?: boolean } = {}) => {
if (formState === 'VALID' && hasErrors) {
setFormState('INVALID');
return;
}
}

setFormState('LOADING');
// passing pkgPolicy with policy_id here as setPackagePolicy doesn't propagate immediately
const { error, data } = await savePackagePolicy({
...packagePolicy,
policy_id: policyId ?? packagePolicy.policy_id,
});
if (!error) {
setSavedPackagePolicy(data!.item);

const hasAgentsAssigned = agentCount && agentPolicy;
if (!hasAgentsAssigned) {
setFormState('SUBMITTED_NO_AGENTS');
if (agentCount !== 0 && formState !== 'CONFIRM') {
setFormState('CONFIRM');
return;
}
onSaveNavigate(data!.item);

notifications.toasts.addSuccess({
title: i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationTitle', {
defaultMessage: `'{packagePolicyName}' integration added.`,
values: {
packagePolicyName: packagePolicy.name,
},
}),
text: hasAgentsAssigned
? i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationMessage', {
defaultMessage: `Fleet will deploy updates to all agents that use the '{agentPolicyName}' policy.`,
values: {
agentPolicyName: agentPolicy!.name,
},
})
: undefined,
'data-test-subj': 'packagePolicyCreateSuccessToast',
});
} else {
notifications.toasts.addError(error, {
title: 'Error',
let policyId;
if (selectedPolicyTab === SelectedPolicyTab.NEW) {
try {
policyId = await createAgentPolicy({ force });
} catch (e) {
notifications.toasts.addError(e, {
title: i18n.translate('xpack.fleet.createAgentPolicy.errorNotificationTitle', {
defaultMessage: 'Unable to create agent policy',
}),
});
return;
}
}

setFormState('LOADING');
// passing pkgPolicy with policy_id here as setPackagePolicy doesn't propagate immediately
const { error, data } = await savePackagePolicy({
...packagePolicy,
policy_id: policyId ?? packagePolicy.policy_id,
force,
});
setFormState('VALID');
}
}, [
formState,
hasErrors,
agentCount,
savePackagePolicy,
onSaveNavigate,
agentPolicy,
notifications.toasts,
packagePolicy,
selectedPolicyTab,
createAgentPolicy,
]);
if (!error) {
setSavedPackagePolicy(data!.item);

const hasAgentsAssigned = agentCount && agentPolicy;
if (!hasAgentsAssigned) {
setFormState('SUBMITTED_NO_AGENTS');
return;
}
onSaveNavigate(data!.item);

notifications.toasts.addSuccess({
title: i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationTitle', {
defaultMessage: `'{packagePolicyName}' integration added.`,
values: {
packagePolicyName: packagePolicy.name,
},
}),
text: hasAgentsAssigned
? i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationMessage', {
defaultMessage: `Fleet will deploy updates to all agents that use the '{agentPolicyName}' policy.`,
values: {
agentPolicyName: agentPolicy!.name,
},
})
: undefined,
'data-test-subj': 'packagePolicyCreateSuccessToast',
});
} else {
if (isVerificationError(error)) {
const forceInstall = await confirmForceInstall(packagePolicy.package!);

if (forceInstall) {
onSubmit({ force: true });
} else {
setFormState('VALID');
}
return;
}
notifications.toasts.addError(error, {
title: 'Error',
});
setFormState('VALID');
}
},
[
formState,
hasErrors,
agentCount,
selectedPolicyTab,
savePackagePolicy,
packagePolicy,
createAgentPolicy,
notifications.toasts,
agentPolicy,
onSaveNavigate,
confirmForceInstall,
]
);

const integrationInfo = useMemo(
() =>
Expand Down Expand Up @@ -597,7 +621,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
onClick={onSubmit}
onClick={() => onSubmit()}
isLoading={formState === 'LOADING'}
disabled={formState !== 'VALID' || hasAgentPolicyError || !validationResults}
iconType="save"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ jest.mock('../../../../integrations/hooks', () => {
return {
...jest.requireActual('../../../../integrations/hooks'),
useGetOnePackagePolicy: jest.fn(),
useConfirmForceInstall: jest.fn(),
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import { sendGetAgents, sendGetAgentStatus } from '../../../hooks';

import { AgentListPage } from '.';

jest.mock('../../../../integrations/hooks/use_confirm_force_install', () => ({
useConfirmForceInstall: () => <>confirmForceInstall</>,
}));
jest.mock('../../../hooks', () => ({
...jest.requireActual('../../../hooks'),
sendGetAgents: jest.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export const IntegrationDebugger: React.FunctionComponent = () => {
const installResponse = await sendInstallPackage(integration.name, integration.version);

if (installResponse.error) {
notifications.toasts.addError(installResponse.error, {
notifications.toasts.addError(new Error(installResponse.error.message), {
title: i18n.translate('xpack.fleet.debug.integrationDebugger.reinstall.error', {
defaultMessage: 'Error reinstalling {integrationTitle}',
values: { integrationTitle: integration.title },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export * from './use_local_search';
export * from './use_package_install';
export * from './use_agent_policy_context';
export * from './use_integrations_state';
export * from './use_confirm_force_install';
Loading