Skip to content

wip: add createWithResult method and AppActionCallStructuredResult type [EXT-6156] #2681

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

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions lib/adapters/REST/endpoints/app-action-call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
AppActionCallProps,
AppActionCallResponse,
CreateAppActionCallProps,
AppActionCallStructuredResult,
} from '../../../entities/app-action-call'
import * as raw from './raw'
import type { RestEndpoint } from '../types'
Expand Down Expand Up @@ -109,3 +110,75 @@ export const createWithResponse: RestEndpoint<'AppActionCall', 'createWithRespon

return callAppActionResult(http, params, { callId })
}

export const createWithResult: RestEndpoint<'AppActionCall', 'createWithResult'> = async (
http: AxiosInstance,
params: CreateWithResponseParams,
data: CreateAppActionCallProps
) => {
const createResponse = await raw.post<AppActionCallProps>(
http,
`/spaces/${params.spaceId}/environments/${params.environmentId}/app_installations/${params.appDefinitionId}/actions/${params.appActionId}/calls`,
data
)

const callId = createResponse.sys.id

return callAppActionStructuredResult(http, params, { callId })
}

async function callAppActionStructuredResult(
http: AxiosInstance,
params: CreateWithResponseParams,
{
callId,
}: {
callId: string
}
): Promise<AppActionCallStructuredResult> {
let checkCount = 1
const retryInterval = params.retryInterval || APP_ACTION_CALL_RETRY_INTERVAL
const retries = params.retries || APP_ACTION_CALL_RETRIES

return new Promise((resolve, reject) => {
const poll = async () => {
try {
// Use format=structured to get AppActionCall format instead of raw webhook logs
const result = await raw.get<AppActionCallStructuredResult>(
http,
`/spaces/${params.spaceId}/environments/${params.environmentId}/actions/${params.appActionId}/calls/${callId}?format=structured`
)

// Check if the app action call is completed
if (result.sys.status === 'succeeded' || result.sys.status === 'failed') {
resolve(result)
}
// The call is still processing, continue polling
else if (result.sys.status === 'processing' && checkCount < retries) {
checkCount++
await waitFor(retryInterval)
poll()
}
// Timeout - the processing is taking too long
else {
const error = new Error(
'The app action response is taking longer than expected to process.'
)
reject(error)
}
} catch (error) {
checkCount++

if (checkCount > retries) {
reject(new Error('The app action response is taking longer than expected to process.'))
return
}
// If the call throws, we re-poll as it might mean that the result is not available yet
await waitFor(retryInterval)
poll()
}
}

poll()
})
}
24 changes: 19 additions & 5 deletions lib/common-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
AppActionCallProps,
AppActionCallResponse,
CreateAppActionCallProps,
AppActionCallStructuredResult,
} from './entities/app-action-call'
import type { AppBundleProps, CreateAppBundleProps } from './entities/app-bundle'
import type {
Expand Down Expand Up @@ -361,7 +362,7 @@ interface CursorPaginationBase {
limit?: number
}

// Interfaces for each exclusive shape
// Interfaces for each "exclusive" shape
interface CursorPaginationPageNext extends CursorPaginationBase {
pageNext: string
pagePrev?: never
Expand Down Expand Up @@ -444,6 +445,10 @@ type MRInternal<UA extends boolean> = {
'AppActionCall',
'createWithResponse'
>
(opts: MROpts<'AppActionCall', 'createWithResult', UA>): MRReturn<
'AppActionCall',
'createWithResult'
>
(opts: MROpts<'AppActionCall', 'getCallDetails', UA>): MRReturn<'AppActionCall', 'getCallDetails'>

(opts: MROpts<'AppBundle', 'get', UA>): MRReturn<'AppBundle', 'get'>
Expand Down Expand Up @@ -1013,15 +1018,24 @@ export type MRActions = {
create: {
params: GetAppActionCallParams
payload: CreateAppActionCallProps
headers?: RawAxiosRequestHeaders
return: AppActionCallProps
}
getCallDetails: {
params: GetAppActionCallDetailsParams
createWithResponse: {
params: CreateWithResponseParams
payload: CreateAppActionCallProps
headers?: RawAxiosRequestHeaders
return: AppActionCallResponse
}
createWithResponse: {
params: GetAppActionCallParams
createWithResult: {
params: CreateWithResponseParams
payload: CreateAppActionCallProps
headers?: RawAxiosRequestHeaders
return: AppActionCallStructuredResult
}
getCallDetails: {
params: GetAppActionCallDetailsParams
headers?: RawAxiosRequestHeaders
return: AppActionCallResponse
}
}
Expand Down
88 changes: 88 additions & 0 deletions lib/entities/app-action-call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,63 @@ export type CreateAppActionCallProps = {
type AppActionCallApi = {
createWithResponse(): Promise<AppActionCallResponse>
getCallDetails(): Promise<AppActionCallResponse>
createWithResult(): Promise<AppActionCallStructuredResult>
}

export type AppActionCallResponse = WebhookCallDetailsProps

// Structured AppActionCall result format that matches the solution document
export type AppActionCallStructuredResult = {
sys: {
type: 'AppActionCall'
id: string
status: 'processing' | 'succeeded' | 'failed'
action: {
sys: {
type: 'Link'
linkType: 'AppAction'
id: string
}
}
space: {
sys: {
type: 'Link'
linkType: 'Space'
id: string
}
}
environment?: {
sys: {
type: 'Link'
linkType: 'Environment'
id: string
}
}
createdAt: string
updatedAt: string
}
result?: any
error?: {
name?: string
message: string
details?: any
sys?: {
type: 'Error'
id: string
}
}
}

export interface AppActionCallResponseData
extends AppActionCallResponse,
DefaultElements<AppActionCallResponse>,
AppActionCallApi {}

export interface AppActionCallStructuredData
extends AppActionCallStructuredResult,
DefaultElements<AppActionCallStructuredResult>,
AppActionCallApi {}

export interface AppActionCall extends AppActionCallProps, DefaultElements<AppActionCallProps> {}

/**
Expand Down Expand Up @@ -76,6 +124,28 @@ export default function createAppActionCallApi(
}).then((data) => wrapAppActionCallResponse(makeRequest, data))
},

createWithResult: function () {
const payload: CreateAppActionCallProps = {
parameters: {
recipient: 'Alice <alice@my-company.com>',
message_body: 'Hello from Bob!',
},
}

return makeRequest({
entityType: 'AppActionCall',
action: 'createWithResult',
params: {
spaceId: 'space-id',
environmentId: 'environment-id',
appDefinitionId: 'app-definiton-id',
appActionId: 'app-action-id',
...retryOptions,
},
payload: payload,
}).then((data) => wrapAppActionCallStructuredResult(makeRequest, data))
},

getCallDetails: function getCallDetails() {
return makeRequest({
entityType: 'AppActionCall',
Expand Down Expand Up @@ -127,3 +197,21 @@ export function wrapAppActionCallResponse(
)
return appActionCallResponseWithMethods
}

/**
* @private
* @param http - HTTP client instance
* @param data - Raw AppActionCall data
* @return Wrapped AppActionCall data
*/
export function wrapAppActionCallStructuredResult(
makeRequest: MakeRequest,
data: AppActionCallStructuredResult
): AppActionCallStructuredData {
const appActionCallStructuredResult = toPlainObject(copy(data))
const appActionCallStructuredResultWithMethods = enhanceWithMethods(
appActionCallStructuredResult,
createAppActionCallApi(makeRequest)
)
return appActionCallStructuredResultWithMethods
}
10 changes: 10 additions & 0 deletions lib/entities/app-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ type BaseAppActionProps = AppActionCategory & {
* Human readable description of the action
*/
description?: string
/**
* JSON Schema defining the expected response format from the action
* Used for validating and structuring app action call results
*/
resultSchema?: Record<string, any>
}

type CreateEndpointAppActionProps = {
Expand Down Expand Up @@ -130,6 +135,11 @@ type LegacyFunctionAppActionProps = Record<string, unknown> & {
export type CreateAppActionProps = AppActionCategory & {
name: string
description?: string
/**
* JSON Schema defining the expected response format from the action
* Used for validating and structuring app action call results
*/
resultSchema?: Record<string, any>
} & (CreateEndpointAppActionProps | CreateFunctionAppActionProps | LegacyFunctionAppActionProps)

export type AppActionProps = BaseAppActionProps &
Expand Down
1 change: 1 addition & 0 deletions lib/export-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type {
export type {
AppActionCall,
AppActionCallProps,
AppActionCallStructuredResult,
CreateAppActionCallProps,
} from './entities/app-action-call'
export type {
Expand Down
48 changes: 42 additions & 6 deletions lib/plain/entities/app-action-call.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { GetAppActionCallDetailsParams, GetAppActionCallParams } from '../../common-types'
import type { GetAppActionCallDetailsParams, GetAppActionCallParams, CreateWithResponseParams } from '../../common-types'
import type {
AppActionCallProps,
AppActionCallResponse,
AppActionCallStructuredResult,
CreateAppActionCallProps,
} from '../../entities/app-action-call'
import type { OptionalDefaults } from '../wrappers/wrap'
import type { RawAxiosRequestHeaders } from 'axios'

export type AppActionCallPlainClientAPI = {
/**
Expand All @@ -30,7 +32,8 @@ export type AppActionCallPlainClientAPI = {
*/
create(
params: OptionalDefaults<GetAppActionCallParams>,
payload: CreateAppActionCallProps
payload: CreateAppActionCallProps,
headers?: RawAxiosRequestHeaders
): Promise<AppActionCallProps>
/**
* Fetches the details of an App Action Call
Expand All @@ -51,10 +54,10 @@ export type AppActionCallPlainClientAPI = {
params: OptionalDefaults<GetAppActionCallDetailsParams>
): Promise<AppActionCallResponse>
/**
* Calls (triggers) an App Action
* Calls (triggers) an App Action and returns raw webhook log format
* @param params entity IDs to identify the App Action to call
* @param payload the payload to be sent to the App Action
* @returns detailed metadata about the App Action Call
* @returns detailed metadata about the App Action Call (raw webhook log format)
* @throws if the request fails, or the App Action is not found
* @example
* ```javascript
Expand All @@ -72,7 +75,40 @@ export type AppActionCallPlainClientAPI = {
* ```
*/
createWithResponse(
params: OptionalDefaults<GetAppActionCallParams>,
payload: CreateAppActionCallProps
params: OptionalDefaults<CreateWithResponseParams>,
payload: CreateAppActionCallProps,
headers?: RawAxiosRequestHeaders
): Promise<AppActionCallResponse>
/**
* Calls (triggers) an App Action and returns structured AppActionCall format
* @param params entity IDs to identify the App Action to call
* @param payload the payload to be sent to the App Action
* @returns structured AppActionCall with result/error fields
* @throws if the request fails, or the App Action is not found
* @example
* ```javascript
* const appActionCall = await client.appActionCall.createWithResult(
* {
* spaceId: "<space_id>",
* environmentId: "<environment_id>",
* appDefinitionId: "<app_definition_id>",
* appActionId: "<app_action_id>",
* },
* {
* parameters: { // ... },
* }
* );
*
* if (appActionCall.sys.status === 'succeeded') {
* console.log('Result:', appActionCall.result);
* } else if (appActionCall.sys.status === 'failed') {
* console.log('Error:', appActionCall.error);
* }
* ```
*/
createWithResult(
params: OptionalDefaults<CreateWithResponseParams>,
payload: CreateAppActionCallProps,
headers?: RawAxiosRequestHeaders
): Promise<AppActionCallStructuredResult>
}
Loading