Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion x-pack/platform/plugins/shared/fleet/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ import { CUSTOM_LOGS_INTEGRATION_NAME, INTEGRATIONS_BASE_PATH } from './constant
import type { RequestError } from './hooks';
import { licenseService, sendGetBulkAssets } from './hooks';
import { setHttpClient } from './hooks/use_request';
import { createPackageSearchProvider } from './search_provider';
import {
createCustomIntegrationsSearchProvider,
createPackageSearchProvider,
} from './search_provider';
import { TutorialDirectoryHeaderLink, TutorialModuleNotice } from './components/home_integration';
import { createExtensionRegistrationCallback } from './services/ui_extensions';
import { ExperimentalFeaturesService } from './services/experimental_features';
Expand Down Expand Up @@ -291,6 +294,9 @@ export class FleetPlugin implements Plugin<FleetSetup, FleetStart, FleetSetupDep

if (deps.globalSearch) {
deps.globalSearch.registerResultProvider(createPackageSearchProvider(core));
deps.globalSearch.registerResultProvider(
createCustomIntegrationsSearchProvider(deps.customIntegrations)
);
}

return {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ import { TestScheduler } from 'rxjs/testing';
import { NEVER } from 'rxjs';

import { coreMock } from '@kbn/core/public/mocks';
import type { CustomIntegrationsSetup } from '@kbn/custom-integrations-plugin/public';

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

jest.mock('./hooks/use_request/epm', () => {
Expand Down Expand Up @@ -667,3 +672,145 @@ describe('Package search provider', () => {
});
});
});

describe('Custom Integrations search provider', () => {
let customIntegrationsMock: CustomIntegrationsSetup;
const customIntegrationsMockData = [
{
id: 'custom1',
title: 'Custom Integration 1',
uiInternalPath: '/app/custom1',
icons: [{ src: 'icon1.svg' }],
},
{
id: 'custom2',
title: 'Custom Integration 2',
uiInternalPath: '/app/custom2',
icons: [{ src: 'icon2.svg' }],
},
];

beforeEach(() => {
customIntegrationsMock = {
getReplacementCustomIntegrations: jest.fn(),
} as unknown as CustomIntegrationsSetup;
});

afterEach(() => {
jest.clearAllMocks();
});

describe('#find', () => {
test('returns formatted results', () => {
getTestScheduler().run(({ expectObservable, hot }) => {
(customIntegrationsMock.getReplacementCustomIntegrations as jest.Mock).mockReturnValue(
hot('--a|', { a: customIntegrationsMockData })
);

const customIntegrationsSearchProvider =
createCustomIntegrationsSearchProvider(customIntegrationsMock);

expectObservable(
customIntegrationsSearchProvider.find(
{ term: 'custom' },
{ aborted$: NEVER, maxResults: 100, preference: '' }
)
).toBe('--a|', {
a: [
{
id: 'custom1',
score: 80,
title: 'Custom Integration 1',
type: 'integration',
url: '/app/custom1',
icon: 'icon1.svg',
},
{
id: 'custom2',
score: 80,
title: 'Custom Integration 2',
type: 'integration',
url: '/app/custom2',
icon: 'icon2.svg',
},
],
});
});

expect(customIntegrationsMock.getReplacementCustomIntegrations).toHaveBeenCalledTimes(1);
});

test('returns empty array if no term is provided', () => {
getTestScheduler().run(({ expectObservable, hot }) => {
(customIntegrationsMock.getReplacementCustomIntegrations as jest.Mock).mockReturnValue(
hot('--a|', { a: [] })
);

const customIntegrationsSearchProvider =
createCustomIntegrationsSearchProvider(customIntegrationsMock);

expectObservable(
customIntegrationsSearchProvider.find(
{ term: '' },
{ aborted$: NEVER, maxResults: 100, preference: '' }
)
).toBe('(a|)', {
a: [],
});
});

expect(customIntegrationsMock.getReplacementCustomIntegrations).toHaveBeenCalledTimes(0);
});

test('completes without returning results if aborted', () => {
getTestScheduler().run(({ expectObservable, hot }) => {
(customIntegrationsMock.getReplacementCustomIntegrations as jest.Mock).mockReturnValue(
hot('--a|', { a: customIntegrationsMockData })
);
const aborted$ = hot('-a', { a: undefined });
const customIntegrationsSearchProvider =
createCustomIntegrationsSearchProvider(customIntegrationsMock);

expectObservable(
customIntegrationsSearchProvider.find(
{ term: 'custom' },
{ aborted$, maxResults: 100, preference: '' }
)
).toBe('-|');
});

expect(customIntegrationsMock.getReplacementCustomIntegrations).toHaveBeenCalledTimes(1);
});

test('respects maximum results', () => {
getTestScheduler().run(({ hot, expectObservable }) => {
(customIntegrationsMock.getReplacementCustomIntegrations as jest.Mock).mockReturnValue(
hot('--a|', { a: customIntegrationsMockData })
);

const customIntegrationsSearchProvider =
createCustomIntegrationsSearchProvider(customIntegrationsMock);

expectObservable(
customIntegrationsSearchProvider.find(
{ term: 'custom' },
{ aborted$: NEVER, maxResults: 1, preference: '' }
)
).toBe('--a|', {
a: [
{
id: 'custom1',
score: 80,
title: 'Custom Integration 1',
type: 'integration',
url: '/app/custom1',
icon: 'icon1.svg',
},
],
});
});

expect(customIntegrationsMock.getReplacementCustomIntegrations).toHaveBeenCalledTimes(1);
});
});
});
46 changes: 46 additions & 0 deletions x-pack/platform/plugins/shared/fleet/public/search_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import type {
GlobalSearchProviderResult,
} from '@kbn/global-search-plugin/public';

import type { CustomIntegrationsSetup } from '@kbn/custom-integrations-plugin/public';

import type { CustomIntegration } from '@kbn/custom-integrations-plugin/common';

import { INTEGRATIONS_PLUGIN_ID } from '../common';
import { filterPolicyTemplatesTiles } from '../common/services';

Expand Down Expand Up @@ -147,3 +151,45 @@ export const createPackageSearchProvider = (core: CoreSetup): GlobalSearchResult
},
};
};

const toCustomItegrationSearchResult = (customIntegration: CustomIntegration) => ({
id: customIntegration.id,
type: packageType,
title: customIntegration.title,
score: 80,
url: customIntegration.uiInternalPath,
icon: customIntegration.icons.find(({ src }) => Boolean(src))?.src,
});

export const createCustomIntegrationsSearchProvider = (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you add some tests for this in search_provider.test.ts?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sure, will do.

customIntegrations: CustomIntegrationsSetup
): GlobalSearchResultProvider => {
return {
id: 'customIntegrations',
getSearchableTypes: () => [packageType],
find: ({ term, types }, { maxResults, aborted$ }) => {
if (types?.includes(packageType) === false) {
return of([]);
}

if (!term) {
return of([]);
}

const customIntegrations$ = from(customIntegrations.getReplacementCustomIntegrations()).pipe(
map((integrations) => integrations),
shareReplay(1)
);

return customIntegrations$.pipe(
takeUntil(aborted$),
map((customIntegrationsData) =>
customIntegrationsData
.map(toCustomItegrationSearchResult)
.filter((res) => term && res.title.toLowerCase().includes(term.toLowerCase()))
.slice(0, maxResults)
)
);
},
};
};