Skip to content

Commit 8a7ee3b

Browse files
committed
feat: ✨ Workflow and Concurrency in request
- Workflow support added - Request concurrency support added
1 parent 71a476b commit 8a7ee3b

File tree

12 files changed

+422
-248
lines changed

12 files changed

+422
-248
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## [v1.2.0](https://github.com/contentstack/contentstack-management-javascript/tree/v1.2.0) (.....)
4+
- Bug Fix
5+
- Release Items issue for API key resolved
6+
- Enhanchment
7+
- Request concurrency added in SDK
8+
- New Feature
9+
- Workflow module support added
310
## [v1.1.2](https://github.com/contentstack/contentstack-management-javascript/tree/v1.1.2) (2021-01-07)
411
- Bug Fix
512
- Retry count on multiple request failuer

lib/contentstack.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export function client (params = {}) {
113113
defaultHostName: 'api.contentstack.io'
114114
}
115115

116-
const sdkAgent = `${packages.name}-javascript/${packages.version}`
116+
const sdkAgent = `contentstack-management-javascript/${packages.version}`
117117
const userAgentHeader = getUserAgent(sdkAgent,
118118
params.application,
119119
params.integration,

lib/contentstackClient.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ export default function contentstackClient ({ http }) {
164164
logout: logout,
165165
getUser: getUser,
166166
stack: stack,
167-
organization: organization
167+
organization: organization,
168+
axiosInstance: http
168169
}
169170
}

lib/core/concurrency-queue.js

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import { set } from "lodash";
2+
import Axios from 'axios'
3+
const defaultConfig = {
4+
maxRequests: 5,
5+
retryLimit: 5,
6+
retryDelay: 300,
7+
}
8+
9+
export function ConcurrencyQueue ({axios, config}) {
10+
if (!axios) {
11+
throw Error('Axios instance is not present');
12+
}
13+
14+
if (config) {
15+
if (config.maxRequests && config.maxRequests <= 0) {
16+
throw Error('Concurrency Manager Error: minimun concurrent requests is 1');
17+
} else if (config.retryLimit && config.retryLimit <= 0) {
18+
throw Error('Retry Policy Error: minimun retry limit is 1');
19+
} else if (config.retryDelay && config.retryDelay < 300) {
20+
throw Error('Retry Policy Error: minimun retry delay for requests is 300');
21+
}
22+
}
23+
24+
this.config = Object.assign({}, defaultConfig, config)
25+
this.queue = []
26+
this.running = []
27+
this.paused = false
28+
29+
// Initial shift will check running request,
30+
// and adds request to running queue if max requests are not running
31+
this.initialShift = () => {
32+
if (this.running.length < this.config.maxRequests && !this.paused) {
33+
shift()
34+
}
35+
}
36+
37+
// INTERNAL: Shift the queued item to running queue
38+
let shift = () => {
39+
if (this.queue.length && !this.paused) {
40+
const queueItem = this.queue.shift()
41+
queueItem.resolve(queueItem.request)
42+
this.running.push(queueItem)
43+
}
44+
}
45+
46+
// Append the request at start of queue
47+
this.unshift = requestPromise => {
48+
this.queue.unshift(requestPromise)
49+
}
50+
51+
this.push = requestPromise => {
52+
this.queue.push(requestPromise)
53+
this.initialShift()
54+
}
55+
56+
this.clear = () => {
57+
const requests = this.queue.splice(0, this.queue.length)
58+
requests.forEach((element) => {
59+
element.request.source.cancel()
60+
})
61+
}
62+
63+
// Detach the interceptors
64+
this.detach = () => {
65+
axios.interceptors.request.eject(this.interceptors.request);
66+
axios.interceptors.response.eject(this.interceptors.response);
67+
this.interceptors = {
68+
request: null,
69+
response: null
70+
}
71+
}
72+
73+
// Request interseptor to queue the request
74+
let requestHandler = request => {
75+
request.retryCount = request.retryCount || 0
76+
if (request.headers.authorization && request.headers.authorization !== undefined) {
77+
delete request.headers.authtoken
78+
}
79+
80+
if (request.cancelToken === undefined) {
81+
const source = Axios.CancelToken.source()
82+
request.cancelToken = source.token
83+
request.source = source
84+
}
85+
86+
if (this.paused && request.retryCount > 0) {
87+
return new Promise(resolve => {
88+
this.unshift({request, resolve})
89+
})
90+
}else if (request.retryCount > 0) {
91+
return request
92+
}
93+
94+
return new Promise(resolve => {
95+
this.push({request, resolve})
96+
})
97+
}
98+
99+
let delay = (time) => {
100+
if (!this.paused) {
101+
this.paused = true
102+
if (this.running.length > 0) {
103+
setTimeout(() => {
104+
delay(time)
105+
}, time)
106+
}
107+
return new Promise(resolve => setTimeout(() => {
108+
this.paused = false
109+
for (let i=0; i<this.config.maxRequests; i++) {
110+
this.initialShift()
111+
}
112+
}, time));
113+
}
114+
}
115+
116+
// Response interceptor used for
117+
let responseHandler = (response) => {
118+
this.running.shift()
119+
shift()
120+
return response
121+
}
122+
123+
let responseErrorHandler = error => {
124+
let networkError = error.config.retryCount
125+
if (!this.config.retryOnError || networkError > this.config.retryLimit) {
126+
return Promise.reject(responseHandler(error))
127+
}
128+
129+
// TODO: Error handling
130+
let wait = this.config.retryDelay
131+
let retryErrorType = null
132+
let response = error.response
133+
if (!response) {
134+
if (error.code === 'ECONNABORTED') {
135+
error.response = {
136+
...error.response,
137+
status: 408,
138+
statusText: `timeout of ${this.config.timeout}ms exceeded`
139+
}
140+
}else {
141+
return Promise.reject(responseHandler(error))
142+
}
143+
}else if (response.status === 429) {
144+
retryErrorType = `Error with status: ${response.status}`
145+
networkError++
146+
147+
if (networkError > this.config.retryLimit) {
148+
return Promise.reject(responseHandler(error))
149+
}
150+
this.running.shift()
151+
// Cooldown the running requests
152+
delay(1000)
153+
error.config.retryCount = networkError
154+
155+
return axios(updateRequestConfig(error, retryErrorType, wait))
156+
}else if (this.config.retryCondition && this.config.retryCondition(error)) {
157+
retryErrorType = `Error with status: ${response.status}`
158+
networkError++
159+
if (networkError > this.config.retryLimit) {
160+
return Promise.reject(responseHandler(error))
161+
}
162+
if (this.config.retryDelayOptions) {
163+
if (this.config.retryDelayOptions.customBackoff) {
164+
wait = this.config.retryDelayOptions.customBackoff(networkError, error)
165+
if (wait && wait <= 0) {
166+
return Promise.reject(responseHandler(error))
167+
}
168+
} else if (this.config.retryDelayOptions.base) {
169+
wait = this.config.retryDelayOptions.base * networkError
170+
}
171+
} else {
172+
wait = this.config.retryDelay
173+
}
174+
error.config.retryCount = networkError
175+
return new Promise(function (resolve) {
176+
return setTimeout(function () {
177+
return resolve(axios(updateRequestConfig(error, retryErrorType, wait)))
178+
}, wait)
179+
})
180+
}
181+
182+
return Promise.reject(responseHandler(error))
183+
}
184+
185+
this.interceptors = {
186+
request: null,
187+
response: null
188+
}
189+
190+
let updateRequestConfig = (error, retryErrorType, wait) => {
191+
const requestConfig = error.config
192+
this.config.logHandler('warning', `${retryErrorType} error occurred. Waiting for ${wait} ms before retrying...`)
193+
if (axios !== undefined && axios.defaults !== undefined) {
194+
if (axios.defaults.agent === requestConfig.agent) {
195+
delete requestConfig.agent
196+
}
197+
if (axios.defaults.httpAgent === requestConfig.httpAgent) {
198+
delete requestConfig.httpAgent
199+
}
200+
if (axios.defaults.httpsAgent === requestConfig.httpsAgent) {
201+
delete requestConfig.httpsAgent
202+
}
203+
}
204+
205+
requestConfig.transformRequest = [function (data) {
206+
return data
207+
}]
208+
return requestConfig
209+
}
210+
211+
// Adds interseptors in axios to queue request
212+
this.interceptors.request = axios.interceptors.request.use(requestHandler)
213+
this.interceptors.response = axios.interceptors.response.use(responseHandler, responseErrorHandler)
214+
215+
}

lib/core/contentstack-retry.js

Lines changed: 0 additions & 86 deletions
This file was deleted.

lib/core/contentstackHTTPClient.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import clonedeep from 'lodash/cloneDeep'
22
import Qs from 'qs'
33
import axios from 'axios'
4-
import contentstackRetry from './contentstack-retry'
54
import { isHost } from './Util'
5+
import { ConcurrencyQueue } from './concurrency-queue'
66

77
export default function contentstackHttpClient (options) {
88
const defaultConfig = {
@@ -77,6 +77,6 @@ export default function contentstackHttpClient (options) {
7777
}
7878
const instance = axios.create(axiosOptions)
7979
instance.httpClientParams = options
80-
contentstackRetry(instance, axiosOptions, config.retyLimit, config.retryDelay)
80+
instance.concurrencyQueue = new ConcurrencyQueue({axios: instance, config})
8181
return instance
8282
}

lib/entity.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,20 @@ export const query = ({ http, wrapperCollection }) => {
128128
export const update = (http, type) => {
129129
return async function (param = {}) {
130130
const updateData = {}
131-
updateData[type] = cloneDeep(this)
131+
const json = cloneDeep(this)
132+
delete json.stackHeaders
133+
delete json.urlPath
134+
delete json.uid
135+
delete json.org_uid
136+
delete json.api_key
137+
delete json.created_at
138+
delete json.created_by
139+
delete json.deleted_at
140+
delete json.updated_at
141+
delete json.updated_by
142+
delete json.updated_at
143+
144+
updateData[type] = json
132145
try {
133146
const response = await http.put(this.urlPath, updateData, { headers: {
134147
...cloneDeep(this.stackHeaders)

lib/stack/release/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export function Release (http, data = {}) {
109109
* .then((items) => console.log(items))
110110
*/
111111
this.item = () => {
112-
return new ReleaseItem(http, { releaseUid: this.uid })
112+
return new ReleaseItem(http, { releaseUid: this.uid , stackHeaders: this.stackHeaders })
113113
}
114114
/**
115115
* @description The Deploy a Release request deploys a specific Release to specific environment(s) and locale(s).

0 commit comments

Comments
 (0)