Skip to content

Commit

Permalink
Address review feedback (elastic#14)
Browse files Browse the repository at this point in the history
* Fix Prettier linting issues

* Escape App Search API endpoint URLs

- per PR feedback
- querystring should automatically encodeURIComponent / escape query param strings

* Update server plugin.ts to use getStartServices() rather than storing local references from start()

- Per feedback: https://github.com/elastic/kibana/blob/master/src/core/CONVENTIONS.md#applications

- Note: savedObjects.registerType needs to be outside of getStartServices, or an error is thrown

- Side update to registerTelemetryUsageCollector to simplify args

- Update/fix tests to account for changes
  • Loading branch information
Constance authored and cee-chen committed Jul 6, 2020
1 parent 17a5fea commit 28d6258
Show file tree
Hide file tree
Showing 16 changed files with 63 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const { intl } = intlProvider.getChildContext();
*
* const wrapper = shallowWithIntl(<Component />);
*/
export const shallowWithIntl = children => {
export const shallowWithIntl = (children) => {
return shallow(<I18nProvider>{children}</I18nProvider>, {
context: { intl },
childContextTypes: { intl },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,7 @@ describe('NoUserState', () => {
getUserName.mockImplementationOnce(() => 'dolores-abernathy');
const wrapper = shallowWithIntl(<NoUserState />);
const prompt = wrapper.find(EuiEmptyPrompt).dive();
const description1 = prompt
.find(FormattedMessage)
.at(1)
.dive();
const description1 = prompt.find(FormattedMessage).at(1).dive();

expect(description1.find(EuiCode).prop('children')).toContain('dolores-abernathy');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,7 @@ describe('EngineOverview', () => {
});

describe('pagination', () => {
const getTablePagination = () =>
wrapper
.find(EngineTable)
.first()
.prop('pagination');
const getTablePagination = () => wrapper.find(EngineTable).first().prop('pagination');

it('passes down page data from the API', () => {
const pagination = getTablePagination();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe('EngineTable', () => {
it('contains engine links which send telemetry', () => {
const engineLinks = wrapper.find(EuiLink);

engineLinks.forEach(link => {
engineLinks.forEach((link) => {
expect(link.prop('href')).toEqual('http://localhost:3002/as/engines/test-engine');
link.simulate('click');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const EngineTable: ReactFC<IEngineTableProps> = ({
pagination: { totalEngines, pageIndex = 0, onPaginate },
}) => {
const { enterpriseSearchUrl, http } = useContext(KibanaContext) as IKibanaContext;
const engineLinkProps = name => ({
const engineLinkProps = (name) => ({
href: `${enterpriseSearchUrl}/as/engines/${name}`,
target: '_blank',
onClick: () =>
Expand All @@ -56,7 +56,7 @@ export const EngineTable: ReactFC<IEngineTableProps> = ({
name: i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.table.column.name', {
defaultMessage: 'Name',
}),
render: name => <EuiLink {...engineLinkProps(name)}>{name}</EuiLink>,
render: (name) => <EuiLink {...engineLinkProps(name)}>{name}</EuiLink>,
width: '30%',
truncateText: true,
mobileOptions: {
Expand All @@ -75,7 +75,7 @@ export const EngineTable: ReactFC<IEngineTableProps> = ({
}
),
dataType: 'string',
render: dateString => (
render: (dateString) => (
// e.g., January 1, 1970
<FormattedDate value={new Date(dateString)} year="numeric" month="long" day="numeric" />
),
Expand All @@ -89,7 +89,7 @@ export const EngineTable: ReactFC<IEngineTableProps> = ({
}
),
dataType: 'number',
render: number => <FormattedNumber value={number} />,
render: (number) => <FormattedNumber value={number} />,
truncateText: true,
},
{
Expand All @@ -101,7 +101,7 @@ export const EngineTable: ReactFC<IEngineTableProps> = ({
}
),
dataType: 'number',
render: number => <FormattedNumber value={number} />,
render: (number) => <FormattedNumber value={number} />,
truncateText: true,
},
{
Expand All @@ -113,7 +113,7 @@ export const EngineTable: ReactFC<IEngineTableProps> = ({
}
),
dataType: 'string',
render: name => (
render: (name) => (
<EuiLink {...engineLinkProps(name)}>
<FormattedMessage
id="xpack.enterpriseSearch.appSearch.enginesOverview.table.action.manage"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const generateBreadcrumb = ({ text, path, history }: IGenerateBreadcrumbP

if (path && history) {
breadcrumb.href = history.createHref({ pathname: path });
breadcrumb.onClick = event => {
breadcrumb.onClick = (event) => {
if (letBrowserHandleEvent(event)) return;
event.preventDefault();
history.push(path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('SetAppSearchBreadcrumbs', () => {
jest.clearAllMocks();
});

const mountSetAppSearchBreadcrumbs = props => {
const mountSetAppSearchBreadcrumbs = (props) => {
return mountWithKibanaContext(<SetAppSearchBreadcrumbs {...props} />, {
http: {},
enterpriseSearchUrl: 'http://localhost:3002',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ interface IEuiReactRouterProps {
export const EuiReactRouterLink: React.FC<IEuiReactRouterProps> = ({ to, isButton, ...rest }) => {
const history = useHistory();

const onClick = event => {
const onClick = (event) => {
if (letBrowserHandleEvent(event)) return;

// Prevent regular link behavior, which causes a browser refresh.
Expand All @@ -42,6 +42,6 @@ export const EuiReactRouterLink: React.FC<IEuiReactRouterProps> = ({ to, isButto
return isButton ? <EuiButton {...props} /> : <EuiLink {...props} />;
};

export const EuiReactRouterButton: React.FC<IEuiReactRouterProps> = props => (
export const EuiReactRouterButton: React.FC<IEuiReactRouterProps> = (props) => (
<EuiReactRouterLink {...props} isButton />
);
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ describe('letBrowserHandleEvent', () => {
});
});

const targetValue = value => {
const targetValue = (value) => {
return {
getAttribute: () => value,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ import { SyntheticEvent } from 'react';

type THandleEvent = (event: SyntheticEvent) => boolean;

export const letBrowserHandleEvent: THandleEvent = event =>
export const letBrowserHandleEvent: THandleEvent = (event) =>
event.defaultPrevented ||
isModifiedEvent(event) ||
!isLeftClickEvent(event) ||
isTargetBlank(event);

const isModifiedEvent: THandleEvent = event =>
const isModifiedEvent: THandleEvent = (event) =>
!!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);

const isLeftClickEvent: THandleEvent = event => event.button === 0;
const isLeftClickEvent: THandleEvent = (event) => event.button === 0;

const isTargetBlank: THandleEvent = event => {
const isTargetBlank: THandleEvent = (event) => {
const target = event.target.getAttribute('target');
return !!target && target !== '_self';
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import { registerTelemetryUsageCollector, incrementUICounter } from './telemetry
describe('App Search Telemetry Usage Collector', () => {
const makeUsageCollectorStub = jest.fn();
const registerStub = jest.fn();
const usageCollectionMock = {
makeUsageCollector: makeUsageCollectorStub,
registerCollector: registerStub,
};

const savedObjectsRepoStub = {
get: () => ({
Expand All @@ -29,14 +33,8 @@ describe('App Search Telemetry Usage Collector', () => {
}),
incrementCounter: jest.fn(),
};
const dependencies = {
usageCollection: {
makeUsageCollector: makeUsageCollectorStub,
registerCollector: registerStub,
},
savedObjects: {
createInternalRepository: jest.fn(() => savedObjectsRepoStub),
},
const savedObjectsMock = {
createInternalRepository: jest.fn(() => savedObjectsRepoStub),
};

beforeEach(() => {
Expand All @@ -45,7 +43,7 @@ describe('App Search Telemetry Usage Collector', () => {

describe('registerTelemetryUsageCollector', () => {
it('should make and register the usage collector', () => {
registerTelemetryUsageCollector(dependencies);
registerTelemetryUsageCollector(usageCollectionMock, savedObjectsMock);

expect(registerStub).toHaveBeenCalledTimes(1);
expect(makeUsageCollectorStub).toHaveBeenCalledTimes(1);
Expand All @@ -55,7 +53,7 @@ describe('App Search Telemetry Usage Collector', () => {

describe('fetchTelemetryMetrics', () => {
it('should return existing saved objects data', async () => {
registerTelemetryUsageCollector(dependencies);
registerTelemetryUsageCollector(usageCollectionMock, savedObjectsMock);
const savedObjectsCounts = await makeUsageCollectorStub.mock.calls[0][0].fetch();

expect(savedObjectsCounts).toEqual({
Expand All @@ -76,10 +74,9 @@ describe('App Search Telemetry Usage Collector', () => {
});

it('should not error & should return a default telemetry object if no saved data exists', async () => {
registerTelemetryUsageCollector({
...dependencies,
savedObjects: { createInternalRepository: () => ({}) },
});
const emptySavedObjectsMock = { createInternalRepository: () => ({}) };

registerTelemetryUsageCollector(usageCollectionMock, emptySavedObjectsMock);
const savedObjectsCounts = await makeUsageCollectorStub.mock.calls[0][0].fetch();

expect(savedObjectsCounts).toEqual({
Expand All @@ -102,10 +99,8 @@ describe('App Search Telemetry Usage Collector', () => {

describe('incrementUICounter', () => {
it('should increment the saved objects internal repository', async () => {
const { savedObjects } = dependencies;

const response = await incrementUICounter({
savedObjects,
savedObjects: savedObjectsMock,
uiAction: 'ui_clicked',
metric: 'button',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,10 @@ import { AS_TELEMETRY_NAME, ITelemetrySavedObject } from '../../saved_objects/ap
* Register the telemetry collector
*/

interface Dependencies {
savedObjects: SavedObjectsServiceStart;
usageCollection: UsageCollectionSetup;
}

export const registerTelemetryUsageCollector = ({
usageCollection,
savedObjects,
}: Dependencies) => {
export const registerTelemetryUsageCollector = (
usageCollection: UsageCollectionSetup,
savedObjects: SavedObjectsServiceStart
) => {
const telemetryUsageCollector = usageCollection.makeUsageCollector({
type: 'app_search',
fetch: async () => fetchTelemetryMetrics(savedObjects),
Expand Down
29 changes: 12 additions & 17 deletions x-pack/plugins/enterprise_search/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
Plugin,
PluginInitializerContext,
CoreSetup,
CoreStart,
Logger,
SavedObjectsServiceStart,
} from 'src/core/server';
Expand Down Expand Up @@ -52,26 +51,22 @@ export class EnterpriseSearchPlugin implements Plugin {
/**
* Bootstrap the routes, saved objects, and collector for telemetry
*/
registerTelemetryRoute({
...dependencies,
getSavedObjectsService: () => {
if (!this.savedObjectsServiceStart) {
throw new Error('Saved Objects Start service not available');
}
return this.savedObjectsServiceStart;
},
});
savedObjects.registerType(appSearchTelemetryType);
if (usageCollection) {
getStartServices().then(([{ savedObjects: savedObjectsStarted }]) => {
registerTelemetryUsageCollector({ usageCollection, savedObjects: savedObjectsStarted });

getStartServices().then(([coreStart]) => {
const savedObjectsStarted = coreStart.savedObjects as SavedObjectsServiceStart;

registerTelemetryRoute({
...dependencies,
getSavedObjectsService: () => savedObjectsStarted,
});
}
if (usageCollection) {
registerTelemetryUsageCollector(usageCollection, savedObjectsStarted);
}
});
}

public start({ savedObjects }: CoreStart) {
this.savedObjectsServiceStart = savedObjects;
}
public start() {}

public stop() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class MockRouter {
this.router = httpServiceMock.createRouter();
};

public callRoute = async request => {
public callRoute = async (request) => {
const [_, handler] = this.router[this.method].mock.calls[0];

const context = {} as jest.Mocked<RequestHandlerContext>;
Expand All @@ -41,18 +41,18 @@ export class MockRouter {
* Schema validation helpers
*/

public validateRoute = request => {
public validateRoute = (request) => {
const [config] = this.router[this.method].mock.calls[0];
const validate = config.validate as RouteValidatorConfig<{}, {}, {}>;

validate[this.payload].validate(request[this.payload]);
};

public shouldValidate = request => {
public shouldValidate = (request) => {
expect(() => this.validateRoute(request)).not.toThrow();
};

public shouldThrow = request => {
public shouldThrow = (request) => {
expect(() => this.validateRoute(request)).toThrow();
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('engine routes', () => {
describe('when the underlying App Search API returns a 200', () => {
beforeEach(() => {
AppSearchAPI.shouldBeCalledWith(
`http://localhost:3002/as/engines/collection?type=indexed&page[current]=1&page[size]=10`,
`http://localhost:3002/as/engines/collection?type=indexed&page%5Bcurrent%5D=1&page%5Bsize%5D=10`,
{ headers: { Authorization: AUTH_HEADER } }
).andReturn({
results: [{ name: 'engine1' }],
Expand All @@ -67,7 +67,7 @@ describe('engine routes', () => {
describe('when the underlying App Search API redirects to /login', () => {
beforeEach(() => {
AppSearchAPI.shouldBeCalledWith(
`http://localhost:3002/as/engines/collection?type=indexed&page[current]=1&page[size]=10`,
`http://localhost:3002/as/engines/collection?type=indexed&page%5Bcurrent%5D=1&page%5Bsize%5D=10`,
{ headers: { Authorization: AUTH_HEADER } }
).andReturnRedirect();
});
Expand All @@ -85,7 +85,7 @@ describe('engine routes', () => {
describe('when the App Search URL is invalid', () => {
beforeEach(() => {
AppSearchAPI.shouldBeCalledWith(
`http://localhost:3002/as/engines/collection?type=indexed&page[current]=1&page[size]=10`,
`http://localhost:3002/as/engines/collection?type=indexed&page%5Bcurrent%5D=1&page%5Bsize%5D=10`,
{ headers: { Authorization: AUTH_HEADER } }
).andReturnError();
});
Expand All @@ -104,7 +104,7 @@ describe('engine routes', () => {
describe('when the App Search API returns invalid data', () => {
beforeEach(() => {
AppSearchAPI.shouldBeCalledWith(
`http://localhost:3002/as/engines/collection?type=indexed&page[current]=1&page[size]=10`,
`http://localhost:3002/as/engines/collection?type=indexed&page%5Bcurrent%5D=1&page%5Bsize%5D=10`,
{ headers: { Authorization: AUTH_HEADER } }
).andReturnInvalidData();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import fetch from 'node-fetch';
import querystring from 'querystring';
import { schema } from '@kbn/config-schema';

import { ENGINES_PAGE_SIZE } from '../../../common/constants';
Expand All @@ -25,7 +26,13 @@ export function registerEnginesRoute({ router, config, log }) {
const appSearchUrl = config.host;
const { type, pageIndex } = request.query;

const url = `${appSearchUrl}/as/engines/collection?type=${type}&page[current]=${pageIndex}&page[size]=${ENGINES_PAGE_SIZE}`;
const params = querystring.stringify({
type,
'page[current]': pageIndex,
'page[size]': ENGINES_PAGE_SIZE,
});
const url = `${encodeURI(appSearchUrl)}/as/engines/collection?${params}`;

const enginesResponse = await fetch(url, {
headers: { Authorization: request.headers.authorization },
});
Expand Down

0 comments on commit 28d6258

Please sign in to comment.