diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_module_notice.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_module_notice.tsx index 3430a4eb5b258..6cd701da61e26 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_module_notice.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/home_integration/tutorial_module_notice.tsx @@ -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(); @@ -41,7 +42,7 @@ const TutorialModuleNotice: TutorialModuleNoticeComponent = memo(({ moduleName } availableAsIntegrationLink: ( '/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}` : ''}`, diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx index 40654645ecd3f..4feff29896459 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx @@ -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, { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx index 9188f0069b8bf..cac133acd4d2d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx @@ -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 ( @@ -76,7 +76,7 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{ ); } - return from === 'edit' ? ( + return from === 'edit' || from === 'package-edit' ? (

{ - return from === 'edit' ? ( + return from === 'edit' || from === 'package-edit' ? ( { ? packageInfo && ( ) : agentPolicy && ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx index f6533a06cea27..b7de9d0afe8f5 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_define_package_policy.tsx @@ -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; @@ -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) { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_package.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_package.tsx index 8c646323c312c..3bcafaecbf8d9 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_package.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_package.tsx @@ -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; @@ -32,7 +33,7 @@ export const StepSelectPackage: React.FunctionComponent<{ }) => { // Selected package state const [selectedPkgKey, setSelectedPkgKey] = useState( - packageInfo ? `${packageInfo.name}-${packageInfo.version}` : undefined + packageInfo ? pkgKeyFromPackageInfo(packageInfo) : undefined ); const [selectedPkgError, setSelectedPkgError] = useState(); @@ -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]); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/types.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/types.ts index c6e16c2cb4d97..7eb5d95c1ab05 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/types.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/types.ts @@ -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'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index 56950d155f782..26f99bd88a923 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -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'; @@ -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 ; +}); + +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(); @@ -76,16 +85,31 @@ 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, + error: packagePolicyError, + } = await sendGetOnePackagePolicy(packagePolicyId); + + if (packagePolicyError) { + throw packagePolicyError; + } + + const { data: agentPolicyData, error: agentPolicyError } = await sendGetOneAgentPolicy( + packagePolicyData!.item.policy_id + ); + + if (agentPolicyError) { + throw agentPolicyError; + } + if (agentPolicyData?.item) { setAgentPolicy(agentPolicyData.item); } @@ -123,7 +147,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); @@ -150,7 +174,7 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => { } }; - if (isFleetEnabled) { + if (isFleetEnabled && policyId) { getAgentCount(); } }, [policyId, isFleetEnabled]); @@ -214,8 +238,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' + ? 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('INVALID'); @@ -237,7 +285,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}'`, @@ -287,7 +335,7 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => { }; const layoutProps = { - from: 'edit' as CreatePackagePolicyFrom, + from, cancelUrl, agentPolicy, packageInfo, @@ -363,13 +411,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', }) } /> ) : ( <> - + {from === 'package' || from === 'package-edit' ? ( + + ) : ( + + )} {formState === 'CONFIRM' && ( { )} ); -}; +}); -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; +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_list_grid.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_list_grid.tsx index b96fda2c23af1..42e4a6051d725 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_list_grid.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_list_grid.tsx @@ -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; @@ -118,7 +119,7 @@ function GridColumn({ list }: GridColumnProps) { {list.length ? ( list.map((item) => ( - + )) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/index.tsx index 8884d1f9d7a75..733aa9dfcf8aa 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/index.tsx @@ -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'); @@ -20,6 +21,9 @@ export const EPMApp: React.FunctionComponent = () => { + + + diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.test.tsx index 7ed14a27c32cf..3d43725f2dc71 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.test.tsx @@ -10,17 +10,26 @@ import React, { lazy, memo } from 'react'; import { PAGE_ROUTING_PATHS, pagePathGetters } from '../../../../constants'; import { Route } from 'react-router-dom'; import { + GetAgentPoliciesResponse, GetFleetStatusResponse, GetInfoResponse, + GetPackagePoliciesResponse, } from '../../../../../../../common/types/rest_spec'; import { DetailViewPanelName, KibanaAssetType } from '../../../../../../../common/types/models'; -import { epmRouteService, fleetSetupRouteService } from '../../../../../../../common/services'; -import { act } from '@testing-library/react'; +import { + agentPolicyRouteService, + epmRouteService, + fleetSetupRouteService, + packagePolicyRouteService, +} from '../../../../../../../common/services'; +import { act, cleanup } from '@testing-library/react'; describe('when on integration detail', () => { - const detailPageUrlPath = pagePathGetters.integration_details({ pkgkey: 'nginx-0.3.7' }); + const pkgkey = 'nginx-0.3.7'; + const detailPageUrlPath = pagePathGetters.integration_details({ pkgkey }); let testRenderer: TestRenderer; let renderResult: ReturnType; + let mockedApi: MockedApi; const render = () => (renderResult = testRenderer.render( @@ -30,10 +39,15 @@ describe('when on integration detail', () => { beforeEach(() => { testRenderer = createTestRendererMock(); - mockApiCalls(testRenderer.startServices.http); + mockedApi = mockApiCalls(testRenderer.startServices.http); testRenderer.history.push(detailPageUrlPath); }); + afterEach(() => { + cleanup(); + window.location.hash = '#/'; + }); + describe('and a custom UI extension is NOT registered', () => { beforeEach(() => render()); @@ -137,9 +151,48 @@ describe('when on integration detail', () => { }); }); }); + + describe('and on the Policies Tab', () => { + const policiesTabURLPath = pagePathGetters.integration_details({ pkgkey, panel: 'policies' }); + beforeEach(() => { + testRenderer.history.push(policiesTabURLPath); + render(); + }); + + it('should display policies list', () => { + const table = renderResult.getByTestId('integrationPolicyTable'); + expect(table).not.toBeNull(); + }); + + it('should link to integration policy detail when an integration policy is clicked', async () => { + await mockedApi.waitForApi(); + const firstPolicy = renderResult.getByTestId('integrationNameLink') as HTMLAnchorElement; + expect(firstPolicy.href).toEqual( + 'http://localhost/mock/app/fleet#/integrations/edit-integration/e8a37031-2907-44f6-89d2-98bd493f60dc' + ); + }); + }); }); -const mockApiCalls = (http: MockedFleetStartServices['http']) => { +interface MockedApi { + /** Will return a promise that resolves when triggered APIs are complete */ + waitForApi: () => Promise; +} + +const mockApiCalls = (http: MockedFleetStartServices['http']): MockedApi => { + let inflightApiCalls = 0; + const apiDoneListeners: Array<() => void> = []; + const markApiCallAsHandled = async () => { + inflightApiCalls++; + await new Promise((r) => setTimeout(r, 1)); + inflightApiCalls--; + + // If no more pending API calls, then notify listeners + if (inflightApiCalls === 0 && apiDoneListeners.length > 0) { + apiDoneListeners.splice(0).forEach((listener) => listener()); + } + }; + // @ts-ignore const epmPackageResponse: GetInfoResponse = { response: { @@ -369,7 +422,7 @@ const mockApiCalls = (http: MockedFleetStartServices['http']) => { owner: { github: 'elastic/integrations-services' }, latestVersion: '0.3.7', removable: true, - status: 'not_installed', + status: 'installed', }, } as GetInfoResponse; @@ -388,24 +441,162 @@ On Windows, the module was tested with Nginx installed from the Chocolatey repos const agentsSetupResponse: GetFleetStatusResponse = { isReady: true, missing_requirements: [] }; + const packagePoliciesResponse: GetPackagePoliciesResponse = { + items: [ + { + id: 'e8a37031-2907-44f6-89d2-98bd493f60dc', + version: 'WzgzMiwxXQ==', + name: 'nginx-1', + description: '', + namespace: 'default', + policy_id: '521c1b70-3976-11eb-ad1c-3baa423084d9', + enabled: true, + output_id: '', + inputs: [ + { + type: 'logfile', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'logs', dataset: 'nginx.access' }, + vars: { paths: { value: ['/var/log/nginx/access.log*'], type: 'text' } }, + id: 'logfile-nginx.access-e8a37031-2907-44f6-89d2-98bd493f60dc', + compiled_stream: { + paths: ['/var/log/nginx/access.log*'], + exclude_files: ['.gz$'], + processors: [{ add_locale: null }], + }, + }, + { + enabled: true, + data_stream: { type: 'logs', dataset: 'nginx.error' }, + vars: { paths: { value: ['/var/log/nginx/error.log*'], type: 'text' } }, + id: 'logfile-nginx.error-e8a37031-2907-44f6-89d2-98bd493f60dc', + compiled_stream: { + paths: ['/var/log/nginx/error.log*'], + exclude_files: ['.gz$'], + multiline: { + pattern: '^\\d{4}\\/\\d{2}\\/\\d{2} ', + negate: true, + match: 'after', + }, + processors: [{ add_locale: null }], + }, + }, + { + enabled: false, + data_stream: { type: 'logs', dataset: 'nginx.ingress_controller' }, + vars: { paths: { value: ['/var/log/nginx/ingress.log*'], type: 'text' } }, + id: 'logfile-nginx.ingress_controller-e8a37031-2907-44f6-89d2-98bd493f60dc', + }, + ], + }, + { + type: 'nginx/metrics', + enabled: true, + streams: [ + { + enabled: true, + data_stream: { type: 'metrics', dataset: 'nginx.stubstatus' }, + vars: { + period: { value: '10s', type: 'text' }, + server_status_path: { value: '/nginx_status', type: 'text' }, + }, + id: 'nginx/metrics-nginx.stubstatus-e8a37031-2907-44f6-89d2-98bd493f60dc', + compiled_stream: { + metricsets: ['stubstatus'], + hosts: ['http://127.0.0.1:80'], + period: '10s', + server_status_path: '/nginx_status', + }, + }, + ], + vars: { hosts: { value: ['http://127.0.0.1:80'], type: 'text' } }, + }, + ], + package: { name: 'nginx', title: 'Nginx', version: '0.3.7' }, + revision: 1, + created_at: '2020-12-09T13:46:31.013Z', + created_by: 'elastic', + updated_at: '2020-12-09T13:46:31.013Z', + updated_by: 'elastic', + }, + ], + total: 1, + page: 1, + perPage: 20, + }; + + const agentPoliciesResponse: GetAgentPoliciesResponse = { + items: [ + { + id: '521c1b70-3976-11eb-ad1c-3baa423084d9', + name: 'Default', + namespace: 'default', + description: 'Default agent policy created by Kibana', + status: 'active', + package_policies: [ + '4d09bd78-b0ad-4238-9fa3-d87d3c887c73', + '2babac18-eb8e-4ce4-b53b-4b7c5f507019', + 'e8a37031-2907-44f6-89d2-98bd493f60dc', + ], + is_default: true, + monitoring_enabled: ['logs', 'metrics'], + revision: 6, + updated_at: '2020-12-09T13:46:31.840Z', + updated_by: 'elastic', + agents: 0, + }, + ], + total: 1, + page: 1, + perPage: 100, + }; + http.get.mockImplementation(async (path) => { if (typeof path === 'string') { if (path === epmRouteService.getInfoPath(`nginx-0.3.7`)) { + markApiCallAsHandled(); return epmPackageResponse; } if (path === epmRouteService.getFilePath('/package/nginx/0.3.7/docs/README.md')) { + markApiCallAsHandled(); return packageReadMe; } if (path === fleetSetupRouteService.getFleetSetupPath()) { + markApiCallAsHandled(); return agentsSetupResponse; } + if (path === packagePolicyRouteService.getListPath()) { + markApiCallAsHandled(); + return packagePoliciesResponse; + } + + if (path === agentPolicyRouteService.getListPath()) { + markApiCallAsHandled(); + return agentPoliciesResponse; + } + const err = new Error(`API [GET ${path}] is not MOCKED!`); // eslint-disable-next-line no-console - console.log(err); + console.error(err); throw err; } }); + + return { + waitForApi() { + return new Promise((resolve) => { + if (inflightApiCalls > 0) { + apiDoneListeners.push(resolve); + } else { + resolve(); + } + }); + }, + }; }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.tsx index 467276f0d0b8c..c70a11db004a6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/index.tsx @@ -43,6 +43,7 @@ import { Content } from './content'; import './index.scss'; import { useUIExtension } from '../../../../hooks/use_ui_extension'; import { PLUGIN_ID } from '../../../../../../../common/constants'; +import { pkgKeyFromPackageInfo } from '../../../../services/pkg_key_from_package_info'; export const DEFAULT_PANEL: DetailViewPanelName = 'overview'; @@ -315,7 +316,7 @@ export function Detail() { isSelected: panelId === panel, 'data-test-subj': `tab-${panelId}`, href: getHref('integration_details', { - pkgkey: `${packageInfo?.name}-${packageInfo?.version}`, + pkgkey: pkgKeyFromPackageInfo(packageInfo || {}), panel: panelId, }), }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/package_policies_panel.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/package_policies_panel.tsx index 8609b08c9a774..4061b86f1f740 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/package_policies_panel.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/package_policies_panel.tsx @@ -36,8 +36,8 @@ const IntegrationDetailsLink = memo<{ return ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/policy/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/policy/index.tsx new file mode 100644 index 0000000000000..fcd4821996efe --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/policy/index.tsx @@ -0,0 +1,17 @@ +/* + * 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 React, { memo } from 'react'; +import { useRouteMatch } from 'react-router-dom'; +import { EditPackagePolicyForm } from '../../../agent_policy/edit_package_policy_page'; + +export const Policy = memo(() => { + const { + params: { packagePolicyId }, + } = useRouteMatch<{ packagePolicyId: string }>(); + + return ; +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/services/pkg_key_from_package_info.ts b/x-pack/plugins/fleet/public/applications/fleet/services/pkg_key_from_package_info.ts new file mode 100644 index 0000000000000..0e38abe6f5160 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/services/pkg_key_from_package_info.ts @@ -0,0 +1,11 @@ +/* + * 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. + */ + +export const pkgKeyFromPackageInfo = ( + packageInfo: T +): string => { + return `${packageInfo.name}-${packageInfo.version}`; +};