mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 03:01:21 -04:00
[Integrations UI] Add support for custom asset definitions in Integration assets tab (#103554)
* Add UI extension logic for assets + set up custom log views * Add endpoint security UI extension * Add synthetics ui extension * Address PR feedback - Remove default filter for log stream url - Fix missing basePath prepend on asset urls - Expand accordion by default on assetless integrations * Fix type errors * Add initial APM extension setup * Fix missing ExtensionWrapper for enrollment extension * Fix custom logs asset extension * Fix type errors * Add new hook for enrollment flyout ui extensions * Address PR review + refactor UI extension usage for flyout * Update limits.yml via script * Fix type errors * Add tests for custom assets UI extensions * Update tests for flyout * Remove unused import * Fix type errors in ui extension tests * Skip view data tests and link to issue * Use RedirectAppLinks + fix synthetics link * Use constants for app ID's where possible * Revert limits.yml * Fix lazy imports for custom asset components * Update endpoint custom assets link + description * Add translation for custom assets UI * Address PR review in APM Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
1c82ad2c95
commit
059ed0821a
39 changed files with 671 additions and 163 deletions
|
@ -3,33 +3,34 @@
|
|||
"version": "8.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"requiredPlugins": [
|
||||
"features",
|
||||
"apmOss",
|
||||
"data",
|
||||
"licensing",
|
||||
"triggersActionsUi",
|
||||
"embeddable",
|
||||
"features",
|
||||
"fleet",
|
||||
"infra",
|
||||
"licensing",
|
||||
"observability",
|
||||
"ruleRegistry"
|
||||
"ruleRegistry",
|
||||
"triggersActionsUi"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"spaces",
|
||||
"cloud",
|
||||
"usageCollection",
|
||||
"taskManager",
|
||||
"actions",
|
||||
"alerting",
|
||||
"security",
|
||||
"ml",
|
||||
"cloud",
|
||||
"home",
|
||||
"maps",
|
||||
"fleet"
|
||||
"ml",
|
||||
"security",
|
||||
"spaces",
|
||||
"taskManager",
|
||||
"usageCollection"
|
||||
],
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"configPath": ["xpack", "apm"],
|
||||
"requiredBundles": [
|
||||
"fleet",
|
||||
"home",
|
||||
"kibanaReact",
|
||||
"kibanaUtils",
|
||||
|
|
|
@ -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 React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
CustomAssetsAccordionProps,
|
||||
CustomAssetsAccordion,
|
||||
} from '../../../../fleet/public';
|
||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { ApmPluginStartDeps } from '../../plugin';
|
||||
|
||||
export function ApmCustomAssetsExtension() {
|
||||
const { http } = useKibana<ApmPluginStartDeps>().services;
|
||||
const basePath = http?.basePath.get();
|
||||
|
||||
const views: CustomAssetsAccordionProps['views'] = [
|
||||
{
|
||||
name: i18n.translate('xpack.apm.fleetIntegration.assets.name', {
|
||||
defaultMessage: 'Services',
|
||||
}),
|
||||
url: `${basePath}/app/apm`,
|
||||
description: i18n.translate(
|
||||
'xpack.apm.fleetIntegration.assets.description',
|
||||
{ defaultMessage: 'View application traces and service maps in APM' }
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return <CustomAssetsAccordion views={views} initialIsOpen />;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { EuiButton, EuiText, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { AgentEnrollmentFlyoutFinalStepExtension } from '../../../../fleet/public';
|
||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { ApmPluginStartDeps } from '../../plugin';
|
||||
|
||||
function StepComponent() {
|
||||
const { http } = useKibana<ApmPluginStartDeps>().services;
|
||||
const installApmAgentLink = http?.basePath.prepend('/app/home#/tutorial/apm');
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.apm.fleetIntegration.enrollmentFlyout.installApmAgentDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'After the agent starts, you can install APM agents on your hosts to collect data from your applications and services.',
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiButton fill href={installApmAgentLink}>
|
||||
{i18n.translate(
|
||||
'xpack.apm.fleetIntegration.enrollmentFlyout.installApmAgentButtonText',
|
||||
{ defaultMessage: 'Install APM Agent' }
|
||||
)}
|
||||
</EuiButton>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function getApmEnrollmentFlyoutData(): Pick<
|
||||
AgentEnrollmentFlyoutFinalStepExtension,
|
||||
'title' | 'Component'
|
||||
> {
|
||||
return {
|
||||
title: i18n.translate(
|
||||
'xpack.apm.fleetIntegration.enrollmentFlyout.installApmAgentTitle',
|
||||
{
|
||||
defaultMessage: 'Install APM Agent',
|
||||
}
|
||||
),
|
||||
Component: StepComponent,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './apm_enrollment_flyout_extension';
|
||||
export * from './lazy_apm_custom_assets_extension';
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { lazy } from 'react';
|
||||
|
||||
export const LazyApmCustomAssetsExtension = lazy(async () => {
|
||||
const { ApmCustomAssetsExtension } = await import(
|
||||
'./apm_custom_assets_extension'
|
||||
);
|
||||
|
||||
return {
|
||||
default: ApmCustomAssetsExtension,
|
||||
};
|
||||
});
|
|
@ -22,6 +22,7 @@ import type {
|
|||
DataPublicPluginStart,
|
||||
} from '../../../../src/plugins/data/public';
|
||||
import type { EmbeddableStart } from '../../../../src/plugins/embeddable/public';
|
||||
import type { FleetStart } from '../../fleet/public';
|
||||
import type { HomePublicPluginSetup } from '../../../../src/plugins/home/public';
|
||||
import type {
|
||||
PluginSetupContract as AlertingPluginPublicSetup,
|
||||
|
@ -43,6 +44,10 @@ import type {
|
|||
} from '../../triggers_actions_ui/public';
|
||||
import { registerApmAlerts } from './components/alerting/register_apm_alerts';
|
||||
import { featureCatalogueEntry } from './featureCatalogueEntry';
|
||||
import {
|
||||
getApmEnrollmentFlyoutData,
|
||||
LazyApmCustomAssetsExtension,
|
||||
} from './components/fleet_integration';
|
||||
|
||||
export type ApmPluginSetup = ReturnType<ApmPlugin['setup']>;
|
||||
|
||||
|
@ -69,6 +74,7 @@ export interface ApmPluginStartDeps {
|
|||
ml?: MlPluginStart;
|
||||
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
||||
observability: ObservabilityPublicStart;
|
||||
fleet: FleetStart;
|
||||
}
|
||||
|
||||
export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
|
||||
|
@ -303,5 +309,22 @@ export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
|
|||
|
||||
return {};
|
||||
}
|
||||
public start(core: CoreStart, plugins: ApmPluginStartDeps) {}
|
||||
public start(core: CoreStart, plugins: ApmPluginStartDeps) {
|
||||
const { fleet } = plugins;
|
||||
|
||||
const agentEnrollmentExtensionData = getApmEnrollmentFlyoutData();
|
||||
|
||||
fleet.registerExtension({
|
||||
package: 'apm',
|
||||
view: 'agent-enrollment-flyout',
|
||||
title: agentEnrollmentExtensionData.title,
|
||||
Component: agentEnrollmentExtensionData.Component,
|
||||
});
|
||||
|
||||
fleet.registerExtension({
|
||||
package: 'apm',
|
||||
view: 'package-detail-assets',
|
||||
Component: LazyApmCustomAssetsExtension,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -425,7 +425,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
[params, updatePackageInfo, agentPolicy, updateAgentPolicy, queryParamsPolicyId]
|
||||
);
|
||||
|
||||
const ExtensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-create');
|
||||
const extensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-create');
|
||||
|
||||
const stepConfigurePackagePolicy = useMemo(
|
||||
() =>
|
||||
|
@ -444,7 +444,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
/>
|
||||
|
||||
{/* Only show the out-of-box configuration step if a UI extension is NOT registered */}
|
||||
{!ExtensionView && (
|
||||
{!extensionView && (
|
||||
<StepConfigurePackagePolicy
|
||||
packageInfo={packageInfo}
|
||||
showOnlyIntegration={integrationInfo?.name}
|
||||
|
@ -456,9 +456,12 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
)}
|
||||
|
||||
{/* If an Agent Policy and a package has been selected, then show UI extension (if any) */}
|
||||
{ExtensionView && packagePolicy.policy_id && packagePolicy.package?.name && (
|
||||
{extensionView && packagePolicy.policy_id && packagePolicy.package?.name && (
|
||||
<ExtensionWrapper>
|
||||
<ExtensionView newPolicy={packagePolicy} onChange={handleExtensionViewOnChange} />
|
||||
<extensionView.Component
|
||||
newPolicy={packagePolicy}
|
||||
onChange={handleExtensionViewOnChange}
|
||||
/>
|
||||
</ExtensionWrapper>
|
||||
)}
|
||||
</>
|
||||
|
@ -474,7 +477,7 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
validationResults,
|
||||
formState,
|
||||
integrationInfo?.name,
|
||||
ExtensionView,
|
||||
extensionView,
|
||||
handleExtensionViewOnChange,
|
||||
]
|
||||
);
|
||||
|
|
|
@ -338,7 +338,7 @@ export const EditPackagePolicyForm = memo<{
|
|||
packageInfo,
|
||||
};
|
||||
|
||||
const ExtensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-edit');
|
||||
const extensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-edit');
|
||||
|
||||
const configurePackage = useMemo(
|
||||
() =>
|
||||
|
@ -354,7 +354,7 @@ export const EditPackagePolicyForm = memo<{
|
|||
/>
|
||||
|
||||
{/* Only show the out-of-box configuration step if a UI extension is NOT registered */}
|
||||
{!ExtensionView && (
|
||||
{!extensionView && (
|
||||
<StepConfigurePackagePolicy
|
||||
packageInfo={packageInfo}
|
||||
packagePolicy={packagePolicy}
|
||||
|
@ -364,12 +364,12 @@ export const EditPackagePolicyForm = memo<{
|
|||
/>
|
||||
)}
|
||||
|
||||
{ExtensionView &&
|
||||
{extensionView &&
|
||||
packagePolicy.policy_id &&
|
||||
packagePolicy.package?.name &&
|
||||
originalPackagePolicy && (
|
||||
<ExtensionWrapper>
|
||||
<ExtensionView
|
||||
<extensionView.Component
|
||||
policy={originalPackagePolicy}
|
||||
newPolicy={packagePolicy}
|
||||
onChange={handleExtensionViewOnChange}
|
||||
|
@ -386,7 +386,7 @@ export const EditPackagePolicyForm = memo<{
|
|||
validationResults,
|
||||
formState,
|
||||
originalPackagePolicy,
|
||||
ExtensionView,
|
||||
extensionView,
|
||||
handleExtensionViewOnChange,
|
||||
]
|
||||
);
|
||||
|
|
|
@ -19,7 +19,7 @@ export const DisplayedAssets: ServiceNameToAssetTypes = {
|
|||
elasticsearch: Object.values(ElasticsearchAssetType),
|
||||
};
|
||||
|
||||
export type DisplayedAssetType = ElasticsearchAssetType | KibanaAssetType;
|
||||
export type DisplayedAssetType = ElasticsearchAssetType | KibanaAssetType | 'view';
|
||||
|
||||
export const AssetTitleMap: Record<DisplayedAssetType, string> = {
|
||||
dashboard: 'Dashboard',
|
||||
|
@ -36,6 +36,7 @@ export const AssetTitleMap: Record<DisplayedAssetType, string> = {
|
|||
lens: 'Lens',
|
||||
security_rule: 'Security Rule',
|
||||
ml_module: 'ML Module',
|
||||
view: 'Views',
|
||||
};
|
||||
|
||||
export const ServiceTitleMap: Record<ServiceName, string> = {
|
||||
|
|
|
@ -10,12 +10,17 @@ import { Redirect } from 'react-router-dom';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { Loading, Error } from '../../../../../components';
|
||||
import { Loading, Error, ExtensionWrapper } from '../../../../../components';
|
||||
|
||||
import type { PackageInfo } from '../../../../../types';
|
||||
import { InstallStatus } from '../../../../../types';
|
||||
|
||||
import { useGetPackageInstallStatus, useLink, useStartServices } from '../../../../../hooks';
|
||||
import {
|
||||
useGetPackageInstallStatus,
|
||||
useLink,
|
||||
useStartServices,
|
||||
useUIExtension,
|
||||
} from '../../../../../hooks';
|
||||
|
||||
import type { AssetSavedObject } from './types';
|
||||
import { allowedAssetTypes } from './constants';
|
||||
|
@ -27,9 +32,12 @@ interface AssetsPanelProps {
|
|||
|
||||
export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
|
||||
const { name, version } = packageInfo;
|
||||
const pkgkey = `${name}-${version}`;
|
||||
|
||||
const {
|
||||
savedObjects: { client: savedObjectsClient },
|
||||
} = useStartServices();
|
||||
const customAssetsExtension = useUIExtension(packageInfo.name, 'package-detail-assets');
|
||||
|
||||
const { getPath } = useLink();
|
||||
const getPackageInstallStatus = useGetPackageInstallStatus();
|
||||
|
@ -76,13 +84,7 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
|
|||
// if they arrive at this page and the package is not installed, send them to overview
|
||||
// this happens if they arrive with a direct url or they uninstall while on this tab
|
||||
if (packageInstallStatus.status !== InstallStatus.installed) {
|
||||
return (
|
||||
<Redirect
|
||||
to={getPath('integration_details_overview', {
|
||||
pkgkey: `${name}-${version}`,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
return <Redirect to={getPath('integration_details_overview', { pkgkey })} />;
|
||||
}
|
||||
|
||||
let content: JSX.Element | Array<JSX.Element | null>;
|
||||
|
@ -102,6 +104,15 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
|
|||
/>
|
||||
);
|
||||
} else if (assetSavedObjects === undefined) {
|
||||
if (customAssetsExtension) {
|
||||
// If a UI extension for custom asset entries is defined, render the custom component here depisite
|
||||
// there being no saved objects found
|
||||
content = (
|
||||
<ExtensionWrapper>
|
||||
<customAssetsExtension.Component />
|
||||
</ExtensionWrapper>
|
||||
);
|
||||
} else {
|
||||
content = (
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
|
@ -112,8 +123,10 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
|
|||
</h2>
|
||||
</EuiTitle>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
content = allowedAssetTypes.map((assetType) => {
|
||||
content = [
|
||||
...allowedAssetTypes.map((assetType) => {
|
||||
const sectionAssetSavedObjects = assetSavedObjects.filter((so) => so.type === assetType);
|
||||
|
||||
if (!sectionAssetSavedObjects.length) {
|
||||
|
@ -126,7 +139,14 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
|
|||
<EuiSpacer size="l" />
|
||||
</>
|
||||
);
|
||||
});
|
||||
}),
|
||||
// Ensure we add any custom assets provided via UI extension to the end of the list of other assets
|
||||
customAssetsExtension ? (
|
||||
<ExtensionWrapper>
|
||||
<customAssetsExtension.Component />
|
||||
</ExtensionWrapper>
|
||||
) : null,
|
||||
];
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -55,6 +55,11 @@ export const AssetsAccordion: FunctionComponent<Props> = ({ savedObjects, type }
|
|||
<EuiSpacer size="m" />
|
||||
<EuiSplitPanel.Outer hasBorder hasShadow={false}>
|
||||
{savedObjects.map(({ id, attributes: { title, description } }, idx) => {
|
||||
// Ignore custom asset views
|
||||
if (type === 'view') {
|
||||
return;
|
||||
}
|
||||
|
||||
const pathToObjectInApp = getHrefToObjectInKibanaApp({
|
||||
http,
|
||||
id,
|
||||
|
|
|
@ -17,4 +17,4 @@ export type AllowedAssetTypes = [
|
|||
KibanaAssetType.visualization
|
||||
];
|
||||
|
||||
export type AllowedAssetType = AllowedAssetTypes[number];
|
||||
export type AllowedAssetType = AllowedAssetTypes[number] | 'view';
|
||||
|
|
|
@ -18,16 +18,16 @@ interface Props {
|
|||
}
|
||||
|
||||
export const CustomViewPage: React.FC<Props> = memo(({ packageInfo }) => {
|
||||
const CustomView = useUIExtension(packageInfo.name, 'package-detail-custom');
|
||||
const customViewExtension = useUIExtension(packageInfo.name, 'package-detail-custom');
|
||||
const { getPath } = useLink();
|
||||
const pkgkey = useMemo(() => pkgKeyFromPackageInfo(packageInfo), [packageInfo]);
|
||||
|
||||
return CustomView ? (
|
||||
return customViewExtension ? (
|
||||
<EuiFlexGroup alignItems="flexStart">
|
||||
<EuiFlexItem grow={1} />
|
||||
<EuiFlexItem grow={6}>
|
||||
<ExtensionWrapper>
|
||||
<CustomView pkgkey={pkgkey} packageInfo={packageInfo} />
|
||||
<customViewExtension.Component pkgkey={pkgkey} packageInfo={packageInfo} />
|
||||
</ExtensionWrapper>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -113,7 +113,7 @@ describe('when on integration detail', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('and a custom UI extension is registered', () => {
|
||||
describe('and a custom tab UI extension is registered', () => {
|
||||
// Because React Lazy components are loaded async (Promise), we setup this "watcher" Promise
|
||||
// that is `resolved` once the lazy components actually renders.
|
||||
let lazyComponentWasRendered: Promise<void>;
|
||||
|
@ -136,7 +136,7 @@ describe('when on integration detail', () => {
|
|||
testRenderer.startInterface.registerExtension({
|
||||
package: 'nginx',
|
||||
view: 'package-detail-custom',
|
||||
component: CustomComponent,
|
||||
Component: CustomComponent,
|
||||
});
|
||||
|
||||
render();
|
||||
|
@ -162,6 +162,53 @@ describe('when on integration detail', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('and a custom assets UI extension is registered', () => {
|
||||
let lazyComponentWasRendered: Promise<void>;
|
||||
|
||||
beforeEach(() => {
|
||||
let setWasRendered: () => void;
|
||||
lazyComponentWasRendered = new Promise((resolve) => {
|
||||
setWasRendered = resolve;
|
||||
});
|
||||
|
||||
const CustomComponent = lazy(async () => {
|
||||
return {
|
||||
default: memo(() => {
|
||||
setWasRendered();
|
||||
return <div data-test-subj="custom-hello">hello</div>;
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
testRenderer.startInterface.registerExtension({
|
||||
package: 'nginx',
|
||||
view: 'package-detail-assets',
|
||||
Component: CustomComponent,
|
||||
});
|
||||
|
||||
render();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// @ts-ignore
|
||||
lazyComponentWasRendered = undefined;
|
||||
});
|
||||
|
||||
it('should display "assets" tab in navigation', () => {
|
||||
expect(renderResult.getByTestId('tab-assets'));
|
||||
});
|
||||
|
||||
it('should display custom assets when tab is clicked', async () => {
|
||||
act(() => {
|
||||
testRenderer.history.push(
|
||||
pagePathGetters.integration_details_assets({ pkgkey: 'nginx-0.3.7' })[1]
|
||||
);
|
||||
});
|
||||
await lazyComponentWasRendered;
|
||||
expect(renderResult.getByTestId('custom-hello'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('and the Add integration button is clicked', () => {
|
||||
beforeEach(() => render());
|
||||
|
||||
|
|
|
@ -101,6 +101,8 @@ export function Detail() {
|
|||
const setPackageInstallStatus = useSetPackageInstallStatus();
|
||||
const getPackageInstallStatus = useGetPackageInstallStatus();
|
||||
|
||||
const CustomAssets = useUIExtension(packageInfo?.name ?? '', 'package-detail-assets');
|
||||
|
||||
const packageInstallStatus = useMemo(() => {
|
||||
if (packageInfo === null || !packageInfo.name) {
|
||||
return undefined;
|
||||
|
@ -418,7 +420,7 @@ export function Detail() {
|
|||
});
|
||||
}
|
||||
|
||||
if (packageInstallStatus === InstallStatus.installed && packageInfo.assets) {
|
||||
if (packageInstallStatus === InstallStatus.installed && (packageInfo.assets || CustomAssets)) {
|
||||
tabs.push({
|
||||
id: 'assets',
|
||||
name: (
|
||||
|
@ -471,7 +473,7 @@ export function Detail() {
|
|||
}
|
||||
|
||||
return tabs;
|
||||
}, [packageInfo, panel, getHref, integration, packageInstallStatus, showCustomTab]);
|
||||
}, [packageInfo, panel, getHref, integration, packageInstallStatus, showCustomTab, CustomAssets]);
|
||||
|
||||
return (
|
||||
<WithHeaderLayout
|
||||
|
|
|
@ -7,7 +7,11 @@
|
|||
import { stringify, parse } from 'query-string';
|
||||
import React, { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { Redirect, useLocation, useHistory } from 'react-router-dom';
|
||||
import type { CriteriaWithPagination, EuiTableFieldDataColumnType } from '@elastic/eui';
|
||||
import type {
|
||||
CriteriaWithPagination,
|
||||
EuiStepProps,
|
||||
EuiTableFieldDataColumnType,
|
||||
} from '@elastic/eui';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiBasicTable,
|
||||
|
@ -29,6 +33,7 @@ import {
|
|||
useUrlPagination,
|
||||
useGetPackageInstallStatus,
|
||||
AgentPolicyRefreshContext,
|
||||
useUIExtension,
|
||||
} from '../../../../../hooks';
|
||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../constants';
|
||||
import {
|
||||
|
@ -88,6 +93,8 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
|||
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: ${name}`,
|
||||
});
|
||||
|
||||
const agentEnrollmentFlyoutExtension = useUIExtension(name, 'agent-enrollment-flyout');
|
||||
|
||||
const handleTableOnChange = useCallback(
|
||||
({ page }: CriteriaWithPagination<PackagePolicyAndAgentPolicy>) => {
|
||||
setPagination({
|
||||
|
@ -98,8 +105,19 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
|||
[setPagination]
|
||||
);
|
||||
|
||||
const renderViewDataStepContent = useCallback(
|
||||
() => (
|
||||
const viewDataStep = useMemo<EuiStepProps>(() => {
|
||||
if (agentEnrollmentFlyoutExtension) {
|
||||
return {
|
||||
title: agentEnrollmentFlyoutExtension.title,
|
||||
children: <agentEnrollmentFlyoutExtension.Component />,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: i18n.translate('xpack.fleet.agentEnrollment.stepViewDataTitle', {
|
||||
defaultMessage: 'View your data',
|
||||
}),
|
||||
children: (
|
||||
<>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
|
@ -125,8 +143,8 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
|||
</EuiButton>
|
||||
</>
|
||||
),
|
||||
[name, version, getHref]
|
||||
);
|
||||
};
|
||||
}, [name, version, getHref, agentEnrollmentFlyoutExtension]);
|
||||
|
||||
const columns: Array<EuiTableFieldDataColumnType<PackagePolicyAndAgentPolicy>> = useMemo(
|
||||
() => [
|
||||
|
@ -230,13 +248,13 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
|||
<PackagePolicyActionsMenu
|
||||
agentPolicy={agentPolicy}
|
||||
packagePolicy={packagePolicy}
|
||||
viewDataStepContent={renderViewDataStepContent()}
|
||||
viewDataStep={viewDataStep}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[renderViewDataStepContent]
|
||||
[viewDataStep]
|
||||
);
|
||||
|
||||
const noItemsMessage = useMemo(() => {
|
||||
|
@ -292,7 +310,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
|||
data?.items.find(({ agentPolicy }) => agentPolicy.id === flyoutOpenForPolicyId)
|
||||
?.agentPolicy
|
||||
}
|
||||
viewDataStepContent={renderViewDataStepContent()}
|
||||
viewDataStep={viewDataStep}
|
||||
/>
|
||||
)}
|
||||
</AgentPolicyRefreshContext.Provider>
|
||||
|
|
|
@ -21,7 +21,7 @@ import { FleetStatusProvider, ConfigContext } from '../../hooks';
|
|||
|
||||
import { useFleetServerInstructions } from '../../applications/fleet/sections/agents/agent_requirements_page/components';
|
||||
|
||||
import { AgentEnrollmentKeySelectionStep, AgentPolicySelectionStep, ViewDataStep } from './steps';
|
||||
import { AgentEnrollmentKeySelectionStep, AgentPolicySelectionStep } from './steps';
|
||||
|
||||
import type { Props } from '.';
|
||||
import { AgentEnrollmentFlyout } from '.';
|
||||
|
@ -129,24 +129,26 @@ describe('<AgentEnrollmentFlyout />', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('"View data" extension point', () => {
|
||||
it('calls the "View data" step when UI extension is provided', async () => {
|
||||
// Skipped due to implementation details in the step components. See https://github.com/elastic/kibana/issues/103894
|
||||
describe.skip('"View data" extension point', () => {
|
||||
it('shows the "View data" step when UI extension is provided', async () => {
|
||||
jest.clearAllMocks();
|
||||
await act(async () => {
|
||||
testBed = await setup({
|
||||
agentPolicies: [],
|
||||
onClose: jest.fn(),
|
||||
viewDataStepContent: <div />,
|
||||
viewDataStep: { title: 'View Data', children: <div /> },
|
||||
});
|
||||
testBed.component.update();
|
||||
});
|
||||
const { exists, actions } = testBed;
|
||||
expect(exists('agentEnrollmentFlyout')).toBe(true);
|
||||
expect(ViewDataStep).toHaveBeenCalled();
|
||||
expect(exists('view-data-step')).toBe(true);
|
||||
|
||||
jest.clearAllMocks();
|
||||
actions.goToStandaloneTab();
|
||||
expect(ViewDataStep).not.toHaveBeenCalled();
|
||||
expect(exists('agentEnrollmentFlyout')).toBe(true);
|
||||
expect(exists('view-data-step')).toBe(false);
|
||||
});
|
||||
|
||||
it('does not call the "View data" step when UI extension is not provided', async () => {
|
||||
|
@ -155,17 +157,17 @@ describe('<AgentEnrollmentFlyout />', () => {
|
|||
testBed = await setup({
|
||||
agentPolicies: [],
|
||||
onClose: jest.fn(),
|
||||
viewDataStepContent: undefined,
|
||||
viewDataStep: undefined,
|
||||
});
|
||||
testBed.component.update();
|
||||
});
|
||||
const { exists, actions } = testBed;
|
||||
expect(exists('agentEnrollmentFlyout')).toBe(true);
|
||||
expect(ViewDataStep).not.toHaveBeenCalled();
|
||||
expect(exists('view-data-step')).toBe(false);
|
||||
|
||||
jest.clearAllMocks();
|
||||
actions.goToStandaloneTab();
|
||||
expect(ViewDataStep).not.toHaveBeenCalled();
|
||||
expect(exists('view-data-step')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -45,7 +45,7 @@ export const AgentEnrollmentFlyout: React.FunctionComponent<Props> = ({
|
|||
onClose,
|
||||
agentPolicy,
|
||||
agentPolicies,
|
||||
viewDataStepContent,
|
||||
viewDataStep,
|
||||
defaultMode = 'managed',
|
||||
}) => {
|
||||
const [mode, setMode] = useState<FlyoutMode>(defaultMode);
|
||||
|
@ -119,14 +119,10 @@ export const AgentEnrollmentFlyout: React.FunctionComponent<Props> = ({
|
|||
<ManagedInstructions
|
||||
agentPolicy={agentPolicy}
|
||||
agentPolicies={agentPolicies}
|
||||
viewDataStepContent={viewDataStepContent}
|
||||
viewDataStep={viewDataStep}
|
||||
/>
|
||||
) : (
|
||||
<StandaloneInstructions
|
||||
agentPolicy={agentPolicy}
|
||||
agentPolicies={agentPolicies}
|
||||
viewDataStepContent={viewDataStepContent}
|
||||
/>
|
||||
<StandaloneInstructions agentPolicy={agentPolicy} agentPolicies={agentPolicies} />
|
||||
)}
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
|
|
|
@ -23,12 +23,7 @@ import {
|
|||
} from '../../applications/fleet/sections/agents/agent_requirements_page/components';
|
||||
import { FleetServerRequirementPage } from '../../applications/fleet/sections/agents/agent_requirements_page';
|
||||
|
||||
import {
|
||||
DownloadStep,
|
||||
AgentPolicySelectionStep,
|
||||
AgentEnrollmentKeySelectionStep,
|
||||
ViewDataStep,
|
||||
} from './steps';
|
||||
import { DownloadStep, AgentPolicySelectionStep, AgentEnrollmentKeySelectionStep } from './steps';
|
||||
import type { BaseProps } from './types';
|
||||
|
||||
type Props = BaseProps;
|
||||
|
@ -61,7 +56,7 @@ const FleetServerMissingRequirements = () => {
|
|||
};
|
||||
|
||||
export const ManagedInstructions = React.memo<Props>(
|
||||
({ agentPolicy, agentPolicies, viewDataStepContent }) => {
|
||||
({ agentPolicy, agentPolicies, viewDataStep }) => {
|
||||
const fleetStatus = useFleetStatus();
|
||||
|
||||
const [selectedApiKeyId, setSelectedAPIKeyId] = useState<string | undefined>();
|
||||
|
@ -118,8 +113,8 @@ export const ManagedInstructions = React.memo<Props>(
|
|||
});
|
||||
}
|
||||
|
||||
if (viewDataStepContent) {
|
||||
baseSteps.push(ViewDataStep(viewDataStepContent));
|
||||
if (viewDataStep) {
|
||||
baseSteps.push({ 'data-test-subj': 'view-data-step', ...viewDataStep });
|
||||
}
|
||||
|
||||
return baseSteps;
|
||||
|
@ -127,12 +122,12 @@ export const ManagedInstructions = React.memo<Props>(
|
|||
agentPolicy,
|
||||
selectedApiKeyId,
|
||||
setSelectedAPIKeyId,
|
||||
viewDataStepContent,
|
||||
agentPolicies,
|
||||
apiKey.data,
|
||||
fleetServerSteps,
|
||||
isFleetServerPolicySelected,
|
||||
settings.data?.item?.fleet_server_hosts,
|
||||
viewDataStep,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
|
|
@ -144,16 +144,3 @@ export const AgentEnrollmentKeySelectionStep = ({
|
|||
),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Send users to assets installed by the package in Kibana so they can
|
||||
* view their data.
|
||||
*/
|
||||
export const ViewDataStep = (content: JSX.Element) => {
|
||||
return {
|
||||
title: i18n.translate('xpack.fleet.agentEnrollment.stepViewDataTitle', {
|
||||
defaultMessage: 'View your data',
|
||||
}),
|
||||
children: content,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { EuiStepProps } from '@elastic/eui';
|
||||
|
||||
import type { AgentPolicy } from '../../types';
|
||||
|
||||
export interface BaseProps {
|
||||
|
@ -24,5 +26,5 @@ export interface BaseProps {
|
|||
* There is a step in the agent enrollment process that allows users to see the data from an integration represented in the UI
|
||||
* in some way. This is an area for consumers to render a button and text explaining how data can be viewed.
|
||||
*/
|
||||
viewDataStepContent?: JSX.Element;
|
||||
viewDataStep?: EuiStepProps;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import type { FunctionComponent } from 'react';
|
||||
import {
|
||||
EuiAccordion,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiNotificationBadge,
|
||||
EuiSpacer,
|
||||
EuiSplitPanel,
|
||||
EuiLink,
|
||||
EuiHorizontalRule,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { AssetTitleMap } from '../applications/integrations/sections/epm/constants';
|
||||
import { useStartServices } from '../hooks';
|
||||
import { RedirectAppLinks } from '../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
export interface CustomAssetsAccordionProps {
|
||||
views: Array<{
|
||||
name: string;
|
||||
url: string;
|
||||
description: string;
|
||||
}>;
|
||||
initialIsOpen?: boolean;
|
||||
}
|
||||
|
||||
export const CustomAssetsAccordion: FunctionComponent<CustomAssetsAccordionProps> = ({
|
||||
views,
|
||||
initialIsOpen = false,
|
||||
}) => {
|
||||
const { application } = useStartServices();
|
||||
|
||||
return (
|
||||
<EuiAccordion
|
||||
initialIsOpen={initialIsOpen}
|
||||
buttonContent={
|
||||
<EuiFlexGroup justifyContent="center" alignItems="center" gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="m">
|
||||
<h3>{AssetTitleMap.view}</h3>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiNotificationBadge color="subdued" size="m">
|
||||
<h3>{views.length}</h3>
|
||||
</EuiNotificationBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
id="custom-assets"
|
||||
>
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiSplitPanel.Outer hasBorder hasShadow={false}>
|
||||
{views.map((view, index) => (
|
||||
<>
|
||||
<EuiSplitPanel.Inner grow={false} key={index}>
|
||||
<EuiText size="m">
|
||||
<p>
|
||||
<RedirectAppLinks application={application}>
|
||||
<EuiLink href={view.url}>{view.name}</EuiLink>
|
||||
</RedirectAppLinks>
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s" color="subdued">
|
||||
<p>{view.description}</p>
|
||||
</EuiText>
|
||||
</EuiSplitPanel.Inner>
|
||||
{index + 1 < views.length && <EuiHorizontalRule margin="none" />}
|
||||
</>
|
||||
))}
|
||||
</EuiSplitPanel.Outer>
|
||||
</>
|
||||
</EuiAccordion>
|
||||
);
|
||||
};
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { EuiContextMenuItem, EuiPortal } from '@elastic/eui';
|
||||
import type { EuiStepProps } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import type { AgentPolicy, PackagePolicy } from '../types';
|
||||
|
@ -21,8 +22,8 @@ import { PackagePolicyDeleteProvider } from './package_policy_delete_provider';
|
|||
export const PackagePolicyActionsMenu: React.FunctionComponent<{
|
||||
agentPolicy: AgentPolicy;
|
||||
packagePolicy: PackagePolicy;
|
||||
viewDataStepContent?: JSX.Element;
|
||||
}> = ({ agentPolicy, packagePolicy, viewDataStepContent }) => {
|
||||
viewDataStep?: EuiStepProps;
|
||||
}> = ({ agentPolicy, packagePolicy, viewDataStep }) => {
|
||||
const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false);
|
||||
const { getHref } = useLink();
|
||||
const hasWriteCapabilities = useCapabilities().write;
|
||||
|
@ -106,7 +107,7 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{
|
|||
<EuiPortal>
|
||||
<AgentEnrollmentFlyout
|
||||
agentPolicy={agentPolicy}
|
||||
viewDataStepContent={viewDataStepContent}
|
||||
viewDataStep={viewDataStep}
|
||||
onClose={onEnrollmentFlyoutClose}
|
||||
/>
|
||||
</EuiPortal>
|
||||
|
|
|
@ -24,3 +24,5 @@ export {
|
|||
export * from './page_paths';
|
||||
|
||||
export const INDEX_NAME = '.kibana';
|
||||
|
||||
export const CUSTOM_LOGS_INTEGRATION_NAME = 'log';
|
||||
|
|
31
x-pack/plugins/fleet/public/custom_logs_assets_extension.tsx
Normal file
31
x-pack/plugins/fleet/public/custom_logs_assets_extension.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { CustomAssetsAccordion } from './components/custom_assets_accordion';
|
||||
import type { CustomAssetsAccordionProps } from './components/custom_assets_accordion';
|
||||
import { useStartServices } from './hooks';
|
||||
import type { PackageAssetsComponent } from './types';
|
||||
|
||||
export const CustomLogsAssetsExtension: PackageAssetsComponent = () => {
|
||||
const { http } = useStartServices();
|
||||
const logStreamUrl = http.basePath.prepend('/app/logs/stream');
|
||||
|
||||
const views: CustomAssetsAccordionProps['views'] = [
|
||||
{
|
||||
name: i18n.translate('xpack.fleet.assets.customLogs.name', { defaultMessage: 'Logs' }),
|
||||
url: logStreamUrl,
|
||||
description: i18n.translate('xpack.fleet.assets.customLogs.description', {
|
||||
defaultMessage: 'View Custom logs data in Logs app',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
return <CustomAssetsAccordion views={views} initialIsOpen />;
|
||||
};
|
|
@ -20,7 +20,7 @@ type NarrowExtensionPoint<V extends UIExtensionPoint['view'], A = UIExtensionPoi
|
|||
export const useUIExtension = <V extends UIExtensionPoint['view'] = UIExtensionPoint['view']>(
|
||||
packageName: UIExtensionPoint['package'],
|
||||
view: V
|
||||
): NarrowExtensionPoint<V>['component'] | undefined => {
|
||||
): NarrowExtensionPoint<V> | undefined => {
|
||||
const registeredExtensions = useContext(UIExtensionsContext);
|
||||
|
||||
if (!registeredExtensions) {
|
||||
|
@ -32,6 +32,6 @@ export const useUIExtension = <V extends UIExtensionPoint['view'] = UIExtensionP
|
|||
if (extension) {
|
||||
// FIXME:PT Revisit ignore below and see if TS error can be addressed
|
||||
// @ts-ignore
|
||||
return extension.component;
|
||||
return extension;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -21,3 +21,7 @@ export * from './types/ui_extensions';
|
|||
|
||||
export { pagePathGetters } from './constants';
|
||||
export { pkgKeyFromPackageInfo } from './services';
|
||||
export {
|
||||
CustomAssetsAccordion,
|
||||
CustomAssetsAccordionProps,
|
||||
} from './components/custom_assets_accordion';
|
||||
|
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { lazy } from 'react';
|
||||
|
||||
import type { PackageAssetsComponent } from './types';
|
||||
import { CustomLogsAssetsExtension } from './custom_logs_assets_extension';
|
||||
|
||||
export const LazyCustomLogsAssetsExtension = lazy<PackageAssetsComponent>(async () => {
|
||||
return {
|
||||
default: CustomLogsAssetsExtension,
|
||||
};
|
||||
});
|
|
@ -32,7 +32,7 @@ import type { CheckPermissionsResponse, PostIngestSetupResponse } from '../commo
|
|||
|
||||
import type { FleetConfigType } from '../common/types';
|
||||
|
||||
import { FLEET_BASE_PATH } from './constants';
|
||||
import { CUSTOM_LOGS_INTEGRATION_NAME, FLEET_BASE_PATH } from './constants';
|
||||
import { licenseService } from './hooks';
|
||||
import { setHttpClient } from './hooks/use_request';
|
||||
import { createPackageSearchProvider } from './search_provider';
|
||||
|
@ -43,6 +43,7 @@ import {
|
|||
} from './components/home_integration';
|
||||
import { createExtensionRegistrationCallback } from './services/ui_extensions';
|
||||
import type { UIExtensionRegistrationCallback, UIExtensionsStorage } from './types';
|
||||
import { LazyCustomLogsAssetsExtension } from './lazy_custom_logs_assets_extension';
|
||||
|
||||
export { FleetConfigType } from '../common/types';
|
||||
|
||||
|
@ -204,6 +205,13 @@ export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDep
|
|||
|
||||
public start(core: CoreStart): FleetStart {
|
||||
let successPromise: ReturnType<FleetStart['isInitialized']>;
|
||||
const registerExtension = createExtensionRegistrationCallback(this.extensions);
|
||||
|
||||
registerExtension({
|
||||
package: CUSTOM_LOGS_INTEGRATION_NAME,
|
||||
view: 'package-detail-assets',
|
||||
Component: LazyCustomLogsAssetsExtension,
|
||||
});
|
||||
|
||||
return {
|
||||
isInitialized: () => {
|
||||
|
@ -229,8 +237,7 @@ export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDep
|
|||
|
||||
return successPromise;
|
||||
},
|
||||
|
||||
registerExtension: createExtensionRegistrationCallback(this.extensions),
|
||||
registerExtension,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -36,13 +36,13 @@ describe('UI Extension services', () => {
|
|||
register({
|
||||
view: 'package-policy-edit',
|
||||
package: 'endpoint',
|
||||
component: LazyCustomView,
|
||||
Component: LazyCustomView,
|
||||
});
|
||||
|
||||
expect(storage.endpoint['package-policy-edit']).toEqual({
|
||||
view: 'package-policy-edit',
|
||||
package: 'endpoint',
|
||||
component: LazyCustomView,
|
||||
Component: LazyCustomView,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -57,21 +57,21 @@ describe('UI Extension services', () => {
|
|||
register({
|
||||
view: 'package-policy-edit',
|
||||
package: 'endpoint',
|
||||
component: LazyCustomView,
|
||||
Component: LazyCustomView,
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
register({
|
||||
view: 'package-policy-edit',
|
||||
package: 'endpoint',
|
||||
component: LazyCustomView2,
|
||||
Component: LazyCustomView2,
|
||||
});
|
||||
}).toThrow();
|
||||
|
||||
expect(storage.endpoint['package-policy-edit']).toEqual({
|
||||
view: 'package-policy-edit',
|
||||
package: 'endpoint',
|
||||
component: LazyCustomView,
|
||||
Component: LazyCustomView,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { EuiStepProps } from '@elastic/eui';
|
||||
import type { ComponentType, LazyExoticComponent } from 'react';
|
||||
|
||||
import type { NewPackagePolicy, PackageInfo, PackagePolicy } from './index';
|
||||
|
@ -48,7 +49,7 @@ export interface PackagePolicyEditExtensionComponentProps {
|
|||
export interface PackagePolicyEditExtension {
|
||||
package: string;
|
||||
view: 'package-policy-edit';
|
||||
component: LazyExoticComponent<PackagePolicyEditExtensionComponent>;
|
||||
Component: LazyExoticComponent<PackagePolicyEditExtensionComponent>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,7 +77,7 @@ export interface PackagePolicyCreateExtensionComponentProps {
|
|||
export interface PackagePolicyCreateExtension {
|
||||
package: string;
|
||||
view: 'package-policy-create';
|
||||
component: LazyExoticComponent<PackagePolicyCreateExtensionComponent>;
|
||||
Component: LazyExoticComponent<PackagePolicyCreateExtensionComponent>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -94,11 +95,32 @@ export interface PackageCustomExtensionComponentProps {
|
|||
export interface PackageCustomExtension {
|
||||
package: string;
|
||||
view: 'package-detail-custom';
|
||||
component: LazyExoticComponent<PackageCustomExtensionComponent>;
|
||||
Component: LazyExoticComponent<PackageCustomExtensionComponent>;
|
||||
}
|
||||
|
||||
/**
|
||||
* UI Component Extension for displaying custom views under the Assets tab for a given Integration
|
||||
*/
|
||||
export type PackageAssetsComponent = ComponentType<{}>;
|
||||
|
||||
/** Extension point registration contract for Integration details Assets view */
|
||||
export interface PackageAssetsExtension {
|
||||
package: string;
|
||||
view: 'package-detail-assets';
|
||||
Component: LazyExoticComponent<PackageAssetsComponent>;
|
||||
}
|
||||
|
||||
export interface AgentEnrollmentFlyoutFinalStepExtension {
|
||||
package: string;
|
||||
view: 'agent-enrollment-flyout';
|
||||
title: EuiStepProps['title'];
|
||||
Component: ComponentType<{}>;
|
||||
}
|
||||
|
||||
/** Fleet UI Extension Point */
|
||||
export type UIExtensionPoint =
|
||||
| PackagePolicyEditExtension
|
||||
| PackageCustomExtension
|
||||
| PackagePolicyCreateExtension;
|
||||
| PackagePolicyCreateExtension
|
||||
| PackageAssetsExtension
|
||||
| AgentEnrollmentFlyoutFinalStepExtension;
|
||||
|
|
|
@ -58,7 +58,7 @@ export function toggleOsqueryPlugin(
|
|||
registerExtension({
|
||||
package: OSQUERY_INTEGRATION_NAME,
|
||||
view: 'package-detail-custom',
|
||||
component: LazyOsqueryManagedCustomButtonExtension,
|
||||
Component: LazyOsqueryManagedCustomButtonExtension,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -146,13 +146,13 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt
|
|||
registerExtension({
|
||||
package: OSQUERY_INTEGRATION_NAME,
|
||||
view: 'package-policy-create',
|
||||
component: LazyOsqueryManagedPolicyCreateImportExtension,
|
||||
Component: LazyOsqueryManagedPolicyCreateImportExtension,
|
||||
});
|
||||
|
||||
registerExtension({
|
||||
package: OSQUERY_INTEGRATION_NAME,
|
||||
view: 'package-policy-edit',
|
||||
component: LazyOsqueryManagedPolicyEditExtension,
|
||||
Component: LazyOsqueryManagedPolicyEditExtension,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useKibana } from '../../../../../common/lib/kibana';
|
||||
import { APP_PATH } from '../../../../../../common/constants';
|
||||
import {
|
||||
CustomAssetsAccordionProps,
|
||||
CustomAssetsAccordion,
|
||||
PackageAssetsComponent,
|
||||
} from '../../../../../../../fleet/public';
|
||||
|
||||
export const EndpointCustomAssetsExtension: PackageAssetsComponent = () => {
|
||||
const { http } = useKibana().services;
|
||||
const views: CustomAssetsAccordionProps['views'] = [
|
||||
{
|
||||
name: i18n.translate('xpack.securitySolution.fleetIntegration.assets.name', {
|
||||
defaultMessage: 'Hosts',
|
||||
}),
|
||||
url: http.basePath.prepend(`${APP_PATH}/administration/endpoints`),
|
||||
description: i18n.translate('xpack.securitySolution.fleetIntegration.assets.description', {
|
||||
defaultMessage: 'View endpoints in Security app',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
return <CustomAssetsAccordion views={views} initialIsOpen />;
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { lazy } from 'react';
|
||||
|
||||
export const LazyEndpointCustomAssetsExtension = lazy(async () => {
|
||||
const { EndpointCustomAssetsExtension } = await import('./endpoint_custom_assets_extension');
|
||||
|
||||
return {
|
||||
default: EndpointCustomAssetsExtension,
|
||||
};
|
||||
});
|
|
@ -60,6 +60,7 @@ import { LazyEndpointPolicyCreateExtension } from './management/pages/policy/vie
|
|||
import { getLazyEndpointPackageCustomExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_package_custom_extension';
|
||||
import { parseExperimentalConfigValue } from '../common/experimental_features';
|
||||
import type { TimelineState } from '../../timelines/public';
|
||||
import { LazyEndpointCustomAssetsExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_custom_assets_extension';
|
||||
|
||||
export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, StartPlugins> {
|
||||
private kibanaVersion: string;
|
||||
|
@ -199,19 +200,25 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
registerExtension({
|
||||
package: 'endpoint',
|
||||
view: 'package-policy-edit',
|
||||
component: getLazyEndpointPolicyEditExtension(core, plugins),
|
||||
Component: getLazyEndpointPolicyEditExtension(core, plugins),
|
||||
});
|
||||
|
||||
registerExtension({
|
||||
package: 'endpoint',
|
||||
view: 'package-policy-create',
|
||||
component: LazyEndpointPolicyCreateExtension,
|
||||
Component: LazyEndpointPolicyCreateExtension,
|
||||
});
|
||||
|
||||
registerExtension({
|
||||
package: 'endpoint',
|
||||
view: 'package-detail-custom',
|
||||
component: getLazyEndpointPackageCustomExtension(core, plugins),
|
||||
Component: getLazyEndpointPackageCustomExtension(core, plugins),
|
||||
});
|
||||
|
||||
registerExtension({
|
||||
package: 'endpoint',
|
||||
view: 'package-detail-assets',
|
||||
Component: LazyEndpointCustomAssetsExtension,
|
||||
});
|
||||
}
|
||||
licenseService.start(plugins.licensing.license$);
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"server": true,
|
||||
"ui": true,
|
||||
"version": "8.0.0",
|
||||
"requiredBundles": ["observability", "kibanaReact", "kibanaUtils", "home", "data", "ml"],
|
||||
"requiredBundles": ["observability", "kibanaReact", "kibanaUtils", "home", "data", "ml", "fleet"],
|
||||
"owner": {
|
||||
"name": "Uptime",
|
||||
"githubTeam": "uptime"
|
||||
|
|
|
@ -42,6 +42,7 @@ import {
|
|||
LazySyntheticsPolicyCreateExtension,
|
||||
LazySyntheticsPolicyEditExtension,
|
||||
} from '../components/fleet_package';
|
||||
import { LazySyntheticsCustomAssetsExtension } from '../components/fleet_package/lazy_synthetics_custom_assets_extension';
|
||||
|
||||
export interface ClientPluginsSetup {
|
||||
data: DataPublicPluginSetup;
|
||||
|
@ -196,13 +197,19 @@ export class UptimePlugin
|
|||
registerExtension({
|
||||
package: 'synthetics',
|
||||
view: 'package-policy-create',
|
||||
component: LazySyntheticsPolicyCreateExtension,
|
||||
Component: LazySyntheticsPolicyCreateExtension,
|
||||
});
|
||||
|
||||
registerExtension({
|
||||
package: 'synthetics',
|
||||
view: 'package-policy-edit',
|
||||
component: LazySyntheticsPolicyEditExtension,
|
||||
Component: LazySyntheticsPolicyEditExtension,
|
||||
});
|
||||
|
||||
registerExtension({
|
||||
package: 'synthetics',
|
||||
view: 'package-detail-assets',
|
||||
Component: LazySyntheticsCustomAssetsExtension,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { lazy } from 'react';
|
||||
|
||||
export const LazySyntheticsCustomAssetsExtension = lazy(async () => {
|
||||
const { SyntheticsCustomAssetsExtension } = await import('./synthetics_custom_assets_extension');
|
||||
|
||||
return {
|
||||
default: SyntheticsCustomAssetsExtension,
|
||||
};
|
||||
});
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
PackageAssetsComponent,
|
||||
CustomAssetsAccordionProps,
|
||||
CustomAssetsAccordion,
|
||||
} from '../../../../fleet/public';
|
||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { ClientPluginsStart } from '../../apps/plugin';
|
||||
import { PLUGIN } from '../../../common/constants/plugin';
|
||||
|
||||
export const SyntheticsCustomAssetsExtension: PackageAssetsComponent = () => {
|
||||
const { http } = useKibana<ClientPluginsStart>().services;
|
||||
const views: CustomAssetsAccordionProps['views'] = [
|
||||
{
|
||||
name: i18n.translate('xpack.uptime.fleetIntegration.assets.name', {
|
||||
defaultMessage: 'Monitors',
|
||||
}),
|
||||
url: http?.basePath.prepend(`/app/${PLUGIN.ID}`) ?? '',
|
||||
description: i18n.translate('xpack.uptime.fleetIntegration.assets.description', {
|
||||
defaultMessage: 'View monitors in Uptime',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
return <CustomAssetsAccordion views={views} initialIsOpen />;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue