diff --git a/examples/chem-sync-local-flask/local_app/benchling_app/canvas_interaction.py b/examples/chem-sync-local-flask/local_app/benchling_app/canvas_interaction.py index b9e157f..9757159 100644 --- a/examples/chem-sync-local-flask/local_app/benchling_app/canvas_interaction.py +++ b/examples/chem-sync-local-flask/local_app/benchling_app/canvas_interaction.py @@ -28,16 +28,30 @@ class UnsupportedButtonError(Exception): pass +""" +This file contains examples of how to handle a user clicking a button on a canvas +For examples of initialization, check out canvas_initialize.py +""" def route_interaction_webhook(app: App, canvas_interaction: CanvasInteractionWebhookV2) -> None: canvas_id = canvas_interaction.canvas_id + + # Access the different actions by checking which button was clicked if canvas_interaction.button_id == SEARCH_BUTTON_ID: with app.create_session_context("Search Chemicals", timeout_seconds=20) as session: session.attach_canvas(canvas_id) + + # Use a CanvasBuilder to update an existing canvas canvas_builder = _canvas_builder_from_canvas_id(app, canvas_id) canvas_inputs = canvas_builder.inputs_to_dict_single_value() + + # Validate and sanitize the user's search input sanitized_inputs = _validate_and_sanitize_inputs(canvas_inputs) + + # Search for the chemical results = search(sanitized_inputs[SEARCH_TEXT_ID]) + + # Render the preview canvas render_preview_canvas(results, canvas_id, canvas_builder, session) elif canvas_interaction.button_id == CANCEL_BUTTON_ID: # Set session_id = None to detach and prior state or messages (essentially, reset) @@ -46,12 +60,17 @@ def route_interaction_webhook(app: App, canvas_interaction: CanvasInteractionWeb .with_session_id(None)\ .with_blocks(input_blocks())\ .to_update() + + # Update the canvas app.benchling.apps.update_canvas(canvas_id, canvas_update) elif canvas_interaction.button_id == CREATE_BUTTON_ID: with app.create_session_context("Create Molecules", timeout_seconds=20) as session: session.attach_canvas(canvas_id) + + # Use a CanvasBuilder to create a new molecule canvas_builder = _canvas_builder_from_canvas_id(app, canvas_id) molecule = _create_molecule_from_canvas(app, canvas_builder) + render_completed_canvas(molecule, canvas_id, canvas_builder, session) else: # Re-enable the Canvas, or it will stay disabled and the user will be stuck diff --git a/examples/chem-sync-local-flask/local_app/benchling_app/handler.py b/examples/chem-sync-local-flask/local_app/benchling_app/handler.py index a75e3c1..ae1e6f4 100644 --- a/examples/chem-sync-local-flask/local_app/benchling_app/handler.py +++ b/examples/chem-sync-local-flask/local_app/benchling_app/handler.py @@ -25,17 +25,25 @@ class UnsupportedWebhookError(Exception): def handle_webhook(webhook_dict: dict[str, Any]) -> None: logger.debug("Handling webhook with payload: %s", webhook_dict) + + # Receive the webhook and initialize the app webhook = WebhookEnvelopeV0.from_dict(webhook_dict) app = init_app_from_webhook(webhook) + + # Route the webhook to the appropriate handler # Could also choose to route on webhook.message.type + # Note: if your manifest specifies more than one item in `features`, # then `webhook.message.feature_id` may also need to be part of your routing logic try: if isinstance(webhook.message, CanvasInitializeWebhookV2): + # This webhook is triggered when a canvas is initialized as a part of a run render_search_canvas(app, webhook.message) elif isinstance(webhook.message, CanvasInteractionWebhookV2): + # This webhook is triggered when a user clicks a button on the canvas route_interaction_webhook(app, webhook.message) elif isinstance(webhook.message, CanvasCreatedWebhookV2Beta): + # This webhook is triggered when a canvas is created in an entry render_search_canvas_for_created_canvas(app, webhook.message) else: # Should only happen if the app's manifest requests webhooks that aren't handled in its code paths diff --git a/examples/chem-sync-local-flask/local_app/benchling_app/views/canvas_initialize.py b/examples/chem-sync-local-flask/local_app/benchling_app/views/canvas_initialize.py index 78342f8..361484f 100644 --- a/examples/chem-sync-local-flask/local_app/benchling_app/views/canvas_initialize.py +++ b/examples/chem-sync-local-flask/local_app/benchling_app/views/canvas_initialize.py @@ -16,6 +16,17 @@ from local_app.benchling_app.views.constants import SEARCH_BUTTON_ID, SEARCH_TEXT_ID +""" +This file contains examples of how create a canvas at initialization +For examples of updates, check out canvas_interaction.py + +Use a CanvasBuilder to create or update a canvas associated with your app +This sdk tool can be used for easy creation and updates + +Check out https://benchling.com/sdk-docs/1.22.0/benchling_sdk.apps.canvas.framework.html +for more details on the CanvasBuilder class +""" + def render_search_canvas(app: App, canvas_initialized: CanvasInitializeWebhookV2) -> None: with app.create_session_context("Show Sync Search", timeout_seconds=20): @@ -24,7 +35,11 @@ def render_search_canvas(app: App, canvas_initialized: CanvasInitializeWebhookV2 feature_id=canvas_initialized.feature_id, resource_id=canvas_initialized.resource_id, ) + + # Add the input blocks to the canvas canvas_builder.blocks.append(input_blocks()) + + # Create the canvas app.benchling.apps.create_canvas(canvas_builder.to_create()) @@ -36,6 +51,13 @@ def render_search_canvas_for_created_canvas(app: App, canvas_created: CanvasCrea def input_blocks() -> list[UiBlock]: + """ + Create 3 blocks to populate the canvas: + + Markdown block to display text + TextInput block to capture user input + Button block to trigger the search + """ return [ MarkdownUiBlock( id="top_instructions",