Skip to content

Commit

Permalink
[Fleet] Update Package Policy UI to Support Upgrading Package Policies (
Browse files Browse the repository at this point in the history
#107171)

* Remove description column, replace w/ tooltip

* Add upgrade tooltip to version column in package policy table

* Add update policy action

* Add inline upgrade package policy button

* Clean up types + add upgrade CTA's to integrations policy table

* Fix i18n

* Fix button widths

* Upgrade package policy when saving integration w/ upgrade param

* Update edit policy page description for upgrades

* Support setting vars on new package version before saving to upgrade

* Add flyout for JSON of previous policy version

* Compile package policy before displaying in flyout

* Support different success redirects following package policy upgrades

* Fix i18n

* Fix more type errors

* Fix even more type errors 🙃

* Fix type errors

* Don't throw errors for missing vars, include them in missingVars response object

* Update tests for new missingVars field

* Fix failing tests

* Address PR feedback

* Fix missing i18n value

* Fix types

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
kpollich and kibanamachine authored Aug 11, 2021
1 parent 6054d7e commit 35f55ce
Show file tree
Hide file tree
Showing 22 changed files with 618 additions and 136 deletions.
4 changes: 4 additions & 0 deletions x-pack/plugins/fleet/common/services/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ export const packagePolicyRouteService = {
getDeletePath: () => {
return PACKAGE_POLICY_API_ROUTES.DELETE_PATTERN;
},

getUpgradePath: () => {
return PACKAGE_POLICY_API_ROUTES.UPGRADE_PATTERN;
},
};

export const agentPolicyRouteService = {
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

export * from './models';
export * from './rest_spec';

import type { PreconfiguredAgentPolicy, PreconfiguredPackage } from './models/preconfiguration';

export interface FleetConfigType {
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/types/models/package_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,5 @@ export type PackagePolicySOAttributes = Omit<PackagePolicy, 'id' | 'version'>;

export type DryRunPackagePolicy = NewPackagePolicy & {
errors?: Array<{ key: string | undefined; message: string }>;
missingVars?: string[];
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import {
import { WithHeaderLayout } from '../../../../layouts';
import type { AgentPolicy, PackageInfo, RegistryPolicyTemplate } from '../../../../types';
import { PackageIcon } from '../../../../components';
import type { CreatePackagePolicyFrom } from '../types';
import type { EditPackagePolicyFrom } from '../types';

export const CreatePackagePolicyPageLayout: React.FunctionComponent<{
from: CreatePackagePolicyFrom;
from: EditPackagePolicyFrom;
cancelUrl: string;
onCancel?: React.ReactEventHandler;
agentPolicy?: AgentPolicy;
Expand All @@ -48,11 +48,48 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{
'data-test-subj': dataTestSubj,
tabs = [],
}) => {
const isAdd = useMemo(() => ['package'].includes(from), [from]);
const isEdit = useMemo(() => ['edit', 'package-edit'].includes(from), [from]);
const isUpgrade = useMemo(
() =>
['upgrade-from-fleet-policy-list', 'upgrade-from-integrations-policy-list'].includes(from),
[from]
);

const pageTitle = useMemo(() => {
if (
(from === 'package' || from === 'package-edit' || from === 'edit' || from === 'policy') &&
packageInfo
) {
if ((isAdd || isEdit || isUpgrade) && packageInfo) {
let pageTitleText = (
<FormattedMessage
id="xpack.fleet.createPackagePolicy.pageTitleWithPackageName"
defaultMessage="Add {packageName} integration"
values={{
packageName: integrationInfo?.title || packageInfo.title,
}}
/>
);

if (isEdit) {
pageTitleText = (
<FormattedMessage
id="xpack.fleet.editPackagePolicy.editPageTitleWithPackageName"
defaultMessage="Edit {packageName} integration"
values={{
packageName: packageInfo.title,
}}
/>
);
} else if (isUpgrade) {
pageTitleText = (
<FormattedMessage
id="xpack.fleet.editPackagePolicy.upgradePageTitleWithPackageName"
defaultMessage="Upgrade {packageName} integration"
values={{
packageName: packageInfo.title,
}}
/>
);
}

return (
<EuiFlexGroup alignItems="center" gutterSize="m">
<EuiFlexItem grow={false}>
Expand All @@ -66,32 +103,14 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{
</EuiFlexItem>
<EuiFlexItem>
<EuiText>
<h1 data-test-subj={`${dataTestSubj}_pageTitle`}>
{from === 'edit' || from === 'package-edit' ? (
<FormattedMessage
id="xpack.fleet.editPackagePolicy.pageTitleWithPackageName"
defaultMessage="Edit {packageName} integration"
values={{
packageName: packageInfo.title,
}}
/>
) : (
<FormattedMessage
id="xpack.fleet.createPackagePolicy.pageTitleWithPackageName"
defaultMessage="Add {packageName} integration"
values={{
packageName: integrationInfo?.title || packageInfo.title,
}}
/>
)}
</h1>
<h1 data-test-subj={`${dataTestSubj}_pageTitle`}>{pageTitleText}</h1>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
}

return from === 'edit' || from === 'package-edit' ? (
return isEdit ? (
<EuiText>
<h1 data-test-subj={`${dataTestSubj}_pageTitle`}>
<FormattedMessage
Expand All @@ -112,31 +131,46 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{
);
}, [
dataTestSubj,
from,
integrationInfo?.icons,
integrationInfo?.name,
integrationInfo?.title,
packageInfo,
isAdd,
isEdit,
isUpgrade,
]);

const pageDescription = useMemo(() => {
return from === 'edit' || from === 'package-edit' ? (
<FormattedMessage
id="xpack.fleet.editPackagePolicy.pageDescription"
defaultMessage="Modify integration settings and deploy changes to the selected agent policy."
/>
) : from === 'policy' ? (
<FormattedMessage
id="xpack.fleet.createPackagePolicy.pageDescriptionfromPolicy"
defaultMessage="Configure an integration for the selected agent policy."
/>
) : (
<FormattedMessage
id="xpack.fleet.createPackagePolicy.pageDescriptionfromPackage"
defaultMessage="Follow these instructions to add this integration to an agent policy."
/>
);
}, [from]);
if (isEdit) {
return (
<FormattedMessage
id="xpack.fleet.editPackagePolicy.pageDescription"
defaultMessage="Modify integration settings and deploy changes to the selected agent policy."
/>
);
} else if (isAdd) {
return (
<FormattedMessage
id="xpack.fleet.createPackagePolicy.pageDescriptionfromPolicy"
defaultMessage="Configure an integration for the selected agent policy."
/>
);
} else if (isUpgrade) {
return (
<FormattedMessage
id="xpack.fleet.upgradePackagePolicy.pageDescriptionFromUpgrade"
defaultMessage="Upgrade this integration and deploy changes to the selected agent policy"
/>
);
} else {
return (
<FormattedMessage
id="xpack.fleet.createPackagePolicy.pageDescriptionfromPackage"
defaultMessage="Follow these instructions to add this integration to an agent policy."
/>
);
}
}, [isAdd, isEdit, isUpgrade]);

const leftColumn = (
<EuiFlexGroup direction="column" gutterSize="s" alignItems="flexStart">
Expand Down Expand Up @@ -167,7 +201,7 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{
);

const rightColumn =
agentPolicy && (from === 'policy' || from === 'edit') ? (
agentPolicy && (isAdd || isEdit) ? (
<EuiDescriptionList className="eui-textRight" textStyle="reverse">
<EuiDescriptionListTitle>
<FormattedMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { PLUGIN_ID } from '../../../../../../common/constants';
import { pkgKeyFromPackageInfo } from '../../../services';

import { CreatePackagePolicyPageLayout } from './components';
import type { CreatePackagePolicyFrom, PackagePolicyFormState } from './types';
import type { EditPackagePolicyFrom, PackagePolicyFormState } from './types';
import type { PackagePolicyValidationResults } from './services';
import { validatePackagePolicy, validationHasErrors } from './services';
import { StepSelectAgentPolicy } from './step_select_agent_policy';
Expand Down Expand Up @@ -104,7 +104,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
* We may want to deprecate the ability to pass in policyId from URL params since there is no package
* creation possible if a user has not chosen one from the packages UI.
*/
const from: CreatePackagePolicyFrom =
const from: EditPackagePolicyFrom =
'policyId' in params || queryParamsPolicyId ? 'policy' : 'package';

// Agent policy and package info states
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,11 @@
* 2.0.
*/

export type CreatePackagePolicyFrom = 'package' | 'package-edit' | 'policy' | 'edit';
export type EditPackagePolicyFrom =
| 'package'
| 'package-edit'
| 'policy'
| 'edit'
| 'upgrade-from-fleet-policy-list'
| 'upgrade-from-integrations-policy-list';
export type PackagePolicyFormState = 'VALID' | 'INVALID' | 'CONFIRM' | 'LOADING' | 'SUBMITTED';
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,20 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiText,
EuiIcon,
EuiToolTip,
} from '@elastic/eui';

import { INTEGRATIONS_PLUGIN_ID } from '../../../../../../../../common';
import { pagePathGetters } from '../../../../../../../constants';
import type { AgentPolicy, PackagePolicy } from '../../../../../types';
import type { AgentPolicy, InMemoryPackagePolicy, PackagePolicy } from '../../../../../types';
import { PackageIcon, PackagePolicyActionsMenu } from '../../../../../components';
import { useCapabilities, useStartServices } from '../../../../../hooks';

interface InMemoryPackagePolicy extends PackagePolicy {
packageName?: string;
packageTitle?: string;
packageVersion?: string;
}
import {
useCapabilities,
useLink,
usePackageInstallations,
useStartServices,
} from '../../../../../hooks';

interface Props {
packagePolicies: PackagePolicy[];
Expand All @@ -53,6 +54,8 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({
}) => {
const { application } = useStartServices();
const hasWriteCapabilities = useCapabilities().write;
const { updatableIntegrations } = usePackageInstallations();
const { getHref } = useLink();

// With the package policies provided on input, generate the list of package policies
// used in the InMemoryTable (flattens some values for search) as well as
Expand All @@ -66,11 +69,22 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({
namespacesValues.push(packagePolicy.namespace);
}

const updatableIntegrationRecord = updatableIntegrations.get(
packagePolicy.package?.name ?? ''
);

const hasUpgrade =
!!updatableIntegrationRecord &&
updatableIntegrationRecord.policiesToUpgrade.some(
({ id }) => id === packagePolicy.policy_id
);

return {
...packagePolicy,
packageName: packagePolicy.package?.name ?? '',
packageTitle: packagePolicy.package?.title ?? '',
packageVersion: packagePolicy.package?.version ?? '',
hasUpgrade,
};
}
);
Expand All @@ -79,7 +93,7 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({
inputTypesValues.sort(stringSortAscending);

return [mappedPackagePolicies, namespacesValues.map(toFilterOption)];
}, [originalPackagePolicies]);
}, [originalPackagePolicies, updatableIntegrations]);

const columns = useMemo(
(): EuiInMemoryTableProps<InMemoryPackagePolicy>['columns'] => [
Expand All @@ -89,24 +103,20 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({
name: i18n.translate('xpack.fleet.policyDetails.packagePoliciesTable.nameColumnTitle', {
defaultMessage: 'Name',
}),
render: (value: string) => (
<span className="eui-textTruncate" title={value}>
{value}
</span>
),
},
{
field: 'description',
name: i18n.translate(
'xpack.fleet.policyDetails.packagePoliciesTable.descriptionColumnTitle',
{
defaultMessage: 'Description',
}
),
render: (value: string) => (
<span className="eui-textTruncate" title={value}>
{value}
</span>
render: (value: string, { description }) => (
<>
<span className="eui-textTruncate" title={value}>
{value}
</span>
{description ? (
<span>
&nbsp;
<EuiToolTip content={description}>
<EuiIcon type="help" />
</EuiToolTip>
</span>
) : null}
</>
),
},
{
Expand Down Expand Up @@ -143,6 +153,35 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({
</EuiText>
</EuiFlexItem>
)}
{packagePolicy.hasUpgrade && (
<>
<EuiFlexItem grow={false}>
<EuiToolTip
content={i18n.translate(
'xpack.fleet.policyDetails.packagePoliciesTable.upgradeAvailable',
{ defaultMessage: 'Upgrade Available' }
)}
>
<EuiIcon type="alert" color="warning" />
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
size="s"
minWidth="0"
href={`${getHref('upgrade_package_policy', {
policyId: agentPolicy.id,
packagePolicyId: packagePolicy.id,
})}?from=fleet-policy-list`}
>
<FormattedMessage
id="xpack.fleet.policyDetails.packagePoliciesTable.upgradeButton"
defaultMessage="Upgrade"
/>
</EuiButton>
</EuiFlexItem>
</>
)}
</EuiFlexGroup>
);
},
Expand All @@ -167,14 +206,21 @@ export const PackagePoliciesTable: React.FunctionComponent<Props> = ({
{
render: (packagePolicy: InMemoryPackagePolicy) => {
return (
<PackagePolicyActionsMenu agentPolicy={agentPolicy} packagePolicy={packagePolicy} />
<PackagePolicyActionsMenu
agentPolicy={agentPolicy}
packagePolicy={packagePolicy}
upgradePackagePolicyHref={`${getHref('upgrade_package_policy', {
policyId: agentPolicy.id,
packagePolicyId: packagePolicy.id,
})}`}
/>
);
},
},
],
},
],
[agentPolicy]
[agentPolicy, getHref]
);

return (
Expand Down
Loading

0 comments on commit 35f55ce

Please sign in to comment.