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] New Integration Policy Details page for use in Integrations section #85355

Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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 @@ -8,6 +8,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { EuiText, EuiLink, EuiSpacer } from '@elastic/eui';
import { TutorialModuleNoticeComponent } from 'src/plugins/home/public';
import { useGetPackages, useLink, useCapabilities } from '../../hooks';
import { pkgKeyFromPackageInfo } from '../../services/pkg_key_from_package_info';

const TutorialModuleNotice: TutorialModuleNoticeComponent = memo(({ moduleName }) => {
const { getHref } = useLink();
Expand Down Expand Up @@ -41,7 +42,7 @@ const TutorialModuleNotice: TutorialModuleNoticeComponent = memo(({ moduleName }
availableAsIntegrationLink: (
<EuiLink
href={getHref('integration_details', {
pkgkey: `${pkgInfo.name}-${pkgInfo.version}`,
pkgkey: pkgKeyFromPackageInfo(pkgInfo),
})}
>
<FormattedMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type StaticPage =

export type DynamicPage =
| 'integration_details'
| 'integration_policy_edit'
| 'policy_details'
| 'add_integration_from_policy'
| 'add_integration_to_policy'
Expand All @@ -41,6 +42,7 @@ export const PAGE_ROUTING_PATHS = {
integrations_all: '/integrations',
integrations_installed: '/integrations/installed',
integration_details: '/integrations/detail/:pkgkey/:panel?',
integration_policy_edit: '/integrations/edit-integration/:packagePolicyId',
policies: '/policies',
policies_list: '/policies',
policy_details: '/policies/:policyId/:tabId?',
Expand Down Expand Up @@ -69,6 +71,8 @@ export const pagePathGetters: {
integrations_installed: () => '/integrations/installed',
integration_details: ({ pkgkey, panel }) =>
`/integrations/detail/${pkgkey}${panel ? `/${panel}` : ''}`,
integration_policy_edit: ({ packagePolicyId }) =>
`/integrations/edit-integration/${packagePolicyId}`,
policies: () => '/policies',
policies_list: () => '/policies',
policy_details: ({ policyId, tabId }) => `/policies/${policyId}${tabId ? `/${tabId}` : ''}`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,20 @@ const breadcrumbGetters: {
},
{ text: pkgTitle },
],
integration_policy_edit: ({ pkgTitle, pkgkey, policyName }) => [
BASE_BREADCRUMB,
{
href: pagePathGetters.integrations(),
text: i18n.translate('xpack.fleet.breadcrumbs.integrationPageTitle', {
defaultMessage: 'Integration',
}),
},
{
href: pagePathGetters.integration_details({ pkgkey, panel: 'policies' }),
text: pkgTitle,
},
{ text: policyName },
],
policies: () => [
BASE_BREADCRUMB,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{
'data-test-subj': dataTestSubj,
}) => {
const pageTitle = useMemo(() => {
if ((from === 'package' || from === 'edit') && packageInfo) {
if ((from === 'package' || from === 'package-edit' || from === 'edit') && packageInfo) {
return (
<EuiFlexGroup alignItems="center" gutterSize="m">
<EuiFlexItem grow={false}>
Expand Down Expand Up @@ -76,7 +76,7 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{
);
}

return from === 'edit' ? (
return from === 'edit' || from === 'package-edit' ? (
<EuiText>
<h1>
<FormattedMessage
Expand All @@ -98,7 +98,7 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{
}, [from, packageInfo]);

const pageDescription = useMemo(() => {
return from === 'edit' ? (
return from === 'edit' || from === 'package-edit' ? (
<FormattedMessage
id="xpack.fleet.editPackagePolicy.pageDescription"
defaultMessage="Modify integration settings and deploy changes to the selected agent policy."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { useUIExtension } from '../../../hooks/use_ui_extension';
import { ExtensionWrapper } from '../../../components/extension_wrapper';
import { PackagePolicyEditExtensionComponentProps } from '../../../types';
import { PLUGIN_ID } from '../../../../../../common/constants';
import { pkgKeyFromPackageInfo } from '../../../services/pkg_key_from_package_info';

const StepsWithLessPadding = styled(EuiSteps)`
.euiStep__content {
Expand Down Expand Up @@ -404,7 +405,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
? packageInfo && (
<IntegrationBreadcrumb
pkgTitle={packageInfo.title}
pkgkey={`${packageInfo.name}-${packageInfo.version}`}
pkgkey={pkgKeyFromPackageInfo(packageInfo)}
/>
)
: agentPolicy && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { AgentPolicy, PackageInfo, PackagePolicy, NewPackagePolicy } from '../..
import { packageToPackagePolicyInputs } from '../../../services';
import { Loading } from '../../../components';
import { PackagePolicyValidationResults } from './services';
import { pkgKeyFromPackageInfo } from '../../../services/pkg_key_from_package_info';

export const StepDefinePackagePolicy: React.FunctionComponent<{
agentPolicy: AgentPolicy;
Expand All @@ -34,8 +35,8 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{
// Update package policy's package and agent policy info
useEffect(() => {
const pkg = packagePolicy.package;
const currentPkgKey = pkg ? `${pkg.name}-${pkg.version}` : '';
const pkgKey = `${packageInfo.name}-${packageInfo.version}`;
const currentPkgKey = pkg ? pkgKeyFromPackageInfo(pkg) : '';
const pkgKey = pkgKeyFromPackageInfo(packageInfo);

// If package has changed, create shell package policy with input&stream values based on package info
if (currentPkgKey !== pkgKey) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
sendGetPackageInfoByKey,
} from '../../../hooks';
import { PackageIcon } from '../../../components/package_icon';
import { pkgKeyFromPackageInfo } from '../../../services/pkg_key_from_package_info';

export const StepSelectPackage: React.FunctionComponent<{
agentPolicyId: string;
Expand All @@ -32,7 +33,7 @@ export const StepSelectPackage: React.FunctionComponent<{
}) => {
// Selected package state
const [selectedPkgKey, setSelectedPkgKey] = useState<string | undefined>(
packageInfo ? `${packageInfo.name}-${packageInfo.version}` : undefined
packageInfo ? pkgKeyFromPackageInfo(packageInfo) : undefined
);
const [selectedPkgError, setSelectedPkgError] = useState<Error>();

Expand Down Expand Up @@ -92,7 +93,7 @@ export const StepSelectPackage: React.FunctionComponent<{
updatePackageInfo(undefined);
}
};
if (!packageInfo || selectedPkgKey !== `${packageInfo.name}-${packageInfo.version}`) {
if (!packageInfo || selectedPkgKey !== pkgKeyFromPackageInfo(packageInfo)) {
fetchPackageInfo();
}
}, [selectedPkgKey, packageInfo, updatePackageInfo, setIsLoadingSecondStep]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/

export type CreatePackagePolicyFrom = 'package' | 'policy' | 'edit';
export type CreatePackagePolicyFrom = 'package' | 'package-edit' | 'policy' | 'edit';
export type PackagePolicyFormState = 'VALID' | 'INVALID' | 'CONFIRM' | 'LOADING' | 'SUBMITTED';
Original file line number Diff line number Diff line change
Expand Up @@ -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, useEffect, useCallback, useMemo } from 'react';
import React, { useState, useEffect, useCallback, useMemo, memo } from 'react';
import { useRouteMatch, useHistory } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
Expand Down Expand Up @@ -45,15 +45,24 @@ import { useUIExtension } from '../../../hooks/use_ui_extension';
import { ExtensionWrapper } from '../../../components/extension_wrapper';
import { GetOnePackagePolicyResponse } from '../../../../../../common/types/rest_spec';
import { PackagePolicyEditExtensionComponentProps } from '../../../types';
import { pkgKeyFromPackageInfo } from '../../../services/pkg_key_from_package_info';

export const EditPackagePolicyPage: React.FunctionComponent = () => {
export const EditPackagePolicyPage = memo(() => {
const {
params: { packagePolicyId },
} = useRouteMatch<{ policyId: string; packagePolicyId: string }>();

return <EditPackagePolicyForm packagePolicyId={packagePolicyId} />;
});

export const EditPackagePolicyForm = memo<{
packagePolicyId: string;
from?: CreatePackagePolicyFrom;
}>(({ packagePolicyId, from = 'edit' }) => {
const { notifications } = useStartServices();
const {
agents: { enabled: isFleetEnabled },
} = useConfig();
const {
params: { policyId, packagePolicyId },
} = useRouteMatch<{ policyId: string; packagePolicyId: string }>();
const history = useHistory();
const { getHref, getPath } = useLink();

Expand All @@ -76,16 +85,19 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => {
GetOnePackagePolicyResponse['item']
>();

const policyId = agentPolicy?.id ?? '';

// Retrieve agent policy, package, and package policy info
useEffect(() => {
const getData = async () => {
setIsLoadingData(true);
setLoadingError(undefined);
try {
const [{ data: agentPolicyData }, { data: packagePolicyData }] = await Promise.all([
sendGetOneAgentPolicy(policyId),
sendGetOnePackagePolicy(packagePolicyId),
]);
const { data: packagePolicyData } = await sendGetOnePackagePolicy(packagePolicyId);
const { data: agentPolicyData } = await sendGetOneAgentPolicy(
packagePolicyData?.item.policy_id ?? 'id-missing-in-package-policy'
Copy link
Member

Choose a reason for hiding this comment

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

this seems odd. Not a big fan of putting.. log-like traces into values.

you also don't technically know that that could not be a valid future policy ID.

You should check the existence of that property and value before calling sendGetOneAgentPolicy.
You're in a try already, could check it, and throw if it's missing (if that's the appropriate behavior).

if (!item.policy_id) { throw new Error("ID missing in package policy") }

Or if it's something you move past, by grabbing a default, perhaps something like

const { data: agentPolicyData } = item.policy_id ? await sendGetOneAgentPolicy() : someDefault()

Or if having the value itself is optional

const { data?: agentPolicyData } = item.policy_id ? await sendGetOneAgentPolicy() : null

something along those lines seems more intentional than sending intentionally fake data down the line to return.. who knows what. An error? A null? Accidentally a real object?

Copy link
Member

Choose a reason for hiding this comment

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

I agree with @pzl here we can probably validate that packagePolicyData is not null and the request succeed before sending this request

Copy link
Member

Choose a reason for hiding this comment

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

Also you probably want to check the error field of the response too, if a request fail we probably want to throw that error

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I meant to comment on this and why I did it.
throwing here would not be good because the entire UI will likely crash. instead, I wanted to piggy back off the error handling in place for failed API calls. If this did happen to error, I wanted to have something that can tell us (or support team) some info about where to go look.

That being said, you just pointed out that all of this is wrapped in a try {} block (why did I not realize that? 🤦‍♂️ ), so I as you suggest, doing a check and then explicitly throw new Error() will be a better approach.

Thanks for the feedback. making the change now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks @nchaulet . Good point on processing each individual API call checking for errors - in the prior code both of those would be handled (use of Promise.all()) but with my change they are not. Will fix

);

if (agentPolicyData?.item) {
setAgentPolicy(agentPolicyData.item);
}
Expand Down Expand Up @@ -123,7 +135,7 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => {
setPackagePolicy(newPackagePolicy);
if (packagePolicyData.item.package) {
const { data: packageData } = await sendGetPackageInfoByKey(
`${packagePolicyData.item.package.name}-${packagePolicyData.item.package.version}`
pkgKeyFromPackageInfo(packagePolicyData.item.package)
);
if (packageData?.response) {
setPackageInfo(packageData.response);
Expand All @@ -150,7 +162,7 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => {
}
};

if (isFleetEnabled) {
if (isFleetEnabled && policyId) {
getAgentCount();
}
}, [policyId, isFleetEnabled]);
Expand Down Expand Up @@ -214,8 +226,32 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => {
[updatePackagePolicy]
);

// Cancel url
const cancelUrl = getHref('policy_details', { policyId });
// Cancel url + Success redirect Path:
// if `from === 'edit'` then it links back to Policy Details
// if `from === 'package-edit'` then it links back to the Integration Policy List
const cancelUrl = useMemo((): string => {
if (packageInfo && policyId) {
return from === 'package-edit'
Copy link
Member

Choose a reason for hiding this comment

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

If we expect this to expand to more pages, perhaps a switch here so we can be explicit about the default case (if from is something unexpected, perhaps new places added down the road, where the PR forgot to add themselves here).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, agreed a switch will make sense if we add more types for from. I don't see that happening at this time and when I get around to introducing the layout that the mocks call for, I hope to revert this in favor of having props that specifically allow for setting things like the cancelUrl that should be used in the form.

? getHref('integration_details', {
pkgkey: pkgKeyFromPackageInfo(packageInfo!),
panel: 'policies',
})
: getHref('policy_details', { policyId });
}
return '/';
}, [from, getHref, packageInfo, policyId]);

const successRedirectPath = useMemo(() => {
if (packageInfo && policyId) {
return from === 'package-edit'
? getPath('integration_details', {
pkgkey: pkgKeyFromPackageInfo(packageInfo!),
panel: 'policies',
})
: getPath('policy_details', { policyId });
}
return '/';
}, [from, getPath, packageInfo, policyId]);

// Save package policy
const [formState, setFormState] = useState<PackagePolicyFormState>('INVALID');
Expand All @@ -237,7 +273,7 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => {
}
const { error } = await savePackagePolicy();
if (!error) {
history.push(getPath('policy_details', { policyId }));
history.push(successRedirectPath);
notifications.toasts.addSuccess({
title: i18n.translate('xpack.fleet.editPackagePolicy.updatedNotificationTitle', {
defaultMessage: `Successfully updated '{packagePolicyName}'`,
Expand Down Expand Up @@ -287,7 +323,7 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => {
};

const layoutProps = {
from: 'edit' as CreatePackagePolicyFrom,
from,
cancelUrl,
agentPolicy,
packageInfo,
Expand Down Expand Up @@ -363,13 +399,21 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => {
error={
loadingError ||
i18n.translate('xpack.fleet.editPackagePolicy.errorLoadingDataMessage', {
defaultMessage: 'There was an error loading this intergration information',
defaultMessage: 'There was an error loading this integration information',
Copy link
Member

Choose a reason for hiding this comment

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

👏

Copy link
Contributor Author

Choose a reason for hiding this comment

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

All credit goes to WebStorm 🎉

})
}
/>
) : (
<>
<Breadcrumb policyName={agentPolicy.name} policyId={policyId} />
{from === 'package' || from === 'package-edit' ? (
<IntegrationsBreadcrumb
pkgkey={pkgKeyFromPackageInfo(packageInfo)}
pkgTitle={packageInfo.title}
policyName={packagePolicy.name}
/>
) : (
<PoliciesBreadcrumb policyName={agentPolicy.name} policyId={policyId} />
)}
{formState === 'CONFIRM' && (
<ConfirmDeployAgentPolicyModal
agentCount={agentCount}
Expand Down Expand Up @@ -424,12 +468,21 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => {
)}
</CreatePackagePolicyPageLayout>
);
};
});

const Breadcrumb: React.FunctionComponent<{ policyName: string; policyId: string }> = ({
const PoliciesBreadcrumb: React.FunctionComponent<{ policyName: string; policyId: string }> = ({
policyName,
policyId,
}) => {
useBreadcrumbs('edit_integration', { policyName, policyId });
return null;
};

const IntegrationsBreadcrumb = memo<{
pkgTitle: string;
policyName: string;
pkgkey: string;
}>(({ pkgTitle, policyName, pkgkey }) => {
useBreadcrumbs('integration_policy_edit', { policyName, pkgTitle, pkgkey });
return null;
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Loading } from '../../../components';
import { PackageList } from '../../../types';
import { useLocalSearch, searchIdField } from '../hooks';
import { PackageCard } from './package_card';
import { pkgKeyFromPackageInfo } from '../../../services/pkg_key_from_package_info';

interface ListProps {
isLoading?: boolean;
Expand Down Expand Up @@ -118,7 +119,7 @@ function GridColumn({ list }: GridColumnProps) {
<EuiFlexGrid gutterSize="l" columns={3}>
{list.length ? (
list.map((item) => (
<EuiFlexItem key={`${item.name}-${item.version}`}>
<EuiFlexItem key={pkgKeyFromPackageInfo(item)}>
<PackageCard {...item} />
</EuiFlexItem>
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useBreadcrumbs } from '../../hooks';
import { CreatePackagePolicyPage } from '../agent_policy/create_package_policy_page';
import { EPMHomePage } from './screens/home';
import { Detail } from './screens/detail';
import { Policy } from './screens/policy';

export const EPMApp: React.FunctionComponent = () => {
useBreadcrumbs('integrations');
Expand All @@ -20,6 +21,9 @@ export const EPMApp: React.FunctionComponent = () => {
<Route path={PAGE_ROUTING_PATHS.add_integration_to_policy}>
<CreatePackagePolicyPage />
</Route>
<Route path={PAGE_ROUTING_PATHS.integration_policy_edit}>
<Policy />
</Route>
<Route path={PAGE_ROUTING_PATHS.integration_details}>
<Detail />
</Route>
Expand Down
Loading