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

[Endpoint] Integrate the Policy list with ingest datasources api #60548

Merged
Show file tree
Hide file tree
Changes from 14 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 @@ -50,13 +50,18 @@ interface RouterProps {
}

const AppRoot: React.FunctionComponent<RouterProps> = React.memo(
({ basename, store, coreStart: { http, notifications, uiSettings }, depsStart: { data } }) => {
({
basename,
store,
coreStart: { http, notifications, uiSettings, application },
depsStart: { data },
}) => {
const isDarkMode = useObservable<boolean>(uiSettings.get$('theme:darkMode'));

return (
<Provider store={store}>
<I18nProvider>
<KibanaContextProvider services={{ http, notifications, data }}>
<KibanaContextProvider services={{ http, notifications, application, data }}>
<EuiThemeProvider darkMode={isDarkMode}>
<BrowserRouter basename={basename}>
<RouteCapture>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { httpServiceMock } from '../../../../../../../src/core/public/mocks';
import { sendGetDatasource, sendGetEndpointSpecificDatasources } from './ingest';

describe('ingest service', () => {
let http: ReturnType<typeof httpServiceMock.createStartContract>;

beforeEach(() => {
http = httpServiceMock.createStartContract();
});

describe('sendGetEndpointSpecificDatasources()', () => {
it('auto adds kuery to api request', async () => {
await sendGetEndpointSpecificDatasources(http);
expect(http.get).toHaveBeenCalledWith('/api/ingest_manager/datasources', {
query: {
kuery: 'datasources.package.name: endpoint',
},
});
});
it('supports additional KQL to be defined on input for query params', async () => {
await sendGetEndpointSpecificDatasources(http, {
query: { kuery: 'someValueHere', page: 1, perPage: 10 },
});
expect(http.get).toHaveBeenCalledWith('/api/ingest_manager/datasources', {
query: {
kuery: 'someValueHere and datasources.package.name: endpoint',
perPage: 10,
page: 1,
},
});
});
});
describe('sendGetDatasource()', () => {
it('builds correct API path', async () => {
await sendGetDatasource(http, '123');
expect(http.get).toHaveBeenCalledWith('/api/ingest_manager/datasources/123', undefined);
});
it('supports http options', async () => {
await sendGetDatasource(http, '123', { query: { page: 1 } });
expect(http.get).toHaveBeenCalledWith('/api/ingest_manager/datasources/123', {
query: {
page: 1,
},
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { HttpFetchOptions, HttpStart } from 'kibana/public';
import { GetDatasourcesRequest } from '../../../../../ingest_manager/common/types/rest_spec';
import { PolicyData } from '../types';

const INGEST_API_ROOT = `/api/ingest_manager`;
const INGEST_API_DATASOURCES = `${INGEST_API_ROOT}/datasources`;

// FIXME: Import from ingest after - https://github.com/elastic/kibana/issues/60677
export interface GetDatasourcesResponse {
items: PolicyData[];
total: number;
page: number;
perPage: number;
success: boolean;
}

// FIXME: Import from Ingest after - https://github.com/elastic/kibana/issues/60677
export interface GetDatasourceResponse {
item: PolicyData;
success: boolean;
}

/**
* Retrieves a list of endpoint specific datasources (those created with a `package.name` of
* `endpoint`) from Ingest
* @param http
* @param options
*/
export const sendGetEndpointSpecificDatasources = (
http: HttpStart,
options: HttpFetchOptions & Partial<GetDatasourcesRequest> = {}
): Promise<GetDatasourcesResponse> => {
return http.get<GetDatasourcesResponse>(INGEST_API_DATASOURCES, {
...options,
query: {
...options.query,
kuery: `${
options?.query?.kuery ? options.query.kuery + ' and ' : ''
}datasources.package.name: endpoint`,
},
});
};

/**
* Retrieves a single datasource based on ID from ingest
* @param http
* @param datasourceId
* @param options
*/
export const sendGetDatasource = (
http: HttpStart,
datasourceId: string,
options?: HttpFetchOptions
) => {
return http.get<GetDatasourceResponse>(`${INGEST_API_DATASOURCES}/${datasourceId}`, options);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@

import { MiddlewareFactory, PolicyDetailsState } from '../../types';
import { selectPolicyIdFromParams, isOnPolicyDetailsPage } from './selectors';
import { sendGetDatasource } from '../../services/ingest';

export const policyDetailsMiddlewareFactory: MiddlewareFactory<PolicyDetailsState> = coreStart => {
const http = coreStart.http;

return ({ getState, dispatch }) => next => async action => {
next(action);
const state = getState();

if (action.type === 'userChangedUrl' && isOnPolicyDetailsPage(state)) {
const id = selectPolicyIdFromParams(state);

const { getFakeDatasourceDetailsApiResponse } = await import('../policy_list/fake_data');
const policyItem = await getFakeDatasourceDetailsApiResponse(id);
const { item: policyItem } = await sendGetDatasource(http, id);

dispatch({
type: 'serverReturnedPolicyDetailsData',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,7 @@ import { AppAction } from '../action';

const initialPolicyDetailsState = (): PolicyDetailsState => {
return {
policyItem: {
name: '',
total: 0,
pending: 0,
failed: 0,
id: '',
created_by: '',
created: '',
updated_by: '',
updated: '',
},
policyItem: undefined,
isLoading: false,
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { PolicyData } from '../../types';
import { PolicyData, ServerApiError } from '../../types';

interface ServerReturnedPolicyListData {
type: 'serverReturnedPolicyListData';
Expand All @@ -16,6 +16,11 @@ interface ServerReturnedPolicyListData {
};
}

interface ServerFailedToReturnPolicyListData {
type: 'serverFailedToReturnPolicyListData';
payload: ServerApiError;
}

interface UserPaginatedPolicyListTable {
type: 'userPaginatedPolicyListTable';
payload: {
Expand All @@ -24,4 +29,7 @@ interface UserPaginatedPolicyListTable {
};
}

export type PolicyListAction = ServerReturnedPolicyListData | UserPaginatedPolicyListTable;
export type PolicyListAction =
| ServerReturnedPolicyListData
| UserPaginatedPolicyListTable
| ServerFailedToReturnPolicyListData;

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
*/

import { MiddlewareFactory, PolicyListState } from '../../types';
import { GetDatasourcesResponse, sendGetEndpointSpecificDatasources } from '../../services/ingest';

export const policyListMiddlewareFactory: MiddlewareFactory<PolicyListState> = coreStart => {
const http = coreStart.http;

return ({ getState, dispatch }) => next => async action => {
next(action);

Expand All @@ -26,10 +29,24 @@ export const policyListMiddlewareFactory: MiddlewareFactory<PolicyListState> = c
pageIndex = state.pageIndex;
}

// Need load data from API and remove fake data below
// Refactor tracked via: https://github.com/elastic/endpoint-app-team/issues/150
const { getFakeDatasourceApiResponse } = await import('./fake_data');
const { items: policyItems, total } = await getFakeDatasourceApiResponse(pageIndex, pageSize);
let response: GetDatasourcesResponse;

try {
response = await sendGetEndpointSpecificDatasources(http, {
query: {
perPage: pageSize,
page: pageIndex + 1,
},
});
} catch (err) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since err will be any, the dispatch call accepts it even though the runtime value may not satisfy the expected payload type.

image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can't just pass the err through because the ApiServerError structure is in the body property of the error object returned by http.* methods, thus why I do the check below. I test this by adding a kuery param on the fetch above to make it fail 😞

dispatch({
type: 'serverFailedToReturnPolicyListData',
payload: err.body ?? err,
});
return;
}

const { items: policyItems, total } = response;

dispatch({
type: 'serverReturnedPolicyListData',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const initialPolicyListState = (): PolicyListState => {
return {
policyItems: [],
isLoading: false,
apiError: undefined,
pageIndex: 0,
pageSize: 10,
total: 0,
Expand All @@ -30,12 +31,21 @@ export const policyListReducer: Reducer<PolicyListState, AppAction> = (
};
}

if (action.type === 'serverFailedToReturnPolicyListData') {
return {
...state,
apiError: action.payload,
isLoading: false,
};
}

if (
action.type === 'userPaginatedPolicyListTable' ||
(action.type === 'userNavigatedToPage' && action.payload === 'policyListPage')
) {
return {
...state,
apiError: undefined,
isLoading: true,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ export const selectPageSize = (state: PolicyListState) => state.pageSize;
export const selectTotal = (state: PolicyListState) => state.total;

export const selectIsLoading = (state: PolicyListState) => state.isLoading;

export const selectApiError = (state: PolicyListState) => state.apiError;
19 changes: 7 additions & 12 deletions x-pack/plugins/endpoint/public/applications/endpoint/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { EndpointPluginStartDependencies } from '../../plugin';
import { AppAction } from './store/action';
import { CoreStart } from '../../../../../../src/core/public';
import { Datasource } from '../../../../ingest_manager/common/types/models';

export { AppAction };
export type MiddlewareFactory<S = GlobalState> = (
Expand Down Expand Up @@ -50,25 +51,19 @@ export interface ServerApiError {
message: string;
}

// REFACTOR to use Types from Ingest Manager - see: https://github.com/elastic/endpoint-app-team/issues/150
export interface PolicyData {
name: string;
total: number;
pending: number;
failed: number;
id: string;
created_by: string;
created: string;
updated_by: string;
updated: string;
}
/**
* An Endpoint Policy.
*/
export type PolicyData = Datasource;

/**
* Policy list store state
*/
export interface PolicyListState {
/** Array of policy items */
policyItems: PolicyData[];
/** API error if loading data failed */
apiError?: ServerApiError;
/** total number of policies */
total: number;
/** Number of policies per page */
Expand Down
Loading