Skip to content

Commit c9ef623

Browse files
authored
Merge pull request #39 from hwrdtm/howard/#32-action-menu
Implement Action Menu API for basic controller
2 parents 9026785 + 51c03e5 commit c9ef623

File tree

4 files changed

+581
-3
lines changed

4 files changed

+581
-3
lines changed

libs/aries-basic-controller/aries_basic_controller/aries_controller.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from .controllers.credential import CredentialController
1717
from .controllers.server import ServerController
1818
from .controllers.oob import OOBController
19+
from .controllers.action_menu import ActionMenuController
1920

2021
import logging
2122

@@ -25,8 +26,18 @@ class AriesAgentController:
2526

2627
## TODO rethink how to initialise. Too many args?
2728
## is it important to let users config connections/issuer etc
28-
def __init__(self, webhook_host: str, webhook_port: int, admin_url: str, webhook_base: str = "",
29-
connections: bool = True, messaging: bool = True, issuer: bool = True, api_key: str = None):
29+
def __init__(
30+
self,
31+
webhook_host: str,
32+
webhook_port: int,
33+
admin_url: str,
34+
webhook_base: str = "",
35+
connections: bool = True,
36+
messaging: bool = True,
37+
issuer: bool = True,
38+
action_menu: bool = True,
39+
api_key: str = None,
40+
):
3041

3142
self.webhook_site = None
3243
self.admin_url = admin_url
@@ -62,7 +73,8 @@ def __init__(self, webhook_host: str, webhook_port: int, admin_url: str, webhook
6273
self.issuer = IssuerController(self.admin_url, self.client_session, self.connections,
6374
self.wallet, self.definitions)
6475

65-
76+
if action_menu:
77+
self.action_menu = ActionMenuController(self.admin_url, self.client_session)
6678

6779
def register_listeners(self, listeners, defaults=True):
6880
if defaults:
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from .base import BaseController
2+
from ..models.connection import Connection
3+
4+
from aiohttp import (
5+
ClientSession,
6+
)
7+
import logging
8+
import asyncio
9+
10+
class ActionMenuController(BaseController):
11+
12+
def __init__(self, admin_url: str, client_session: ClientSession):
13+
super().__init__(admin_url, client_session)
14+
15+
async def close_menu(self, connection_id: str):
16+
return await self.admin_POST(f"/action-menu/{connection_id}/close")
17+
18+
async def get_menu(self, connection_id: str):
19+
return await self.admin_POST(f"/action-menu/{connection_id}/fetch")
20+
21+
async def request_menu(self, connection_id: str):
22+
return await self.admin_POST(f"/action-menu/{connection_id}/request")
23+
24+
async def perform(self, connection_id: str, menu_params, menu_option_name):
25+
body = {
26+
"params": menu_params,
27+
"name": menu_option_name
28+
}
29+
30+
return await self.admin_POST(f"/action-menu/{connection_id}/perform", json_data=body)
31+
32+
async def send_menu(
33+
self,
34+
connection_id: str,
35+
menu_description: str,
36+
menu_errormsg: str,
37+
menu_title: str,
38+
menu_options,
39+
):
40+
body = {
41+
"menu": {
42+
"description": menu_description,
43+
"errormsg": menu_errormsg,
44+
"title": menu_title,
45+
"options": menu_options
46+
}
47+
}
48+
49+
return await self.admin_POST(f"/connections/{connection_id}/send-menu", json_data=body)
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Action Menu - Alice\n",
8+
"\n",
9+
"## Role - Requester\n",
10+
"\n",
11+
"In this notebook we'll be going through the Action Menu Protocol. For details on the protocol, please refer to the [RFC](https://github.com/hyperledger/aries-rfcs/tree/master/features/0509-action-menu).\n",
12+
"\n",
13+
"A requirement for this protocol to work is an active DIDComm communication channel between Alice and Bob. To achieve that, you should have completed the [Establishing a Connection](http://127.0.0.1:8888/notebooks/Part%203%20-%20Establishing%20a%20Connection.ipynb) tutorial."
14+
]
15+
},
16+
{
17+
"cell_type": "markdown",
18+
"metadata": {},
19+
"source": [
20+
"## 1. Initialise Alice controller\n",
21+
"\n",
22+
"We begin by initializing the controller for the Alice agent."
23+
]
24+
},
25+
{
26+
"cell_type": "code",
27+
"execution_count": null,
28+
"metadata": {},
29+
"outputs": [],
30+
"source": [
31+
"%autoawait\n",
32+
"import time\n",
33+
"import asyncio\n",
34+
"from aries_basic_controller.aries_controller import AriesAgentController\n",
35+
" \n",
36+
"WEBHOOK_HOST = \"0.0.0.0\"\n",
37+
"WEBHOOK_PORT = 8022\n",
38+
"WEBHOOK_BASE = \"\"\n",
39+
"ADMIN_URL = \"http://alice-agent:8021\"\n",
40+
"\n",
41+
"# WARNING: You should use environment variables for this\n",
42+
"# TODO: Make env variables accessible through juypter notebooks\n",
43+
"API_KEY = \"alice_api_123456789\"\n",
44+
"\n",
45+
"# Based on the aca-py agent you wish to control\n",
46+
"agent_controller = AriesAgentController(webhook_host=WEBHOOK_HOST, webhook_port=WEBHOOK_PORT,\n",
47+
" webhook_base=WEBHOOK_BASE, admin_url=ADMIN_URL, api_key=API_KEY)\n",
48+
" "
49+
]
50+
},
51+
{
52+
"cell_type": "markdown",
53+
"metadata": {},
54+
"source": [
55+
"## 2. Listen for webhooks and register default listeners\n",
56+
"\n",
57+
"Everytime a webhook is received from the agent, the controller reemits the hook using [PyPubSub](https://pypubsub.readthedocs.io/en/v4.0.3/). The default listeners are used to update state and print logs."
58+
]
59+
},
60+
{
61+
"cell_type": "code",
62+
"execution_count": null,
63+
"metadata": {},
64+
"outputs": [],
65+
"source": [
66+
"loop = asyncio.get_event_loop()\n",
67+
"loop.create_task(agent_controller.listen_webhooks())"
68+
]
69+
},
70+
{
71+
"cell_type": "code",
72+
"execution_count": null,
73+
"metadata": {},
74+
"outputs": [],
75+
"source": [
76+
"def actionmenu_handler(payload):\n",
77+
" print(f\"Action Menu Webhook: {payload}\\n\")\n",
78+
" menu = payload[\"menu\"]\n",
79+
" print(f\"Menu: {menu}\\n\")\n",
80+
" menu_options = payload[\"menu\"][\"options\"]\n",
81+
" print(f\"Options: {menu_options}\")\n",
82+
"\n",
83+
"actionmenu_listener = {\n",
84+
" \"topic\": \"actionmenu\",\n",
85+
" \"handler\": actionmenu_handler\n",
86+
"}\n",
87+
"\n",
88+
"agent_controller.register_listeners([actionmenu_listener], defaults=True)"
89+
]
90+
},
91+
{
92+
"cell_type": "markdown",
93+
"metadata": {},
94+
"source": [
95+
"## 3. Check the agent has an active connection\n",
96+
"\n",
97+
"An active connection between Alice and Bob is required. You can either:\n",
98+
"\n",
99+
"- complete the [Establishing a Connection](http://127.0.0.1:8888/notebooks/Part%203%20-%20Establishing%20a%20Connection.ipynb) tutorial, or\n",
100+
"- running the python script `create_connection.py` in the setup folder"
101+
]
102+
},
103+
{
104+
"cell_type": "code",
105+
"execution_count": null,
106+
"metadata": {
107+
"scrolled": true
108+
},
109+
"outputs": [],
110+
"source": [
111+
"response = await agent_controller.connections.get_connections()\n",
112+
"results = response['results']\n",
113+
"print(\"Results : \", results)\n",
114+
"if len(results) > 0:\n",
115+
" connection = response['results'][0]\n",
116+
" print(\"Connection :\", connection)\n",
117+
" if connection['state'] == 'active': \n",
118+
" connection_id = connection[\"connection_id\"]\n",
119+
" print(\"Active Connection ID : \", connection_id)\n",
120+
"else:\n",
121+
" print(\"You must create a connection\")"
122+
]
123+
},
124+
{
125+
"cell_type": "markdown",
126+
"metadata": {},
127+
"source": [
128+
"## 4. Request Action Menu from Bob\n",
129+
"\n",
130+
"Alice begins by making a request for the action menu from Bob."
131+
]
132+
},
133+
{
134+
"cell_type": "code",
135+
"execution_count": null,
136+
"metadata": {},
137+
"outputs": [],
138+
"source": [
139+
"await agent_controller.action_menu.request_menu(connection_id)"
140+
]
141+
},
142+
{
143+
"cell_type": "markdown",
144+
"metadata": {},
145+
"source": [
146+
"## 5. Continue with step 6 of [Bob's notebook](http://127.0.0.1:8889/notebooks/Part%209%20-%20Action%20Menu.ipynb)"
147+
]
148+
},
149+
{
150+
"cell_type": "markdown",
151+
"metadata": {},
152+
"source": [
153+
"## 8. Perform action upon receiving action menu\n",
154+
"\n",
155+
"Upon receiving the action menu from Bob, Alice can now perform an action."
156+
]
157+
},
158+
{
159+
"cell_type": "code",
160+
"execution_count": null,
161+
"metadata": {},
162+
"outputs": [],
163+
"source": [
164+
"# retrieve the menu options sent from Bob\n",
165+
"menu = await agent_controller.action_menu.get_menu(connection_id)\n",
166+
"menu"
167+
]
168+
},
169+
{
170+
"cell_type": "code",
171+
"execution_count": null,
172+
"metadata": {},
173+
"outputs": [],
174+
"source": [
175+
"menu_options = menu[\"result\"][\"options\"]\n",
176+
"menu_options"
177+
]
178+
},
179+
{
180+
"cell_type": "code",
181+
"execution_count": null,
182+
"metadata": {},
183+
"outputs": [],
184+
"source": [
185+
"# as an example, we work with the first menu option\n",
186+
"menu_params = {\n",
187+
" \"begin_issue_cred\": \"True\"\n",
188+
"}\n",
189+
"\n",
190+
"await agent_controller.action_menu.perform(\n",
191+
" connection_id=connection_id,\n",
192+
" menu_params=menu_params,\n",
193+
" menu_option_name=menu_options[0][\"name\"]\n",
194+
")"
195+
]
196+
},
197+
{
198+
"cell_type": "markdown",
199+
"metadata": {},
200+
"source": [
201+
"## 9. Close the active menu\n",
202+
"\n",
203+
"Once the requester is done with the action menu, the menu can now be closed."
204+
]
205+
},
206+
{
207+
"cell_type": "code",
208+
"execution_count": null,
209+
"metadata": {},
210+
"outputs": [],
211+
"source": [
212+
"# close active menu\n",
213+
"await agent_controller.action_menu.close_menu(connection_id)"
214+
]
215+
},
216+
{
217+
"cell_type": "markdown",
218+
"metadata": {},
219+
"source": [
220+
"Retrieving the active menu now would return `None`."
221+
]
222+
},
223+
{
224+
"cell_type": "code",
225+
"execution_count": null,
226+
"metadata": {
227+
"scrolled": true
228+
},
229+
"outputs": [],
230+
"source": [
231+
"menu = await agent_controller.action_menu.get_menu(connection_id)\n",
232+
"menu"
233+
]
234+
},
235+
{
236+
"cell_type": "markdown",
237+
"metadata": {},
238+
"source": [
239+
"## End of Tutorial\n",
240+
"\n",
241+
"Be sure to terminate the controller before running another tutorial."
242+
]
243+
},
244+
{
245+
"cell_type": "code",
246+
"execution_count": null,
247+
"metadata": {},
248+
"outputs": [],
249+
"source": [
250+
"response = await agent_controller.terminate()\n",
251+
"print(response)"
252+
]
253+
},
254+
{
255+
"cell_type": "code",
256+
"execution_count": null,
257+
"metadata": {},
258+
"outputs": [],
259+
"source": []
260+
}
261+
],
262+
"metadata": {
263+
"kernelspec": {
264+
"display_name": "Python 3",
265+
"language": "python",
266+
"name": "python3"
267+
},
268+
"language_info": {
269+
"codemirror_mode": {
270+
"name": "ipython",
271+
"version": 3
272+
},
273+
"file_extension": ".py",
274+
"mimetype": "text/x-python",
275+
"name": "python",
276+
"nbconvert_exporter": "python",
277+
"pygments_lexer": "ipython3",
278+
"version": "3.7.6"
279+
}
280+
},
281+
"nbformat": 4,
282+
"nbformat_minor": 4
283+
}

0 commit comments

Comments
 (0)