Skip to content
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

498 Allow intents to be resolved on output type (where they return data) #499

Closed
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
167a7d4
Merge branch '432-return-data-from-an-intent' into 498-resolve-intent…
kriswest Nov 12, 2021
85619ec
Add suport for metadata on output types for intent to appD and the fi…
kriswest Nov 12, 2021
0adfd83
changelog
kriswest Nov 12, 2021
bd97624
WIP
kriswest Nov 18, 2021
ea69042
WIP
kriswest Nov 18, 2021
22cd3be
Merge branch '432-return-data-from-an-intent' into 498-resolve-intent…
kriswest Nov 18, 2021
6d68022
Changing IntentResolution.getData() to IntentResolution.getResult()
kriswest Nov 18, 2021
bb1e120
Changing IntentResolution.getData() to IntentResolution.getResult()
kriswest Nov 18, 2021
e3a962e
WIP
kriswest Nov 18, 2021
240b424
WIP
kriswest Nov 19, 2021
9b0b3b4
completed draft of feeds
kriswest Nov 19, 2021
075067a
changelog
kriswest Nov 19, 2021
ea3e2eb
outputContext -> resultContext and other comments from review
kriswest Nov 22, 2021
157a46f
Mergeing updates from review of upstream PR
kriswest Nov 22, 2021
4c274a4
Apply suggestions from code review
kriswest Dec 13, 2021
461bf95
Merge branch 'master' into 498-resolve-intents-on-output-type
kriswest Jan 25, 2022
3fa81cb
Merge branch '498-resolve-intents-on-output-type' into 433-private-ch…
kriswest Jan 25, 2022
dbd3e81
Merge branch 'master' into 433-private-channels-returned-by-intents
kriswest Feb 4, 2022
ef1d315
Merge branch 'master' into 433-private-channels-returned-by-intents
kriswest Feb 21, 2022
6412fba
Removing defunct paragraph from docs/api/spec.md (bad merge)
kriswest Feb 21, 2022
1da923a
prettier
kriswest Feb 21, 2022
b88b4af
Update docs/api/ref/DesktopAgent.md
kriswest Feb 15, 2022
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ 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 a field to specify the Context type that intent can return to the AppD Application schema and extended the findIntent API calls to be able to use it for resolution. ([#499](https://github.com/finos/FDC3/pull/499))
### Changed
* Consolidated `Listener` documentation with other types ([#404](https://github.com/finos/FDC3/pull/404))
* Updated definition of the `Position` context type to support negative (short) positions ([#419](https://github.com/finos/FDC3/pull/419))
Expand Down
124 changes: 89 additions & 35 deletions docs/api/ref/DesktopAgent.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ interface DesktopAgent {
addContextListener(handler: ContextHandler): Listener;

// intents
findIntent(intent: string, context?: Context): Promise<AppIntent>;
findIntentsByContext(context: Context): Promise<Array<AppIntent>>;
findIntent(intent: string, context?: Context, outputContextType?: string): Promise<AppIntent>;
kriswest marked this conversation as resolved.
Show resolved Hide resolved
findIntentsByContext(context: Context, outputContextType?: string): Promise<Array<AppIntent>>;
raiseIntent(intent: string, context: Context, app?: TargetApp): Promise<IntentResolution>;
raiseIntentForContext(context: Context, app?: TargetApp): Promise<IntentResolution>;
addIntentListener(intent: string, handler: ContextHandler): Listener;
addIntentListener(intent: string, handler: IntentHandler): Listener;

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



### `addIntentListener`

```ts
addIntentListener(intent: string, handler: ContextHandler): Listener;
addIntentListener(intent: string, handler: IntentHandler): 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 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`.
kriswest marked this conversation as resolved.
Show resolved Hide resolved

#### Examples

```js
//Handle a raised intent
const listener = fdc3.addIntentListener('StartChat', context => {
// start chat has been requested by another application
// 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 @@ -125,10 +137,10 @@ fdc3.broadcast(instrument);
### `findIntent`

```ts
findIntent(intent: string, context?: Context): Promise<AppIntent>;
findIntent(intent: string, context?: Context, outputContextType?: string): Promise<AppIntent>;
```

Find out more information about a particular intent by passing its name, and optionally its context.
Find out more information about a particular intent by passing its name, and optionally its context and/or a desired output context type.

`findIntent` is effectively granting programmatic access to the Desktop Agent's resolver.
It returns a promise resolving to the intent, its metadata and metadata about the apps that are registered to handle it.
Expand All @@ -137,8 +149,9 @@ This can be used to raise the intent against a specific app.
If the resolution fails, the promise will return an `Error` with a string from the [`ResolveError`](ResolveError) enumeration.

#### Examples
I know 'StartChat' exists as a concept, and want to know which apps can resolve it:

```js
// I know 'StartChat' exists as a concept, and want to know more about it ...
const appIntent = await fdc3.findIntent("StartChat");
// returns a single AppIntent:
// {
Expand All @@ -150,45 +163,79 @@ const appIntent = await fdc3.findIntent("StartChat");
await fdc3.raiseIntent(appIntent.intent.name, context, appIntent.apps[0].name);
```

An optional input context object and/or output context type may be specified, which the resolver MUST use to filter the returned applications such that each supports the specified input and output types.
```js
const appIntent = await fdc3.findIntent("StartChat", contact);
kriswest marked this conversation as resolved.
Show resolved Hide resolved

// returns only apps that support the type of the specified input context:
// {
// intent: { name: "StartChat", displayName: "Chat" },
// apps: { name: "Symphony" }]
// }

const appIntent = await fdc3.findIntent("ViewContact", "fdc3.ContactList");
// returns only apps that return the specified output Context type:
// {
// intent: { name: "ViewContact", displayName: "View Contact Details" },
// apps: { name: "MyCRM", outputContext: "fdc3.ContactList"}]
// }
```

#### See also
* [`ResolveError`](Errors#resolveerror)

### `findIntentsByContext`

```ts
findIntentsByContext(context: Context): Promise<Array<AppIntent>>;
findIntentsByContext(context: Context, outputContextType?: string): Promise<Array<AppIntent>>;
```

Find all the avalable intents for a particular context.
Find all the avalable intents for a particular context, and optionally a desired output context type.

`findIntentsByContext` is effectively granting programmatic access to the Desktop Agent's resolver.
A promise resolving to all the intents, their metadata and metadata about the apps that registered as handlers is returned, based on the context types the intents have registered.

If the resolution fails, the promise will return an `Error` with a string from the [`ResolveError`](ResolveError) enumeration.

#### Example
I have a context object, and I want to know what I can do with it, hence, I look for intents and apps to resolve them...

```js
// I have a context object, and I want to know what I can do with it, hence, I look for intents...
const appIntents = await fdc3.findIntentsByContext(context);

// returns, for example:
// [{
// intent: { name: "StartCall", displayName: "Call" },
// apps: [{ name: "Skype" }]
// },
// {
// intent: { name: "StartChat", displayName: "Chat" },
// apps: [{ name: "Skype" }, { name: "Symphony" }, { name: "Slack" }]
// }];

// select a particular intent to raise
const startChat = appIntents[1];

// target a particular app
const selectedApp = startChat.apps[0];
// returns, for example:
// [{
// intent: { name: "StartCall", displayName: "Call" },
// apps: [{ name: "Skype" }]
// },
// {
// intent: { name: "StartChat", displayName: "Chat" },
// apps: [{ name: "Skype" }, { name: "Symphony" }, { name: "Slack" }]
// },
// {
// intent: { name: "ViewContact", displayName: "View Contact" },
// apps: [{ name: "Symphony" }, { name: "MyCRM", outputContext: "fdc3.ContactList"}]
// }];
```

// raise the intent, passing the given context, targeting the app
await fdc3.raiseIntent(startChat.intent.name, context, selectedApp.name);
```
or I look for only intents that are resolved by apps returning a particular context type
```js
const appIntentsForType = await fdc3.findIntentsByContext(context, "fdc3.ContactList");
// returns for example:
// [{
// intent: { name: "ViewContact", displayName: "View Contacts" },
// apps: [{ name: "MyCRM", outputContext: "fdc3.ContactList"}]
// }];

// select a particular intent to raise
const resolvedIntent = appIntents[0];

// target a particular app
const selectedApp = resolvedIntent.apps[0];

// raise the intent, passing the given context, targeting the app
await fdc3.raiseIntent(resolvedIntent.intent.name, context, selectedApp.name);
```

#### See also
* [`ResolveError`](Errors#resolveerror)
Expand Down Expand Up @@ -389,7 +436,7 @@ Alternatively, the specific app to target can also be provided. A list of valid

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 that was selected 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 is thrown by the handler function, the promise returned is rejected, or a promse is not returned then the Desktop Agent MUST reject the promise returned by the `getResult()` function of the `IntentResolution` with a string from the `DataError` enumeration.
kriswest marked this conversation as resolved.
Show resolved Hide resolved

If a target app for the intent cannot be found with the criteria provided, an `Error` with a string from the [`ResolveError`](Errors#resolverrror) enumeration is returned.

Expand All @@ -398,20 +445,27 @@ If a target app for the intent cannot be found with the criteria provided, an `E
```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
target applied, if more than one application can resolve the intent
// target applied, if more than one application can resolve the intent
await fdc3.raiseIntent("StartChat", context);

// or find apps to resolve an intent to start a chat with a given contact
const appIntent = await fdc3.findIntent("StartChat", context);

// use the name of one of the associated apps returned by findIntent as the specific intent target
await fdc3.raiseIntent("StartChat", context, appIntent.apps[0].name);

// or use the metadata of the app to fully describe the target app for the intent
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 @@ -432,7 +486,7 @@ Alternatively, the specific app to target can also be provided, in which case an

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 is thrown by the handler function, the promise returned is rejected, or a promse is not returned then the Desktop Agent MUST reject the promise returned by the `getResult()` function of the `IntentResolution` with a string from the `DataError` enumeration.
kriswest marked this conversation as resolved.
Show resolved Hide resolved

If a target app for the intent cannot be found with the criteria provided, an `Error` with a string from the [`ResolveError`](Errors#resolveerror) enumeration is 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 @@ -36,6 +36,22 @@ Contains constants representing the errors that can be encountered when calling
* [`DesktopAgent.findIntent`](DesktopAgent#findintent)
* [`DesktopAgent.findIntentsByContext`](DesktopAgent#findintentsbycontext)

## `DataError`

```typescript
enum DataError {
NoDataReturned = 'NoDataReturned',
IntentHandlerRejected = 'IntentHandlerRejected',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may need some more discussion around error handling. For instance, a request for a quote would likely return some sort of well-defined "Quote" context, but what if the end user is not authorized to receive that market data? We wouldn't want to muddy the Quote context with arbitrary error messages, but then the DataError enum type provides no information about why the intent was rejected, which in this case would result in quite a bit of potential confusion for the end user.

We may want to either (a) consider allowing at least a simple error string to be rejected/thrown, or (b) allow for the possibility of more than one context being returned (e.g. an Error context. Receiving apps may then disambiguate by examining the type member).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At present, an app resolving an intent has no means to identify the app raising the intent and couldn't assess authorization. Hence, any such mechanism would have to be a function of the Desktop Agent. However, I take the point on returning some info about why the promise was rejected (for feedback to the end-user - 'IntentHandlerRejected' is not all that informative and we are deliberately losing any info on why the rejection occurred).

I don't particularly like the idea of using an 'ErrorContext' type (I think that overloads the purpose of context). However, we could use the javascript Error type (or similar that we define), setting the message to these values and setting the optional cause property to the error/rejection message returned by the intent handler... However, its a departure from all other rejects, which reject with just a string.

}
```

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
Loading