Reducers: get_approvers

🚧

The get_approvers reducer is deprecated

We recommend using a combination of get_permission and get_request_notification reducers.

The get_approvers Reducer is Sym's legacy solution to request routing and permissions, which has been replaced by a combination of the new get_permissions and get_request_notifications Reducers.

It accepts an Event representing a Sym Request Event, and returns a RequestDestination or RequestDestinationFallback object that defines where that request should be sent.

This Reducer was the core of Sym's routing logic. If get_permissions is not present, get_approvers is required.

Basic Usage

The simplest implementation of get_approvers is to explicitly declare a destination channel for all executions of a given Flow. In the below example, we are also declaring that users may not approve their own requests.

from sym.sdk.annotations import reducer
from sym.sdk.integrations import slack

@reducer
def get_approvers(event):
    return slack.channel("#sym-requests", allow_self=False)

Advanced Usage

Send requests to the channel they came from

Some Sym workflows will be best served in their originating context—that is to say, they should be sent into the same channel from which they were made.

Sym's Slack Integration makes this easy by providing both access to your initiating channel (event.run.source_channel.identifier), and a method to define a fallback channel in case our Slack App doesn't have access to the originating channel:

from sym.sdk.annotations import reducer
from sym.sdk.integrations import slack

@reducer
def get_approvers(event):
    return slack.fallback(
      slack.channel(event.run.source_channel.identifier), 
      slack.channel("#sym-access")
    )

Configuring guest user permissions

By default, guests are not allowed to interact with requests.

This behavior can be controlled by the allow_guest_interaction boolean parameter that can optionally be set on the Flow resource in Terraform. When the boolean is set to true, guests are able to interact with the Approve, Deny, and Revoke buttons on a request. A guest can only take a revoke action when they are the ones who have approved a request. When not provided, or set to false, guests' clicks will be ignored, and they will receive a DM explaining why.

Configuring backup approvers

In order to use the backup approver's feature, your get_approvers Reducer must return a RequestDestinationFallback object. The values passed into the parameters continue_on_delivery_failure and continue_on_timeout will determine whether the system will try the next approver based on the failure type.

Failure TypeBehavior when TrueBehavior when False or NoneDefaults
continue_on_delivery_failureThe next destination will be tried immediately after a failed delivery to the current destination.If False, after a delivery failure, the next approver will not be tried.True
continue_on_timeoutThe next RequestDestination will be tried after the timeout (in seconds) specified on the current RequestDestination.If None, the next approver will not be tried in the case of a timeout.False

Example

In this example, we will return a RequestDestinationFallback object from get_approvers to walk through what the SDK will do in various scenarios.

from sym.sdk.annotations import reducer
from sym.sdk.integrations import slack


@reducer
def get_approvers(event):
    return slack.fallback(
        slack.channel("#sym-requests", allow_self=True, timeout=600),
        slack.channel("#sym-backup", allow_self=True, timeout=1200),
        slack.user("[email protected]", timeout=None),
        continue_on_timeout=True
    )

In this example, assuming no delivery failures:

  1. The request will be sent to the #sym-requests Slack channel via a request event.
  2. If there is no response within 600 seconds (10 mins), the request will be sent to the #sym-backup Slack channel via a request_forward event.
  3. If there is no response within 1200 seconds (20 mins), the request will be sent via DM to the Slack user with the email [email protected] via a request_forward event . Since there is no timeout specified on that RequestDestination, it will hang there indefinitely until [email protected] responds.

Adding in a timeout

However, if we modify the example slightly to add a timeout on the slack.user object, like so:

from sym.sdk.annotations import reducer
from sym.sdk.integrations import slack


@reducer
def get_approvers(event):
    return slack.fallback(
        slack.channel("#sym-requests", allow_self=True, timeout=600),
        slack.channel("#sym-backup", allow_self=True, timeout=1200),
        slack.user("[email protected]", timeout=1800),
        continue_on_timeout=True
    )

In this modified example, if there is no response from [email protected] within 1800 seconds (30 mins), the SDK will fire a request_expire event, and the message in Slack will be updated to show that the request has timed out.

Events

As with all transitions in the Sym SDK, Backup Approvers exposes hooks to the SDK.

It is important to note that the Events that occur in the SDK will be slightly different depending on the behavior specified by the RequestDestinationFallback returned by get_approvers.

In general:

  • When a continue_on_timeout occurs, Sym will fire a request_forward Event.
  • When a continue_on_delivery failure occurs, no new event will be fired. The logic will simply try the next destination without firing any events.
  • When we have reached the end of our list of destinations and the last one times out, Sym will fire a request_expire Event.

Just like other Events, you can write a hook in your impl.py with custom Python logic that will run on or after request_forward and request_expire.

Note:request_forward hooks take an additional param request_forward_context (RequestForwardContext), which contains information about the full forwarding context of a Request, including both the full list of potential destinations, and its current index in moving through that list.

For example, you might want to DM a requester each time their request went to a new, valid destination:

from sym.sdk.annotations import hook
from sym.sdk.integrations import slack

@hook
def on_request_forward(event, request_forward_context):
    index = request_forward_context.current_destination_index
    current_destination = request_forward_context.all_request_destinations[index]
    if current_destination:
        slack.send_message(
            event.get_actor("request"),
            f"Your request was forwarded to {current_destination}"
        )

📘

Further Reading

For more information about Backup Approvers and the event hooks you can use, see our docs here.