Skip to content

Commit

Permalink
[data views] Field editor endpoint versioning and schema validation (#…
Browse files Browse the repository at this point in the history
…159626)

## Summary

- Move field preview to internal route
- Add versioning to route
- Endpoint is called with version
- Response schema validation

Closes #159158

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
mattkime and kibanamachine authored Jun 30, 2023
1 parent 50049ac commit a915352
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { act } from 'react-dom/test-utils';
import { ReactWrapper } from 'enzyme';
import { registerTestBed, TestBed } from '@kbn/test-jest-helpers';

import { API_BASE_PATH } from '../../common/constants';
import { FIELD_PREVIEW_PATH } from '../../common/constants';
import { Context } from '../../public/components/field_editor_context';
import {
FieldEditorFlyoutContent,
Expand Down Expand Up @@ -118,7 +118,7 @@ const getActions = (testBed: TestBed) => {

while (i >= 0) {
const request = server.requests[i];
if (request.method === 'POST' && request.url === `${API_BASE_PATH}/field_preview`) {
if (request.method === 'POST' && request.url === FIELD_PREVIEW_PATH) {
return {
...request,
requestBody: JSON.parse(JSON.parse(request.requestBody).body),
Expand Down
4 changes: 3 additions & 1 deletion src/plugins/data_view_field_editor/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@
* Side Public License, v 1.
*/

export const API_BASE_PATH = '/api/index_pattern_field_editor';
export const FIELD_PREVIEW_PATH = '/internal/data_view_field_editor/field_preview';

export const INITIAL_REST_VERSION = '1';
8 changes: 6 additions & 2 deletions src/plugins/data_view_field_editor/public/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
* Side Public License, v 1.
*/
import { HttpSetup } from '@kbn/core/public';
import { API_BASE_PATH } from '../../common/constants';
import {
FIELD_PREVIEW_PATH as path,
INITIAL_REST_VERSION as version,
} from '../../common/constants';
import { sendRequest } from '../shared_imports';
import { PainlessExecuteContext, FieldPreviewResponse } from '../components/preview';

Expand All @@ -23,14 +26,15 @@ export const initApi = (httpClient: HttpSetup) => {
document: Record<string, unknown>;
}) => {
return sendRequest<FieldPreviewResponse>(httpClient, {
path: `${API_BASE_PATH}/field_preview`,
path,
method: 'post',
body: {
index,
context,
script,
document,
},
version,
});
};

Expand Down
39 changes: 30 additions & 9 deletions src/plugins/data_view_field_editor/server/routes/field_preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
*/

import { schema } from '@kbn/config-schema';
import { HttpResponsePayload } from '@kbn/core/server';

import { API_BASE_PATH } from '../../common/constants';
import { FIELD_PREVIEW_PATH as path } from '../../common/constants';
import { RouteDependencies } from '../types';
import { handleEsError } from '../shared_imports';

Expand All @@ -29,12 +27,35 @@ const bodySchema = schema.object({
document: schema.object({}, { unknowns: 'allow' }),
});

const geoPoint = schema.object({
type: schema.literal('Point'),
coordinates: schema.arrayOf(schema.number(), { minSize: 2, maxSize: 2 }),
});

const valueSchema = schema.oneOf([schema.boolean(), schema.number(), schema.string(), geoPoint]);

export const registerFieldPreviewRoute = ({ router }: RouteDependencies): void => {
router.post(
router.versioned.post({ path, access: 'internal' }).addVersion(
{
path: `${API_BASE_PATH}/field_preview`,
version: '1',
validate: {
body: bodySchema,
request: {
body: bodySchema,
},
response: {
200: {
body: schema.object({
values: schema.oneOf([
// composite field
schema.recordOf(schema.string(), schema.arrayOf(valueSchema)),
// primitive field
schema.arrayOf(valueSchema),
]),
error: schema.maybe(schema.object({}, { unknowns: 'allow' })),
status: schema.maybe(schema.number()),
}),
},
},
},
},
async (ctx, req, res) => {
Expand All @@ -51,17 +72,17 @@ export const registerFieldPreviewRoute = ({ router }: RouteDependencies): void =

try {
// client types need to be update to support this request format
// when it does, supply response types
// @ts-expect-error
const { result } = await client.asCurrentUser.scriptsPainlessExecute(body);
const fieldValue = result as HttpResponsePayload;

return res.ok({ body: { values: fieldValue } });
return res.ok({ body: { values: result } });
} catch (error) {
// Assume invalid painless script was submitted
// Return 200 with error object
const handleCustomError = () => {
return res.ok({
body: { values: [], ...error.body },
body: { values: [], error: error.body?.error, status: error.statusCode },
});
};

Expand Down
9 changes: 0 additions & 9 deletions test/api_integration/apis/data_view_field_editor/constants.ts

This file was deleted.

21 changes: 15 additions & 6 deletions test/api_integration/apis/data_view_field_editor/field_preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@

import expect from '@kbn/expect';

import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
import { getErrorCodeFromErrorReason } from '@kbn/data-view-field-editor-plugin/public/lib/runtime_field_validation';
import {
FIELD_PREVIEW_PATH,
INITIAL_REST_VERSION,
} from '@kbn/data-view-field-editor-plugin/common/constants';
import { FtrProviderContext } from '../../ftr_provider_context';
import { API_BASE_PATH } from './constants';

const INDEX_NAME = 'api-integration-test-field-preview';

Expand Down Expand Up @@ -83,7 +87,8 @@ export default function ({ getService }: FtrProviderContext) {
};

const { body: response } = await supertest
.post(`${API_BASE_PATH}/field_preview`)
.post(FIELD_PREVIEW_PATH)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION)
.send(payload)
.set('kbn-xsrf', 'xxx')
.expect(200);
Expand All @@ -96,7 +101,8 @@ export default function ({ getService }: FtrProviderContext) {
describe('payload validation', () => {
it('should require a script', async () => {
await supertest
.post(`${API_BASE_PATH}/field_preview`)
.post(FIELD_PREVIEW_PATH)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION)
.send({
context: 'keyword_field',
index: INDEX_NAME,
Expand All @@ -107,7 +113,8 @@ export default function ({ getService }: FtrProviderContext) {

it('should require a context', async () => {
await supertest
.post(`${API_BASE_PATH}/field_preview`)
.post(FIELD_PREVIEW_PATH)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION)
.send({
script: { source: 'emit("hello")' },
index: INDEX_NAME,
Expand All @@ -118,7 +125,8 @@ export default function ({ getService }: FtrProviderContext) {

it('should require an index', async () => {
await supertest
.post(`${API_BASE_PATH}/field_preview`)
.post(FIELD_PREVIEW_PATH)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION)
.send({
script: { source: 'emit("hello")' },
context: 'keyword_field',
Expand All @@ -134,7 +142,8 @@ export default function ({ getService }: FtrProviderContext) {
// If this test fail we'll need to update the "getErrorCodeFromErrorReason()" handler
it('should detect a script casting error', async () => {
const { body: response } = await supertest
.post(`${API_BASE_PATH}/field_preview`)
.post(FIELD_PREVIEW_PATH)
.set(ELASTIC_HTTP_VERSION_HEADER, INITIAL_REST_VERSION)
.send({
script: { source: 'emit(123)' }, // We send a long but the type is "keyword"
context: 'keyword_field',
Expand Down

0 comments on commit a915352

Please sign in to comment.