Use axios instead of vue resource - step 1
This commit is contained in:
parent
4f09d099e9
commit
9400ed3b3e
12 changed files with 128 additions and 162 deletions
|
@ -1,10 +1,7 @@
|
|||
import axios from 'axios';
|
||||
import setAxiosCsrfToken from '../../lib/utils/axios_utils';
|
||||
import axios from '../../lib/utils/axios_utils';
|
||||
|
||||
export default class ClusterService {
|
||||
constructor(options = {}) {
|
||||
setAxiosCsrfToken();
|
||||
|
||||
this.options = options;
|
||||
this.appInstallEndpointMap = {
|
||||
helm: this.options.installHelmEndpoint,
|
||||
|
@ -18,7 +15,6 @@ export default class ClusterService {
|
|||
}
|
||||
|
||||
installApplication(appId) {
|
||||
const endpoint = this.appInstallEndpointMap[appId];
|
||||
return axios.post(endpoint);
|
||||
return axios.post(this.appInstallEndpointMap[appId]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,8 +29,8 @@ export default class JobMediator {
|
|||
this.poll = new Poll({
|
||||
resource: this.service,
|
||||
method: 'getJob',
|
||||
successCallback: this.successCallback.bind(this),
|
||||
errorCallback: this.errorCallback.bind(this),
|
||||
successCallback: response => this.successCallback(response),
|
||||
errorCallback: () => this.errorCallback(),
|
||||
});
|
||||
|
||||
if (!Visibility.hidden()) {
|
||||
|
@ -57,7 +57,7 @@ export default class JobMediator {
|
|||
|
||||
successCallback(response) {
|
||||
this.state.isLoading = false;
|
||||
return response.json().then(data => this.store.storeJob(data));
|
||||
return this.store.storeJob(response.data);
|
||||
}
|
||||
|
||||
errorCallback() {
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import Vue from 'vue';
|
||||
import VueResource from 'vue-resource';
|
||||
|
||||
Vue.use(VueResource);
|
||||
import axios from '../../lib/utils/axios_utils';
|
||||
|
||||
export default class JobService {
|
||||
constructor(endpoint) {
|
||||
this.job = Vue.resource(endpoint);
|
||||
this.job = endpoint;
|
||||
}
|
||||
|
||||
getJob() {
|
||||
return this.job.get();
|
||||
return axios.get(this.job);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,22 @@
|
|||
import axios from 'axios';
|
||||
import csrf from './csrf';
|
||||
|
||||
export default function setAxiosCsrfToken() {
|
||||
axios.defaults.headers.common[csrf.headerKey] = csrf.token;
|
||||
}
|
||||
axios.defaults.headers.common[csrf.headerKey] = csrf.token;
|
||||
|
||||
// Maintain a global counter for active requests
|
||||
// see: spec/support/wait_for_requests.rb
|
||||
axios.interceptors.request.use((config) => {
|
||||
window.activeVueResources = window.activeVueResources || 0;
|
||||
window.activeVueResources += 1;
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
// Remove the global counter
|
||||
axios.interceptors.response.use((config) => {
|
||||
window.activeVueResources -= 1;
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
export default axios;
|
||||
|
|
|
@ -3,7 +3,9 @@ import { normalizeHeaders } from './common_utils';
|
|||
|
||||
/**
|
||||
* Polling utility for handling realtime updates.
|
||||
* Service for vue resouce and method need to be provided as props
|
||||
* Requirements: Promise based HTTP client
|
||||
*
|
||||
* Service for promise based http client and method need to be provided as props
|
||||
*
|
||||
* @example
|
||||
* new Poll({
|
||||
|
|
68
doc/development/fe_guide/axios.md
Normal file
68
doc/development/fe_guide/axios.md
Normal file
|
@ -0,0 +1,68 @@
|
|||
# Axios
|
||||
We use [axios][axios] to communicate with the server in Vue applications and most new code.
|
||||
|
||||
In order to guarantee all defaults are set you *should not use `axios` directly*, you should import `axios` from `axios_utils`.
|
||||
|
||||
## CSRF token
|
||||
All our request require a CSRF token.
|
||||
To guarantee this token is set, we are importing [axios][axios], setting the token, and exporting `axios` .
|
||||
|
||||
This exported module should be used instead of directly using `axios` to ensure the token is set.
|
||||
|
||||
## Usage
|
||||
```javascript
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
||||
axios.get(url)
|
||||
.then((response) => {
|
||||
// `data` is the response that was provided by the server
|
||||
const data = response.data;
|
||||
|
||||
// `headers` the headers that the server responded with
|
||||
// All header names are lower cased
|
||||
const paginationData = response.headers;
|
||||
})
|
||||
.catch(() => {
|
||||
//handle the error
|
||||
});
|
||||
```
|
||||
|
||||
## Mock axios response on tests
|
||||
|
||||
To help us mock the responses we need we use [axios-mock-adapter][axios-mock-adapter]
|
||||
|
||||
|
||||
```javascript
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
|
||||
let mock;
|
||||
beforeEach(() => {
|
||||
// This sets the mock adapter on the default instance
|
||||
mock = new MockAdapter(axios);
|
||||
// Mock any GET request to /users
|
||||
// arguments for reply are (status, data, headers)
|
||||
mock.onGet('/users').reply(200, {
|
||||
users: [
|
||||
{ id: 1, name: 'John Smith' }
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.reset();
|
||||
});
|
||||
```
|
||||
|
||||
### Mock poll requests on tests with axios
|
||||
|
||||
Because polling function requires an header object, we need to always include an object as the third argument:
|
||||
|
||||
```javascript
|
||||
mock.onGet('/users').reply(200, { foo: 'bar' }, {});
|
||||
```
|
||||
|
||||
[axios]: https://github.com/axios/axios
|
||||
[axios-instance]: #creating-an-instance
|
||||
[axios-interceptors]: https://github.com/axios/axios#interceptors
|
||||
[axios-mock-adapter]: https://github.com/ctimmerm/axios-mock-adapter
|
|
@ -71,8 +71,8 @@ Vue specific design patterns and practices.
|
|||
|
||||
---
|
||||
|
||||
## [Vue Resource](vue_resource.md)
|
||||
Vue resource specific practices and gotchas.
|
||||
## [Axios](axios.md)
|
||||
Axios specific practices and gotchas.
|
||||
|
||||
## [Icons](icons.md)
|
||||
How we use SVG for our Icons.
|
||||
|
|
|
@ -178,16 +178,13 @@ itself, please read this guide: [State Management][state-management]
|
|||
|
||||
The Service is a class used only to communicate with the server.
|
||||
It does not store or manipulate any data. It is not aware of the store or the components.
|
||||
We use [vue-resource][vue-resource-repo] to communicate with the server.
|
||||
Refer to [vue resource](vue_resource.md) for more details.
|
||||
We use [axios][axios] to communicate with the server.
|
||||
Refer to [axios](axios.md) for more details.
|
||||
|
||||
Vue Resource should only be imported in the service file.
|
||||
Axios instance should only be imported in the service file.
|
||||
|
||||
```javascript
|
||||
import Vue from 'vue';
|
||||
import VueResource from 'vue-resource';
|
||||
|
||||
Vue.use(VueResource);
|
||||
import axios from 'javascripts/lib/utils/axios_utils';
|
||||
```
|
||||
|
||||
### End Result
|
||||
|
@ -230,15 +227,14 @@ export default class Store {
|
|||
}
|
||||
|
||||
// service.js
|
||||
import Vue from 'vue';
|
||||
import VueResource from 'vue-resource';
|
||||
import 'vue_shared/vue_resource_interceptor';
|
||||
|
||||
Vue.use(VueResource);
|
||||
import axios from 'javascripts/lib/utils/axios_utils'
|
||||
|
||||
export default class Service {
|
||||
constructor(options) {
|
||||
this.todos = Vue.resource(endpoint.todosEndpoint);
|
||||
this.todos = axios.create({
|
||||
baseURL: endpoint.todosEndpoint
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
getTodos() {
|
||||
|
@ -477,50 +473,8 @@ The main return value of a Vue component is the rendered output. In order to tes
|
|||
need to test the rendered output. [Vue][vue-test] guide's to unit test show us exactly that:
|
||||
|
||||
### Stubbing API responses
|
||||
[Vue Resource Interceptors][vue-resource-interceptor] allow us to add a interceptor with
|
||||
the response we need:
|
||||
Refer to [mock axios](axios.md#mock-axios-response-on-tests)
|
||||
|
||||
```javascript
|
||||
// Mock the service to return data
|
||||
const interceptor = (request, next) => {
|
||||
next(request.respondWith(JSON.stringify([{
|
||||
title: 'This is a todo',
|
||||
body: 'This is the text'
|
||||
}]), {
|
||||
status: 200,
|
||||
}));
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
Vue.http.interceptors.push(interceptor);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
|
||||
});
|
||||
|
||||
it('should do something', (done) => {
|
||||
setTimeout(() => {
|
||||
// Test received data
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
```
|
||||
|
||||
1. Headers interceptor
|
||||
Refer to [this section](vue.md#headers)
|
||||
|
||||
1. Use `$.mount()` to mount the component
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
new Component({
|
||||
el: document.createElement('div')
|
||||
});
|
||||
|
||||
// good
|
||||
new Component().$mount();
|
||||
```
|
||||
|
||||
## Vuex
|
||||
To manage the state of an application you may use [Vuex][vuex-docs].
|
||||
|
@ -721,7 +675,6 @@ describe('component', () => {
|
|||
[component-system]: https://vuejs.org/v2/guide/#Composing-with-Components
|
||||
[state-management]: https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
|
||||
[one-way-data-flow]: https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow
|
||||
[vue-resource-interceptor]: https://github.com/pagekit/vue-resource/blob/develop/docs/http.md#interceptors
|
||||
[vue-test]: https://vuejs.org/v2/guide/unit-testing.html
|
||||
[issue-boards-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/boards/services/board_service.js.es6
|
||||
[flux]: https://facebook.github.io/flux
|
||||
|
@ -729,3 +682,6 @@ describe('component', () => {
|
|||
[vuex-structure]: https://vuex.vuejs.org/en/structure.html
|
||||
[vuex-mutations]: https://vuex.vuejs.org/en/mutations.html
|
||||
[vuex-testing]: https://vuex.vuejs.org/en/testing.html
|
||||
[axios]: https://github.com/axios/axios
|
||||
[axios-interceptors]: https://github.com/axios/axios#interceptors
|
||||
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
# Vue Resouce
|
||||
In Vue applications we use [vue-resource][vue-resource-repo] to communicate with the server.
|
||||
|
||||
## HTTP Status Codes
|
||||
|
||||
### `.json()`
|
||||
When making a request to the server, you will most likely need to access the body of the response.
|
||||
Use `.json()` to convert. Because `.json()` returns a Promise the follwoing structure should be used:
|
||||
|
||||
```javascript
|
||||
service.get('url')
|
||||
.then(resp => resp.json())
|
||||
.then((data) => {
|
||||
this.store.storeData(data);
|
||||
})
|
||||
.catch(() => new Flash('Something went wrong'));
|
||||
```
|
||||
|
||||
|
||||
When using `Poll` (`app/assets/javascripts/lib/utils/poll.js`), the `successCallback` needs to handle `.json()` as a Promise:
|
||||
```javascript
|
||||
successCallback: (response) => {
|
||||
return response.json().then((data) => {
|
||||
// handle the response
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 204
|
||||
Some endpoints - usually `delete` endpoints - return `204` as the success response.
|
||||
When handling `204 - No Content` responses, we cannot use `.json()` since it tries to parse the non-existant body content.
|
||||
|
||||
When handling `204` responses, do not use `.json`, otherwise the promise will throw an error and will enter the `catch` statement:
|
||||
|
||||
```javascript
|
||||
Vue.http.delete('path')
|
||||
.then(() => {
|
||||
// success!
|
||||
})
|
||||
.catch(() => {
|
||||
// handle error
|
||||
})
|
||||
```
|
||||
|
||||
## Headers
|
||||
Headers are being parsed into a plain object in an interceptor.
|
||||
In Vue-resource 1.x `headers` object was changed into an `Headers` object. In order to not change all old code, an interceptor was added.
|
||||
|
||||
If you need to write a unit test that takes the headers in consideration, you need to include an interceptor to parse the headers after your test interceptor.
|
||||
You can see an example in `spec/javascripts/environments/environment_spec.js`:
|
||||
```javascript
|
||||
import { headersInterceptor } from './helpers/vue_resource_helper';
|
||||
|
||||
beforeEach(() => {
|
||||
Vue.http.interceptors.push(myInterceptor);
|
||||
Vue.http.interceptors.push(headersInterceptor);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Vue.http.interceptors = _.without(Vue.http.interceptors, myInterceptor);
|
||||
Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
|
||||
});
|
||||
```
|
||||
|
||||
## CSRF token
|
||||
We use a Vue Resource interceptor to manage the CSRF token.
|
||||
`app/assets/javascripts/vue_shared/vue_resource_interceptor.js` holds all our common interceptors.
|
||||
Note: You don't need to load `app/assets/javascripts/vue_shared/vue_resource_interceptor.js`
|
||||
since it's already being loaded by `common_vue.js`.
|
||||
|
||||
|
||||
[vue-resource-repo]: https://github.com/pagekit/vue-resource
|
|
@ -15,6 +15,7 @@
|
|||
"dependencies": {
|
||||
"autosize": "^4.0.0",
|
||||
"axios": "^0.16.2",
|
||||
"axios-mock-adapter": "^1.10.0",
|
||||
"babel-core": "^6.22.1",
|
||||
"babel-eslint": "^7.2.1",
|
||||
"babel-loader": "^7.1.1",
|
||||
|
|
|
@ -1,39 +1,35 @@
|
|||
import Vue from 'vue';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import JobMediator from '~/jobs/job_details_mediator';
|
||||
import job from './mock_data';
|
||||
|
||||
describe('JobMediator', () => {
|
||||
let mediator;
|
||||
let mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mediator = new JobMediator({ endpoint: 'foo' });
|
||||
mediator = new JobMediator({ endpoint: 'jobs/40291672.json' });
|
||||
mock = new MockAdapter(axios);
|
||||
});
|
||||
|
||||
it('should set defaults', () => {
|
||||
expect(mediator.store).toBeDefined();
|
||||
expect(mediator.service).toBeDefined();
|
||||
expect(mediator.options).toEqual({ endpoint: 'foo' });
|
||||
expect(mediator.options).toEqual({ endpoint: 'jobs/40291672.json' });
|
||||
expect(mediator.state.isLoading).toEqual(false);
|
||||
});
|
||||
|
||||
describe('request and store data', () => {
|
||||
const interceptor = (request, next) => {
|
||||
next(request.respondWith(JSON.stringify(job), {
|
||||
status: 200,
|
||||
}));
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
Vue.http.interceptors.push(interceptor);
|
||||
mock.onGet().reply(200, job, {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Vue.http.interceptors = _.without(Vue.http.interceptor, interceptor);
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
it('should store received data', (done) => {
|
||||
mediator.fetchJob();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(mediator.store.state.job).toEqual(job);
|
||||
done();
|
||||
|
|
|
@ -264,6 +264,12 @@ aws4@^1.2.1:
|
|||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
|
||||
|
||||
axios-mock-adapter@^1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.10.0.tgz#3ccee65466439a2c7567e932798fc0377d39209d"
|
||||
dependencies:
|
||||
deep-equal "^1.0.1"
|
||||
|
||||
axios@^0.16.2:
|
||||
version "0.16.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d"
|
||||
|
|
Loading…
Reference in a new issue