Migrating From `get_approvers` to `get_permissions`

Overview

Historically, the get_approvers Reducer has defined where Sym requests go and who can interact with them. In January 2024, we introduced two new Reducers: get_permissions and get_request_notifications. These new Reducers separate the configuration for:

  • Who has permission to see and interact with this request?
  • Where do notifications about this request go?

This is mostly a reorganization of the same functionality that get_approvers supports, but using the new Reducers does provide additional functionality not available via get_approvers:

  • With get_permissions, non-admins may be permitted to view and act upon others' requests in the Sym web app.
  • With get_request_notifications, notifications about new Sym requests can be sent via email to users, in addition to Slack channels.

Migrating Permissions

With get_approvers, permissions other than allow_self (a flag that determines whether the requester is allowed to approve their own requests) could only be managed in two ways:

  1. Controlling whether a user could see the request (e.g. if you can't see it, you can't act on it)
  2. Using Hooks (e.g. if user X clicks "approve", reject it)

With get_permissions, these permissions are now explicitly defined in Sym. To migrate from get_approvers, copy the following template Reducer.

The only part of the template that may differ based on your current get_approvers definition is the allow_self_approval flag, which is identical to the allow_self flag from get_approvers.

from sym.sdk.annotations import reducer
from sym.sdk.request_permission import PermissionLevel, RequestPermission

@reducer
def get_permissions(event):
    return RequestPermission(
        # Only admins can view requests made by other users in the Sym web app.
        webapp_view=PermissionLevel.ADMIN, 
        # Members and admins may approve or deny Sym requests.
        approve_deny=PermissionLevel.MEMBER, 
        # Users may not approve their own requests.
        allow_self_approval=False
    )

Migrating Notifications

Basic Slack notifications

Basic get_approvers configurations that just send messages to Slack when new Sym requests are made should be fairly straightforward to migrate. Where get_approvers would return a slack.channel object get_request_notifications will wrap that object in a new Notification class.

In the following template, slack.channel may be replaced with either slack.user or slack.group, depending on your get_approvers configuration.

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


@reducer
def get_request_notifications(event):
    # Send notifications about new Sym requests to the #sym-requests channel in Slack.
    return [Notification(destinations=[slack.channel("#sym-requests")])]

Slack notifications with fallbacks and timeouts

More complex notification behavior that would previously have been defined using slack.fallback is now built in to the get_request_notifications Reducer. To fall back between different Slack destinations and eventually time out, use the following template.

You will likely want to modify the template to change:

  • The number of fallbacks
  • The time lengths between fallbacks or timeout
  • The specific Slack destinations for each step
from sym.sdk.annotations import reducer
from sym.sdk.integrations import slack
from sym.sdk.notifications import Notification


@reducer
def get_request_notifications(event):
    return [
        # Send a notification to #sym-requests in Slack. After 10 seconds, forward it to the next
        # Notification in the list.
        Notification(destinations=[slack.channel("#sym-requests")], timeout=10),
        
        # Send a notification to #sym-requests-fallback in Slack. After 60 seconds, the request expires.
        Notification(destinations=[slack.channel("#sym-requests-fallback")], timeout=60),
    ]

Examples

The following are some example migrations that may match your existing get_approvers configuration.

Sending to a Slack channel with allow_self

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


@reducer
def get_approvers(event):
    return slack.channel("#sym-requests", allow_self=True)
from sym.sdk.annotations import reducer
from sym.sdk.integrations import slack
from sym.sdk.request_permission import PermissionLevel, RequestPermission
from sym.sdk.notifications import Notification


@reducer
def get_permissions(event):
    return RequestPermission(
        webapp_view=PermissionLevel.ADMIN, 
        approve_deny=PermissionLevel.MEMBER, 
        allow_self_approval=True
    )


@reducer
def get_request_notifications(event):
    return [Notification(destinations=[slack.channel("#sym-requests")])]

Sending to a Slack user without allow_self

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


@reducer
def get_approvers(event):
    return slack.user(event.user, allow_self=False)
from sym.sdk.annotations import reducer
from sym.sdk.integrations import slack
from sym.sdk.request_permission import PermissionLevel, RequestPermission
from sym.sdk.notifications import Notification


@reducer
def get_permissions(event):
    return RequestPermission(
        webapp_view=PermissionLevel.ADMIN, 
        approve_deny=PermissionLevel.MEMBER, 
        allow_self_approval=False
    )


@reducer
def get_request_notifications(event):
    return [Notification(destinations=[slack.user(event.user)])]

Sending to Slack destinations with fallbacks

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


@reducer
def get_approvers(event):
    return slack.fallback(
        # Send to #first in Slack. After 5s, forward to next fallback.
        slack.channel("#first", timeout=5), 
        # Send to #second in Slack. On delivery failure, forward to next fallback.
        slack.channel("#second"), 
        # Send a DM to the user in Slack.
        slack.user(event.user),
        continue_on_timeout=True
    )
from sym.sdk.annotations import reducer
from sym.sdk.integrations import slack
from sym.sdk.request_permission import PermissionLevel, RequestPermission
from sym.sdk.notifications import Notification


@reducer
def get_permissions(event):
    return RequestPermission(
        webapp_view=PermissionLevel.ADMIN, 
        approve_deny=PermissionLevel.MEMBER, 
        allow_self_approval=False
    )


@reducer
def get_request_notifications(event):
    return [
        # Send to #first in Slack. After 5s, forward to next Notification in the list.
        Notification(destinations=[slack.channel("#first")], timeout=5),
        # Send to #second in Slack. On delivery failure, forward to next Notification in the list.
        Notification(destinations=[slack.channel("second")]),
        # Send a DM to the user in Slack.
        Notification(destinations=[slack.user(event.user)])
    ]

Timing out a request after an amount of time

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


@reducer
def get_approvers(event):
    # Send to #sym-requests in Slack. After 60s, expire the request.
    return slack.channel("#sym-requests", timeout=5)
from sym.sdk.annotations import reducer
from sym.sdk.integrations import slack
from sym.sdk.request_permission import PermissionLevel, RequestPermission
from sym.sdk.notifications import Notification


@reducer
def get_permissions(event):
    return RequestPermission(
        webapp_view=PermissionLevel.ADMIN, 
        approve_deny=PermissionLevel.MEMBER, 
        allow_self_approval=False
    )


@reducer
def get_request_notifications(event):
    # Send to #sym-requests in Slack. After 60s, expire the request.
    return [
        Notification(destinations=[slack.channel("#sym-requests")], timeout=60)
    ]