Skip to content

Commit

Permalink
[Fleet] Add icons to Integrations global search results (#111131) (#1…
Browse files Browse the repository at this point in the history
…11373)

Co-authored-by: Josh Dover <[email protected]>
  • Loading branch information
kibanamachine and joshdover authored Sep 7, 2021
1 parent 7ccf7fe commit 17a5743
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 12 deletions.
40 changes: 39 additions & 1 deletion x-pack/plugins/fleet/public/search_provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { NEVER } from 'rxjs';

import { coreMock } from 'src/core/public/mocks';

import { createPackageSearchProvider } from './search_provider';
import { createPackageSearchProvider, toSearchResult } from './search_provider';
import type { GetPackagesResponse } from './types';

jest.mock('./hooks/use_request/epm', () => {
Expand Down Expand Up @@ -286,4 +286,42 @@ describe('Package search provider', () => {
});
});
});

describe('toSearchResult', () => {
let startMock: ReturnType<typeof coreMock.createStart>;

beforeEach(() => {
startMock = coreMock.createStart();
});

it('uses svg icon if available', () => {
const pkg = {
...testResponse[0],
icons: [{ type: 'image/svg+xml', src: '/img_nginx.svg', path: '' }],
};
const { icon } = toSearchResult(pkg, startMock.application, startMock.http.basePath);
expect(icon).toMatchInlineSnapshot(`"/api/fleet/epm/packages/test/test/img_nginx.svg"`);
});

it('prepends base path to svg URL', () => {
startMock = coreMock.createStart({ basePath: '/foo' });
const pkg = {
...testResponse[0],
icons: [{ type: 'image/svg+xml', src: '/img_nginx.svg', path: '' }],
};
const { icon } = toSearchResult(pkg, startMock.application, startMock.http.basePath);
expect(icon).toMatchInlineSnapshot(`"/foo/api/fleet/epm/packages/test/test/img_nginx.svg"`);
});

// ICON_TYPES is empty in EUI: https://github.com/elastic/eui/issues/5138
it.skip('uses eui icon type as fallback', () => {
const pkg = {
...testResponse[0],
name: 'elasticsearch',
icons: [{ type: 'image/jpg', src: '/img_nginx.svg', path: '' }],
};
const { icon } = toSearchResult(pkg, startMock.application, startMock.http.basePath);
expect(icon).toMatchInlineSnapshot(`"logoElasticsearch"`);
});
});
});
35 changes: 26 additions & 9 deletions x-pack/plugins/fleet/public/search_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { CoreSetup, CoreStart, ApplicationStart } from 'src/core/public';
import type { CoreSetup, CoreStart, ApplicationStart, IBasePath } from 'src/core/public';

import type { Observable } from 'rxjs';
import { from, of, combineLatest } from 'rxjs';
import { map, shareReplay, takeUntil } from 'rxjs/operators';

import { ICON_TYPES } from '@elastic/eui';

import type {
GlobalSearchResultProvider,
GlobalSearchProviderResult,
} from '../../global_search/public';

import { INTEGRATIONS_PLUGIN_ID } from '../common';
import { epmRouteService, INTEGRATIONS_PLUGIN_ID } from '../common';

import { sendGetPackages } from './hooks';
import type { GetPackagesResponse } from './types';
import type { GetPackagesResponse, PackageListItem } from './types';
import { pagePathGetters } from './constants';

const packageType = 'integration';
Expand All @@ -34,16 +36,31 @@ const createPackages$ = () =>
shareReplay(1)
);

const toSearchResult = (
pkg: GetPackagesResponse['response'][number],
application: ApplicationStart
) => {
const getEuiIconType = (pkg: PackageListItem, basePath: IBasePath): string | undefined => {
const pkgIcon = pkg.icons?.find((icon) => icon.type === 'image/svg+xml');
if (!pkgIcon) {
// If no valid SVG is available, attempt to fallback to built-in EUI icons
return ICON_TYPES.find((key) => key.toLowerCase() === `logo${pkg.name}`);
}

return basePath.prepend(
epmRouteService.getFilePath(`/package/${pkg.name}/${pkg.version}${pkgIcon.src}`)
);
};

/** Exported for testing only @internal */
export const toSearchResult = (
pkg: PackageListItem,
application: ApplicationStart,
basePath: IBasePath
): GlobalSearchProviderResult => {
const pkgkey = `${pkg.name}-${pkg.version}`;
return {
id: pkgkey,
type: packageType,
title: pkg.title,
score: 80,
icon: getEuiIconType(pkg, basePath),
url: {
// prettier-ignore
path: `${application.getUrlForApp(INTEGRATIONS_PLUGIN_ID)}${pagePathGetters.integration_details_overview({ pkgkey })[1]}`,
Expand Down Expand Up @@ -95,13 +112,13 @@ export const createPackageSearchProvider = (core: CoreSetup): GlobalSearchResult
return packagesResponse
.flatMap(
includeAllPackages
? (pkg) => toSearchResult(pkg, coreStart.application)
? (pkg) => toSearchResult(pkg, coreStart.application, coreStart.http.basePath)
: (pkg) => {
if (!term || !pkg.title.toLowerCase().includes(term)) {
return [];
}

return toSearchResult(pkg, coreStart.application);
return toSearchResult(pkg, coreStart.application, coreStart.http.basePath);
}
)
.slice(0, maxResults);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ const resultToOption = (
): EuiSelectableTemplateSitewideOption => {
const { id, title, url, icon, type, meta = {} } = result;
const { tagIds = [], categoryLabel = '' } = meta as { tagIds: string[]; categoryLabel: string };
// only displaying icons for applications
const useIcon = type === 'application';
// only displaying icons for applications and integrations
const useIcon = type === 'application' || type === 'integration';
const option: EuiSelectableTemplateSitewideOption = {
key: id,
label: title,
Expand Down

0 comments on commit 17a5743

Please sign in to comment.