-
Notifications
You must be signed in to change notification settings - Fork 8.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[kfetch] Add support for interceptors #22128
Conversation
💔 Build Failed |
@spalger I need some of your typescript foo:
|
0998359
to
d4e17b3
Compare
💔 Build Failed |
@epixa @sqren Just so I'm clear on this, we intend to update all places where we use E.g. we will update xsrf.js like this (and we'll do the same elsewhere): import $ from 'jquery';
import { set } from 'lodash';
import { interceptors } from 'ui/kfetch';
const xsrfRequestInterceptor = {
request: function (opts) {
const { kbnXsrfToken = true } = opts;
if (kbnXsrfToken) {
set(opts, ['headers', 'kbn-version'], internals.version);
}
return opts;
}
};
export function initChromeXsrfApi(chrome, internals) {
chrome.getXsrfToken = function () {
return internals.version;
};
$.ajaxPrefilter(function ({ kbnXsrfToken = true }, originalOptions, jqXHR) {
if (kbnXsrfToken) {
jqXHR.setRequestHeader('kbn-version', internals.version);
}
});
chrome.$setupXsrfRequestInterceptor = function ($httpProvider) {
// Ensure all requests go through the interceptor, regardless of which fetch module is used.
$httpProvider.interceptors.push(xsrfRequestInterceptor);
interceptors.push(xsrfRequestInterceptor);
};
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we still need to export interceptors
from index.ts
, right?
export { kfetch, interceptors } from './kfetch';
export { kfetchAbortable } from './kfetch_abortable';
Though personally, I think I'd lean towards just exposing an "add" method, to avoid exposing the data structure:
export { kfetch, addInterceptor } from './kfetch';
export { kfetchAbortable } from './kfetch_abortable';
Good timing on this PR. @elastic/kibana-sharing is in the process of moving the reporting top nav menus to EUI and react. I would like to migrate all of the reporting API calls from $http to kfetch. Will this PR add intercepters for checkXPackInfoChange? I have a WIP PR that is trying to update the xpackInfo into vanilla javascript |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥 This code looks AWESOME. Just had a few suggestions and requests, mostly around testing.
src/ui/public/kfetch/fetch_error.ts
Outdated
* under the License. | ||
*/ | ||
|
||
export class FetchError extends Error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is a tiny bit outside of scope but can we rename this KFetchError
? This way it will be really obvious which module it has originated from. I think FetchError
is just a bit too vague. I mention this because I've introduced a SearchError
in the rollups feature branch and seeing this error makes me think that the SearchError
name is too vague too. I'll probably rename it CourierSearchError
or something.
Doing a rename will require making a change in one other place in the codebase: https://github.com/elastic/kibana/blob/master/src/ui/public/error_auto_create_index/error_auto_create_index.test.js
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, will change.
src/ui/public/kfetch/kfetch.test.ts
Outdated
addedByInterceptor: true, | ||
foo: 'bar', | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a test verifying that a Promise
is a valid return value?
it('response: should modify response via Promise interceptor', async () => {
fetchMock.get(matcherName, new Response(JSON.stringify({ foo: 'bar' })));
interceptors.push({
response: res => {
return new Promise((resolve) => {
resolve({
...res,
addedByInterceptor: true,
});
}
},
});
const resp = await kfetch({ pathname: 'my/path' });
expect(resp).toEqual({
addedByInterceptor: true,
foo: 'bar',
});
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also begs the question, what's the expected behavior if an interceptor returns a rejected promise?
src/ui/public/kfetch/kfetch.test.ts
Outdated
'kbn-version': 'my-version', | ||
}, | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we also verify a promise return val here?
it('request: should add headers via Promise interceptor', async () => {
fetchMock.get(matcherName, new Response(JSON.stringify({ foo: 'bar' })));
interceptors.push({
request: config => {
return new Promise(resolve => {
resolve({
...config,
headers: {
...config.headers,
addedByInterceptor: true,
},
});
});
},
});
await kfetch({
pathname: 'my/path',
headers: { myHeader: 'foo' },
});
expect(fetchMock.lastOptions(matcherName)).toEqual({
method: 'GET',
credentials: 'same-origin',
headers: {
addedByInterceptor: true,
myHeader: 'foo',
'Content-Type': 'application/json',
'kbn-version': 'my-version',
},
});
});
src/ui/public/kfetch/kfetch.test.ts
Outdated
}); | ||
|
||
expect(resp).rejects.toThrow('my custom error'); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And here.
it('responseError: should throw custom error when a rejected Promise is returned', () => {
fetchMock.get(matcherName, {
status: 404,
});
interceptors.push({
responseError: e => {
return new Promise((resolve, reject) => {
reject(new Error('my custom error'));
})
},
});
const resp = kfetch({
pathname: 'my/path',
});
expect(resp).rejects.toThrow('my custom error');
});
src/ui/public/kfetch/kfetch.test.ts
Outdated
|
||
expect(resp).resolves.toBe('resolved valued'); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add some tests for requestError
?
import { metadata } from 'ui/metadata'; | ||
import { Interceptor } from './kfetch'; | ||
|
||
export const defaultInterceptor: Interceptor = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a "core principles" standpoint, I like the dogfooding and consistency aspect of creating a default interceptor to apply these defaults, but at the same this adds indirection to the code. In the end this indirection hurts more than it helps, in my opinion. I'll leave it up to you but personally I think I would find the code easier to follow if we assigned defaults within kfetch
, as we were doing originally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree this is overabstraction. Will change.
@nreese I'm assuming that as a second part of this PR, we should update all the places where we consume |
I'm okay with doing that. Reason I exposed access to the data structure is because I don't know the exact use-cases, and then I lean towards more flexibility. Eg. will plugin authors want to delete or re-order interceptors? If not, |
💔 Build Failed |
retest |
💔 Build Failed |
If you're on the fence, I think I'd lean towards YAGNI here, and wait for the need to delete/re-order to arise before adding that to the interface. But I don't feel very strongly about this, so I'm totally OK leaving it as-is if you have a strong hunch it will be useful. 😅 |
Yeah, that's probably a good idea. Do you think we should keep them as interceptors, or move them into kfetch core? I agree with your earlier comment that interceptors causes additional indirection, and perhaps we should use interceptors as an escape hatch mostly aimed at 3rd parties who can't just modify the core. What do you think? The interceptor in xsrf.js can probably be removed. |
b99783a
to
5a81a38
Compare
@sqren, my opinion is since those other files are in x-pack and you can run Kibana without them using the OSS version, they should be interceptors so that they don't cause problems for the OSS version. |
💔 Build Failed |
5a81a38
to
343ad74
Compare
I agree with @trevan. I think we should also try to avoid making any more changes than we have to, to reduce risk of introducing bugs. Longer term I think it could make sense to look at what we’re using the interceptors for and consider another mechanism. I’m sure the platform team will have thoughts on that! |
💔 Build Failed |
retest |
retest |
💚 Build Succeeded |
Yay! CI passing. |
It is all $http at the moment but will be migrated to kfetch in the near future |
Odd. The tests have been failing consistently until I made the last commit. |
💚 Build Succeeded |
868ddcd
to
bd970fd
Compare
💔 Build Failed |
retest |
💚 Build Succeeded |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, would you mind updating the PR description before merging?
@spalger What do you think of holding off on merging until #22128 (comment) has been addressed? I hate to put off merging this PR any longer than necessary, but it seems important we ensure that all existing interceptors are integrated into |
Personally I think that migrating $http interceptors is a separate task. Merging this in will fix #21982, unblock @trevan and allow anybody to migrate $http interceptors as they like. I don't want to delay that too much. I'll gladly help migrating the current $http interceptors but since I don't know their domain, I don't want to lead that effort. Eg. just like the initial PR for |
Good point @sqren. And I suppose people today are already assuming that |
Thanks @cjcenizal! Merging now, then. |
Btw. @spalger I updated the description so usage is now correct, and added your notes about the behaviour. Ok? |
💔 Build Failed |
Created #22594 to track interceptor parity between kfetch and $http. |
Closes #19060
Closes #21982
This PR adds support for http interceptors similar to angular http interceptors.
This makes it possible for plugins to transform request config and response of http requests made with kfetch. This is useful for adding additional headers.
To add an interceptor:
Behaviour
Request hooks start from the newest interceptor and end with the oldest, and the response hooks are the opposite. That means that if you add an interceptor before any other code runs your interceptor will always be the last to run before sending the request and the first to run when the response comes back. Each additional interceptor adds an additional "layer" to the wrapper around the request, it's the new first and last interceptor, which makes a lot of sense to me.