Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] Allow to install Kibana assets in multiple spaces #186620

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@
],
"enterprise_search_telemetry": [],
"epm-packages": [
"additionnal_spaces_installed_kibana",
Copy link
Member

Choose a reason for hiding this comment

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

nit: additional has one n 😇

"es_index_patterns",
"experimental_data_stream_features",
"experimental_data_stream_features.data_stream",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,10 @@
},
"epm-packages": {
"properties": {
"additionnal_spaces_installed_kibana": {
"index": false,
"type": "flattened"
},
"es_index_patterns": {
"dynamic": false,
"properties": {}
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/fleet/common/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export const EPM_API_ROUTES = {
INSTALL_BY_UPLOAD_PATTERN: EPM_PACKAGES_MANY,
CUSTOM_INTEGRATIONS_PATTERN: `${EPM_API_ROOT}/custom_integrations`,
DELETE_PATTERN: EPM_PACKAGES_ONE,
INSTALL_KIBANA_ASSETS_PATTERN: `${EPM_PACKAGES_ONE}/kibana_assets`,
DELETE_KIBANA_ASSETS_PATTERN: `${EPM_PACKAGES_ONE}/kibana_assets`,
FILEPATH_PATTERN: `${EPM_PACKAGES_ONE}/{filePath*}`,
CATEGORIES_PATTERN: `${EPM_API_ROOT}/categories`,
VERIFICATION_KEY_ID: `${EPM_API_ROOT}/verification_key_id`,
Expand Down
25 changes: 15 additions & 10 deletions x-pack/plugins/fleet/common/experimental_features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@

export type ExperimentalFeatures = typeof allowedExperimentalValues;

/**
* A list of allowed values that can be used in `xpack.fleet.enableExperimental`.
* This object is then used to validate and parse the value entered.
*/
export const allowedExperimentalValues = Object.freeze<Record<string, boolean>>({
const _allowedExperimentalValues = {
createPackagePolicyMultiPageLayout: true,
packageVerification: true,
showDevtoolsRequest: true,
Expand All @@ -32,9 +28,18 @@ export const allowedExperimentalValues = Object.freeze<Record<string, boolean>>(
advancedPolicySettings: true,
useSpaceAwareness: false,
enableReusableIntegrationPolicies: false,
});
};

/**
* A list of allowed values that can be used in `xpack.fleet.enableExperimental`.
* This object is then used to validate and parse the value entered.
*/
export const allowedExperimentalValues = Object.freeze<
Copy link
Member Author

@nchaulet nchaulet Jun 21, 2024

Choose a reason for hiding this comment

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

Not really related to that feature, but it allow to add some type safety to allowedExperimentalValues , and fix the autocompletion that was bothering me

Record<keyof typeof _allowedExperimentalValues, boolean>
>({ ..._allowedExperimentalValues });

type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;
type ExperimentalConfigKey = keyof ExperimentalFeatures;
type ExperimentalConfigKeys = ExperimentalConfigKey[];
type Mutable<T> = { -readonly [P in keyof T]: T[P] };

const allowedKeys = Object.keys(allowedExperimentalValues) as Readonly<ExperimentalConfigKeys>;
Expand All @@ -46,7 +51,7 @@ const allowedKeys = Object.keys(allowedExperimentalValues) as Readonly<Experimen
* @param configValue
*/
export const parseExperimentalConfigValue = (configValue: string[]): ExperimentalFeatures => {
const enabledFeatures: Mutable<ExperimentalFeatures> = {};
const enabledFeatures: Mutable<ExperimentalFeatures> = { ...allowedExperimentalValues };

for (const value of configValue) {
if (isValidExperimentalValue(value)) {
Expand All @@ -60,8 +65,8 @@ export const parseExperimentalConfigValue = (configValue: string[]): Experimenta
};
};

export const isValidExperimentalValue = (value: string) => {
return allowedKeys.includes(value);
export const isValidExperimentalValue = (value: string): value is ExperimentalConfigKey => {
return (allowedKeys as string[]).includes(value);
};

export const getExperimentalAllowedValues = (): string[] => [...allowedKeys];
6 changes: 6 additions & 0 deletions x-pack/plugins/fleet/common/services/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ export const epmRouteService = {
.replace(/\/$/, ''); // trim trailing slash
},

getInstallKibanaAssetsPath: (pkgName: string, pkgVersion: string) => {
return EPM_API_ROUTES.INSTALL_KIBANA_ASSETS_PATTERN.replace('{pkgName}', pkgName)
.replace('{pkgVersion}', pkgVersion)
.replace(/\/$/, ''); // trim trailing slash
},

getUpdatePath: (pkgName: string, pkgVersion: string) => {
return EPM_API_ROUTES.INFO_PATTERN.replace('{pkgName}', pkgName).replace(
'{pkgVersion}',
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/fleet/common/types/models/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,7 @@ export interface StateContext<T> {

export interface Installation {
installed_kibana: KibanaAssetReference[];
additionnal_spaces_installed_kibana?: Record<string, KibanaAssetReference[]>;
installed_es: EsAssetReference[];
package_assets?: PackageAssetReference[];
es_index_patterns: Record<string, string>;
Expand Down Expand Up @@ -649,6 +650,7 @@ export type AssetReference = KibanaAssetReference | EsAssetReference;

export interface KibanaAssetReference {
id: string;
originId?: string;
type: KibanaSavedObjectType;
}
export interface EsAssetReference {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,23 +62,30 @@ export const AssetsPage = ({ packageInfo, refetchPackageInfo }: AssetsPanelProps
'installationInfo' in packageInfo ? packageInfo.installationInfo : undefined;

const installedSpaceId = pkgInstallationInfo?.installed_kibana_space_id;
const assetsInstalledInCurrentSpace = !installedSpaceId || installedSpaceId === spaceId;

const assetsInstalledInCurrentSpace =
!installedSpaceId ||
installedSpaceId === spaceId ||
pkgInstallationInfo?.additionnal_spaces_installed_kibana?.[spaceId || 'default'];
const [assetSavedObjectsByType, setAssetsSavedObjectsByType] = useState<
Record<string, Record<string, SimpleSOAssetType & { appLink?: string }>>
>({});
const [deferredInstallations, setDeferredInstallations] = useState<EsAssetReference[]>();

const kibanaAssets = useMemo(() => {
return !installedSpaceId || installedSpaceId === spaceId
? pkgInstallationInfo?.installed_kibana || []
: pkgInstallationInfo?.additionnal_spaces_installed_kibana?.[spaceId || 'default'] || [];
}, [
installedSpaceId,
spaceId,
pkgInstallationInfo?.installed_kibana,
pkgInstallationInfo?.additionnal_spaces_installed_kibana,
]);
const pkgAssets = useMemo(
() => [
...(assetsInstalledInCurrentSpace ? pkgInstallationInfo?.installed_kibana || [] : []),
...(pkgInstallationInfo?.installed_es || []),
],
[
assetsInstalledInCurrentSpace,
pkgInstallationInfo?.installed_es,
pkgInstallationInfo?.installed_kibana,
]
() => [...kibanaAssets, ...(pkgInstallationInfo?.installed_es || [])],
[kibanaAssets, pkgInstallationInfo?.installed_es]
);

const pkgAssetsByType = useMemo(
() =>
pkgAssets.reduce((acc, asset) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* 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 { EuiButton, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
import React, { useCallback } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';

import type { InstallationInfo } from '../../../../../../../../common/types';
import {
useAuthz,
useFleetStatus,
useInstallKibanaAssetsMutation,
useStartServices,
} from '../../../../../hooks';

interface InstallKibanaAssetsPanelProps {
title: string;
installInfo: InstallationInfo;
}

export function InstallKibanaAssetsPanel({ installInfo, title }: InstallKibanaAssetsPanelProps) {
const { spaceId } = useFleetStatus();
const { notifications } = useStartServices();
const { name, version } = installInfo;
const canInstallPackages = useAuthz().integrations.installPackages;
const { mutateAsync, isLoading } = useInstallKibanaAssetsMutation();

const assetsInstalledInCurrentSpace =
(!installInfo.installed_kibana_space_id && spaceId === 'default') ||
installInfo.installed_kibana_space_id === spaceId ||
installInfo.additionnal_spaces_installed_kibana?.[spaceId || 'default'];

const handleClickInstall = useCallback(async () => {
try {
await mutateAsync({
pkgName: name,
pkgVersion: version,
});
} catch (err) {
notifications.toasts.addError(err, {
title: i18n.translate('xpack.fleet.fleetServerSetup.kibanaInstallAssetsErrorTitle', {
defaultMessage: 'Error installing kibana assets',
}),
});
}
}, [mutateAsync, name, version, notifications.toasts]);

if (!canInstallPackages) {
return null;
}

return (
<EuiFlexGroup direction="column" gutterSize="m">
<EuiFlexItem>
<EuiTitle>
<h4>
<FormattedMessage
id="xpack.fleet.integrations.settings.kibanaInstallAssetsTitle"
defaultMessage="Kibana assets"
/>
</h4>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem>
<FormattedMessage
id="xpack.fleet.integrations.settings.kibanaInstallAssetsDescription"
defaultMessage="Install or reinstall Kibana assets in that space."
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<div>
<EuiButton iconType="refresh" isLoading={isLoading} onClick={handleClickInstall}>
{isLoading ? (
assetsInstalledInCurrentSpace ? (
<FormattedMessage
id="xpack.fleet.integrations.installPackage.kibanaAssetsReinstallingButtonLabel"
defaultMessage="Reinstalling {title} kibana assets"
values={{
title,
}}
/>
) : (
<FormattedMessage
id="xpack.fleet.integrations.installPackage.kibanaAssetsInstallingButtonLabel"
defaultMessage="Installing {title} kibana assets"
values={{
title,
}}
/>
)
) : assetsInstalledInCurrentSpace ? (
<FormattedMessage
id="xpack.fleet.integrations.installPackage.kibanaAssetsReinstallButtonLabel"
defaultMessage="Reinstall {title} kibana assets"
values={{
title,
}}
/>
) : (
<FormattedMessage
id="xpack.fleet.integrations.installPackage.kibanaAssetsInstallButtonLabel"
defaultMessage="Install {title} kibana assets"
values={{
title,
}}
/>
)}
</EuiButton>
</div>
</EuiFlexItem>
</EuiFlexGroup>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { ReinstallButton } from './reinstall_button';
import { UpdateButton } from './update_button';
import { UninstallButton } from './uninstall_button';
import { ChangelogModal } from './changelog_modal';
import { InstallKibanaAssetsPanel } from './install_kibana_assets_panel';

const SettingsTitleCell = styled.td`
padding-right: ${(props) => props.theme.eui.euiSizeXL};
Expand Down Expand Up @@ -469,6 +470,17 @@ export const SettingsPage: React.FC<Props> = memo(({ packageInfo, startServices
</div>
</EuiFlexItem>
</EuiFlexGroup>
{!hideInstallOptions &&
'installationInfo' in packageInfo &&
packageInfo.installationInfo ? (
<>
<EuiSpacer size="l" />
<InstallKibanaAssetsPanel
title={packageInfo.title}
installInfo={packageInfo.installationInfo}
/>
</>
) : null}
</>
)}
</div>
Expand Down
23 changes: 22 additions & 1 deletion x-pack/plugins/fleet/public/hooks/use_request/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { useMutation, useQuery } from '@tanstack/react-query';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';

import { useState } from 'react';

Expand Down Expand Up @@ -289,6 +289,11 @@ interface UpdatePackageArgs {
body: UpdatePackageRequest['body'];
}

interface InstallKibanaAssetsArgs {
pkgName: string;
pkgVersion: string;
}

export const useUpdatePackageMutation = () => {
return useMutation<UpdatePackageResponse, RequestError, UpdatePackageArgs>(
({ pkgName, pkgVersion, body }: UpdatePackageArgs) =>
Expand All @@ -301,6 +306,22 @@ export const useUpdatePackageMutation = () => {
);
};

export const useInstallKibanaAssetsMutation = () => {
const queryClient = useQueryClient();

return useMutation<any, RequestError, InstallKibanaAssetsArgs>({
mutationFn: ({ pkgName, pkgVersion }: InstallKibanaAssetsArgs) =>
sendRequestForRq({
path: epmRouteService.getInstallKibanaAssetsPath(pkgName, pkgVersion),
method: 'post',
version: API_VERSIONS.public.v1,
}),
onSuccess: (data, { pkgName, pkgVersion }) => {
return queryClient.invalidateQueries([pkgName, pkgVersion]);
},
});
};

export const sendUpdatePackage = (
pkgName: string,
pkgVersion: string,
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/fleet/server/routes/epm/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ export const installPackageFromRegistryHandler: FleetRequestHandler<
return await defaultFleetErrorHandler({ error: res.error, response });
}
};

export const createCustomIntegrationHandler: FleetRequestHandler<
undefined,
undefined,
Expand Down Expand Up @@ -640,6 +641,7 @@ const soToInstallationInfo = (pkg: PackageListItem | PackageInfo) => {
...pick(pkg.savedObject, ['created_at', 'updated_at', 'namespaces', 'type']),
installed_kibana: attributes.installed_kibana,
installed_kibana_space_id: attributes.installed_kibana_space_id,
additionnal_spaces_installed_kibana: attributes.additionnal_spaces_installed_kibana,
installed_es: attributes.installed_es,
install_status: attributes.install_status,
install_source: attributes.install_source,
Expand Down
Loading