Skip to content

Commit

Permalink
[nit][pre-req] Split EPM Home into components (#114431)
Browse files Browse the repository at this point in the history
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
2 people authored and thomasneirynck committed Oct 13, 2021
1 parent 50b0dff commit 04fa293
Show file tree
Hide file tree
Showing 6 changed files with 408 additions and 355 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import React from 'react';

import { action } from '@storybook/addon-actions';

import type { ListProps } from './package_list_grid';
import type { Props } from './package_list_grid';
import { PackageListGrid } from './package_list_grid';

export default {
component: PackageListGrid,
title: 'Sections/EPM/Package List Grid',
};

type Args = Pick<ListProps, 'title' | 'isLoading' | 'showMissingIntegrationMessage'>;
type Args = Pick<Props, 'title' | 'isLoading' | 'showMissingIntegrationMessage'>;

const args: Args = {
title: 'Installed integrations',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
EuiLink,
EuiSpacer,
EuiTitle,
// @ts-ignore
EuiSearchBar,
EuiText,
} from '@elastic/eui';
Expand All @@ -29,7 +28,7 @@ import type { IntegrationCardItem } from '../../../../../../common/types/models'

import { PackageCard } from './package_card';

export interface ListProps {
export interface Props {
isLoading?: boolean;
controls?: ReactNode;
title: string;
Expand All @@ -51,7 +50,7 @@ export function PackageListGrid({
setSelectedCategory,
showMissingIntegrationMessage = false,
callout,
}: ListProps) {
}: Props) {
const [searchTerm, setSearchTerm] = useState(initialSearch || '');
const localSearchRef = useLocalSearch(list);

Expand Down Expand Up @@ -107,7 +106,12 @@ export function PackageListGrid({
}}
onChange={onQueryChange}
/>
{callout ? callout : null}
{callout ? (
<>
<EuiSpacer />
{callout}
</>
) : null}
<EuiSpacer />
{gridContent}
{showMissingIntegrationMessage && (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/*
* 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, { memo, useMemo } from 'react';
import { useLocation, useHistory, useParams } from 'react-router-dom';
import { i18n } from '@kbn/i18n';

import { pagePathGetters } from '../../../../constants';
import {
useGetCategories,
useGetPackages,
useBreadcrumbs,
useGetAppendCustomIntegrations,
useGetReplacementCustomIntegrations,
useLink,
} from '../../../../hooks';
import { doesPackageHaveIntegrations } from '../../../../services';
import type { PackageList } from '../../../../types';
import { PackageListGrid } from '../../components/package_list_grid';

import type { CustomIntegration } from '../../../../../../../../../../src/plugins/custom_integrations/common';

import type { PackageListItem } from '../../../../types';

import type { IntegrationCategory } from '../../../../../../../../../../src/plugins/custom_integrations/common';

import { useMergeEprPackagesWithReplacements } from '../../../../../../hooks/use_merge_epr_with_replacements';

import { mergeAndReplaceCategoryCounts } from './util';
import { CategoryFacets } from './category_facets';
import type { CategoryFacet } from './category_facets';

import type { CategoryParams } from '.';
import { getParams, categoryExists, mapToCard } from '.';

// Packages can export multiple integrations, aka `policy_templates`
// In the case where packages ship >1 `policy_templates`, we flatten out the
// list of packages by bringing all integrations to top-level so that
// each integration is displayed as its own tile
const packageListToIntegrationsList = (packages: PackageList): PackageList => {
return packages.reduce((acc: PackageList, pkg) => {
const { policy_templates: policyTemplates = [], ...restOfPackage } = pkg;
return [
...acc,
restOfPackage,
...(doesPackageHaveIntegrations(pkg)
? policyTemplates.map((integration) => {
const { name, title, description, icons } = integration;
return {
...restOfPackage,
id: `${restOfPackage}-${name}`,
integration: name,
title,
description,
icons: icons || restOfPackage.icons,
};
})
: []),
];
}, []);
};

const title = i18n.translate('xpack.fleet.epmList.allTitle', {
defaultMessage: 'Browse by category',
});

// TODO: clintandrewhall - this component is hard to test due to the hooks, particularly those that use `http`
// or `location` to load data. Ideally, we'll split this into "connected" and "pure" components.
export const AvailablePackages: React.FC = memo(() => {
useBreadcrumbs('integrations_all');

const { selectedCategory, searchParam } = getParams(
useParams<CategoryParams>(),
useLocation().search
);

const history = useHistory();

const { getHref, getAbsolutePath } = useLink();

function setSelectedCategory(categoryId: string) {
const url = pagePathGetters.integrations_all({
category: categoryId,
searchTerm: searchParam,
})[1];
history.push(url);
}

function setSearchTerm(search: string) {
// Use .replace so the browser's back button is not tied to single keystroke
history.replace(
pagePathGetters.integrations_all({ category: selectedCategory, searchTerm: search })[1]
);
}

const { data: allCategoryPackagesRes, isLoading: isLoadingAllPackages } = useGetPackages({
category: '',
});

const { data: categoryPackagesRes, isLoading: isLoadingCategoryPackages } = useGetPackages({
category: selectedCategory,
});

const { data: categoriesRes, isLoading: isLoadingCategories } = useGetCategories({
include_policy_templates: true,
});

const eprPackages = useMemo(
() => packageListToIntegrationsList(categoryPackagesRes?.response || []),
[categoryPackagesRes]
);

const allEprPackages = useMemo(
() => packageListToIntegrationsList(allCategoryPackagesRes?.response || []),
[allCategoryPackagesRes]
);

const { value: replacementCustomIntegrations } = useGetReplacementCustomIntegrations();

const mergedEprPackages: Array<PackageListItem | CustomIntegration> =
useMergeEprPackagesWithReplacements(
eprPackages || [],
replacementCustomIntegrations || [],
selectedCategory as IntegrationCategory
);

const { loading: isLoadingAppendCustomIntegrations, value: appendCustomIntegrations } =
useGetAppendCustomIntegrations();

const filteredAddableIntegrations = appendCustomIntegrations
? appendCustomIntegrations.filter((integration: CustomIntegration) => {
if (!selectedCategory) {
return true;
}
return integration.categories.indexOf(selectedCategory as IntegrationCategory) >= 0;
})
: [];

const eprAndCustomPackages: Array<CustomIntegration | PackageListItem> = [
...mergedEprPackages,
...filteredAddableIntegrations,
];

eprAndCustomPackages.sort((a, b) => {
return a.title.localeCompare(b.title);
});

const categories = useMemo(() => {
const eprAndCustomCategories: CategoryFacet[] =
isLoadingCategories ||
isLoadingAppendCustomIntegrations ||
!appendCustomIntegrations ||
!categoriesRes
? []
: mergeAndReplaceCategoryCounts(
categoriesRes.response as CategoryFacet[],
appendCustomIntegrations
);

return [
{
id: '',
count: (allEprPackages?.length || 0) + (appendCustomIntegrations?.length || 0),
},
...(eprAndCustomCategories ? eprAndCustomCategories : []),
] as CategoryFacet[];
}, [
allEprPackages?.length,
appendCustomIntegrations,
categoriesRes,
isLoadingAppendCustomIntegrations,
isLoadingCategories,
]);

if (!isLoadingCategories && !categoryExists(selectedCategory, categories)) {
history.replace(pagePathGetters.integrations_all({ category: '', searchTerm: searchParam })[1]);
return null;
}

const controls = categories ? (
<CategoryFacets
showCounts={false}
isLoading={isLoadingCategories || isLoadingAllPackages || isLoadingAppendCustomIntegrations}
categories={categories}
selectedCategory={selectedCategory}
onCategoryChange={({ id }: CategoryFacet) => {
setSelectedCategory(id);
}}
/>
) : null;

const cards = eprAndCustomPackages.map((item) => {
return mapToCard(getAbsolutePath, getHref, item);
});

return (
<PackageListGrid
isLoading={isLoadingCategoryPackages}
title={title}
controls={controls}
initialSearch={searchParam}
list={cards}
setSelectedCategory={setSelectedCategory}
onSearchChange={setSearchTerm}
showMissingIntegrationMessage
/>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,21 @@ interface ALL_CATEGORY {

export type CategoryFacet = IntegrationCategoryCount | ALL_CATEGORY;

export interface Props {
showCounts: boolean;
isLoading?: boolean;
categories: CategoryFacet[];
selectedCategory: string;
onCategoryChange: (category: CategoryFacet) => unknown;
}

export function CategoryFacets({
showCounts,
isLoading,
categories,
selectedCategory,
onCategoryChange,
}: {
showCounts: boolean;
isLoading?: boolean;
categories: CategoryFacet[];
selectedCategory: string;
onCategoryChange: (category: CategoryFacet) => unknown;
}) {
}: Props) {
const controls = (
<EuiFacetGroup>
{isLoading ? (
Expand Down
Loading

0 comments on commit 04fa293

Please sign in to comment.