[App Search] Analytics - initial logic file & server API routes (#88250)
* Set up server API routes * Set up very basic AnalyticsLogic file - mostly just contains API call & basic loading/error state for now - actual usable vars will be defined in a future PR * [PR feedback] Unnecessary exports * [PR feedback] Clean up analyticsAvailable reducer * [PR feedback] Types order * [PR feedback] Unnecessary API validation
This commit is contained in:
parent
e4293a85fc
commit
53efccbc3b
|
@ -0,0 +1,308 @@
|
||||||
|
/*
|
||||||
|
* 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 { LogicMounter } from '../../../__mocks__/kea.mock';
|
||||||
|
|
||||||
|
jest.mock('../../../shared/kibana', () => ({
|
||||||
|
KibanaLogic: { values: { history: { location: { search: '' } } } },
|
||||||
|
}));
|
||||||
|
import { KibanaLogic } from '../../../shared/kibana';
|
||||||
|
|
||||||
|
jest.mock('../../../shared/http', () => ({
|
||||||
|
HttpLogic: { values: { http: { get: jest.fn() } } },
|
||||||
|
}));
|
||||||
|
import { HttpLogic } from '../../../shared/http';
|
||||||
|
|
||||||
|
jest.mock('../../../shared/flash_messages', () => ({
|
||||||
|
flashAPIErrors: jest.fn(),
|
||||||
|
}));
|
||||||
|
import { flashAPIErrors } from '../../../shared/flash_messages';
|
||||||
|
|
||||||
|
jest.mock('../engine', () => ({
|
||||||
|
EngineLogic: { values: { engineName: 'test-engine' } },
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { AnalyticsLogic } from './';
|
||||||
|
|
||||||
|
describe('AnalyticsLogic', () => {
|
||||||
|
const DEFAULT_VALUES = {
|
||||||
|
dataLoading: true,
|
||||||
|
analyticsUnavailable: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const MOCK_TOP_QUERIES = [
|
||||||
|
{
|
||||||
|
doc_count: 5,
|
||||||
|
key: 'some-key',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc_count: 0,
|
||||||
|
key: 'another-key',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const MOCK_RECENT_QUERIES = [
|
||||||
|
{
|
||||||
|
document_ids: ['1', '2'],
|
||||||
|
query_string: 'some-query',
|
||||||
|
tags: ['some-tag'],
|
||||||
|
timestamp: 'some-timestamp',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const MOCK_TOP_CLICKS = [
|
||||||
|
{
|
||||||
|
key: 'highly-clicked-query',
|
||||||
|
doc_count: 1,
|
||||||
|
document: {
|
||||||
|
id: 'some-id',
|
||||||
|
engine: 'some-engine',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
clicks: {
|
||||||
|
doc_count: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const MOCK_ANALYTICS_RESPONSE = {
|
||||||
|
analyticsUnavailable: false,
|
||||||
|
allTags: ['some-tag'],
|
||||||
|
recentQueries: MOCK_RECENT_QUERIES,
|
||||||
|
topQueries: MOCK_TOP_QUERIES,
|
||||||
|
topQueriesNoResults: MOCK_TOP_QUERIES,
|
||||||
|
topQueriesNoClicks: MOCK_TOP_QUERIES,
|
||||||
|
topQueriesWithClicks: MOCK_TOP_QUERIES,
|
||||||
|
totalClicks: 1000,
|
||||||
|
totalQueries: 5000,
|
||||||
|
totalQueriesNoResults: 500,
|
||||||
|
clicksPerDay: [0, 10, 50],
|
||||||
|
queriesPerDay: [10, 50, 100],
|
||||||
|
queriesNoResultsPerDay: [1, 2, 3],
|
||||||
|
};
|
||||||
|
const MOCK_QUERY_RESPONSE = {
|
||||||
|
analyticsUnavailable: false,
|
||||||
|
allTags: ['some-tag'],
|
||||||
|
totalQueriesForQuery: 50,
|
||||||
|
queriesPerDayForQuery: [25, 0, 25],
|
||||||
|
topClicksForQuery: MOCK_TOP_CLICKS,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { mount } = new LogicMounter(AnalyticsLogic);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
KibanaLogic.values.history.location.search = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has expected default values', () => {
|
||||||
|
mount();
|
||||||
|
expect(AnalyticsLogic.values).toEqual(DEFAULT_VALUES);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('actions', () => {
|
||||||
|
describe('onAnalyticsUnavailable', () => {
|
||||||
|
it('should set state', () => {
|
||||||
|
mount();
|
||||||
|
AnalyticsLogic.actions.onAnalyticsUnavailable();
|
||||||
|
|
||||||
|
expect(AnalyticsLogic.values).toEqual({
|
||||||
|
...DEFAULT_VALUES,
|
||||||
|
dataLoading: false,
|
||||||
|
analyticsUnavailable: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onAnalyticsDataLoad', () => {
|
||||||
|
it('should set state', () => {
|
||||||
|
mount();
|
||||||
|
AnalyticsLogic.actions.onAnalyticsDataLoad(MOCK_ANALYTICS_RESPONSE);
|
||||||
|
|
||||||
|
expect(AnalyticsLogic.values).toEqual({
|
||||||
|
...DEFAULT_VALUES,
|
||||||
|
dataLoading: false,
|
||||||
|
analyticsUnavailable: false,
|
||||||
|
// TODO: more state will get set here in future PRs
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onQueryDataLoad', () => {
|
||||||
|
it('should set state', () => {
|
||||||
|
mount();
|
||||||
|
AnalyticsLogic.actions.onQueryDataLoad(MOCK_QUERY_RESPONSE);
|
||||||
|
|
||||||
|
expect(AnalyticsLogic.values).toEqual({
|
||||||
|
...DEFAULT_VALUES,
|
||||||
|
dataLoading: false,
|
||||||
|
analyticsUnavailable: false,
|
||||||
|
// TODO: more state will get set here in future PRs
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('listeners', () => {
|
||||||
|
describe('loadAnalyticsData', () => {
|
||||||
|
it('should set state', () => {
|
||||||
|
mount({ dataLoading: false });
|
||||||
|
|
||||||
|
AnalyticsLogic.actions.loadAnalyticsData();
|
||||||
|
|
||||||
|
expect(AnalyticsLogic.values).toEqual({
|
||||||
|
...DEFAULT_VALUES,
|
||||||
|
dataLoading: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should make an API call and set state based on the response', async () => {
|
||||||
|
const promise = Promise.resolve(MOCK_ANALYTICS_RESPONSE);
|
||||||
|
(HttpLogic.values.http.get as jest.Mock).mockReturnValueOnce(promise);
|
||||||
|
mount();
|
||||||
|
jest.spyOn(AnalyticsLogic.actions, 'onAnalyticsDataLoad');
|
||||||
|
|
||||||
|
AnalyticsLogic.actions.loadAnalyticsData();
|
||||||
|
await promise;
|
||||||
|
|
||||||
|
expect(HttpLogic.values.http.get).toHaveBeenCalledWith(
|
||||||
|
'/api/app_search/engines/test-engine/analytics/queries',
|
||||||
|
{
|
||||||
|
query: { size: 20 },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
expect(AnalyticsLogic.actions.onAnalyticsDataLoad).toHaveBeenCalledWith(
|
||||||
|
MOCK_ANALYTICS_RESPONSE
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses and passes the current search query string', async () => {
|
||||||
|
(HttpLogic.values.http.get as jest.Mock).mockReturnValueOnce({});
|
||||||
|
KibanaLogic.values.history.location.search =
|
||||||
|
'?start=1970-01-01&end=1970-01-02&&tag=some_tag';
|
||||||
|
mount();
|
||||||
|
|
||||||
|
AnalyticsLogic.actions.loadAnalyticsData();
|
||||||
|
|
||||||
|
expect(HttpLogic.values.http.get).toHaveBeenCalledWith(
|
||||||
|
'/api/app_search/engines/test-engine/analytics/queries',
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
start: '1970-01-01',
|
||||||
|
end: '1970-01-02',
|
||||||
|
tag: 'some_tag',
|
||||||
|
size: 20,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onAnalyticsUnavailable if analyticsUnavailable is in response', async () => {
|
||||||
|
const promise = Promise.resolve({ analyticsUnavailable: true });
|
||||||
|
(HttpLogic.values.http.get as jest.Mock).mockReturnValueOnce(promise);
|
||||||
|
mount();
|
||||||
|
jest.spyOn(AnalyticsLogic.actions, 'onAnalyticsUnavailable');
|
||||||
|
|
||||||
|
AnalyticsLogic.actions.loadAnalyticsData();
|
||||||
|
await promise;
|
||||||
|
|
||||||
|
expect(AnalyticsLogic.actions.onAnalyticsUnavailable).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles errors', async () => {
|
||||||
|
const promise = Promise.reject('error');
|
||||||
|
(HttpLogic.values.http.get as jest.Mock).mockReturnValueOnce(promise);
|
||||||
|
mount();
|
||||||
|
jest.spyOn(AnalyticsLogic.actions, 'onAnalyticsUnavailable');
|
||||||
|
|
||||||
|
try {
|
||||||
|
AnalyticsLogic.actions.loadAnalyticsData();
|
||||||
|
await promise;
|
||||||
|
} catch {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(flashAPIErrors).toHaveBeenCalledWith('error');
|
||||||
|
expect(AnalyticsLogic.actions.onAnalyticsUnavailable).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('loadQueryData', () => {
|
||||||
|
it('should set state', () => {
|
||||||
|
mount({ dataLoading: false });
|
||||||
|
|
||||||
|
AnalyticsLogic.actions.loadQueryData('some-query');
|
||||||
|
|
||||||
|
expect(AnalyticsLogic.values).toEqual({
|
||||||
|
...DEFAULT_VALUES,
|
||||||
|
dataLoading: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should make an API call and set state based on the response', async () => {
|
||||||
|
const promise = Promise.resolve(MOCK_QUERY_RESPONSE);
|
||||||
|
(HttpLogic.values.http.get as jest.Mock).mockReturnValueOnce(promise);
|
||||||
|
mount();
|
||||||
|
jest.spyOn(AnalyticsLogic.actions, 'onQueryDataLoad');
|
||||||
|
|
||||||
|
AnalyticsLogic.actions.loadQueryData('some-query');
|
||||||
|
await promise;
|
||||||
|
|
||||||
|
expect(HttpLogic.values.http.get).toHaveBeenCalledWith(
|
||||||
|
'/api/app_search/engines/test-engine/analytics/queries/some-query',
|
||||||
|
expect.any(Object) // empty query obj
|
||||||
|
);
|
||||||
|
expect(AnalyticsLogic.actions.onQueryDataLoad).toHaveBeenCalledWith(MOCK_QUERY_RESPONSE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses and passes the current search query string', async () => {
|
||||||
|
(HttpLogic.values.http.get as jest.Mock).mockReturnValueOnce({});
|
||||||
|
KibanaLogic.values.history.location.search =
|
||||||
|
'?start=1970-12-30&end=1970-12-31&&tag=another_tag';
|
||||||
|
mount();
|
||||||
|
|
||||||
|
AnalyticsLogic.actions.loadQueryData('some-query');
|
||||||
|
|
||||||
|
expect(HttpLogic.values.http.get).toHaveBeenCalledWith(
|
||||||
|
'/api/app_search/engines/test-engine/analytics/queries/some-query',
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
start: '1970-12-30',
|
||||||
|
end: '1970-12-31',
|
||||||
|
tag: 'another_tag',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onAnalyticsUnavailable if analyticsUnavailable is in response', async () => {
|
||||||
|
const promise = Promise.resolve({ analyticsUnavailable: true });
|
||||||
|
(HttpLogic.values.http.get as jest.Mock).mockReturnValueOnce(promise);
|
||||||
|
mount();
|
||||||
|
jest.spyOn(AnalyticsLogic.actions, 'onAnalyticsUnavailable');
|
||||||
|
|
||||||
|
AnalyticsLogic.actions.loadQueryData('some-query');
|
||||||
|
await promise;
|
||||||
|
|
||||||
|
expect(AnalyticsLogic.actions.onAnalyticsUnavailable).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles errors', async () => {
|
||||||
|
const promise = Promise.reject('error');
|
||||||
|
(HttpLogic.values.http.get as jest.Mock).mockReturnValueOnce(promise);
|
||||||
|
mount();
|
||||||
|
jest.spyOn(AnalyticsLogic.actions, 'onAnalyticsUnavailable');
|
||||||
|
|
||||||
|
try {
|
||||||
|
AnalyticsLogic.actions.loadQueryData('some-query');
|
||||||
|
await promise;
|
||||||
|
} catch {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(flashAPIErrors).toHaveBeenCalledWith('error');
|
||||||
|
expect(AnalyticsLogic.actions.onAnalyticsUnavailable).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* 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 { kea, MakeLogicType } from 'kea';
|
||||||
|
import queryString from 'query-string';
|
||||||
|
|
||||||
|
import { KibanaLogic } from '../../../shared/kibana';
|
||||||
|
import { HttpLogic } from '../../../shared/http';
|
||||||
|
import { flashAPIErrors } from '../../../shared/flash_messages';
|
||||||
|
import { EngineLogic } from '../engine';
|
||||||
|
|
||||||
|
import { AnalyticsData, QueryDetails } from './types';
|
||||||
|
|
||||||
|
interface AnalyticsValues extends AnalyticsData, QueryDetails {
|
||||||
|
dataLoading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AnalyticsActions {
|
||||||
|
onAnalyticsUnavailable(): void;
|
||||||
|
onAnalyticsDataLoad(data: AnalyticsData): AnalyticsData;
|
||||||
|
onQueryDataLoad(data: QueryDetails): QueryDetails;
|
||||||
|
loadAnalyticsData(): void;
|
||||||
|
loadQueryData(query: string): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AnalyticsLogic = kea<MakeLogicType<AnalyticsValues, AnalyticsActions>>({
|
||||||
|
path: ['enterprise_search', 'app_search', 'analytics_logic'],
|
||||||
|
actions: () => ({
|
||||||
|
onAnalyticsUnavailable: true,
|
||||||
|
onAnalyticsDataLoad: (data) => data,
|
||||||
|
onQueryDataLoad: (data) => data,
|
||||||
|
loadAnalyticsData: true,
|
||||||
|
loadQueryData: (query) => query,
|
||||||
|
}),
|
||||||
|
reducers: () => ({
|
||||||
|
dataLoading: [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
loadAnalyticsData: () => true,
|
||||||
|
loadQueryData: () => true,
|
||||||
|
onAnalyticsUnavailable: () => false,
|
||||||
|
onAnalyticsDataLoad: () => false,
|
||||||
|
onQueryDataLoad: () => false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
analyticsUnavailable: [
|
||||||
|
false,
|
||||||
|
{
|
||||||
|
onAnalyticsUnavailable: () => true,
|
||||||
|
onAnalyticsDataLoad: () => false,
|
||||||
|
onQueryDataLoad: () => false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
listeners: ({ actions }) => ({
|
||||||
|
loadAnalyticsData: async () => {
|
||||||
|
const { history } = KibanaLogic.values;
|
||||||
|
const { http } = HttpLogic.values;
|
||||||
|
const { engineName } = EngineLogic.values;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { start, end, tag } = queryString.parse(history.location.search);
|
||||||
|
const query = { start, end, tag, size: 20 };
|
||||||
|
const url = `/api/app_search/engines/${engineName}/analytics/queries`;
|
||||||
|
|
||||||
|
const response = await http.get(url, { query });
|
||||||
|
|
||||||
|
if (response.analyticsUnavailable) {
|
||||||
|
actions.onAnalyticsUnavailable();
|
||||||
|
} else {
|
||||||
|
actions.onAnalyticsDataLoad(response);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
flashAPIErrors(e);
|
||||||
|
actions.onAnalyticsUnavailable();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loadQueryData: async (query) => {
|
||||||
|
const { history } = KibanaLogic.values;
|
||||||
|
const { http } = HttpLogic.values;
|
||||||
|
const { engineName } = EngineLogic.values;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { start, end, tag } = queryString.parse(history.location.search);
|
||||||
|
const queryParams = { start, end, tag };
|
||||||
|
const url = `/api/app_search/engines/${engineName}/analytics/queries/${query}`;
|
||||||
|
|
||||||
|
const response = await http.get(url, { query: queryParams });
|
||||||
|
|
||||||
|
if (response.analyticsUnavailable) {
|
||||||
|
actions.onAnalyticsUnavailable();
|
||||||
|
} else {
|
||||||
|
actions.onQueryDataLoad(response);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
flashAPIErrors(e);
|
||||||
|
actions.onAnalyticsUnavailable();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { ANALYTICS_TITLE } from './constants';
|
export { ANALYTICS_TITLE } from './constants';
|
||||||
|
export { AnalyticsLogic } from './analytics_logic';
|
||||||
export { AnalyticsRouter } from './analytics_router';
|
export { AnalyticsRouter } from './analytics_router';
|
||||||
export { AnalyticsChart } from './components';
|
export { AnalyticsChart } from './components';
|
||||||
export { convertToChartData } from './utils';
|
export { convertToChartData } from './utils';
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface Query {
|
||||||
|
doc_count: number;
|
||||||
|
key: string;
|
||||||
|
clicks?: { doc_count: number };
|
||||||
|
searches?: { doc_count: number };
|
||||||
|
tags?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QueryClick extends Query {
|
||||||
|
document?: {
|
||||||
|
id: string;
|
||||||
|
engine: string;
|
||||||
|
tags?: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RecentQuery {
|
||||||
|
document_ids: string[];
|
||||||
|
query_string: string;
|
||||||
|
tags: string[];
|
||||||
|
timestamp: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API response data
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface BaseData {
|
||||||
|
analyticsUnavailable: boolean;
|
||||||
|
allTags: string[];
|
||||||
|
// NOTE: The API sends us back even more data than this (e.g.,
|
||||||
|
// startDate, endDate, currentTag, logRetentionSettings, query),
|
||||||
|
// but we currently don't need that data in our front-end code,
|
||||||
|
// so I'm opting not to list them in our types
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AnalyticsData extends BaseData {
|
||||||
|
recentQueries: RecentQuery[];
|
||||||
|
topQueries: Query[];
|
||||||
|
topQueriesWithClicks: Query[];
|
||||||
|
topQueriesNoClicks: Query[];
|
||||||
|
topQueriesNoResults: Query[];
|
||||||
|
totalClicks: number;
|
||||||
|
totalQueries: number;
|
||||||
|
totalQueriesNoResults: number;
|
||||||
|
clicksPerDay: number[];
|
||||||
|
queriesPerDay: number[];
|
||||||
|
queriesNoResultsPerDay: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueryDetails extends BaseData {
|
||||||
|
totalQueriesForQuery: number;
|
||||||
|
queriesPerDayForQuery: number[];
|
||||||
|
topClicksForQuery: QueryClick[];
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* 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 { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__';
|
||||||
|
|
||||||
|
import { registerAnalyticsRoutes } from './analytics';
|
||||||
|
|
||||||
|
describe('analytics routes', () => {
|
||||||
|
describe('GET /api/app_search/engines/{engineName}/analytics/queries', () => {
|
||||||
|
let mockRouter: MockRouter;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
mockRouter = new MockRouter({
|
||||||
|
method: 'get',
|
||||||
|
path: '/api/app_search/engines/{engineName}/analytics/queries',
|
||||||
|
payload: 'query',
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAnalyticsRoutes({
|
||||||
|
...mockDependencies,
|
||||||
|
router: mockRouter.router,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a request handler', () => {
|
||||||
|
mockRouter.callRoute({
|
||||||
|
params: { engineName: 'some-engine' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
|
||||||
|
path: '/as/engines/some-engine/analytics/queries',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validates', () => {
|
||||||
|
it('correctly without optional query params', () => {
|
||||||
|
const request = { query: {} };
|
||||||
|
mockRouter.shouldValidate(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly with all optional query params', () => {
|
||||||
|
const request = {
|
||||||
|
query: {
|
||||||
|
size: 20,
|
||||||
|
start: '1970-01-01',
|
||||||
|
end: '1970-01-02',
|
||||||
|
tag: 'some-tag',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockRouter.shouldValidate(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('incorrect types', () => {
|
||||||
|
const request = {
|
||||||
|
query: {
|
||||||
|
start: 100,
|
||||||
|
size: '100',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockRouter.shouldThrow(request);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GET /api/app_search/engines/{engineName}/analytics/queries/{query}', () => {
|
||||||
|
let mockRouter: MockRouter;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
mockRouter = new MockRouter({
|
||||||
|
method: 'get',
|
||||||
|
path: '/api/app_search/engines/{engineName}/analytics/queries/{query}',
|
||||||
|
payload: 'query',
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAnalyticsRoutes({
|
||||||
|
...mockDependencies,
|
||||||
|
router: mockRouter.router,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a request handler', () => {
|
||||||
|
mockRouter.callRoute({
|
||||||
|
params: { engineName: 'some-engine', query: 'some-query' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({
|
||||||
|
path: '/as/engines/some-engine/analytics/query/some-query',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validates', () => {
|
||||||
|
it('correctly without optional query params', () => {
|
||||||
|
const request = { query: {} };
|
||||||
|
mockRouter.shouldValidate(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly with all optional query params', () => {
|
||||||
|
const request = {
|
||||||
|
query: {
|
||||||
|
start: '1970-01-01',
|
||||||
|
end: '1970-01-02',
|
||||||
|
tag: 'some-tag',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockRouter.shouldValidate(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('incorrect types', () => {
|
||||||
|
const request = {
|
||||||
|
query: {
|
||||||
|
start: 100,
|
||||||
|
tag: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockRouter.shouldThrow(request);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* 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 { schema } from '@kbn/config-schema';
|
||||||
|
|
||||||
|
import { RouteDependencies } from '../../plugin';
|
||||||
|
|
||||||
|
const querySchema = {
|
||||||
|
start: schema.maybe(schema.string()), // Date string, expected format 'YYYY-MM-DD'
|
||||||
|
end: schema.maybe(schema.string()), // Date string, expected format 'YYYY-MM-DD'
|
||||||
|
tag: schema.maybe(schema.string()),
|
||||||
|
};
|
||||||
|
const queriesSchema = {
|
||||||
|
...querySchema,
|
||||||
|
size: schema.maybe(schema.number()),
|
||||||
|
};
|
||||||
|
|
||||||
|
export function registerAnalyticsRoutes({
|
||||||
|
router,
|
||||||
|
enterpriseSearchRequestHandler,
|
||||||
|
}: RouteDependencies) {
|
||||||
|
router.get(
|
||||||
|
{
|
||||||
|
path: '/api/app_search/engines/{engineName}/analytics/queries',
|
||||||
|
validate: {
|
||||||
|
params: schema.object({
|
||||||
|
engineName: schema.string(),
|
||||||
|
}),
|
||||||
|
query: schema.object(queriesSchema),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async (context, request, response) => {
|
||||||
|
const { engineName } = request.params;
|
||||||
|
|
||||||
|
return enterpriseSearchRequestHandler.createRequest({
|
||||||
|
path: `/as/engines/${engineName}/analytics/queries`,
|
||||||
|
})(context, request, response);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
{
|
||||||
|
path: '/api/app_search/engines/{engineName}/analytics/queries/{query}',
|
||||||
|
validate: {
|
||||||
|
params: schema.object({
|
||||||
|
engineName: schema.string(),
|
||||||
|
query: schema.string(),
|
||||||
|
}),
|
||||||
|
query: schema.object(querySchema),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async (context, request, response) => {
|
||||||
|
const { engineName, query } = request.params;
|
||||||
|
|
||||||
|
return enterpriseSearchRequestHandler.createRequest({
|
||||||
|
path: `/as/engines/${engineName}/analytics/query/${query}`,
|
||||||
|
})(context, request, response);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
|
@ -9,12 +9,14 @@ import { RouteDependencies } from '../../plugin';
|
||||||
import { registerEnginesRoutes } from './engines';
|
import { registerEnginesRoutes } from './engines';
|
||||||
import { registerCredentialsRoutes } from './credentials';
|
import { registerCredentialsRoutes } from './credentials';
|
||||||
import { registerSettingsRoutes } from './settings';
|
import { registerSettingsRoutes } from './settings';
|
||||||
|
import { registerAnalyticsRoutes } from './analytics';
|
||||||
import { registerDocumentsRoutes, registerDocumentRoutes } from './documents';
|
import { registerDocumentsRoutes, registerDocumentRoutes } from './documents';
|
||||||
|
|
||||||
export const registerAppSearchRoutes = (dependencies: RouteDependencies) => {
|
export const registerAppSearchRoutes = (dependencies: RouteDependencies) => {
|
||||||
registerEnginesRoutes(dependencies);
|
registerEnginesRoutes(dependencies);
|
||||||
registerCredentialsRoutes(dependencies);
|
registerCredentialsRoutes(dependencies);
|
||||||
registerSettingsRoutes(dependencies);
|
registerSettingsRoutes(dependencies);
|
||||||
|
registerAnalyticsRoutes(dependencies);
|
||||||
registerDocumentsRoutes(dependencies);
|
registerDocumentsRoutes(dependencies);
|
||||||
registerDocumentRoutes(dependencies);
|
registerDocumentRoutes(dependencies);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue