Skip to content

Commit a45f308

Browse files
authored
feat: app action call returning response[EXT-4402] (#1777)
* feat: app action call returning response * fix: changed types to fix build * refactor: function separated for polling * fix: error changed according to app action * refactor: return type changed, retry params removed from public * fix: return types fixed * test: test cases skeleton * refactor: more tests added * fix: test cases * test: fixed unit test cases * test: create app action call test modified * refactor: removed unnecessary code * test: included test for checking response for get app action call * refactor: new method called createAppActionCall * fix: test cases, lint types * refactor: function name changed
1 parent 3ac836f commit a45f308

File tree

7 files changed

+332
-9
lines changed

7 files changed

+332
-9
lines changed

lib/adapters/REST/endpoints/app-action-call.ts

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import type { AxiosInstance } from 'contentful-sdk-core'
2-
import { CreateAppActionCallProps, AppActionCallProps } from '../../../entities/app-action-call'
3-
import { WebhookCallDetailsProps } from '../../../entities/webhook'
2+
import {
3+
AppActionCallProps,
4+
AppActionCallResponse,
5+
CreateAppActionCallProps,
6+
FetchAppActionResponse,
7+
} from '../../../entities/app-action-call'
48
import * as raw from './raw'
59
import { RestEndpoint } from '../types'
610
import { GetAppActionCallDetailsParams, GetAppActionCallParams } from '../../../common-types'
@@ -21,8 +25,74 @@ export const getCallDetails: RestEndpoint<'AppActionCall', 'getCallDetails'> = (
2125
http: AxiosInstance,
2226
params: GetAppActionCallDetailsParams
2327
) => {
24-
return raw.get<WebhookCallDetailsProps>(
28+
return raw.get<AppActionCallResponse>(
2529
http,
2630
`/spaces/${params.spaceId}/environments/${params.environmentId}/actions/${params.appActionId}/calls/${params.callId}`
2731
)
2832
}
33+
34+
const APP_ACTION_CALL_RETRY_INTERVAL = 2000
35+
const APP_ACTION_CALL_RETRIES = 10
36+
37+
async function callAppActionResult(
38+
http: AxiosInstance,
39+
params: GetAppActionCallParams,
40+
{
41+
resolve,
42+
reject,
43+
retryInterval = APP_ACTION_CALL_RETRY_INTERVAL,
44+
retries = APP_ACTION_CALL_RETRIES,
45+
checkCount = 0,
46+
callId,
47+
}: FetchAppActionResponse & {
48+
resolve: (appActionResponse: AppActionCallResponse) => unknown
49+
reject: (err: Error) => unknown
50+
callId: string
51+
}
52+
) {
53+
const appActionResponse = await getCallDetails(http, { ...params, callId })
54+
55+
if (appActionResponse && appActionResponse.sys && appActionResponse.sys.id) {
56+
resolve(appActionResponse)
57+
} else if (checkCount === retries) {
58+
const error = new Error()
59+
error.message = 'The app action response is taking longer than expected to process.'
60+
reject(error)
61+
} else {
62+
checkCount++
63+
setTimeout(
64+
() =>
65+
callAppActionResult(http, params, {
66+
resolve,
67+
reject,
68+
retryInterval,
69+
retries,
70+
checkCount,
71+
callId,
72+
}),
73+
retryInterval
74+
)
75+
}
76+
}
77+
78+
export const createWithResponse: RestEndpoint<'AppActionCall', 'createWithResponse'> = async (
79+
http: AxiosInstance,
80+
params: GetAppActionCallParams,
81+
data: CreateAppActionCallProps
82+
) => {
83+
const createResponse = await raw.post<AppActionCallProps>(
84+
http,
85+
`/spaces/${params.spaceId}/environments/${params.environmentId}/app_installations/${params.appDefinitionId}/actions/${params.appActionId}/calls`,
86+
data
87+
)
88+
89+
const callId = createResponse.sys.id
90+
91+
return new Promise<AppActionCallResponse>((resolve, reject) =>
92+
callAppActionResult(http, params, {
93+
resolve,
94+
reject,
95+
callId,
96+
})
97+
)
98+
}

lib/common-types.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { AxiosRequestConfig, AxiosRequestHeaders } from 'axios'
22
import { OpPatch } from 'json-patch'
33
import { Stream } from 'stream'
44
import { AppActionProps, CreateAppActionProps } from './entities/app-action'
5-
import { AppActionCallProps, CreateAppActionCallProps } from './entities/app-action-call'
5+
import {
6+
AppActionCallProps,
7+
AppActionCallResponse,
8+
CreateAppActionCallProps,
9+
} from './entities/app-action-call'
610
import { AppBundleProps, CreateAppBundleProps } from './entities/app-bundle'
711
import { ApiKeyProps, CreateApiKeyProps } from './entities/api-key'
812
import { AppDefinitionProps, CreateAppDefinitionProps } from './entities/app-definition'
@@ -298,6 +302,11 @@ type MRInternal<UA extends boolean> = {
298302
(opts: MROpts<'AppAction', 'update', UA>): MRReturn<'AppAction', 'update'>
299303

300304
(opts: MROpts<'AppActionCall', 'create', UA>): MRReturn<'AppActionCall', 'create'>
305+
(opts: MROpts<'AppActionCall', 'createWithResponse', UA>): MRReturn<
306+
'AppActionCall',
307+
'createWithResponse'
308+
>
309+
(opts: MROpts<'AppActionCall', 'getCallDetails', UA>): MRReturn<'AppActionCall', 'getCallDetails'>
301310

302311
(opts: MROpts<'AppBundle', 'get', UA>): MRReturn<'AppBundle', 'get'>
303312
(opts: MROpts<'AppBundle', 'getMany', UA>): MRReturn<'AppBundle', 'getMany'>
@@ -705,7 +714,12 @@ export type MRActions = {
705714
}
706715
getCallDetails: {
707716
params: GetAppActionCallDetailsParams
708-
return: WebhookCallDetailsProps
717+
return: AppActionCallResponse
718+
}
719+
createWithResponse: {
720+
params: GetAppActionCallParams
721+
payload: CreateAppActionCallProps
722+
return: AppActionCallResponse
709723
}
710724
}
711725
AppBundle: {

lib/entities/app-action-call.ts

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
import copy from 'fast-copy'
22
import { toPlainObject } from 'contentful-sdk-core'
33
import { Except } from 'type-fest'
4-
import { BasicMetaSysProps, DefaultElements, MakeRequest, SysLink } from '../common-types'
4+
import {
5+
BasicMetaSysProps,
6+
DefaultElements,
7+
GetAppActionCallDetailsParams,
8+
GetAppActionCallParams,
9+
MakeRequest,
10+
SysLink,
11+
} from '../common-types'
12+
import { WebhookCallDetailsProps } from './webhook'
13+
import enhanceWithMethods from '../enhance-with-methods'
514

6-
type AppActionCallSys = Except<BasicMetaSysProps, 'version' | 'id'> & {
15+
type AppActionCallSys = Except<BasicMetaSysProps, 'version'> & {
716
appDefinition: SysLink
817
space: SysLink
918
environment: SysLink
@@ -22,8 +31,65 @@ export type CreateAppActionCallProps = {
2231
parameters: { [key: string]: any }
2332
}
2433

34+
type AppActionCallApi = {
35+
createWithResponse(): Promise<AppActionCallResponse>
36+
getCallDetails(): Promise<AppActionCallResponse>
37+
}
38+
39+
export type AppActionCallResponse = WebhookCallDetailsProps
40+
41+
export interface AppActionCallResponseData
42+
extends AppActionCallResponse,
43+
DefaultElements<AppActionCallResponse>,
44+
AppActionCallApi {}
45+
2546
export interface AppActionCall extends AppActionCallProps, DefaultElements<AppActionCallProps> {}
2647

48+
/**
49+
* @private
50+
*/
51+
export default function createAppActionCallApi(makeRequest: MakeRequest): AppActionCallApi {
52+
return {
53+
createWithResponse: function () {
54+
const payload: CreateAppActionCallProps = {
55+
parameters: {
56+
recipient: 'Alice <alice@my-company.com>',
57+
message_body: 'Hello from Bob!',
58+
},
59+
}
60+
61+
return makeRequest({
62+
entityType: 'AppActionCall',
63+
action: 'createWithResponse',
64+
params: {
65+
spaceId: 'space-id',
66+
environmentId: 'environment-id',
67+
appDefinitionId: 'app-definiton-id',
68+
appActionId: 'app-action-id',
69+
},
70+
payload: payload,
71+
}).then((data) => wrapAppActionCallResponse(makeRequest, data))
72+
},
73+
74+
getCallDetails: function getCallDetails() {
75+
const getParams = (raw: GetAppActionCallDetailsParams): GetAppActionCallDetailsParams => ({
76+
spaceId: raw.spaceId,
77+
environmentId: raw.environmentId,
78+
callId: raw.callId,
79+
appActionId: raw.appActionId,
80+
})
81+
82+
const raw = this.toPlainObject() as GetAppActionCallDetailsParams
83+
84+
return makeRequest({
85+
entityType: 'AppActionCall',
86+
action: 'getCallDetails',
87+
params: getParams(raw),
88+
}).then((data) => wrapAppActionCallResponse(makeRequest, data))
89+
},
90+
}
91+
}
92+
2793
/**
2894
* @private
2995
* @param http - HTTP client instance
@@ -37,3 +103,27 @@ export function wrapAppActionCall(
37103
const signedRequest = toPlainObject(copy(data))
38104
return signedRequest
39105
}
106+
107+
/**
108+
* @private
109+
* @param http - HTTP client instance
110+
* @param data - Raw AppActionCall data
111+
* @return Wrapped AppActionCall data
112+
*/
113+
export function wrapAppActionCallResponse(
114+
makeRequest: MakeRequest,
115+
data: AppActionCallResponse
116+
): AppActionCallResponseData {
117+
const appActionCallResponse = toPlainObject(copy(data))
118+
const appActionCallResponseWithMethods = enhanceWithMethods(
119+
appActionCallResponse,
120+
createAppActionCallApi(makeRequest)
121+
)
122+
return appActionCallResponseWithMethods
123+
}
124+
125+
export interface FetchAppActionResponse {
126+
retryInterval?: number
127+
retries?: number
128+
checkCount?: number
129+
}

lib/plain/common-types.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,11 @@ import { DefaultParams, OptionalDefaults } from './wrappers/wrap'
117117
import { AssetKeyProps, CreateAssetKeyProps } from '../entities/asset-key'
118118
import { AppUploadProps } from '../entities/app-upload'
119119
import { AppActionProps, CreateAppActionProps } from '../entities/app-action'
120-
import { AppActionCallProps, CreateAppActionCallProps } from '../entities/app-action-call'
120+
import {
121+
AppActionCallProps,
122+
AppActionCallResponse,
123+
CreateAppActionCallProps,
124+
} from '../entities/app-action-call'
121125
import { AppBundleProps, CreateAppBundleProps } from '../entities/app-bundle'
122126
import { AppDetailsProps, CreateAppDetailsProps } from '../entities/app-details'
123127
import { AppSignedRequestProps, CreateAppSignedRequestProps } from '../entities/app-signed-request'
@@ -215,7 +219,11 @@ export type PlainClientAPI = {
215219
): Promise<AppActionCallProps>
216220
getCallDetails(
217221
params: OptionalDefaults<GetAppActionCallDetailsParams>
218-
): Promise<WebhookCallDetailsProps>
222+
): Promise<AppActionCallResponse>
223+
createWithResponse(
224+
params: OptionalDefaults<GetAppActionCallParams>,
225+
payload: CreateAppActionCallProps
226+
): Promise<AppActionCallResponse>
219227
}
220228
appBundle: {
221229
get(params: OptionalDefaults<GetAppBundleParams>): Promise<AppBundleProps>

lib/plain/plain-client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export const createPlainClient = (
6868
appActionCall: {
6969
create: wrap(wrapParams, 'AppActionCall', 'create'),
7070
getCallDetails: wrap(wrapParams, 'AppActionCall', 'getCallDetails'),
71+
createWithResponse: wrap(wrapParams, 'AppActionCall', 'createWithResponse'),
7172
},
7273
appBundle: {
7374
get: wrap(wrapParams, 'AppBundle', 'get'),
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { expect } from 'chai'
2+
import { describe, it } from 'mocha'
3+
import { cloneMock } from '../../../mocks/entities'
4+
import setupRestAdapter from '../helpers/setupRestAdapter'
5+
import sinon from 'sinon'
6+
import {
7+
CreateAppActionCallProps,
8+
wrapAppActionCallResponse,
9+
} from '../../../../../lib/entities/app-action-call'
10+
import { MakeRequest, MakeRequestOptions } from '../../../../../lib/export-types'
11+
12+
function setup(mockName, params = {}) {
13+
const entityMock = cloneMock(mockName)
14+
const promise = Promise.resolve({ data: entityMock })
15+
return {
16+
...setupRestAdapter(promise, params),
17+
entityMock,
18+
}
19+
}
20+
21+
describe('Rest App Action Call', () => {
22+
it('should create a new App Action Call', async () => {
23+
const { httpMock, adapterMock, entityMock } = setup('appActionCallResponse')
24+
const entity = wrapAppActionCallResponse(
25+
((...args: [MakeRequestOptions]) => adapterMock.makeRequest(...args)) as MakeRequest,
26+
entityMock
27+
)
28+
29+
const payload: CreateAppActionCallProps = {
30+
parameters: {
31+
recipient: 'Alice <alice@my-company.com>',
32+
message_body: 'Hello from Bob!',
33+
},
34+
}
35+
36+
const response = await entity.createWithResponse()
37+
38+
expect(response).to.be.an('object')
39+
expect(response).to.deep.equal(entityMock)
40+
41+
sinon.assert.calledWith(
42+
httpMock.post,
43+
`/spaces/space-id/environments/environment-id/app_installations/app-definiton-id/actions/app-action-id/calls`,
44+
payload
45+
)
46+
47+
sinon.assert.calledWith(
48+
httpMock.get,
49+
`/spaces/space-id/environments/environment-id/actions/app-action-id/calls/call-id`
50+
)
51+
})
52+
53+
it('should get details of an App Action Call', async () => {
54+
const { httpMock, adapterMock, entityMock } = setup('appActionCallDetails')
55+
const entity = wrapAppActionCallResponse(
56+
((...args: [MakeRequestOptions]) => adapterMock.makeRequest(...args)) as MakeRequest,
57+
entityMock
58+
)
59+
60+
const response = await entity.getCallDetails()
61+
62+
expect(response).to.be.an('object')
63+
expect(response).to.deep.equal(entityMock)
64+
65+
sinon.assert.calledWith(
66+
httpMock.get,
67+
`/spaces/space-id/environments/environment-id/actions/app-action-id/calls/call-id`
68+
)
69+
})
70+
71+
it('should throw an error if maximum retries is reached', async () => {
72+
const { httpMock } = setup('appActionCall')
73+
74+
const callAppActionResultStub = sinon
75+
.stub()
76+
.rejects(new Error('The app action response is taking longer than expected to process.'))
77+
78+
const params = {
79+
spaceId: 'space-id',
80+
environmentId: 'environment-id',
81+
appDefinitionId: 'app-definition-id',
82+
appActionId: 'app-action-id',
83+
}
84+
85+
try {
86+
await callAppActionResultStub(httpMock, params, {
87+
resolve: sinon.fake(),
88+
reject: sinon.fake(),
89+
callId: 'call-id',
90+
retries: 2,
91+
checkCount: 2,
92+
})
93+
} catch (error) {
94+
expect(error.message).to.equal(
95+
'The app action response is taking longer than expected to process.'
96+
)
97+
}
98+
})
99+
})

0 commit comments

Comments
 (0)