mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[8.12] [Fleet] Fix displaying prerelease labels correctly in the installed integration tab (#173319) (#174760)
# Backport This will backport the following commits from `main` to `8.12`: - [[Fleet] Fix displaying prerelease labels correctly in the installed integration tab (#173319)](https://github.com/elastic/kibana/pull/173319) <!--- Backport version: 8.9.8 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Nicolas Chaulet","email":"nicolas.chaulet@elastic.co"},"sourceCommit":{"committedDate":"2023-12-14T15:44:20Z","message":"[Fleet] Fix displaying prerelease labels correctly in the installed integration tab (#173319)","sha":"a0e0c4a860bcc875192aa9c09cc732268d2b8c36","branchLabelMapping":{"^v8.13.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:skip","Team:Fleet","v8.13.0"],"number":173319,"url":"https://github.com/elastic/kibana/pull/173319","mergeCommit":{"message":"[Fleet] Fix displaying prerelease labels correctly in the installed integration tab (#173319)","sha":"a0e0c4a860bcc875192aa9c09cc732268d2b8c36"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.13.0","labelRegex":"^v8.13.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/173319","number":173319,"mergeCommit":{"message":"[Fleet] Fix displaying prerelease labels correctly in the installed integration tab (#173319)","sha":"a0e0c4a860bcc875192aa9c09cc732268d2b8c36"}}]}] BACKPORT-->
This commit is contained in:
parent
68739f97f7
commit
d1d01cf2d7
9 changed files with 181 additions and 87 deletions
|
@ -36,7 +36,7 @@ export const IntegrationPreference = () => {
|
|||
<Component
|
||||
initialType="recommended"
|
||||
onChange={action('onChange')}
|
||||
onPrereleaseEnabledChange={() => {}}
|
||||
prereleaseIntegrationsEnabled={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -23,7 +23,7 @@ import {
|
|||
EuiSwitch,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { sendPutSettings, useGetSettings, useStartServices } from '../../../hooks';
|
||||
import { usePutSettingsMutation, useStartServices } from '../../../hooks';
|
||||
|
||||
export type IntegrationPreferenceType = 'recommended' | 'beats' | 'agent';
|
||||
|
||||
|
@ -35,7 +35,7 @@ interface Option {
|
|||
export interface Props {
|
||||
initialType: IntegrationPreferenceType;
|
||||
onChange: (type: IntegrationPreferenceType) => void;
|
||||
onPrereleaseEnabledChange: (prerelease: boolean) => void;
|
||||
prereleaseIntegrationsEnabled: boolean;
|
||||
}
|
||||
|
||||
const recommendedTooltip = (
|
||||
|
@ -86,42 +86,39 @@ const options: Option[] = [
|
|||
export const IntegrationPreference = ({
|
||||
initialType,
|
||||
onChange,
|
||||
onPrereleaseEnabledChange,
|
||||
prereleaseIntegrationsEnabled,
|
||||
}: Props) => {
|
||||
const [idSelected, setIdSelected] = React.useState<IntegrationPreferenceType>(initialType);
|
||||
|
||||
const { docLinks } = useStartServices();
|
||||
|
||||
const [prereleaseIntegrationsEnabled, setPrereleaseIntegrationsEnabled] = React.useState<
|
||||
const [prereleaseIntegrationsChecked, setPrereleaseIntegrationsChecked] = React.useState<
|
||||
boolean | undefined
|
||||
>(undefined);
|
||||
|
||||
const { data: settings, error: settingsError } = useGetSettings();
|
||||
const { docLinks, notifications } = useStartServices();
|
||||
|
||||
useEffect(() => {
|
||||
const isEnabled = Boolean(settings?.item.prerelease_integrations_enabled);
|
||||
if (settings?.item) {
|
||||
setPrereleaseIntegrationsEnabled(isEnabled);
|
||||
} else if (settingsError) {
|
||||
setPrereleaseIntegrationsEnabled(false);
|
||||
}
|
||||
}, [settings?.item, settingsError]);
|
||||
const { mutateAsync: mutateSettingsAsync } = usePutSettingsMutation();
|
||||
|
||||
useEffect(() => {
|
||||
if (prereleaseIntegrationsEnabled !== undefined) {
|
||||
onPrereleaseEnabledChange(prereleaseIntegrationsEnabled);
|
||||
}
|
||||
}, [onPrereleaseEnabledChange, prereleaseIntegrationsEnabled]);
|
||||
const updateSettings = useCallback(
|
||||
async (prerelease: boolean) => {
|
||||
try {
|
||||
setPrereleaseIntegrationsChecked(prerelease);
|
||||
const res = await mutateSettingsAsync({
|
||||
prerelease_integrations_enabled: prerelease,
|
||||
});
|
||||
|
||||
const updateSettings = useCallback(async (prerelease: boolean) => {
|
||||
const res = await sendPutSettings({
|
||||
prerelease_integrations_enabled: prerelease,
|
||||
});
|
||||
|
||||
if (res.error) {
|
||||
throw res.error;
|
||||
}
|
||||
}, []);
|
||||
if (res.error) {
|
||||
throw res.error;
|
||||
}
|
||||
} catch (error) {
|
||||
setPrereleaseIntegrationsChecked(!prerelease);
|
||||
notifications.toasts.addError(error, {
|
||||
title: i18n.translate('xpack.fleet.errorUpdatingSettings', {
|
||||
defaultMessage: 'Error updating settings',
|
||||
}),
|
||||
});
|
||||
}
|
||||
},
|
||||
[mutateSettingsAsync, notifications.toasts]
|
||||
);
|
||||
|
||||
const link = (
|
||||
<EuiLink href={docLinks.links.fleet.beatsAgentComparison}>
|
||||
|
@ -153,16 +150,18 @@ export const IntegrationPreference = ({
|
|||
EventTarget & { checked: boolean }
|
||||
>
|
||||
) => {
|
||||
const isChecked = event.target.checked;
|
||||
setPrereleaseIntegrationsEnabled(isChecked);
|
||||
updateSettings(isChecked);
|
||||
updateSettings(event.target.checked);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiPanel hasShadow={false} paddingSize="none">
|
||||
<EuiSwitchNoWrap
|
||||
label="Display beta integrations"
|
||||
checked={!!prereleaseIntegrationsEnabled}
|
||||
checked={
|
||||
typeof prereleaseIntegrationsChecked !== 'undefined'
|
||||
? prereleaseIntegrationsChecked
|
||||
: prereleaseIntegrationsEnabled
|
||||
}
|
||||
onChange={onPrereleaseSwitchChange}
|
||||
/>
|
||||
<EuiSpacer size="l" />
|
||||
|
|
|
@ -97,9 +97,9 @@ function OnPremLink() {
|
|||
);
|
||||
}
|
||||
|
||||
export const AvailablePackages: React.FC<{
|
||||
setPrereleaseEnabled: (isEnabled: boolean) => void;
|
||||
}> = ({ setPrereleaseEnabled }) => {
|
||||
export const AvailablePackages: React.FC<{ prereleaseIntegrationsEnabled: boolean }> = ({
|
||||
prereleaseIntegrationsEnabled,
|
||||
}) => {
|
||||
useBreadcrumbs('integrations_all');
|
||||
|
||||
const {
|
||||
|
@ -121,11 +121,10 @@ export const AvailablePackages: React.FC<{
|
|||
setUrlandPushHistory,
|
||||
setUrlandReplaceHistory,
|
||||
filteredCards,
|
||||
setPrereleaseIntegrationsEnabled,
|
||||
availableSubCategories,
|
||||
selectedSubCategory,
|
||||
setSelectedSubCategory,
|
||||
} = useAvailablePackages();
|
||||
} = useAvailablePackages({ prereleaseIntegrationsEnabled });
|
||||
|
||||
const onCategoryChange = useCallback(
|
||||
({ id }: { id: string }) => {
|
||||
|
@ -137,14 +136,6 @@ export const AvailablePackages: React.FC<{
|
|||
[setCategory, setSearchTerm, setSelectedSubCategory, setUrlandPushHistory]
|
||||
);
|
||||
|
||||
const onPrereleaseEnabledChange = useCallback(
|
||||
(isEnabled: boolean) => {
|
||||
setPrereleaseIntegrationsEnabled(isEnabled);
|
||||
setPrereleaseEnabled(isEnabled);
|
||||
},
|
||||
[setPrereleaseIntegrationsEnabled, setPrereleaseEnabled]
|
||||
);
|
||||
|
||||
if (!isLoading && !categoryExists(initialSelectedCategory, allCategories)) {
|
||||
setUrlandReplaceHistory({ searchString: searchTerm, categoryId: '', subCategoryId: '' });
|
||||
return null;
|
||||
|
@ -155,8 +146,8 @@ export const AvailablePackages: React.FC<{
|
|||
<EuiHorizontalRule margin="m" />
|
||||
<IntegrationPreference
|
||||
initialType={preference}
|
||||
prereleaseIntegrationsEnabled={prereleaseIntegrationsEnabled}
|
||||
onChange={setPreference}
|
||||
onPrereleaseEnabledChange={onPrereleaseEnabledChange}
|
||||
/>
|
||||
</EuiFlexItem>,
|
||||
];
|
||||
|
|
|
@ -9,8 +9,9 @@ import React from 'react';
|
|||
|
||||
import { createIntegrationsTestRendererMock } from '../../../../../../mock';
|
||||
import type { PackageListItem } from '../../../../types';
|
||||
import { ExperimentalFeaturesService } from '../../../../services';
|
||||
|
||||
import { getIntegrationLabels } from './card_utils';
|
||||
import { getIntegrationLabels, mapToCard } from './card_utils';
|
||||
|
||||
function renderIntegrationLabels(item: Partial<PackageListItem>) {
|
||||
const renderer = createIntegrationsTestRendererMock();
|
||||
|
@ -18,7 +19,73 @@ function renderIntegrationLabels(item: Partial<PackageListItem>) {
|
|||
return renderer.render(<>{getIntegrationLabels(item as any)}</>);
|
||||
}
|
||||
|
||||
const addBasePath = (s: string) => s;
|
||||
const getHref = (k: string) => k;
|
||||
|
||||
describe('Card utils', () => {
|
||||
describe('mapToCard', () => {
|
||||
beforeEach(() => {
|
||||
ExperimentalFeaturesService.init({});
|
||||
});
|
||||
|
||||
it('should use the installed version if available, without prelease', () => {
|
||||
const cardItem = mapToCard({
|
||||
item: {
|
||||
id: 'test',
|
||||
version: '2.0.0-preview-1',
|
||||
installationInfo: {
|
||||
version: '1.0.0',
|
||||
},
|
||||
},
|
||||
addBasePath,
|
||||
getHref,
|
||||
} as any);
|
||||
|
||||
expect(cardItem).toMatchObject({
|
||||
release: 'ga',
|
||||
version: '1.0.0',
|
||||
isUpdateAvailable: true,
|
||||
extraLabelsBadges: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the installed version if available, with prelease ', () => {
|
||||
const cardItem = mapToCard({
|
||||
item: {
|
||||
id: 'test',
|
||||
version: '2.0.0',
|
||||
installationInfo: {
|
||||
version: '1.0.0-preview-1',
|
||||
},
|
||||
},
|
||||
addBasePath,
|
||||
getHref,
|
||||
} as any);
|
||||
|
||||
expect(cardItem).toMatchObject({
|
||||
release: 'preview',
|
||||
version: '1.0.0-preview-1',
|
||||
isUpdateAvailable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the registry version if no installation is available ', () => {
|
||||
const cardItem = mapToCard({
|
||||
item: {
|
||||
id: 'test',
|
||||
version: '2.0.0-preview-1',
|
||||
},
|
||||
addBasePath,
|
||||
getHref,
|
||||
} as any);
|
||||
|
||||
expect(cardItem).toMatchObject({
|
||||
release: 'preview',
|
||||
version: '2.0.0-preview-1',
|
||||
isUpdateAvailable: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('getIntegrationLabels', () => {
|
||||
it('should return an empty list for an integration without errors', () => {
|
||||
const res = renderIntegrationLabels({
|
||||
|
|
|
@ -75,7 +75,7 @@ export const mapToCard = ({
|
|||
|
||||
let isUnverified = false;
|
||||
|
||||
const version = 'version' in item ? item.version || '' : '';
|
||||
let version = 'version' in item ? item.version || '' : '';
|
||||
|
||||
let isUpdateAvailable = false;
|
||||
let isReauthorizationRequired = false;
|
||||
|
@ -84,9 +84,8 @@ export const mapToCard = ({
|
|||
? addBasePath(item.uiInternalPath)
|
||||
: item.uiExternalLink || getAbsolutePath(item.uiInternalPath);
|
||||
} else {
|
||||
let urlVersion = item.version;
|
||||
if (item?.installationInfo?.version) {
|
||||
urlVersion = item.installationInfo.version || item.version;
|
||||
version = item.installationInfo.version || item.version;
|
||||
isUnverified = isPackageUnverified(item, packageVerificationKeyId);
|
||||
isUpdateAvailable = isPackageUpdatable(item);
|
||||
|
||||
|
@ -94,7 +93,7 @@ export const mapToCard = ({
|
|||
}
|
||||
|
||||
const url = getHref('integration_details_overview', {
|
||||
pkgkey: `${item.name}-${urlVersion}`,
|
||||
pkgkey: `${item.name}-${version}`,
|
||||
...(item.integration ? { integration: item.integration } : {}),
|
||||
});
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { useState, useMemo } from 'react';
|
||||
|
||||
import { uniq } from 'lodash';
|
||||
|
||||
|
@ -103,11 +103,13 @@ const packageListToIntegrationsList = (packages: PackageList): PackageList => {
|
|||
}, []);
|
||||
};
|
||||
|
||||
export const useAvailablePackages = () => {
|
||||
export const useAvailablePackages = ({
|
||||
prereleaseIntegrationsEnabled,
|
||||
}: {
|
||||
prereleaseIntegrationsEnabled: boolean;
|
||||
}) => {
|
||||
const [preference, setPreference] = useState<IntegrationPreferenceType>('recommended');
|
||||
const [prereleaseIntegrationsEnabled, setPrereleaseIntegrationsEnabled] = React.useState<
|
||||
boolean | undefined
|
||||
>(undefined);
|
||||
|
||||
const { showIntegrationsSubcategories } = ExperimentalFeaturesService.get();
|
||||
|
||||
const {
|
||||
|
@ -245,6 +247,5 @@ export const useAvailablePackages = () => {
|
|||
eprPackageLoadingError,
|
||||
eprCategoryLoadingError,
|
||||
filteredCards,
|
||||
setPrereleaseIntegrationsEnabled,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Routes, Route } from '@kbn/shared-ux-router';
|
||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
|
||||
import { installationStatuses } from '../../../../../../../common/constants';
|
||||
|
||||
|
@ -14,7 +15,7 @@ import { INTEGRATIONS_ROUTING_PATHS, INTEGRATIONS_SEARCH_QUERYPARAM } from '../.
|
|||
import { DefaultLayout } from '../../../../layouts';
|
||||
import { isPackageUpdatable } from '../../../../services';
|
||||
|
||||
import { useGetPackagesQuery } from '../../../../hooks';
|
||||
import { useAuthz, useGetPackagesQuery, useGetSettingsQuery } from '../../../../hooks';
|
||||
|
||||
import type { CategoryFacet, ExtendedIntegrationCategory } from './category_facets';
|
||||
|
||||
|
@ -41,13 +42,24 @@ export const categoryExists = (category: string, categories: CategoryFacet[]) =>
|
|||
};
|
||||
|
||||
export const EPMHomePage: React.FC = () => {
|
||||
const [prereleaseEnabled, setPrereleaseEnabled] = useState<boolean>(false);
|
||||
|
||||
// loading packages to find installed ones
|
||||
const { data: allPackages, isLoading } = useGetPackagesQuery({
|
||||
prerelease: prereleaseEnabled,
|
||||
const authz = useAuthz();
|
||||
const isAuthorizedToFetchSettings = authz.fleet.all;
|
||||
const { data: settings, isFetchedAfterMount: isSettingsFetched } = useGetSettingsQuery({
|
||||
enabled: isAuthorizedToFetchSettings,
|
||||
});
|
||||
|
||||
const prereleaseIntegrationsEnabled = settings?.item.prerelease_integrations_enabled ?? false;
|
||||
const shouldFetchPackages = !isAuthorizedToFetchSettings || isSettingsFetched;
|
||||
// loading packages to find installed ones
|
||||
const { data: allPackages, isLoading } = useGetPackagesQuery(
|
||||
{
|
||||
prerelease: prereleaseIntegrationsEnabled,
|
||||
},
|
||||
{
|
||||
enabled: shouldFetchPackages,
|
||||
}
|
||||
);
|
||||
|
||||
const installedPackages = useMemo(
|
||||
() =>
|
||||
(allPackages?.items || []).filter(
|
||||
|
@ -76,6 +88,11 @@ export const EPMHomePage: React.FC = () => {
|
|||
const notificationsBySection = {
|
||||
manage: unverifiedPackageCount + upgradeablePackageCount,
|
||||
};
|
||||
|
||||
if (!shouldFetchPackages) {
|
||||
return <EuiLoadingSpinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route path={INTEGRATIONS_ROUTING_PATHS.integrations_installed}>
|
||||
|
@ -85,7 +102,7 @@ export const EPMHomePage: React.FC = () => {
|
|||
</Route>
|
||||
<Route path={INTEGRATIONS_ROUTING_PATHS.integrations_all}>
|
||||
<DefaultLayout section="browse" notificationsBySection={notificationsBySection}>
|
||||
<AvailablePackages setPrereleaseEnabled={setPrereleaseEnabled} />
|
||||
<AvailablePackages prereleaseIntegrationsEnabled={prereleaseIntegrationsEnabled} />
|
||||
</DefaultLayout>
|
||||
</Route>
|
||||
</Routes>
|
||||
|
|
|
@ -82,15 +82,21 @@ export const useGetPackages = (query: GetPackagesRequest['query'] = {}) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const useGetPackagesQuery = (query: GetPackagesRequest['query']) => {
|
||||
return useQuery<GetPackagesResponse, RequestError>(['get-packages', query], () =>
|
||||
sendRequestForRq<GetPackagesResponse>({
|
||||
path: epmRouteService.getListPath(),
|
||||
method: 'get',
|
||||
version: API_VERSIONS.public.v1,
|
||||
query,
|
||||
})
|
||||
);
|
||||
export const useGetPackagesQuery = (
|
||||
query: GetPackagesRequest['query'],
|
||||
options?: { enabled?: boolean }
|
||||
) => {
|
||||
return useQuery<GetPackagesResponse, RequestError>({
|
||||
queryKey: ['get-packages', query],
|
||||
queryFn: () =>
|
||||
sendRequestForRq<GetPackagesResponse>({
|
||||
path: epmRouteService.getListPath(),
|
||||
method: 'get',
|
||||
version: API_VERSIONS.public.v1,
|
||||
query,
|
||||
}),
|
||||
enabled: options?.enabled,
|
||||
});
|
||||
};
|
||||
|
||||
export const sendGetPackages = (query: GetPackagesRequest['query'] = {}) => {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { settingsRoutesService } from '../../services';
|
||||
import type { PutSettingsResponse, PutSettingsRequest, GetSettingsResponse } from '../../types';
|
||||
|
@ -15,14 +15,17 @@ import { API_VERSIONS } from '../../../common/constants';
|
|||
import type { RequestError } from './use_request';
|
||||
import { sendRequest, sendRequestForRq, useRequest } from './use_request';
|
||||
|
||||
export function useGetSettingsQuery() {
|
||||
return useQuery<GetSettingsResponse, RequestError>(['settings'], () =>
|
||||
sendRequestForRq<GetSettingsResponse>({
|
||||
method: 'get',
|
||||
path: settingsRoutesService.getInfoPath(),
|
||||
version: API_VERSIONS.public.v1,
|
||||
})
|
||||
);
|
||||
export function useGetSettingsQuery(options?: { enabled?: boolean }) {
|
||||
return useQuery<GetSettingsResponse, RequestError>({
|
||||
queryKey: ['settings'],
|
||||
enabled: options?.enabled,
|
||||
queryFn: () =>
|
||||
sendRequestForRq<GetSettingsResponse>({
|
||||
method: 'get',
|
||||
path: settingsRoutesService.getInfoPath(),
|
||||
version: API_VERSIONS.public.v1,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetSettings() {
|
||||
|
@ -41,6 +44,17 @@ export function sendGetSettings() {
|
|||
});
|
||||
}
|
||||
|
||||
export function usePutSettingsMutation() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: sendPutSettings,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(['settings']);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function sendPutSettings(body: PutSettingsRequest['body']) {
|
||||
return sendRequest<PutSettingsResponse>({
|
||||
method: 'put',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue