Skip to content

Commit

Permalink
Add support for PATCH requests in Console (#165634)
Browse files Browse the repository at this point in the history
## Summary

This PR adds support for PATCH requests in Console.


![patch-request](https://github.com/elastic/kibana/assets/721858/8257ca4b-303e-4f46-bbcc-6e6f95336c30)

Closes #154274

### Checklist

- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

## Release note

Adds support for PATCH requests in Console.

---------

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
sakurai-youhei and kibanamachine authored Sep 9, 2023
1 parent fac644c commit b46a737
Show file tree
Hide file tree
Showing 11 changed files with 47 additions and 12 deletions.
2 changes: 2 additions & 0 deletions docs/user/api.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Calls to the API endpoints require different operations. To interact with the {k

* *PUT* - Updates the existing information.

* *PATCH* - Applies partial modifications to the existing information.

* *DELETE* - Removes the information.

[float]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2090,6 +2090,12 @@ ace.define(
case 'p':
next('p');
switch (ch) {
case 'a':
next('a');
next('t');
next('c');
next('h');
return 'patch';
case 'u':
next('u');
next('t');
Expand All @@ -2106,6 +2112,12 @@ ace.define(
case 'P':
next('P');
switch (ch) {
case 'A':
next('A');
next('T');
next('C');
next('H');
return 'PATCH';
case 'U':
next('U');
next('T');
Expand All @@ -2120,7 +2132,7 @@ ace.define(
}
break;
default:
error('Expected one of GET/POST/PUT/DELETE/HEAD');
error('Expected one of GET/POST/PUT/DELETE/HEAD/PATCH');
}
},
value, // Place holder for the value function.
Expand Down Expand Up @@ -2254,7 +2266,7 @@ ace.define(
annotate('error', e.message);
// snap
const substring = text.substr(at);
const nextMatch = substring.search(/^POST|HEAD|GET|PUT|DELETE/m);
const nextMatch = substring.search(/^POST|HEAD|GET|PUT|DELETE|PATCH/m);
if (nextMatch < 1) return;
reset(at + nextMatch);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ export function parseCURL(text: string) {
const EscapedQuotes = /^((?:[^\\"']|\\.)+)/;

const LooksLikeCurl = /^\s*curl\s+/;
const CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE)/;
const CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE|PATCH)/;

const HasProtocol = /[\s"']https?:\/\//;
const CurlRequestWithProto = /[\s"']https?:\/\/[^\/ ]+\/+([^\s"']+)/;
const CurlRequestWithoutProto = /[\s"'][^\/ ]+\/+([^\s"']+)/;
const CurlData = /^.+\s(--data|-d)\s*/;
const SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE)\s+\/?(.+)/;
const SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE|PATCH)\s+\/?(.+)/;

if (lines.length > 0 && ExecutionComment.test(lines[0])) {
lines.shift();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -985,7 +985,7 @@ describe('Integration', () => {
{
name: 'Cursor rows after request end',
cursor: { lineNumber: 5, column: 1 },
autoCompleteSet: ['GET', 'PUT', 'POST', 'DELETE', 'HEAD'],
autoCompleteSet: ['GET', 'PUT', 'POST', 'DELETE', 'HEAD', 'PATCH'],
prefixToAdd: '',
suffixToAdd: ' ',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,7 @@ export default function ({
}

function addMethodAutoCompleteSetToContext(context: AutoCompleteContext) {
context.autoCompleteSet = ['GET', 'PUT', 'POST', 'DELETE', 'HEAD'].map((m, i) => ({
context.autoCompleteSet = ['GET', 'PUT', 'POST', 'DELETE', 'HEAD', 'PATCH'].map((m, i) => ({
name: m,
score: -i,
meta: i18n.translate('console.autocomplete.addMethodMetaText', { defaultMessage: 'method' }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class UrlPatternMatcher {
// We'll group endpoints by the methods which are attached to them,
//to avoid suggesting endpoints that are incompatible with the
//method that the user has entered.
['HEAD', 'GET', 'PUT', 'POST', 'DELETE'].forEach((method) => {
['HEAD', 'GET', 'PUT', 'POST', 'DELETE', 'PATCH'].forEach((method) => {
this[method] = {
rootComponent: new SharedComponent('ROOT'),
parametrizedComponentFactories: parametrizedComponentFactories || {
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/console/public/lib/curl_parsing/curl.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ export function parseCURL(text) {
const EscapedQuotes = /^((?:[^\\"']|\\.)+)/;

const LooksLikeCurl = /^\s*curl\s+/;
const CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE)/;
const CurlVerb = /-X ?(GET|HEAD|POST|PUT|DELETE|PATCH)/;

const HasProtocol = /[\s"']https?:\/\//;
const CurlRequestWithProto = /[\s"']https?:\/\/[^\/ ]+\/+([^\s"']+)/;
const CurlRequestWithoutProto = /[\s"'][^\/ ]+\/+([^\s"']+)/;
const CurlData = /^.+\s(--data|-d)\s*/;
const SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE)\s+\/?(.+)/;
const SenseLine = /^\s*(GET|HEAD|POST|PUT|DELETE|PATCH)\s+\/?(.+)/;

if (lines.length > 0 && ExecutionComment.test(lines[0])) {
lines.shift();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,11 @@ describe('Console Proxy Route', () => {
});
});
});
describe('PATCH request', () => {
it('returns the exact body', async () => {
const { payload } = await request('PATCH', '/', 'foobar');
expect(await readStream(payload)).toBe('foobar');
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ export type Body = TypeOf<typeof routeValidationConfig.body>;

const acceptedHttpVerb = schema.string({
validate: (method) => {
return ['HEAD', 'GET', 'POST', 'PUT', 'DELETE'].some(
return ['HEAD', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH'].some(
(verb) => verb.toLowerCase() === method.toLowerCase()
)
? undefined
: `Method must be one of, case insensitive ['HEAD', 'GET', 'POST', 'PUT', 'DELETE']. Received '${method}'.`;
: `Method must be one of, case insensitive ['HEAD', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH']. Received '${method}'.`;
},
});

Expand Down
4 changes: 3 additions & 1 deletion test/functional/apps/console/_autocomplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('HTTP methods', async () => {
const suggestions = {
G: ['GET'],
P: ['PUT', 'POST'],
P: ['PUT', 'POST', 'PATCH'],
D: ['DELETE'],
H: ['HEAD'],
};
Expand Down Expand Up @@ -234,6 +234,8 @@ GET _search
dELETE dELETe dELEtE dELEte dELeTE dELeTe dELetE dELete dElETE dElETe dElEtE dElEte dEleTE dEleTe dEletE dElete
deLETE deLETe deLEtE deLEte deLeTE deLeTe deLetE deLete delETE delETe delEtE delEte deleTE deleTe deletE delete
HEAD HEAd HEaD HEad HeAD HeAd HeaD Head hEAD hEAd hEaD hEad heAD heAd heaD head
PATCH PATCh PATcH PATch PAtCH PAtCh PAtcH PAtch PaTCH PaTCh PaTcH PaTch PatCH PatCh PatcH Patch pATCH pATCh pATcH
pATch pAtCH pAtCh pAtcH pAtch paTCH paTCh paTcH paTch patCH patCh patcH patch
`.split(/\s+/m)
),
20 // 20 of 112 (approx. one-fifth) should be enough for testing
Expand Down
13 changes: 13 additions & 0 deletions test/functional/apps/console/_console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(initialSize.width).to.be.greaterThan(afterSize.width);
});

it('should return statusCode 400 to unsupported HTTP verbs', async () => {
const expectedResponseContains = '"statusCode": 400';
await PageObjects.console.enterRequest('\n OPTIONS /');
await PageObjects.console.clickPlay();
await retry.try(async () => {
const actualResponse = await PageObjects.console.getResponse();
log.debug(actualResponse);
expect(actualResponse).to.contain(expectedResponseContains);

expect(await PageObjects.console.hasSuccessBadge()).to.be(false);
});
});

describe('with kbn: prefix in request', () => {
before(async () => {
await PageObjects.console.clearTextArea();
Expand Down

0 comments on commit b46a737

Please sign in to comment.