Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Slack Shortcuts, Modals, & Celery Beat Scheduled Tasks #4

Merged
merged 7 commits into from
Feb 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 135 additions & 11 deletions Settings/blocks.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
from itertools import groupby
from operator import itemgetter

from pkgbot import config
from pkgbot.db import models


config = config.load_config()

secure = "s" if config.PkgBot.get("enable_ssl") else ""
pkgbot_server = f"http{secure}://{config.PkgBot.get('host')}:{config.PkgBot.get('port')}"
SECURE = "s" if config.PkgBot.get("enable_ssl") else ""
PKGBOT_SERVER = f"http{SECURE}://{config.PkgBot.get('host')}:{config.PkgBot.get('port')}"


async def brick_header(pkg_object: models.Package_In):
Expand All @@ -29,7 +32,7 @@ async def brick_main(pkg_object: models.Package_In):
},
"accessory": {
"type": "image",
"image_url": f"{pkgbot_server}/static/icons/{pkg_object.dict().get('icon')}",
"image_url": f"{PKGBOT_SERVER}/static/icons/{pkg_object.dict().get('icon')}",
"alt_text": ":new:"
}
}
Expand Down Expand Up @@ -134,7 +137,7 @@ async def brick_error(recipe_id, error):
},
"accessory": {
"type": "image",
"image_url": f"{pkgbot_server}/static/icons/{config.PkgBot.get('icon_error')}",
"image_url": f"{PKGBOT_SERVER}/static/icons/{config.PkgBot.get('icon_error')}",
"alt_text": ":x:"
}
},
Expand Down Expand Up @@ -198,7 +201,7 @@ async def brick_update_trust_error_msg(trust_object, msg):
},
"accessory": {
"type": "image",
"image_url": f"{pkgbot_server}/static/icons/{config.PkgBot.get('icon_error')}",
"image_url": f"{PKGBOT_SERVER}/static/icons/{config.PkgBot.get('icon_error')}",
"alt_text": ":x:"
}
}]
Expand Down Expand Up @@ -226,7 +229,7 @@ async def brick_deny_trust(trust_object):
},
"accessory": {
"type": "image",
"image_url": f"{pkgbot_server}/static/icons/{config.PkgBot.get('icon_denied')}",
"image_url": f"{PKGBOT_SERVER}/static/icons/{config.PkgBot.get('icon_denied')}",
"alt_text": ":denied:"
}
}
Expand Down Expand Up @@ -254,7 +257,7 @@ async def brick_trust_diff_main(recipe):
},
"accessory": {
"type": "image",
"image_url": f"{pkgbot_server}/static/icons/{config.PkgBot.get('icon_warning')}",
"image_url": f"{PKGBOT_SERVER}/static/icons/{config.PkgBot.get('icon_warning')}",
"alt_text": ":warning:"
}
}
Expand Down Expand Up @@ -321,7 +324,7 @@ async def unauthorized(user):
},
"accessory": {
"type": "image",
"image_url": f"{pkgbot_server}/static/icons/{config.PkgBot.get('icon_permission_denied')}",
"image_url": f"{PKGBOT_SERVER}/static/icons/{config.PkgBot.get('icon_permission_denied')}",
"alt_text": ":denied:"
}
}
Expand All @@ -340,12 +343,14 @@ async def brick_section_text(text):
}


async def brick_accessory_image(image, alt_text=":notification:"):
async def brick_accessory_image(image, alt_text):

alt_text = alt_text or ":notification:"

return {
"accessory": {
"type": "image",
"image_url": f"{pkgbot_server}/static/icons/{image}",
"image_url": f"{PKGBOT_SERVER}/static/icons/{image}",
"alt_text": alt_text
}
}
Expand All @@ -371,7 +376,7 @@ async def brick_disk_space_msg(header, msg, image):
},
"accessory": {
"type": "image",
"image_url": f"{pkgbot_server}/static/icons/{image}",
"image_url": f"{PKGBOT_SERVER}/static/icons/{image}",
"alt_text": ":warning:"
}
},
Expand All @@ -391,3 +396,122 @@ async def brick_disk_space_msg(header, msg, image):
]
}
]


async def modal_notification(title_txt: str, button_text: str):
# An undocumented limitation: maximum 26 characters in the `title.text` string

return {
"type": "modal",
"callback_id": "notification",
# "private_metadata": f"{private_metadata}",
"title": {
"type": "plain_text",
"text": f"{title_txt}",
"emoji": True
},
"close": {
"type": "plain_text",
"text": f"{button_text}",
"emoji": True
}
}


async def modal_promote_pkg(pkg_name: str):

return {
"type": "modal",
"callback_id": "promote_pkg",
"private_metadata": f"{pkg_name}",
"title": {
"type": "plain_text",
"text": "Promote pkg to Jamf Pro"
},
"submit": {
"type": "plain_text",
"text": "Promote :rocket:",
"emoji": True
},
"close": {
"type": "plain_text",
"text": "Cancel"
},
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Select Policy to add or update the existing version of the selected pkg."
}
},
{
"type": "divider"
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"Pkg to promote: `{pkg_name}`"
}
},
{
"type": "input",
"element": {
"type": "external_select",
"placeholder": {
"type": "plain_text",
"text": "Select a Policy",
"emoji": True
},
"min_query_length": 5,
"action_id": "policy_list"
},
"label": {
"type": "plain_text",
"text": "Select a Policy and :ship_it_parrot:",
"emoji": True
}
}
]
}


async def policy_list(policies: str):

option_groups = []
policies = sorted(policies, key=itemgetter("site"))

# This _is_ a documented limitation: maximum of 100 options can be included in a list
for site, value in groupby(policies[:99], key=itemgetter("site")):
options = []

for policy in value:
options.append(await create_static_option(policy))

if options:
option_groups.append({
"label": {
"type": "plain_text",
"text": f"Site: {site}"
},
"options": options
})

return {
"option_groups": option_groups
}


async def create_static_option(policy):

return {
"text": {
"type": "plain_text",
# This seems to be an undocumented limitation:
# maximum of 76 characters in the `text` string
"text": policy.get("name")[:75],
"emoji": True
},
"value": f"{policy.get('policy_id')}"
}
18 changes: 18 additions & 0 deletions Settings/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,21 @@ async def basic(text: str, image: str | None = None, alt_image_text: str | None
async def disk_space(header: str, msg: str, image: str | None = None):

return await format_json(await block.brick_disk_space_msg(header, msg, image))


async def modal_notification(title_txt: str, msg_text: str,
button_text: str, image: str | None = None, alt_image_text: str | None = None):

blocks = await block.brick_section_text(msg_text)

if image:
blocks = blocks | await block.brick_accessory_image(image, alt_image_text)

blocks = await block.modal_notification(title_txt, button_text) | {"blocks": [blocks]}

return await format_json(blocks)


async def modal_promote_pkg(pkg_name: str):

return await format_json(await block.modal_promote_pkg(pkg_name))
3 changes: 2 additions & 1 deletion examples/settings/pkgbot_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ Slack:
bot_name: PkgBot
slack_id:
channel:

slash_cmds_enabled: False
shortcuts_enabled: False

PkgBot:
# enable_ssl: True
Expand Down
2 changes: 1 addition & 1 deletion pkgbot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def create_pkgbot() -> FastAPI:
title="PkgBot API",
description="A framework to manage software packaging, testing, and promoting from a "
"development to production environment.",
version="0.4.0",
version="0.5.0",
openapi_tags=settings.api.tags_metadata,
docs_url="/api"
)
Expand Down
22 changes: 14 additions & 8 deletions pkgbot/api/slackbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,20 @@ async def receive(request: Request):
payload_type = payload_object.get("type")
# log.debug(f"Received Payload Type: {payload_type}")

if payload_type == "message_action":
await core.chatbot.events.message_shortcut(payload_object)

elif (
payload_type == "block_actions" and
payload_object.get("actions")[0].get("type") == "button"
):
await core.chatbot.events.button_click(payload_object)
match payload_type:

case "block_actions":
if payload_object.get("actions")[0].get("type") == "button":
await core.chatbot.events.button_click(payload_object)

case "block_suggestion":
return await core.chatbot.events.external_lists(payload_object)

case "message_action":
await core.chatbot.events.message_shortcut(payload_object)

case "view_submission":
await core.chatbot.events.view_submission(payload_object)

return Response(status_code=status.HTTP_200_OK)

Expand Down
1 change: 1 addition & 0 deletions pkgbot/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from . import error
from . import jamf_pro
from . import package
from . import policy
from . import recipe
from . import user

Expand Down
5 changes: 2 additions & 3 deletions pkgbot/core/autopkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ async def run_recipe(recipe_id: str, autopkg_cmd: models.AutoPkgCMD_Run):

if a_recipe.enabled:

queued_task = pkgbot_celery_app.send_task(
return pkgbot_celery_app.send_task(
"autopkg:verb_parser",
kwargs = {
"recipes": [ a_recipe.dict() ],
Expand All @@ -136,8 +136,7 @@ async def run_recipe(recipe_id: str, autopkg_cmd: models.AutoPkgCMD_Run):
queue="autopkg",
priority=4
)

return { "result": "Queued background task" , "task_id": queued_task.id }
# return { "result": "Queued background task" , "task_id": queued_task.id }

log.info(f"Recipe '{recipe_id}' is disabled.")
return { "result": "Recipe is disabled" }
Expand Down
12 changes: 12 additions & 0 deletions pkgbot/core/chatbot/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,15 @@ async def basic_msg(text: str, image: str | None = None, alt_image_text: str | N
async def disk_space_msg(header: str, msg: str, image: str | None = None):

return await messages.disk_space(header, msg, image)


async def modal_notification(title_txt: str, msg_text: str,
button_text: str, image: str | None = None, alt_image_text: str | None = None):

return await messages.modal_notification(
title_txt, msg_text, button_text, image, alt_image_text)


async def modal_promote_pkg(pkg_name: str):

return await messages.modal_promote_pkg(pkg_name)
Loading