[SIEM] add middleware for handling refetch (#38697) (#39050)

* add middleware for handling refetch

* add unit test for error links

* add unit test
This commit is contained in:
Angela Chuang 2019-06-17 02:52:40 +08:00 committed by GitHub
parent 8aa6b14867
commit aa28b84e7f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 200 additions and 22 deletions

View file

@ -0,0 +1,108 @@
/*
* 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 { reTryOneTimeOnErrorHandler, errorLinkHandler } from '.';
import { ServerError } from 'apollo-link-http-common';
import { Operation } from 'apollo-link';
import { GraphQLError } from 'graphql';
import * as store from '../../store';
import { onError } from 'apollo-link-error';
const mockDispatch = jest.fn();
jest.mock('apollo-link-error');
jest.mock('../../store');
// @ts-ignore
store.getStore.mockReturnValue({ dispatch: mockDispatch });
describe('errorLinkHandler', () => {
const mockGraphQLErrors: GraphQLError = {
message: 'GraphQLError',
} as GraphQLError;
const mockNetworkError: ServerError = {
result: {},
statusCode: 503,
name: '',
message: 'error',
response: {
ok: false,
} as Response,
};
const mockOperation: Operation = {} as Operation;
const mockForward = jest.fn();
afterEach(() => {
mockDispatch.mockClear();
});
test('it should display error if graphQLErrors exist', () => {
errorLinkHandler({
graphQLErrors: [mockGraphQLErrors],
operation: mockOperation,
forward: mockForward,
});
expect(store.getStore).toBeCalled();
expect(mockDispatch.mock.calls.length).toBe(1);
});
test('it should display error if networkError exist', () => {
errorLinkHandler({
networkError: mockNetworkError,
operation: mockOperation,
forward: mockForward,
});
expect(store.getStore).toBeCalled();
expect(mockDispatch.mock.calls.length).toBe(1);
});
});
describe('errorLink', () => {
test('onError should be called with errorLinkHandler', () => {
expect(onError).toHaveBeenCalledWith(errorLinkHandler);
});
});
describe('reTryOneTimeOnErrorHandler', () => {
const mockNetworkError: ServerError = {
result: {},
statusCode: 503,
name: '',
message: 'error',
response: {
ok: false,
} as Response,
};
const mockOperation: Operation = {} as Operation;
const mockForward = jest.fn();
afterEach(() => {
mockForward.mockClear();
});
test('it should retry only if network status code is 503', () => {
reTryOneTimeOnErrorHandler({
networkError: mockNetworkError,
operation: mockOperation,
forward: mockForward,
});
expect(mockForward).toBeCalledWith(mockOperation);
});
test('it should not retry if other error happens', () => {
reTryOneTimeOnErrorHandler({
networkError: { ...mockNetworkError, statusCode: 500 },
operation: mockOperation,
forward: mockForward,
});
expect(mockForward).not.toBeCalled();
});
});
describe('reTryOneTimeOnErrorLink', () => {
test('onError should be called with reTryOneTimeOnErrorHandler', () => {
expect(onError).toHaveBeenCalledWith(reTryOneTimeOnErrorHandler);
});
});

View file

@ -4,16 +4,18 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { onError } from 'apollo-link-error';
import { onError, ErrorLink } from 'apollo-link-error';
import { get } from 'lodash/fp';
import uuid from 'uuid';
import * as i18n from './translations';
import { getStore } from '../../store';
import { appActions } from '../../store/actions';
import * as i18n from './translations';
export const errorLink = onError(({ graphQLErrors, networkError }) => {
export const errorLinkHandler: ErrorLink.ErrorHandler = ({ graphQLErrors, networkError }) => {
const store = getStore();
if (graphQLErrors != null && store != null) {
graphQLErrors.forEach(({ message }) =>
store.dispatch(
@ -31,4 +33,20 @@ export const errorLink = onError(({ graphQLErrors, networkError }) => {
})
);
}
});
};
export const errorLink = onError(errorLinkHandler);
export const reTryOneTimeOnErrorHandler: ErrorLink.ErrorHandler = ({
networkError,
operation,
forward,
}) => {
if (networkError != null) {
const statusCode = get('statusCode', networkError);
if (statusCode != null && statusCode === 503) {
return forward(operation);
}
}
};
export const reTryOneTimeOnErrorLink = onError(reTryOneTimeOnErrorHandler);

View file

@ -0,0 +1,40 @@
/*
* 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 { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import { errorLink, reTryOneTimeOnErrorLink } from '../../containers/errors';
import { getLinks } from './helpers';
import { withClientState } from 'apollo-link-state';
import * as apolloLinkHttp from 'apollo-link-http';
import introspectionQueryResultData from '../../graphql/introspection.json';
jest.mock('apollo-cache-inmemory');
jest.mock('apollo-link-http');
jest.mock('apollo-link-state');
jest.mock('../../containers/errors');
const mockWithClientState = 'mockWithClientState';
const mockHttpLink = { mockHttpLink: 'mockHttpLink' };
// @ts-ignore
withClientState.mockReturnValue(mockWithClientState);
// @ts-ignore
apolloLinkHttp.HttpLink.mockImplementation(() => mockHttpLink);
describe('getLinks helper', () => {
test('It should return links in correct order', () => {
const mockCache = new InMemoryCache({
dataIdFromObject: () => null,
fragmentMatcher: new IntrospectionFragmentMatcher({
introspectionQueryResultData,
}),
});
const links = getLinks(mockCache);
expect(links[0]).toEqual(errorLink);
expect(links[1]).toEqual(reTryOneTimeOnErrorLink);
expect(links[2]).toEqual(mockWithClientState);
expect(links[3]).toEqual(mockHttpLink);
});
});

View file

@ -0,0 +1,27 @@
/*
* 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 { HttpLink } from 'apollo-link-http';
import { withClientState } from 'apollo-link-state';
import { InMemoryCache } from 'apollo-cache-inmemory';
import chrome from 'ui/chrome';
import { errorLink, reTryOneTimeOnErrorLink } from '../../containers/errors';
export const getLinks = (cache: InMemoryCache) => [
errorLink,
reTryOneTimeOnErrorLink,
withClientState({
cache,
resolvers: {},
}),
new HttpLink({
credentials: 'same-origin',
headers: {
'kbn-xsrf': chrome.getXsrfToken(),
},
uri: `${chrome.getBasePath()}/api/siem/graphql`,
}),
];

View file

@ -7,8 +7,6 @@
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import ApolloClient from 'apollo-client';
import { ApolloLink } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { withClientState } from 'apollo-link-state';
import 'ui/autoload/all';
// @ts-ignore: path dynamic for kibana
import chrome from 'ui/chrome';
@ -18,11 +16,11 @@ import uiRoutes from 'ui/routes';
// @ts-ignore: path dynamic for kibana
import { timezoneProvider } from 'ui/vis/lib/timezone';
import { errorLink } from '../../containers/errors';
import introspectionQueryResultData from '../../graphql/introspection.json';
import { AppKibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter';
import { AppKibanaObservableApiAdapter } from '../adapters/observable_api/kibana_observable_api';
import { AppFrontendLibs } from '../lib';
import { getLinks } from './helpers';
export function compose(): AppFrontendLibs {
const cache = new InMemoryCache({
@ -40,20 +38,7 @@ export function compose(): AppFrontendLibs {
const graphQLOptions = {
connectToDevTools: process.env.NODE_ENV !== 'production',
cache,
link: ApolloLink.from([
errorLink,
withClientState({
cache,
resolvers: {},
}),
new HttpLink({
credentials: 'same-origin',
headers: {
'kbn-xsrf': chrome.getXsrfToken(),
},
uri: `${chrome.getBasePath()}/api/siem/graphql`,
}),
]),
link: ApolloLink.from(getLinks(cache)),
};
const apolloClient = new ApolloClient(graphQLOptions);