Skip to content

Commit 46b89a4

Browse files
committed
Initial work on adding addIntentWithContextListener to workbench
1 parent 16db505 commit 46b89a4

2 files changed

Lines changed: 283 additions & 0 deletions

File tree

toolbox/fdc3-workbench/src/components/Intents.tsx

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,28 +180,39 @@ export const Intents = observer(({ handleTabChange }: { handleTabChange: any })
180180
const [intentValue, setIntentValue] = useState<ListenerOptionType | null>(null);
181181
const [raiseIntentError, setRaiseIntentError] = useState<string | false>(false);
182182
const [intentListener, setIntentListener] = useState<ListenerOptionType | null>(null);
183+
const [intentListenerWithContext, setIntentListenerWithContext] = useState<ListenerOptionType | null>(null);
183184
const [intentsForContext, setIntentsForContext] = useState<ListenerOptionType[] | null>(null);
184185
const [targetApp, setTargetApp] = useState<string>('None');
185186
const [contextTargetApp, setContextTargetApp] = useState<string>('None');
186187
const [raiseIntentContext, setRaiseIntentContext] = useState<ContextType | null>(null);
187188
const [raiseIntentWithContextContext, setRaiseIntentWithContextContext] = useState<ContextType | null>(null);
188189
const [intentError, setIntentError] = useState<string | false>(false);
190+
const [intentWithContextError, setIntentWithContextError] = useState<string | false>(false);
189191
const [intentResolution, setIntentResolution] = useState<IntentResolution | undefined | null>(null);
190192
const [intentForContextResolution, setIntentForContextResolution] = useState<IntentResolution | undefined | null>(
191193
null
192194
);
193195
const intentListenersOptions: ListenerOptionType[] = intentStore.intentsList;
194196
const [contextFields, setContextFields] = useState<any[]>([]);
197+
const [contextFieldsWithContext, setContextFieldsWithContext] = useState<any[]>([]);
195198
const [resultTypeContext, setResultTypeContext] = useState<ContextType | null>(null);
199+
const [resultTypeListenerWithContextContext, setResultTypeListenerWithContextContext] = useState<ContextType | null>(
200+
null
201+
);
196202
const [resultOverChannelContextList, setResultOverChannelContextList] = useState<any>({});
197203
const [resultOverChannelContextDelays, setResultOverChannelContextDelays] = useState<any>({});
198204
const [sendIntentResult, setSendIntentResult] = useState<boolean | undefined>(false);
205+
const [sendIntentWithIntentResult, setSendIntentWithIntentResult] = useState<boolean | undefined>(false);
199206
const [resultType, setResultType] = useState<string | null>(null);
207+
const [listenerWithContextResultType, setListenerWithContextResultType] = useState<string | null>(null);
200208
const [useTargets, setUseTargets] = useState<boolean>(false);
201209
const [useContextTargets, setUseContextTargets] = useState<boolean>(false);
202210
const [channelType, setChannelType] = useState<string | null>('app-channel');
211+
const [channelTypeWithContext, setChannelTypeWithContext] = useState<string | null>('app-channel');
203212
const [sendResultOverChannel, setSendResultOverChannel] = useState<boolean | undefined>(false);
213+
const [sendResultOverChannelWithContext, setSendResultOverChannelWithContext] = useState<boolean | undefined>(false);
204214
const [currentAppChannelId, setCurrentAppChannelId] = useState<string>('');
215+
const [currentAppChannelIdWithContext, setCurrentAppChannelIdWithContext] = useState<string>('');
205216
const [targetOptions, setTargetOptions] = useState<ReactElement[]>([]);
206217
const [targetOptionsforContext, setTargetOptionsforContext] = useState<ReactElement[]>([]);
207218

@@ -466,10 +477,32 @@ export const Intents = observer(({ handleTabChange }: { handleTabChange: any })
466477
setSendIntentResult(false);
467478
};
468479

480+
const handleAddIntentListenerWithContext = () => {
481+
if (!intentListener) {
482+
setIntentError('Enter intent');
483+
return;
484+
} else {
485+
intentStore.addIntentListener(
486+
intentListener.value,
487+
sendIntentResult && resultType === 'context-result' ? toJS(resultTypeListenerWithContextContext) : null,
488+
sendIntentResult && resultType === 'channel-result' ? currentAppChannelId : undefined,
489+
sendIntentResult && resultType === 'channel-result' ? channelType === 'private-channel' : undefined,
490+
sendIntentResult && resultType === 'channel-result' ? resultOverChannelContextList : undefined,
491+
sendIntentResult && resultType === 'channel-result' ? resultOverChannelContextDelays : undefined
492+
);
493+
setIntentListener(null);
494+
}
495+
setSendIntentResult(false);
496+
};
497+
469498
const handleChannelTypeChange = (event: React.MouseEvent<HTMLElement>, nextView: string) => {
470499
setChannelType(nextView);
471500
};
472501

502+
const handleChannelTypeWithContextChange = (event: React.MouseEvent<HTMLElement>, nextView: string) => {
503+
setChannelTypeWithContext(nextView);
504+
};
505+
473506
const handleTargetToggle = (event: ChangeEvent<HTMLInputElement>, checked: boolean) => {
474507
setUseTargets(checked);
475508
if (!checked) {
@@ -520,6 +553,29 @@ export const Intents = observer(({ handleTabChange }: { handleTabChange: any })
520553
]);
521554
};
522555

556+
const handleAddContextWithContextField = () => {
557+
setContextFieldsWithContext(current => [
558+
...current,
559+
<Grid container direction="row" key={contextFieldsWithContext.length}>
560+
<Grid item className={classes.indentLeft}>
561+
<TextField
562+
variant="outlined"
563+
label="Delay (ms)"
564+
type="number"
565+
size="small"
566+
onChange={e => setChannelContextDelay(e.target.value, contextFieldsWithContext.length)}
567+
/>
568+
</Grid>
569+
<Grid item className={`${classes.indentLeft} ${classes.field}`}>
570+
<ContextTemplates
571+
handleTabChange={handleTabChange}
572+
contextStateSetter={(context: any) => setChannelContextList(context, contextFieldsWithContext.length)}
573+
/>
574+
</Grid>
575+
</Grid>,
576+
]);
577+
};
578+
523579
useEffect(() => {
524580
setIntentValue(null);
525581
const fetchIntents = async () => {
@@ -996,6 +1052,205 @@ export const Intents = observer(({ handleTabChange }: { handleTabChange: any })
9961052
</RadioGroup>
9971053
</Grid>
9981054
)}
1055+
1056+
<Grid container item spacing={2} justifyContent="flex-end" className={classes.spread}>
1057+
<Grid item xs={12}>
1058+
<Typography className={classes.bottomMargin} variant="h5">
1059+
Add intent listener with context
1060+
</Typography>
1061+
</Grid>
1062+
<Grid item className={`${classes.field} ${classes.removeSidePadding}`}>
1063+
<Autocomplete
1064+
id="intent-listener-with-context"
1065+
size="small"
1066+
selectOnFocus
1067+
blurOnSelect
1068+
clearOnBlur
1069+
handleHomeEndKeys
1070+
value={intentListenerWithContext}
1071+
onChange={handleChangeListener(setIntentListenerWithContext, setIntentWithContextError)}
1072+
filterOptions={filterOptions}
1073+
options={intentListenersOptions}
1074+
getOptionLabel={getOptionLabel}
1075+
renderOption={option => option.title}
1076+
renderInput={params => (
1077+
<TemplateTextField
1078+
label="INTENT LISTENER"
1079+
placeholder="Enter Intent Type"
1080+
variant="outlined"
1081+
{...params}
1082+
error={!!intentWithContextError}
1083+
helperText={intentWithContextError}
1084+
/>
1085+
)}
1086+
/>
1087+
</Grid>
1088+
1089+
<Grid item className={classes.controls}>
1090+
<Button
1091+
variant="contained"
1092+
color="primary"
1093+
onClick={handleAddIntentListenerWithContext}
1094+
disabled={intentListenerWithContext === null}
1095+
>
1096+
Add listener
1097+
</Button>
1098+
1099+
<Tooltip title="Copy code example" aria-label="Copy code example">
1100+
<IconButton
1101+
size="small"
1102+
aria-label="Copy code example"
1103+
color="primary"
1104+
onClick={() => {
1105+
let exampleToUse = codeExamples.intentListenerWithContext;
1106+
if (listenerWithContextResultType === 'context-result') {
1107+
exampleToUse = codeExamples.intentListenerWithContextContextResult;
1108+
} else if (listenerWithContextResultType === 'channel-result') {
1109+
if (channelType === 'app-channel') {
1110+
exampleToUse = codeExamples.intentListenerWithContextAppChannel;
1111+
} else {
1112+
exampleToUse = codeExamples.intentListenerWithContextPrivateChannel;
1113+
}
1114+
}
1115+
copyToClipboard(exampleToUse, 'addIntentListener')();
1116+
}}
1117+
>
1118+
<FileCopyIcon />
1119+
</IconButton>
1120+
</Tooltip>
1121+
1122+
<Link
1123+
onClick={openApiDocsLink}
1124+
target="FDC3APIDocs"
1125+
href="https://fdc3.finos.org/docs/api/ref/DesktopAgent#addintentlistenerWithContext"
1126+
>
1127+
<InfoOutlinedIcon />
1128+
</Link>
1129+
</Grid>
1130+
</Grid>
1131+
{window.fdc3Version === '2.0' && (
1132+
<Grid item xs={12}>
1133+
<FormGroup>
1134+
<FormControlLabel
1135+
control={
1136+
<Checkbox
1137+
className={classes.input}
1138+
color="default"
1139+
checked={sendIntentWithIntentResult}
1140+
onChange={e => setSendIntentWithIntentResult(e.target.checked)}
1141+
/>
1142+
}
1143+
label="Send intent result"
1144+
/>
1145+
</FormGroup>
1146+
</Grid>
1147+
)}
1148+
{sendIntentWithIntentResult && (
1149+
<Grid item xs={12} className={classes.indentLeft}>
1150+
<RadioGroup
1151+
name="intent-result-type"
1152+
value={listenerWithContextResultType}
1153+
onChange={e => setListenerWithContextResultType(e.target.value)}
1154+
>
1155+
<FormControlLabel
1156+
value="context-result"
1157+
control={<Radio className={classes.input} />}
1158+
label="Context result"
1159+
/>
1160+
{listenerWithContextResultType === 'context-result' && (
1161+
<Grid item className={classes.indentLeft}>
1162+
<ContextTemplates
1163+
handleTabChange={handleTabChange}
1164+
contextStateSetter={setResultTypeListenerWithContextContext}
1165+
/>
1166+
</Grid>
1167+
)}
1168+
<FormControlLabel
1169+
value="channel-result"
1170+
control={<Radio className={classes.input} />}
1171+
label="Channel result"
1172+
/>
1173+
{listenerWithContextResultType === 'channel-result' && (
1174+
<Grid item className={classes.indentLeft}>
1175+
<ToggleButtonGroup
1176+
value={channelTypeWithContext}
1177+
exclusive
1178+
onChange={handleChannelTypeWithContextChange}
1179+
aria-label="result channel type"
1180+
>
1181+
<ToggleButton className={classes.toggle} value="app-channel" aria-label="left aligned">
1182+
App channel
1183+
</ToggleButton>
1184+
<ToggleButton className={classes.toggle} value="private-channel" aria-label="left aligned">
1185+
Private channel
1186+
</ToggleButton>
1187+
</ToggleButtonGroup>
1188+
1189+
{channelType === 'app-channel' && (
1190+
<Grid item className={classes.field}>
1191+
<TextField
1192+
fullWidth
1193+
variant="outlined"
1194+
label="Channel Name"
1195+
type="text"
1196+
size="small"
1197+
onChange={(e: any) => setCurrentAppChannelIdWithContext(e.target.value)}
1198+
value={currentAppChannelIdWithContext}
1199+
/>
1200+
</Grid>
1201+
)}
1202+
<FormGroup>
1203+
{channelType === 'private-channel' && (
1204+
<Typography variant="caption" className={classes.caption}>
1205+
Context streaming will start AFTER a context listener is added to the channel
1206+
</Typography>
1207+
)}
1208+
<FormControlLabel
1209+
control={
1210+
<Checkbox
1211+
className={classes.input}
1212+
color="default"
1213+
checked={sendResultOverChannelWithContext}
1214+
onChange={e => setSendResultOverChannelWithContext(e.target.checked)}
1215+
/>
1216+
}
1217+
label="Send context result over channel"
1218+
/>
1219+
</FormGroup>
1220+
{sendResultOverChannelWithContext && (
1221+
<>
1222+
{contextFieldsWithContext.map((field, index) => (
1223+
<React.Fragment key={index}>{field}</React.Fragment>
1224+
))}
1225+
<Grid item className={`${classes.indentLeft} ${classes.controls}`}>
1226+
<Tooltip
1227+
title="Add context result (delays will trigger sequentially)"
1228+
aria-label="Add context result (delays will trigger sequentially)"
1229+
>
1230+
<IconButton
1231+
size="small"
1232+
aria-label="Add context result (delays will trigger sequentially)"
1233+
color="primary"
1234+
onClick={handleAddContextWithContextField}
1235+
>
1236+
<AddCircleOutlineIcon />
1237+
</IconButton>
1238+
</Tooltip>
1239+
1240+
<Link
1241+
target="FDC3APIDocs"
1242+
href="https://fdc3.finos.org/docs/api/ref/DesktopAgent#addintentlistener"
1243+
>
1244+
<InfoOutlinedIcon />
1245+
</Link>
1246+
</Grid>
1247+
</>
1248+
)}
1249+
</Grid>
1250+
)}
1251+
</RadioGroup>
1252+
</Grid>
1253+
)}
9991254
<Grid item xs={12}>
10001255
<Alert severity="info">
10011256
Desktop Agents often require apps that listen for intents to include the intent in their appD record.

toolbox/fdc3-workbench/src/fixtures/codeExamples.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,34 @@ const listener = fdc3.addIntentListener('StartChat', context => {
106106
return channel;
107107
});`,
108108

109+
intentListenerWithContext: `const listener = fdc3.addIntentListener('StartChat', context => {
110+
// start chat has been requested by another application
111+
});`,
112+
113+
intentListenerWithContextContextResult: `const instrument = {
114+
type: 'fdc3.instrument',
115+
id: {
116+
ticker: 'AAPL'
117+
}
118+
};
119+
120+
const listener = fdc3.addIntentListener('StartChat', context => {
121+
// start chat has been requested by another application
122+
return instrument;
123+
});`,
124+
125+
intentListenerWithContextAppChannel: `const listener = fdc3.addIntentListener('StartChat', context => {
126+
// start chat has been requested by another application
127+
const channel = await appChannelStore.getOrCreateChannel(channelName);
128+
return channel;
129+
});`,
130+
131+
intentListenerWithContextPrivateChannel: `const listener = fdc3.addIntentListener('StartChat', context => {
132+
// start chat has been requested by another application
133+
const channel = await fdc3.createPrivateChannel();
134+
return channel;
135+
});`,
136+
109137
raiseIntentForContext: (context: string) =>
110138
`let context = ${
111139
context !== 'null' ? context : '{type: "fdc3.instrument", name: "Tesla, inc.", id: {ticker: "TSLA"}}'

0 commit comments

Comments
 (0)