mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
* very wip * added new assets screen * added routes to new assets view on the package details view * Finished styling the assets page layout, need to work on adding links * rather use EuiHorizontalRule * only show the assets tab if installed * Added hacky version of linking to assets. * added comment about deprecation of current linking functionality * added an initial version of the success toast with a link to the agent flyout * First iteration of end-to-end UX working. Need to add a lot of tests! * fixed navigation bug and added a comment * added a lot more padding to bottom of form * restructured code for clarity, updated deprecation comments and moved relevant code closer together * added a longer form comment about the origin policyId * added logic for handling load error * refactor assets accordions out of assets page component * slightly larger text in badge * added some basic jest test for view data step in enrollment flyout * adjusted sizing of numbers in badges again, EuiText does not know about size="l" * updated size limits for fleet * updated styling and layout of assets accordion based on original designs * remove unused EuiTitle Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Jean-Louis Leysens <jloleysens@gmail.com>
This commit is contained in:
parent
b911f5df4f
commit
ba95a28dd8
21 changed files with 676 additions and 125 deletions
|
@ -34,7 +34,7 @@ pageLoadAssetSize:
|
|||
indexManagement: 140608
|
||||
indexPatternManagement: 28222
|
||||
infra: 184320
|
||||
fleet: 450005
|
||||
fleet: 465774
|
||||
ingestPipelines: 58003
|
||||
inputControlVis: 172819
|
||||
inspector: 148999
|
||||
|
|
|
@ -43,7 +43,7 @@ export type InstallSource = 'registry' | 'upload';
|
|||
|
||||
export type EpmPackageInstallStatus = 'installed' | 'installing';
|
||||
|
||||
export type DetailViewPanelName = 'overview' | 'policies' | 'settings' | 'custom';
|
||||
export type DetailViewPanelName = 'overview' | 'policies' | 'assets' | 'settings' | 'custom';
|
||||
export type ServiceName = 'kibana' | 'elasticsearch';
|
||||
export type AgentAssetType = typeof agentAssetTypes;
|
||||
export type DocAssetType = 'doc' | 'notice';
|
||||
|
|
|
@ -19,10 +19,12 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
import type { EuiStepProps } from '@elastic/eui/src/components/steps/step';
|
||||
import type { ApplicationStart } from 'kibana/public';
|
||||
|
||||
import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public';
|
||||
import type {
|
||||
AgentPolicy,
|
||||
PackageInfo,
|
||||
|
@ -60,7 +62,7 @@ const StepsWithLessPadding = styled(EuiSteps)`
|
|||
`;
|
||||
|
||||
const CustomEuiBottomBar = styled(EuiBottomBar)`
|
||||
// Set a relatively _low_ z-index value here to account for EuiComboBox popover that might appear under the bottom bar
|
||||
/* A relatively _low_ z-index value here to account for EuiComboBox popover that might appear under the bottom bar */
|
||||
z-index: 50;
|
||||
`;
|
||||
|
||||
|
@ -84,11 +86,26 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
const history = useHistory();
|
||||
const handleNavigateTo = useNavigateToCallback();
|
||||
const routeState = useIntraAppState<CreatePackagePolicyRouteState>();
|
||||
const from: CreatePackagePolicyFrom = 'policyId' in params ? 'policy' : 'package';
|
||||
|
||||
const { search } = useLocation();
|
||||
const queryParams = useMemo(() => new URLSearchParams(search), [search]);
|
||||
const policyId = useMemo(() => queryParams.get('policyId') ?? undefined, [queryParams]);
|
||||
const queryParamsPolicyId = useMemo(() => queryParams.get('policyId') ?? undefined, [
|
||||
queryParams,
|
||||
]);
|
||||
|
||||
/**
|
||||
* Please note: policyId can come from one of two sources. The URL param (in the URL path) or
|
||||
* in the query params (?policyId=foo).
|
||||
*
|
||||
* Either way, we take this as an indication that a user is "coming from" the fleet policy UI
|
||||
* since we link them out to packages (a.k.a. integrations) UI when choosing a new package. It is
|
||||
* no longer possible to choose a package directly in the create package form.
|
||||
*
|
||||
* We may want to deprecate the ability to pass in policyId from URL params since there is no package
|
||||
* creation possible if a user has not chosen one from the packages UI.
|
||||
*/
|
||||
const from: CreatePackagePolicyFrom =
|
||||
'policyId' in params || queryParamsPolicyId ? 'policy' : 'package';
|
||||
|
||||
// Agent policy and package info states
|
||||
const [agentPolicy, setAgentPolicy] = useState<AgentPolicy | undefined>();
|
||||
|
@ -280,6 +297,13 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
);
|
||||
}
|
||||
|
||||
const fromPolicyWithoutAgentsAssigned = from === 'policy' && agentPolicy && agentCount === 0;
|
||||
|
||||
const fromPackageWithoutAgentsAssigned =
|
||||
from === 'package' && packageInfo && agentPolicy && agentCount === 0;
|
||||
|
||||
const hasAgentsAssigned = agentCount && agentPolicy;
|
||||
|
||||
notifications.toasts.addSuccess({
|
||||
title: i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationTitle', {
|
||||
defaultMessage: `'{packagePolicyName}' integration added.`,
|
||||
|
@ -287,22 +311,47 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
packagePolicyName: packagePolicy.name,
|
||||
},
|
||||
}),
|
||||
text:
|
||||
agentCount && agentPolicy
|
||||
? i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationMessage', {
|
||||
defaultMessage: `Fleet will deploy updates to all agents that use the '{agentPolicyName}' policy.`,
|
||||
values: {
|
||||
agentPolicyName: agentPolicy.name,
|
||||
},
|
||||
})
|
||||
: (params as AddToPolicyParams)?.policyId && agentPolicy && agentCount === 0
|
||||
? i18n.translate('xpack.fleet.createPackagePolicy.addAgentNextNotification', {
|
||||
text: fromPolicyWithoutAgentsAssigned
|
||||
? i18n.translate(
|
||||
'xpack.fleet.createPackagePolicy.policyContextAddAgentNextNotificationMessage',
|
||||
{
|
||||
defaultMessage: `The policy has been updated. Add an agent to the '{agentPolicyName}' policy to deploy this policy.`,
|
||||
values: {
|
||||
agentPolicyName: agentPolicy.name,
|
||||
agentPolicyName: agentPolicy!.name,
|
||||
},
|
||||
})
|
||||
: undefined,
|
||||
}
|
||||
)
|
||||
: fromPackageWithoutAgentsAssigned
|
||||
? toMountPoint(
|
||||
// To render the link below we need to mount this JSX in the success toast
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.createPackagePolicy.integrationsContextaddAgentNextNotificationMessage"
|
||||
defaultMessage="Next, {link} to start ingesting data."
|
||||
values={{
|
||||
link: (
|
||||
<EuiLink
|
||||
href={getHref('integration_details_policies', {
|
||||
pkgkey: `${packageInfo!.name}-${packageInfo!.version}`,
|
||||
addAgentToPolicyId: agentPolicy!.id,
|
||||
})}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.fleet.createPackagePolicy.integrationsContextAddAgentLinkMessage',
|
||||
{ defaultMessage: 'add an agent' }
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)
|
||||
: hasAgentsAssigned
|
||||
? i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationMessage', {
|
||||
defaultMessage: `Fleet will deploy updates to all agents that use the '{agentPolicyName}' policy.`,
|
||||
values: {
|
||||
agentPolicyName: agentPolicy!.name,
|
||||
},
|
||||
})
|
||||
: undefined,
|
||||
'data-test-subj': 'packagePolicyCreateSuccessToast',
|
||||
});
|
||||
} else {
|
||||
|
@ -312,6 +361,9 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
setFormState('VALID');
|
||||
}
|
||||
}, [
|
||||
getHref,
|
||||
from,
|
||||
packageInfo,
|
||||
agentCount,
|
||||
agentPolicy,
|
||||
formState,
|
||||
|
@ -353,13 +405,13 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
<StepSelectAgentPolicy
|
||||
pkgkey={(params as AddToPolicyParams).pkgkey}
|
||||
updatePackageInfo={updatePackageInfo}
|
||||
defaultAgentPolicyId={policyId}
|
||||
defaultAgentPolicyId={queryParamsPolicyId}
|
||||
agentPolicy={agentPolicy}
|
||||
updateAgentPolicy={updateAgentPolicy}
|
||||
setIsLoadingSecondStep={setIsLoadingAgentPolicyStep}
|
||||
/>
|
||||
),
|
||||
[params, updatePackageInfo, agentPolicy, updateAgentPolicy, policyId]
|
||||
[params, updatePackageInfo, agentPolicy, updateAgentPolicy, queryParamsPolicyId]
|
||||
);
|
||||
|
||||
const ExtensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-create');
|
||||
|
@ -455,7 +507,8 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => {
|
|||
<PolicyBreadcrumb policyName={agentPolicy.name} policyId={agentPolicy.id} />
|
||||
)}
|
||||
<StepsWithLessPadding steps={steps} />
|
||||
<EuiSpacer size="l" />
|
||||
<EuiSpacer size="xl" />
|
||||
<EuiSpacer size="xl" />
|
||||
<CustomEuiBottomBar data-test-subj="integrationsBottomBar">
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
|
|
|
@ -9,10 +9,11 @@ import React, { memo } from 'react';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
|
||||
|
||||
import { useCapabilities, useLink } from '../../../../../hooks';
|
||||
import { useCapabilities, useStartServices } from '../../../../../hooks';
|
||||
import { pagePathGetters, INTEGRATIONS_PLUGIN_ID } from '../../../../../constants';
|
||||
|
||||
export const NoPackagePolicies = memo<{ policyId: string }>(({ policyId }) => {
|
||||
const { getHref } = useLink();
|
||||
const { application } = useStartServices();
|
||||
const hasWriteCapabilities = useCapabilities().write;
|
||||
|
||||
return (
|
||||
|
@ -36,7 +37,12 @@ export const NoPackagePolicies = memo<{ policyId: string }>(({ policyId }) => {
|
|||
<EuiButton
|
||||
isDisabled={!hasWriteCapabilities}
|
||||
fill
|
||||
href={getHref('add_integration_from_policy', { policyId })}
|
||||
onClick={() =>
|
||||
application.navigateToApp(INTEGRATIONS_PLUGIN_ID, {
|
||||
path: `#${pagePathGetters.integrations_all()[1]}`,
|
||||
state: { forAgentPolicyId: policyId },
|
||||
})
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.policyDetailsPackagePolicies.createFirstButtonText"
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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, { useEffect, useState } from 'react';
|
||||
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 type { PackageInfo } from '../../../../../types';
|
||||
import { InstallStatus } from '../../../../../types';
|
||||
|
||||
import { useGetPackageInstallStatus, useLink, useStartServices } from '../../../../../hooks';
|
||||
|
||||
import type { AssetSavedObject } from './types';
|
||||
import { allowedAssetTypes } from './constants';
|
||||
import { AssetsAccordion } from './assets_accordion';
|
||||
|
||||
interface AssetsPanelProps {
|
||||
packageInfo: PackageInfo;
|
||||
}
|
||||
|
||||
export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
|
||||
const { name, version } = packageInfo;
|
||||
const {
|
||||
savedObjects: { client: savedObjectsClient },
|
||||
} = useStartServices();
|
||||
|
||||
const { getPath } = useLink();
|
||||
const getPackageInstallStatus = useGetPackageInstallStatus();
|
||||
const packageInstallStatus = getPackageInstallStatus(packageInfo.name);
|
||||
|
||||
const [assetSavedObjects, setAssetsSavedObjects] = useState<undefined | AssetSavedObject[]>();
|
||||
const [fetchError, setFetchError] = useState<undefined | Error>();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAssetSavedObjects = async () => {
|
||||
if ('savedObject' in packageInfo) {
|
||||
const {
|
||||
savedObject: { attributes: packageAttributes },
|
||||
} = packageInfo;
|
||||
|
||||
if (
|
||||
!packageAttributes.installed_kibana ||
|
||||
packageAttributes.installed_kibana.length === 0
|
||||
) {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const objectsToGet = packageAttributes.installed_kibana.map(({ id, type }) => ({
|
||||
id,
|
||||
type,
|
||||
}));
|
||||
const { savedObjects } = await savedObjectsClient.bulkGet(objectsToGet);
|
||||
setAssetsSavedObjects(savedObjects as AssetSavedObject[]);
|
||||
} catch (e) {
|
||||
setFetchError(e);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
fetchAssetSavedObjects();
|
||||
}, [savedObjectsClient, packageInfo]);
|
||||
|
||||
// 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}`,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let content: JSX.Element | Array<JSX.Element | null>;
|
||||
|
||||
if (isLoading) {
|
||||
content = <Loading />;
|
||||
} else if (fetchError) {
|
||||
content = (
|
||||
<Error
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.assets.fetchAssetsErrorTitle"
|
||||
defaultMessage="Error loading assets"
|
||||
/>
|
||||
}
|
||||
error={fetchError}
|
||||
/>
|
||||
);
|
||||
} else if (assetSavedObjects === undefined) {
|
||||
content = (
|
||||
<EuiTitle>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetails.assets.noAssetsFoundLabel"
|
||||
defaultMessage="No assets found"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
);
|
||||
} else {
|
||||
content = allowedAssetTypes.map((assetType) => {
|
||||
const sectionAssetSavedObjects = assetSavedObjects.filter((so) => so.type === assetType);
|
||||
|
||||
if (!sectionAssetSavedObjects.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<AssetsAccordion savedObjects={sectionAssetSavedObjects} type={assetType} />
|
||||
<EuiSpacer size="l" />
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup alignItems="flexStart">
|
||||
<EuiFlexItem grow={1} />
|
||||
<EuiFlexItem grow={6}>{content}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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,
|
||||
EuiSplitPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiLink,
|
||||
EuiHorizontalRule,
|
||||
EuiNotificationBadge,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { AssetTitleMap } from '../../../../../constants';
|
||||
|
||||
import { getHrefToObjectInKibanaApp, useStartServices } from '../../../../../hooks';
|
||||
|
||||
import type { AllowedAssetType, AssetSavedObject } from './types';
|
||||
|
||||
interface Props {
|
||||
type: AllowedAssetType;
|
||||
savedObjects: AssetSavedObject[];
|
||||
}
|
||||
|
||||
export const AssetsAccordion: FunctionComponent<Props> = ({ savedObjects, type }) => {
|
||||
const { http } = useStartServices();
|
||||
return (
|
||||
<EuiAccordion
|
||||
buttonContent={
|
||||
<EuiFlexGroup justifyContent="center" alignItems="center" gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="m">
|
||||
<h3>{AssetTitleMap[type]}</h3>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiNotificationBadge color="subdued" size="m">
|
||||
<h3>{savedObjects.length}</h3>
|
||||
</EuiNotificationBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
id={type}
|
||||
>
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiSplitPanel.Outer hasBorder hasShadow={false}>
|
||||
{savedObjects.map(({ id, attributes: { title, description } }, idx) => {
|
||||
const pathToObjectInApp = getHrefToObjectInKibanaApp({
|
||||
http,
|
||||
id,
|
||||
type,
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<EuiSplitPanel.Inner grow={false} key={idx}>
|
||||
<EuiText size="m">
|
||||
<p>
|
||||
{pathToObjectInApp ? (
|
||||
<EuiLink href={pathToObjectInApp}>{title}</EuiLink>
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
{description && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="s" color="subdued">
|
||||
<p>{description}</p>
|
||||
</EuiText>
|
||||
</>
|
||||
)}
|
||||
</EuiSplitPanel.Inner>
|
||||
{idx + 1 < savedObjects.length && <EuiHorizontalRule margin="none" />}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</EuiSplitPanel.Outer>
|
||||
</>
|
||||
</EuiAccordion>
|
||||
);
|
||||
};
|
|
@ -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 { KibanaAssetType } from '../../../../../types';
|
||||
|
||||
import type { AllowedAssetTypes } from './types';
|
||||
|
||||
export const allowedAssetTypes: AllowedAssetTypes = [
|
||||
KibanaAssetType.dashboard,
|
||||
KibanaAssetType.search,
|
||||
KibanaAssetType.visualization,
|
||||
];
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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 { AssetsPage } from './assets';
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 type { SimpleSavedObject } from 'src/core/public';
|
||||
|
||||
import type { KibanaAssetType } from '../../../../../types';
|
||||
|
||||
export type AssetSavedObject = SimpleSavedObject<{ title: string; description?: string }>;
|
||||
|
||||
export type AllowedAssetTypes = [
|
||||
KibanaAssetType.dashboard,
|
||||
KibanaAssetType.search,
|
||||
KibanaAssetType.visualization
|
||||
];
|
||||
|
||||
export type AllowedAssetType = AllowedAssetTypes[number];
|
|
@ -56,6 +56,7 @@ import { WithHeaderLayout } from '../../../../layouts';
|
|||
import { RELEASE_BADGE_DESCRIPTION, RELEASE_BADGE_LABEL } from '../../components/release_badge';
|
||||
|
||||
import { IntegrationAgentPolicyCount, UpdateIcon, IconPanel, LoadingIconPanel } from './components';
|
||||
import { AssetsPage } from './assets';
|
||||
import { OverviewPage } from './overview';
|
||||
import { PackagePoliciesPage } from './policies';
|
||||
import { SettingsPage } from './settings';
|
||||
|
@ -408,6 +409,24 @@ export function Detail() {
|
|||
});
|
||||
}
|
||||
|
||||
if (packageInstallStatus === InstallStatus.installed && packageInfo.assets) {
|
||||
tabs.push({
|
||||
id: 'assets',
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.epm.packageDetailsNav.packageAssetsLinkText"
|
||||
defaultMessage="Assets"
|
||||
/>
|
||||
),
|
||||
isSelected: panel === 'assets',
|
||||
'data-test-subj': `tab-assets`,
|
||||
href: getHref('integration_details_assets', {
|
||||
pkgkey: packageInfoKey,
|
||||
...(integration ? { integration } : {}),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
tabs.push({
|
||||
id: 'settings',
|
||||
name: (
|
||||
|
@ -476,6 +495,9 @@ export function Detail() {
|
|||
<Route path={INTEGRATIONS_ROUTING_PATHS.integration_details_settings}>
|
||||
<SettingsPage packageInfo={packageInfo} />
|
||||
</Route>
|
||||
<Route path={INTEGRATIONS_ROUTING_PATHS.integration_details_assets}>
|
||||
<AssetsPage packageInfo={packageInfo} />
|
||||
</Route>
|
||||
<Route path={INTEGRATIONS_ROUTING_PATHS.integration_details_policies}>
|
||||
<PackagePoliciesPage name={packageInfo.name} version={packageInfo.version} />
|
||||
</Route>
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { stringify, parse } from 'query-string';
|
||||
import React, { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { Redirect, useLocation, useHistory } from 'react-router-dom';
|
||||
import type { CriteriaWithPagination, EuiTableFieldDataColumnType } from '@elastic/eui';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
|
@ -15,6 +15,9 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiToolTip,
|
||||
EuiText,
|
||||
EuiButton,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedRelative, FormattedMessage } from '@kbn/i18n/react';
|
||||
|
@ -66,8 +69,16 @@ interface PackagePoliciesPanelProps {
|
|||
version: string;
|
||||
}
|
||||
export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps) => {
|
||||
const [flyoutOpenForPolicyId, setFlyoutOpenForPolicyId] = useState<string | null>(null);
|
||||
const { getPath } = useLink();
|
||||
const { search } = useLocation();
|
||||
const history = useHistory();
|
||||
const queryParams = useMemo(() => new URLSearchParams(search), [search]);
|
||||
const agentPolicyIdFromParams = useMemo(() => queryParams.get('addAgentToPolicyId'), [
|
||||
queryParams,
|
||||
]);
|
||||
const [flyoutOpenForPolicyId, setFlyoutOpenForPolicyId] = useState<string | null>(
|
||||
agentPolicyIdFromParams
|
||||
);
|
||||
const { getPath, getHref } = useLink();
|
||||
const getPackageInstallStatus = useGetPackageInstallStatus();
|
||||
const packageInstallStatus = getPackageInstallStatus(name);
|
||||
const { pagination, pageSizeOptions, setPagination } = useUrlPagination();
|
||||
|
@ -87,6 +98,36 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
|||
[setPagination]
|
||||
);
|
||||
|
||||
const renderViewDataStepContent = useCallback(
|
||||
() => (
|
||||
<>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentEnrollment.viewDataDescription"
|
||||
defaultMessage="After your agent starts, you can view your data in Kibana by using the integration's installed assets. {pleaseNote}: it may take a few minutes for the initial data to arrive."
|
||||
values={{
|
||||
pleaseNote: (
|
||||
<strong>
|
||||
{i18n.translate(
|
||||
'xpack.fleet.epm.agentEnrollment.viewDataDescription.pleaseNoteLabel',
|
||||
{ defaultMessage: 'Please note' }
|
||||
)}
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiButton href={getHref('integration_details_assets', { pkgkey: `${name}-${version}` })}>
|
||||
{i18n.translate('xpack.fleet.epm.agentEnrollment.viewDataAssetsLabel', {
|
||||
defaultMessage: 'View assets',
|
||||
})}
|
||||
</EuiButton>
|
||||
</>
|
||||
),
|
||||
[name, version, getHref]
|
||||
);
|
||||
|
||||
const columns: Array<EuiTableFieldDataColumnType<PackagePolicyAndAgentPolicy>> = useMemo(
|
||||
() => [
|
||||
{
|
||||
|
@ -186,12 +227,16 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
|||
align: 'right',
|
||||
render({ agentPolicy, packagePolicy }) {
|
||||
return (
|
||||
<PackagePolicyActionsMenu agentPolicy={agentPolicy} packagePolicy={packagePolicy} />
|
||||
<PackagePolicyActionsMenu
|
||||
agentPolicy={agentPolicy}
|
||||
packagePolicy={packagePolicy}
|
||||
viewDataStepContent={renderViewDataStepContent()}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[]
|
||||
[renderViewDataStepContent]
|
||||
);
|
||||
|
||||
const noItemsMessage = useMemo(() => {
|
||||
|
@ -236,14 +281,18 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{flyoutOpenForPolicyId && (
|
||||
{flyoutOpenForPolicyId && !isLoading && (
|
||||
<AgentEnrollmentFlyout
|
||||
onClose={() => setFlyoutOpenForPolicyId(null)}
|
||||
agentPolicies={
|
||||
data?.items
|
||||
.filter(({ agentPolicy }) => agentPolicy.id === flyoutOpenForPolicyId)
|
||||
.map(({ agentPolicy }) => agentPolicy) ?? []
|
||||
onClose={() => {
|
||||
setFlyoutOpenForPolicyId(null);
|
||||
const { addAgentToPolicyId, ...rest } = parse(search);
|
||||
history.replace({ search: stringify(rest) });
|
||||
}}
|
||||
agentPolicy={
|
||||
data?.items.find(({ agentPolicy }) => agentPolicy.id === flyoutOpenForPolicyId)
|
||||
?.agentPolicy
|
||||
}
|
||||
viewDataStepContent={renderViewDataStepContent()}
|
||||
/>
|
||||
)}
|
||||
</AgentPolicyRefreshContext.Provider>
|
||||
|
|
|
@ -37,6 +37,7 @@ jest.mock('./steps', () => {
|
|||
...module,
|
||||
AgentPolicySelectionStep: jest.fn(),
|
||||
AgentEnrollmentKeySelectionStep: jest.fn(),
|
||||
ViewDataStep: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ import { FleetStatusProvider, ConfigContext } from '../../hooks';
|
|||
|
||||
import { useFleetServerInstructions } from '../../applications/fleet/sections/agents/agent_requirements_page';
|
||||
|
||||
import { AgentEnrollmentKeySelectionStep, AgentPolicySelectionStep } from './steps';
|
||||
import { AgentEnrollmentKeySelectionStep, AgentPolicySelectionStep, ViewDataStep } from './steps';
|
||||
|
||||
import type { Props } from '.';
|
||||
import { AgentEnrollmentFlyout } from '.';
|
||||
|
@ -128,6 +128,46 @@ describe('<AgentEnrollmentFlyout />', () => {
|
|||
expect(AgentEnrollmentKeySelectionStep).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('"View data" extension point', () => {
|
||||
it('calls the "View data" step when UI extension is provided', async () => {
|
||||
jest.clearAllMocks();
|
||||
await act(async () => {
|
||||
testBed = await setup({
|
||||
agentPolicies: [],
|
||||
onClose: jest.fn(),
|
||||
viewDataStepContent: <div />,
|
||||
});
|
||||
testBed.component.update();
|
||||
});
|
||||
const { exists, actions } = testBed;
|
||||
expect(exists('agentEnrollmentFlyout')).toBe(true);
|
||||
expect(ViewDataStep).toHaveBeenCalled();
|
||||
|
||||
jest.clearAllMocks();
|
||||
actions.goToStandaloneTab();
|
||||
expect(ViewDataStep).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not call the "View data" step when UI extension is not provided', async () => {
|
||||
jest.clearAllMocks();
|
||||
await act(async () => {
|
||||
testBed = await setup({
|
||||
agentPolicies: [],
|
||||
onClose: jest.fn(),
|
||||
viewDataStepContent: undefined,
|
||||
});
|
||||
testBed.component.update();
|
||||
});
|
||||
const { exists, actions } = testBed;
|
||||
expect(exists('agentEnrollmentFlyout')).toBe(true);
|
||||
expect(ViewDataStep).not.toHaveBeenCalled();
|
||||
|
||||
jest.clearAllMocks();
|
||||
actions.goToStandaloneTab();
|
||||
expect(ViewDataStep).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('standalone instructions', () => {
|
||||
|
|
|
@ -42,6 +42,7 @@ export const AgentEnrollmentFlyout: React.FunctionComponent<Props> = ({
|
|||
onClose,
|
||||
agentPolicy,
|
||||
agentPolicies,
|
||||
viewDataStepContent,
|
||||
}) => {
|
||||
const [mode, setMode] = useState<'managed' | 'standalone'>('managed');
|
||||
|
||||
|
@ -109,9 +110,17 @@ export const AgentEnrollmentFlyout: React.FunctionComponent<Props> = ({
|
|||
}
|
||||
>
|
||||
{fleetServerHosts.length === 0 && mode === 'managed' ? null : mode === 'managed' ? (
|
||||
<ManagedInstructions agentPolicy={agentPolicy} agentPolicies={agentPolicies} />
|
||||
<ManagedInstructions
|
||||
agentPolicy={agentPolicy}
|
||||
agentPolicies={agentPolicies}
|
||||
viewDataStepContent={viewDataStepContent}
|
||||
/>
|
||||
) : (
|
||||
<StandaloneInstructions agentPolicy={agentPolicy} agentPolicies={agentPolicies} />
|
||||
<StandaloneInstructions
|
||||
agentPolicy={agentPolicy}
|
||||
agentPolicies={agentPolicies}
|
||||
viewDataStepContent={viewDataStepContent}
|
||||
/>
|
||||
)}
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
|
|
|
@ -21,7 +21,12 @@ import {
|
|||
useFleetServerInstructions,
|
||||
} from '../../applications/fleet/sections/agents/agent_requirements_page';
|
||||
|
||||
import { DownloadStep, AgentPolicySelectionStep, AgentEnrollmentKeySelectionStep } from './steps';
|
||||
import {
|
||||
DownloadStep,
|
||||
AgentPolicySelectionStep,
|
||||
AgentEnrollmentKeySelectionStep,
|
||||
ViewDataStep,
|
||||
} from './steps';
|
||||
import type { BaseProps } from './types';
|
||||
|
||||
type Props = BaseProps;
|
||||
|
@ -53,83 +58,91 @@ const FleetServerMissingRequirements = () => {
|
|||
return <FleetServerRequirementPage />;
|
||||
};
|
||||
|
||||
export const ManagedInstructions = React.memo<Props>(({ agentPolicy, agentPolicies }) => {
|
||||
const fleetStatus = useFleetStatus();
|
||||
export const ManagedInstructions = React.memo<Props>(
|
||||
({ agentPolicy, agentPolicies, viewDataStepContent }) => {
|
||||
const fleetStatus = useFleetStatus();
|
||||
|
||||
const [selectedAPIKeyId, setSelectedAPIKeyId] = useState<string | undefined>();
|
||||
const [isFleetServerPolicySelected, setIsFleetServerPolicySelected] = useState<boolean>(false);
|
||||
const [selectedAPIKeyId, setSelectedAPIKeyId] = useState<string | undefined>();
|
||||
const [isFleetServerPolicySelected, setIsFleetServerPolicySelected] = useState<boolean>(false);
|
||||
|
||||
const apiKey = useGetOneEnrollmentAPIKey(selectedAPIKeyId);
|
||||
const settings = useGetSettings();
|
||||
const fleetServerInstructions = useFleetServerInstructions(apiKey?.data?.item?.policy_id);
|
||||
const apiKey = useGetOneEnrollmentAPIKey(selectedAPIKeyId);
|
||||
const settings = useGetSettings();
|
||||
const fleetServerInstructions = useFleetServerInstructions(apiKey?.data?.item?.policy_id);
|
||||
|
||||
const steps = useMemo(() => {
|
||||
const {
|
||||
serviceToken,
|
||||
getServiceToken,
|
||||
isLoadingServiceToken,
|
||||
installCommand,
|
||||
platform,
|
||||
setPlatform,
|
||||
} = fleetServerInstructions;
|
||||
const fleetServerHosts = settings.data?.item?.fleet_server_hosts || [];
|
||||
const baseSteps: EuiContainedStepProps[] = [
|
||||
DownloadStep(),
|
||||
!agentPolicy
|
||||
? AgentPolicySelectionStep({
|
||||
agentPolicies,
|
||||
setSelectedAPIKeyId,
|
||||
setIsFleetServerPolicySelected,
|
||||
})
|
||||
: AgentEnrollmentKeySelectionStep({ agentPolicy, setSelectedAPIKeyId }),
|
||||
];
|
||||
if (isFleetServerPolicySelected) {
|
||||
baseSteps.push(
|
||||
...[
|
||||
ServiceTokenStep({ serviceToken, getServiceToken, isLoadingServiceToken }),
|
||||
FleetServerCommandStep({ serviceToken, installCommand, platform, setPlatform }),
|
||||
]
|
||||
);
|
||||
} else {
|
||||
baseSteps.push({
|
||||
title: i18n.translate('xpack.fleet.agentEnrollment.stepEnrollAndRunAgentTitle', {
|
||||
defaultMessage: 'Enroll and start the Elastic Agent',
|
||||
}),
|
||||
children: selectedAPIKeyId && apiKey.data && (
|
||||
<ManualInstructions apiKey={apiKey.data.item} fleetServerHosts={fleetServerHosts} />
|
||||
),
|
||||
});
|
||||
}
|
||||
return baseSteps;
|
||||
}, [
|
||||
agentPolicy,
|
||||
agentPolicies,
|
||||
selectedAPIKeyId,
|
||||
apiKey.data,
|
||||
isFleetServerPolicySelected,
|
||||
settings.data?.item?.fleet_server_hosts,
|
||||
fleetServerInstructions,
|
||||
]);
|
||||
const steps = useMemo(() => {
|
||||
const {
|
||||
serviceToken,
|
||||
getServiceToken,
|
||||
isLoadingServiceToken,
|
||||
installCommand,
|
||||
platform,
|
||||
setPlatform,
|
||||
} = fleetServerInstructions;
|
||||
const fleetServerHosts = settings.data?.item?.fleet_server_hosts || [];
|
||||
const baseSteps: EuiContainedStepProps[] = [
|
||||
DownloadStep(),
|
||||
!agentPolicy
|
||||
? AgentPolicySelectionStep({
|
||||
agentPolicies,
|
||||
setSelectedAPIKeyId,
|
||||
setIsFleetServerPolicySelected,
|
||||
})
|
||||
: AgentEnrollmentKeySelectionStep({ agentPolicy, setSelectedAPIKeyId }),
|
||||
];
|
||||
if (isFleetServerPolicySelected) {
|
||||
baseSteps.push(
|
||||
...[
|
||||
ServiceTokenStep({ serviceToken, getServiceToken, isLoadingServiceToken }),
|
||||
FleetServerCommandStep({ serviceToken, installCommand, platform, setPlatform }),
|
||||
]
|
||||
);
|
||||
} else {
|
||||
baseSteps.push({
|
||||
title: i18n.translate('xpack.fleet.agentEnrollment.stepEnrollAndRunAgentTitle', {
|
||||
defaultMessage: 'Enroll and start the Elastic Agent',
|
||||
}),
|
||||
children: selectedAPIKeyId && apiKey.data && (
|
||||
<ManualInstructions apiKey={apiKey.data.item} fleetServerHosts={fleetServerHosts} />
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{fleetStatus.isReady ? (
|
||||
<>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentEnrollment.managedDescription"
|
||||
defaultMessage="Enroll an Elastic Agent in Fleet to automatically deploy updates and centrally manage the agent."
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiSteps steps={steps} />
|
||||
</>
|
||||
) : fleetStatus.missingRequirements?.length === 1 &&
|
||||
fleetStatus.missingRequirements[0] === 'fleet_server' ? (
|
||||
<FleetServerMissingRequirements />
|
||||
) : (
|
||||
<DefaultMissingRequirements />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
if (viewDataStepContent) {
|
||||
baseSteps.push(ViewDataStep(viewDataStepContent));
|
||||
}
|
||||
|
||||
return baseSteps;
|
||||
}, [
|
||||
agentPolicy,
|
||||
agentPolicies,
|
||||
selectedAPIKeyId,
|
||||
apiKey.data,
|
||||
isFleetServerPolicySelected,
|
||||
settings.data?.item?.fleet_server_hosts,
|
||||
fleetServerInstructions,
|
||||
viewDataStepContent,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{fleetStatus.isReady ? (
|
||||
<>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentEnrollment.managedDescription"
|
||||
defaultMessage="Enroll an Elastic Agent in Fleet to automatically deploy updates and centrally manage the agent."
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiSteps steps={steps} />
|
||||
</>
|
||||
) : fleetStatus.missingRequirements?.length === 1 &&
|
||||
fleetStatus.missingRequirements[0] === 'fleet_server' ? (
|
||||
<FleetServerMissingRequirements />
|
||||
) : (
|
||||
<DefaultMissingRequirements />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -138,3 +138,16 @@ 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,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -9,12 +9,20 @@ import type { AgentPolicy } from '../../types';
|
|||
|
||||
export interface BaseProps {
|
||||
/**
|
||||
* The user selected policy to be used
|
||||
* The user selected policy to be used. If this value is `undefined` a value must be provided for `agentPolicies`.
|
||||
*/
|
||||
agentPolicy?: AgentPolicy;
|
||||
|
||||
/**
|
||||
* A selection of policies for the user to choose from, will be ignored if `agentPolicy` has been provided
|
||||
* A selection of policies for the user to choose from, will be ignored if `agentPolicy` has been provided.
|
||||
*
|
||||
* If this value is `undefined` a value must be provided for `agentPolicy`.
|
||||
*/
|
||||
agentPolicies?: AgentPolicy[];
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,8 @@ import { PackagePolicyDeleteProvider } from './package_policy_delete_provider';
|
|||
export const PackagePolicyActionsMenu: React.FunctionComponent<{
|
||||
agentPolicy: AgentPolicy;
|
||||
packagePolicy: PackagePolicy;
|
||||
}> = ({ agentPolicy, packagePolicy }) => {
|
||||
viewDataStepContent?: JSX.Element;
|
||||
}> = ({ agentPolicy, packagePolicy, viewDataStepContent }) => {
|
||||
const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false);
|
||||
const { getHref } = useLink();
|
||||
const hasWriteCapabilities = useCapabilities().write;
|
||||
|
@ -103,7 +104,11 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{
|
|||
<>
|
||||
{isEnrollmentFlyoutOpen && (
|
||||
<EuiPortal>
|
||||
<AgentEnrollmentFlyout agentPolicies={[agentPolicy]} onClose={onEnrollmentFlyoutClose} />
|
||||
<AgentEnrollmentFlyout
|
||||
agentPolicy={agentPolicy}
|
||||
viewDataStepContent={viewDataStepContent}
|
||||
onClose={onEnrollmentFlyoutClose}
|
||||
/>
|
||||
</EuiPortal>
|
||||
)}
|
||||
<ContextMenuActions items={menuItems} />
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { stringify } from 'query-string';
|
||||
|
||||
export type StaticPage =
|
||||
| 'base'
|
||||
| 'overview'
|
||||
|
@ -19,6 +21,7 @@ export type StaticPage =
|
|||
export type DynamicPage =
|
||||
| 'integration_details_overview'
|
||||
| 'integration_details_policies'
|
||||
| 'integration_details_assets'
|
||||
| 'integration_details_settings'
|
||||
| 'integration_details_custom'
|
||||
| 'integration_policy_edit'
|
||||
|
@ -66,6 +69,7 @@ export const INTEGRATIONS_ROUTING_PATHS = {
|
|||
integration_details: '/detail/:pkgkey/:panel?',
|
||||
integration_details_overview: '/detail/:pkgkey/overview',
|
||||
integration_details_policies: '/detail/:pkgkey/policies',
|
||||
integration_details_assets: '/detail/:pkgkey/assets',
|
||||
integration_details_settings: '/detail/:pkgkey/settings',
|
||||
integration_details_custom: '/detail/:pkgkey/custom',
|
||||
integration_policy_edit: '/edit-integration/:packagePolicyId',
|
||||
|
@ -86,9 +90,13 @@ export const pagePathGetters: {
|
|||
INTEGRATIONS_BASE_PATH,
|
||||
`/detail/${pkgkey}/overview${integration ? `?integration=${integration}` : ''}`,
|
||||
],
|
||||
integration_details_policies: ({ pkgkey, integration }) => [
|
||||
integration_details_policies: ({ pkgkey, integration, addAgentToPolicyId }) => {
|
||||
const qs = stringify({ integration, addAgentToPolicyId });
|
||||
return [INTEGRATIONS_BASE_PATH, `/detail/${pkgkey}/policies${qs ? `?${qs}` : ''}`];
|
||||
},
|
||||
integration_details_assets: ({ pkgkey, integration }) => [
|
||||
INTEGRATIONS_BASE_PATH,
|
||||
`/detail/${pkgkey}/policies${integration ? `?integration=${integration}` : ''}`,
|
||||
`/detail/${pkgkey}/assets${integration ? `?integration=${integration}` : ''}`,
|
||||
],
|
||||
integration_details_settings: ({ pkgkey, integration }) => [
|
||||
INTEGRATIONS_BASE_PATH,
|
||||
|
@ -108,6 +116,7 @@ export const pagePathGetters: {
|
|||
FLEET_BASE_PATH,
|
||||
`/policies/${policyId}${tabId ? `/${tabId}` : ''}`,
|
||||
],
|
||||
// TODO: This might need to be removed because we do not have a way to pick an integration in line anymore
|
||||
add_integration_from_policy: ({ policyId }) => [
|
||||
FLEET_BASE_PATH,
|
||||
`/policies/${policyId}/add-integration`,
|
||||
|
|
|
@ -11,7 +11,7 @@ export { useConfig, ConfigContext } from './use_config';
|
|||
export { useKibanaVersion, KibanaVersionContext } from './use_kibana_version';
|
||||
export { licenseService, useLicense } from './use_license';
|
||||
export { useLink } from './use_link';
|
||||
export { useKibanaLink } from './use_kibana_link';
|
||||
export { useKibanaLink, getHrefToObjectInKibanaApp } from './use_kibana_link';
|
||||
export { usePackageIconType, UsePackageIconType } from './use_package_icon_type';
|
||||
export { usePagination, Pagination, PAGE_SIZE_OPTIONS } from './use_pagination';
|
||||
export { useUrlPagination } from './use_url_pagination';
|
||||
|
|
|
@ -4,12 +4,62 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { HttpStart } from 'src/core/public';
|
||||
|
||||
import { KibanaAssetType } from '../types';
|
||||
|
||||
import { useStartServices } from './';
|
||||
|
||||
const KIBANA_BASE_PATH = '/app/kibana';
|
||||
|
||||
const getKibanaLink = (http: HttpStart, path: string) => {
|
||||
return http.basePath.prepend(`${KIBANA_BASE_PATH}#${path}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: This is a temporary solution for getting links to various assets. It is very risky because:
|
||||
*
|
||||
* 1. The plugin might not exist/be enabled
|
||||
* 2. URLs and paths might not always be supported
|
||||
*
|
||||
* We should migrate to using the new URL service locators.
|
||||
*
|
||||
* @deprecated {@link Locators} from the new URL service need to be used instead.
|
||||
|
||||
*/
|
||||
export const getHrefToObjectInKibanaApp = ({
|
||||
type,
|
||||
id,
|
||||
http,
|
||||
}: {
|
||||
type: KibanaAssetType;
|
||||
id: string;
|
||||
http: HttpStart;
|
||||
}): undefined | string => {
|
||||
let kibanaAppPath: undefined | string;
|
||||
switch (type) {
|
||||
case KibanaAssetType.dashboard:
|
||||
kibanaAppPath = `/dashboard/${id}`;
|
||||
break;
|
||||
case KibanaAssetType.search:
|
||||
kibanaAppPath = `/discover/${id}`;
|
||||
break;
|
||||
case KibanaAssetType.visualization:
|
||||
kibanaAppPath = `/visualize/edit/${id}`;
|
||||
break;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getKibanaLink(http, kibanaAppPath);
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: This functionality needs to be replaced with use of the new URL service locators
|
||||
*
|
||||
* @deprecated {@link Locators} from the new URL service need to be used instead.
|
||||
*/
|
||||
export function useKibanaLink(path: string = '/') {
|
||||
const core = useStartServices();
|
||||
return core.http.basePath.prepend(`${KIBANA_BASE_PATH}#${path}`);
|
||||
const { http } = useStartServices();
|
||||
return getKibanaLink(http, path);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue