Skip to content

Commit

Permalink
Merge pull request #495 from ChartIQ/432-return-data-from-an-intent
Browse files Browse the repository at this point in the history
432 Added the ability to return data from a raised intent
  • Loading branch information
kriswest authored Feb 15, 2022
2 parents 095481b + e3ccac0 commit 9d5969a
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 51 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* Added support for raiseIntent without a context via the addition of the `fdc3.nothing` context type ([#375](https://github.com/finos/FDC3/pull/375))
* Added [**FDC3 Workbench**](https://fdc3.finos.org/toolbox/fdc3-workbench/), an FDC3 API developer application ([#457](https://github.com/finos/FDC3/pull/457))
* Added advice on how to `broadcast` complex context types, composed of other types, so that other apps can listen for both the complex type and simpler constituent types ([#464](https://github.com/finos/FDC3/pull/464))
* Added the ability to return data from an intent, via the addition of an IntentHandler type and a `getResult()` to IntentResolution, both of which return a Promise of a Context object. ([#495](https://github.com/finos/FDC3/pull/495))
* Added error `UserCancelled` to the `ResolveError` enumeration to be used when user closes the resolver UI or otherwise cancels resolution of a raised intent ([#522 ](https://github.com/finos/FDC3/pull/522))
* Added an `instanceId` (and optional `instanceMetadata`) field to `AppMetadata` allowing it to refer to specific app instances and thereby supporting targetting of intents to specific app instances. Also added a `findInstanes()` function to the desktop agent. ([#509]((https://github.com/finos/FDC3/pull/509))
* Added a References and Bibliography section to the Standard's documentation to hold links to 'normative references' and other documentation that is useful for understanding the standard ([#530](https://github.com/finos/FDC3/pull/530))
Expand Down
37 changes: 29 additions & 8 deletions docs/api/ref/DesktopAgent.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ interface DesktopAgent {
findIntentsByContext(context: Context): Promise<Array<AppIntent>>;
raiseIntent(intent: string, context: Context, app?: TargetApp): Promise<IntentResolution>;
raiseIntentForContext(context: Context, app?: TargetApp): Promise<IntentResolution>;
addIntentListener(intent: string, handler: ContextHandler): Promise<Listener>;
addIntentListener(intent: string, handler: IntentHandler): Promise<Listener>;

// channels
getOrCreateChannel(channelId: string): Promise<Channel>;
Expand Down Expand Up @@ -75,27 +75,39 @@ const contactListener = await fdc3.addContextListener('fdc3.contact', contact =>
#### See also
* [`Listener`](Types#listener)
* [`Context`](Types#context)
* [`ContextHandler`](Types#contexthandler)



### `addIntentListener`

```ts
addIntentListener(intent: string, handler: ContextHandler): Promise<Listener>;
addIntentListener(intent: string, handler: IntentHandler): Promise<Listener>;
```
Adds a listener for incoming Intents from the Agent.
Adds a listener for incoming Intents from the Agent. The handler function may return void or a promise that should resolve to a context object representing any data that should be returned to the app that raised the intent. If an error is thrown by the handler function, the promise returned is rejected, or a promise is not returned then the Desktop Agent MUST reject the promise returned by the `getResult()` function of the `IntentResolution`.

#### Examples

```js
const listener = await fdc3.addIntentListener('StartChat', context => {
// start chat has been requested by another application
//Handle a raised intent
const listener = fdc3.addIntentListener('StartChat', context => {
// start chat has been requested by another application
return;
});

//Handle a raised intent and return Context data via a promise
fdc3.addIntentListener("CreateOrder", (context) => {
return new Promise<Context>((resolve) => {
// go create the order
resolve({type: "fdc3.order", id: { "orderId": 1234 }});
});
});
```

#### See also
* [`Listener`](Types#listener)
* [`Context`](Types#context)
* [`IntentHandler`](Types#intenthandler)



Expand Down Expand Up @@ -443,15 +455,15 @@ Alternatively, the specific app or app instance to target can also be provided.

If you wish to raise an intent without a context, use the `fdc3.nothing` context type. This type exists so that apps can explicitly declare support for raising an intent without context.

Returns an `IntentResolution` object with details of the app instance that was selected (or started) to respond to the intent.
Returns an `IntentResolution` object with details of the app that was selected to respond to the intent. If the application that resolves the intent returns a promise of Context data, this may be retrieved via the `getResult()` function of the IntentResolution object. If an error occurs (i.e. an error is thrown by the handler function, the promise it returns is rejected, or a promse is not returned by the handler function) then the Desktop Agent MUST reject the promise returned by the `getResult()` function of the `IntentResolution` with a string from the `DataError` enumeration.

If a target app for the intent cannot be found with the criteria provided or the user either closes the resolver UI or otherwise cancels resolution, an `Error` with a string from the [`ResolveError`](Errors#resolveerror) enumeration is returned. If a specific target `app` parameter was set, but either the app or app instance is not available then the `ResolveError.TargetAppUnavailable` or `ResolveError.TargetInstanceUnavailable` errors MUST be returned.

#### Example

```js
// raise an intent for resolution by the desktop agent
// a resolver UI may be displayed, or another method of resolving the intent to a
// a resolver UI may be displayed, or another method of resolving the intent to a
// target applied, if more than one application can resolve the intent
await fdc3.raiseIntent("StartChat", context);

Expand All @@ -463,6 +475,15 @@ await fdc3.raiseIntent("StartChat", context, appIntent.apps[0]);

//Raise an intent without a context by using the null context type
await fdc3.raiseIntent("StartChat", {type: "fdc3.nothing"});

//Raise an intent and retrieve data from the IntentResolution
let resolution = await agent.raiseIntent("intentName", context);
try {
const result = await resolution.getResult();
console.log(`${resolution.source} returned ${JSON.stringify(result)}`);
} catch(error) {
console.error(`${resolution.source} returned a data error: ${error}`);
}
```
#### See also
* [`Context`](Types#context)
Expand All @@ -483,7 +504,7 @@ Alternatively, the specific app or app instance to target can also be provided,

Using `raiseIntentForContext` is similar to calling `findIntentsByContext`, and then raising an intent against one of the returned apps, except in this case the desktop agent has the opportunity to provide the user with a richer selection interface where they can choose both the intent and target app.

Returns an `IntentResolution` object with a handle to the app that responded to the selected intent.
Returns an `IntentResolution` object with details of the app that was selected to respond to the intent. If the application that resolves the intent returns a promise of Context data, this may be retrieved via the `getResult()` function of the IntentResolution object. If an error occurs (i.e. an error is thrown by the handler function, the promise it returns is rejected, or a promse is not returned by the handler function) then the Desktop Agent MUST reject the promise returned by the `getResult()` function of the `IntentResolution` with a string from the `DataError` enumeration.

If a target app for the intent cannot be found with the criteria provided or the user either closes the resolver UI or otherwise cancels resolution, an `Error` with a string from the [`ResolveError`](Errors#resolveerror) enumeration is returned. If a specific target `app` parameter was set, but either the app or app instance is not available then the `ResolveError.TargetAppUnavailable` or `ResolveError.TargetInstanceUnavailable` errors MUST be returned.

Expand Down
16 changes: 16 additions & 0 deletions docs/api/ref/Errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ Contains constants representing the errors that can be encountered when calling
* [`DesktopAgent.raiseIntent`](DesktopAgent#raiseintent)
* [`DesktopAgent.raiseIntentForContext`](DesktopAgent#raiseintentforcontext)

## `DataError`

```typescript
enum DataError {
NoDataReturned = 'NoDataReturned',
IntentHandlerRejected = 'IntentHandlerRejected',
}
```

Contains constants representing the errors that can be encountered when calling the [`getResult`](DesktopAgent#findintent) method on the [IntentResolution](Metadata#intentresolution) Object.

#### See also
* [`DesktopAgent.addIntentListener`](DesktopAgent#addintentlistener)
* [`DesktopAgent.raiseIntent`](DesktopAgent#raiseintent)
* [`IntentResolution`](Metadata#intentresolution)

## `ChannelError`

```typescript
Expand Down
28 changes: 21 additions & 7 deletions docs/api/ref/Metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,24 +161,29 @@ interface IntentResolution {
* chose in response to `fdc3.raiseIntentForContext()`.
*/
readonly intent: string;
/**
* @deprecated not assignable from intent listeners
*/
readonly data?: object;
/**
* The version number of the Intents schema being used.
*/
readonly version?: string;

/**
* Retrieves a promise that will resolve to data returned by the
* application that resolves the raised intent. If an error occurs
* (i.e. an error is thrown by the handler function, the promise
* returned by the handler function is rejected, or no promise is
* returned) then the Desktop Agent MUST reject the promise
* returned by the `getResult()` function of the `IntentResolution`
* with a string from the `DataError` enumeration.
*/
getResult(): Promise<Context>;
}
```

IntentResolution provides a standard format for data returned upon resolving an intent.

#### Examples
```js
// resolve a "Chain" type intent
let resolution = await fdc3.raiseIntent("intentName", context);
//resolve a "Chain" type intent
let resolution = await agent.raiseIntent("intentName", context);

// Use metadata about the resolving app instance to target a further intent
try {
Expand All @@ -189,6 +194,15 @@ try {
await agent.raiseIntent("UpdateOrder", context, resolution.source);
}
catch (err) { ... }

//resolve a "Client-Service" type intent with a data response
let resolution = await agent.raiseIntent("intentName", context);
try {
const result = await resolution.getResult();
console.log(`${resolution.source} returned ${JSON.stringify(result)}`);
} catch(error) {
console.error(`${resolution.source} returned an error: ${error}`);
}
```

#### See also
Expand Down
18 changes: 16 additions & 2 deletions docs/api/ref/Types.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,28 @@ type ContextHandler = (context: Context) => void;

Describes a callback that handles a context event.

Used when attaching listeners for context broadcasts and raised intents.
Used when attaching listeners for context broadcasts.

#### See also
* [`Context`](#context)
* [`DesktopAgent.addIntentListener`](DesktopAgent#addintentlistener)
* [`DesktopAgent.addContextListener`](DesktopAgent#addcontextlistener)
* [`Channel.addContextListener`](Channel#addcontextlistener)

## `IntentHandler`

```typescript
type IntentHandler = (context: Context) => Promise<Context> | void;
```

Describes a callback that handles a context event and may return a promise of a Context object to be returned to the application that raised the intent.

Used when attaching listeners for raised intents.

#### See also
* [`Context`](#context)
* [`DesktopAgent.addIntentListener`](DesktopAgent#addintentlistener)
* [`Channel.addContextListener`](Channel#addcontextlistener)

## `Listener`

A Listener object is returned when an application subscribes to intents or context broadcasts via the [`addIntentListener`](#addintentlistener) or [`addContextListener`](#addcontextlistener) methods on the [DesktopAgent](DesktopAgent) object.
Expand Down
40 changes: 25 additions & 15 deletions docs/api/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The global `window.fdc3` must only be available after the API is ready to use. T

```js
function fdc3Stuff() {
// Make some fdc3 API calls here
// Make fdc3 API calls here
}

if (window.fdc3) {
Expand Down Expand Up @@ -99,12 +99,14 @@ Intents provide a way for an app to request functionality from another app and d
- **Remote API**: An app wants to remote an entire API that it owns to another App. In this case, the API for the App cannot be standardized. However, the FDC3 API can address how an App connects to another App in order to get access to a proprietary API.

#### Intents and Context
When raising an intent a specific context may be provided. The type of the provided context may determine which applications can resolve the intent.
When raising an intent a specific context may be provided as input. The type of the provided context may determine which applications can resolve the intent.

A context type may also be associated with multiple intents. For example, an `fdc3.instrument` could be associated with `ViewChart`, `ViewNews`, `ViewAnalysis` or other intents. In addition to raising a specific intent, you can raise an intent for a specific context allowing the Desktop Agent or the user (if the intent is ambiguous) to select the appropriate intent for the selected context and then to raise that intent for resolution.

To raise an intent without a context, use the `fdc3.nothing` context type. This type exists so that applications can explicitly declare that they support raising an intent without a context (when registering an intent listener or in an App Directory).

An optional context object may also be returned as output by an application resolving an intent. For example, an application resolving a `CreateOrder` intent might return a context representing the order and including an ID, allowing the application that raised the intent to make further calls using that ID.

#### Intent Resolution
Raising an intent will return a Promise-type object that will resolve/reject based on a number of factors.

Expand All @@ -119,14 +121,6 @@ Raising an intent will return a Promise-type object that will resolve/reject bas

##### Resolution Object

> **Deprecation notice**
>
> It is not currently possible to provide a value for the `data` property described below,
as intent listeners don't currently offer a way to return values.
>
> Future versions of FDC3 plan to remove the optional `data` property from the intent resolution object,
and include a more robust mechanism for intents that need to return data back to the caller.

If the raising of the intent resolves (or rejects), a standard object will be passed into the resolver function with the following format:

```ts
Expand All @@ -142,14 +136,18 @@ interface IntentResolution {
* chose in response to `fdc3.raiseIntentForContext()`.
*/
readonly intent: string;
/**
* @deprecated not assignable from intent listeners
*/
readonly data?: object;
/**
* The version number of the Intents schema being used.
*/
readonly version?: string;
/**
* Retrieves a promise that will resolve to data returned by the application that
* resolves the raised intent. If an error occurs (i.e. an error is thrown by the handler
* function, the promise returned is rejected, or no promise is returned) then the Desktop
* Agent MUST reject the promise returned by the `getResult()` function of the
* `IntentResolution` with a string from the `DataError` enumeration.
*/
getResult(): Promise<Context>;
}
```

Expand All @@ -162,7 +160,8 @@ try {
catch (err){ ... }
```

or to raise an unspecified intent for a specific context, where the user will select an intent from a resolver dialog:
or to raise an unspecified intent for a specific context, where the user may select an intent from a resolver dialog:

```js
try {
const resolution = await fdc3.raiseIntentForContext(context);
Expand All @@ -185,6 +184,17 @@ try {
catch (err) { ... }
```

Raise an intent and retrieve data from the IntentResolution:
```js
let resolution = await agent.raiseIntent("intentName", context);
try {
const result = await resolution.getResult();
console.log(`${resolution.source} returned ${JSON.stringify(result)}`);
} catch(error) {
console.error(`${resolution.source} returned a data error: ${error}`);
}
```

#### Resolvers
Intents functionality is dependent on resolver functionality to map the intent to a specific App. This will often require end-user input. Resolution can either be performed by the Desktop Agent (for example, by displaying a resolver UI allowing the user to pick the desired app or app instance for the intent) or by the app handling the resolution itself (by using the `findIntents` API and specifying a target app or app instance when invoking the Intent), e.g.:

Expand Down
Loading

0 comments on commit 9d5969a

Please sign in to comment.