Skip to content
This repository has been archived by the owner on Oct 21, 2024. It is now read-only.

Commit

Permalink
Duplicate polling tasks as Flask CLI commands (#238)
Browse files Browse the repository at this point in the history
  • Loading branch information
alysivji authored Apr 12, 2020
1 parent 1a75277 commit 96a20f1
Show file tree
Hide file tree
Showing 15 changed files with 199 additions and 9 deletions.
13 changes: 11 additions & 2 deletions busy_beaver/app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from flask import Flask, request

from .blueprints import github_bp, healthcheck_bp, poller_bp, slack_bp
from .blueprints import (
events_bp,
github_bp,
healthcheck_bp,
poller_bp,
slack_bp,
twitter_bp,
)
from .common.oauth import OAuthError
from .config import DATABASE_URI, REDIS_URI, SECRET_KEY
from .exceptions import NotAuthorized, ValidationError
Expand Down Expand Up @@ -49,10 +56,12 @@ def create_app(*, testing=False):
app.register_error_handler(OAuthError, handle_oauth_error)
app.register_error_handler(ValidationError, handle_validation_error)

app.register_blueprint(events_bp, cli_group=None)
app.register_blueprint(healthcheck_bp)
app.register_blueprint(github_bp, url_prefix="/github")
app.register_blueprint(github_bp, url_prefix="/github", cli_group=None)
app.register_blueprint(poller_bp, url_prefix="/poll")
app.register_blueprint(slack_bp, url_prefix="/slack")
app.register_blueprint(twitter_bp, cli_group=None)

@app.before_request
def add_internal_dictionary():
Expand Down
5 changes: 1 addition & 4 deletions busy_beaver/apps/github_integration/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
from flask import blueprints

from ..blueprint import github_bp
from .event_subscription import GitHubEventSubscriptionResource
from .oauth import GitHubIdentityVerificationCallbackResource

github_bp = blueprints.Blueprint("github", __name__)

github_bp.add_url_rule(
"/event-subscription",
view_func=GitHubEventSubscriptionResource.as_view("github_event_subscription"),
Expand Down
6 changes: 6 additions & 0 deletions busy_beaver/apps/github_integration/blueprint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from flask import blueprints

github_bp = blueprints.Blueprint("github", __name__)

from . import api # noqa isort:skip
from . import summary # noqa isort:skip
20 changes: 19 additions & 1 deletion busy_beaver/apps/github_integration/summary/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import random
from typing import List

import click
from sqlalchemy import and_

from ..blueprint import github_bp
from .summary import GitHubUserEvents
from busy_beaver.common.wrappers import SlackClient
from busy_beaver.exceptions import ValidationError
Expand All @@ -20,6 +22,22 @@
logger = logging.getLogger(__name__)


@click.option("--workspace", required=True) # Slack Workspace ID
@github_bp.cli.command("post_github_summary", help="Post a GitHub summary")
def post_github_summary_to_slack_cli(workspace: str):
boundary_dt = utc_now_minus(timedelta(days=1))
slack_installation = SlackInstallation.query.filter_by(
workspace_id=workspace
).first()
if not slack_installation:
raise ValidationError("workspace not found")

# we should log that we did something somewhere
# also keep track of how long a summary took
# TODO once we are migrated over
fetch_github_summary_post_to_slack(slack_installation.id, boundary_dt)


def start_post_github_summary_task(task_owner: ApiUser, workspace_id: str):
boundary_dt = utc_now_minus(timedelta(days=1))
slack_installation = SlackInstallation.query.filter_by(
Expand Down Expand Up @@ -64,7 +82,7 @@ def fetch_github_summary_post_to_slack(installation_id, boundary_dt):
message = ""
num_users = len(users)
for idx, user in enumerate(users):
logger.info("[Busy Beaver] Compiling stats for {0}".format(user))
logger.info("Compiling stats for {0}".format(user))
user_events = GitHubUserEvents(user, boundary_dt)
message += user_events.generate_summary_text()
set_task_progress(idx / (num_users + 1) * 100)
Expand Down
5 changes: 5 additions & 0 deletions busy_beaver/apps/retweeter/blueprint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from flask import blueprints

twitter_bp = blueprints.Blueprint("twitter", __name__)

from . import task # noqa isort:skip
10 changes: 10 additions & 0 deletions busy_beaver/apps/retweeter/task.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from datetime import timedelta
import logging

import click

from .blueprint import twitter_bp
from busy_beaver.clients import chipy_slack, twitter
from busy_beaver.common.wrappers import KeyValueStoreClient
from busy_beaver.config import TWITTER_USERNAME
Expand All @@ -13,6 +16,13 @@
kv_store = KeyValueStoreClient()


@click.option("--channel_name", required=True)
@twitter_bp.cli.command("post_tweets_to_slack", help="Find new tweets to post to Slack")
def post_new_tweets_to_slack(channel_name: str):
# TODO add logging and times
fetch_tweets_post_to_slack(channel_name, username=TWITTER_USERNAME)


def start_post_tweets_to_slack_task(task_owner: ApiUser, channel_name):
logger.info("[Busy Beaver] Kick off retweeter task")

Expand Down
6 changes: 6 additions & 0 deletions busy_beaver/apps/upcoming_events/blueprint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from flask import blueprints

events_bp = blueprints.Blueprint("events", __name__)

from . import workflow # noqa isort:skip
from .event_database import task # noqa isort:skip
9 changes: 9 additions & 0 deletions busy_beaver/apps/upcoming_events/event_database/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import logging
import time

import click

from ..blueprint import events_bp
from .sync_database import SyncEventDatabase
from busy_beaver.clients import meetup
from busy_beaver.config import MEETUP_GROUP_NAME
Expand All @@ -19,6 +22,12 @@
logger = logging.getLogger(__name__)


@click.option("--group_name", required=True)
@events_bp.cli.command("sync_events_database", help="Sync Events Database")
def sync_events_database_cli(group_name: str):
sync_database_with_fetched_events(group_name)


def start_sync_event_database_task(task_owner: ApiUser):
logger.info("[Busy Beaver] Kick off fetch meetup events task")

Expand Down
14 changes: 14 additions & 0 deletions busy_beaver/apps/upcoming_events/workflow.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import time

import click

from .blueprint import events_bp
from busy_beaver.apps.upcoming_events.cards import UpcomingEventList
from busy_beaver.clients import chipy_slack
from busy_beaver.common.wrappers.meetup import EventDetails
Expand All @@ -18,6 +21,17 @@ def generate_next_event_message(group_name: str):
return _next_event_attachment(event)


@click.option("--count", default=5, required=True)
@click.option("--group_name", required=True)
@click.option("--channel", required=True)
@events_bp.cli.command("post_upcoming_events", help="Post Upcoming Events Summary")
def post_upcoming_events_message_to_slack_cli(
channel: str, group_name: str, count: int
):
blocks = generate_upcoming_events_message(group_name, count)
chipy_slack.post_message(blocks=blocks, channel=channel)


def post_upcoming_events_message_to_slack(channel: str, group_name: str, count: int):
blocks = generate_upcoming_events_message(group_name, count)
chipy_slack.post_message(blocks=blocks, channel=channel)
Expand Down
4 changes: 3 additions & 1 deletion busy_beaver/blueprints.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from busy_beaver.apps.debug.api.healthcheck import healthcheck_bp # noqa
from busy_beaver.apps.github_integration.api import github_bp # noqa
from busy_beaver.apps.github_integration.blueprint import github_bp # noqa
from busy_beaver.apps.poller.api import poller_bp # noqa
from busy_beaver.apps.retweeter.blueprint import twitter_bp # noqa
from busy_beaver.apps.slack_integration.api import slack_bp # noqa
from busy_beaver.apps.upcoming_events.blueprint import events_bp # noqa
9 changes: 8 additions & 1 deletion tests/_utilities/fixtures/flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ def app():

@pytest.fixture(scope="module")
def client(app):
"""Create flask test client where we can trigger test requests to app"""
"""Create Flask test client where we can trigger test requests to app"""
client = app.test_client()
yield client


@pytest.fixture(scope="module")
def runner(app):
"""Create Flask CliRunner that can be used to invoke commands"""
runner = app.test_cli_runner()
yield runner
32 changes: 32 additions & 0 deletions tests/apps/github_integration/summary/task_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from busy_beaver.apps.github_integration.summary.task import (
fetch_github_summary_post_to_slack,
post_github_summary_to_slack_cli,
start_post_github_summary_task,
)
from busy_beaver.models import ApiUser
Expand Down Expand Up @@ -207,3 +208,34 @@ def test_post_github_summary_task__integration(
post_message_args = slack.mock.call_args_list[-1]
args, kwargs = post_message_args
assert "<@user1>" in kwargs["message"]


##########
# Test CLI
##########
@pytest.mark.end2end
def test_post_github_summary_to_slack_cli(
runner, session, factory, t_minus_one_day, patched_slack, patched_github_user_events
):
# Arrange
slack_installation = factory.SlackInstallation(workspace_id="abc")
channel = "general"
github_summary_config = factory.GitHubSummaryConfiguration(
channel=channel, slack_installation=slack_installation
)
slack_installation = github_summary_config.slack_installation
slack = patched_slack(members=["user1", "user2"])
patched_github_user_events(messages=["a", "b"])

# Act
runner.invoke(post_github_summary_to_slack_cli, ["--workspace", "abc"])

# Assert
slack_adapter_initalize_args = slack.mock.call_args_list[0]
args, kwargs = slack_adapter_initalize_args
assert slack_installation.bot_access_token in args

post_message_args = slack.mock.call_args_list[-1]
args, kwargs = post_message_args
assert "does it make a sound" in kwargs["message"]
assert "general" in kwargs["channel"]
32 changes: 32 additions & 0 deletions tests/apps/retweeter/task_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from busy_beaver.apps.retweeter.task import (
LAST_TWEET_KEY,
fetch_tweets_post_to_slack,
post_new_tweets_to_slack,
start_post_tweets_to_slack_task,
)
from busy_beaver.models import ApiUser
Expand Down Expand Up @@ -97,3 +98,34 @@ def test_post_tweets_to_slack(
args, kwargs = patched_slack.mock.call_args
assert "test_username/statuses/1" in args[0]
assert "test_channel" in kwargs["channel"]


##########
# Test CLI
##########
@pytest.mark.end2end
def test_post_new_tweets_to_slack(
mocker, runner, factory, kv_store, patched_twitter, patched_slack
):
"""
GIVEN: 3 tweets to post (2 within the window)
WHEN: post_tweets_to_slack is called
THEN: we post one tweet
"""
# Arrange
kv_store.put_int(LAST_TWEET_KEY, 0)
tweets = [
factory.Tweet(id=3, created_at=utc_now_minus(timedelta())),
factory.Tweet(id=2, created_at=utc_now_minus(timedelta(days=1))),
factory.Tweet(id=1, created_at=utc_now_minus(timedelta(days=1))),
]
patched_twitter(tweets)

# Act
runner.invoke(post_new_tweets_to_slack, ["--channel_name", "test_channel"])

# Assert
assert len(patched_slack.mock.mock_calls) == 1
args, kwargs = patched_slack.mock.call_args
assert "ChicagoPython/statuses/1" in args[0]
assert "test_channel" in kwargs["channel"]
23 changes: 23 additions & 0 deletions tests/apps/upcoming_events/event_database/task_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from busy_beaver.apps.upcoming_events.event_database.task import (
start_sync_event_database_task,
sync_database_with_fetched_events,
sync_events_database_cli,
)
from busy_beaver.models import ApiUser, Event

Expand Down Expand Up @@ -145,3 +146,25 @@ def test_sync_database(session, factory, patched_meetup):
all_event_ids_in_database = set(event.remote_id for event in all_events_in_database)
for event in new_events:
assert event.id in all_event_ids_in_database


##########
# Test CLI
##########
@pytest.mark.end2end
def test_sync_events_database_cli(runner, session, factory, patched_meetup):
"""
GIVEN: Empty database
WHEN: add_events_to_database is called
THEN: add all events to database
"""
# Arrange
events = factory.EventDetails.create_batch(size=20)
patched_meetup(events=events)

# Act
runner.invoke(sync_events_database_cli, ["--group_name", "test_group"])

# Assert
all_events_in_database = Event.query.all()
assert len(all_events_in_database) == len(events)
20 changes: 20 additions & 0 deletions tests/apps/upcoming_events/workflow_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
generate_next_event_message,
generate_upcoming_events_message,
post_upcoming_events_message_to_slack,
post_upcoming_events_message_to_slack_cli,
)

MODULE_TO_TEST = "busy_beaver.apps.upcoming_events.workflow"
Expand Down Expand Up @@ -48,3 +49,22 @@ def test_post_upcoming_events_message_to_slack(mocker, session, factory, patched
post_message_args = patched_slack.mock.call_args_list[-1]
args, kwargs = post_message_args
assert len(kwargs["blocks"]) == 15 # sections: 3 in the header, each block is 3


@pytest.mark.integration
def test_cli_post_upcoming_events_message_to_slack(
mocker, runner, session, factory, patched_slack
):
# Arrange
factory.Event.create_batch(size=10)

# Act
runner.invoke(
post_upcoming_events_message_to_slack_cli,
["--channel", "announcements", "--group_name", "ChiPy", "--count", 4],
)

# Assert
post_message_args = patched_slack.mock.call_args_list[-1]
args, kwargs = post_message_args
assert len(kwargs["blocks"]) == 15 # sections: 3 in the header, each block is 3

0 comments on commit 96a20f1

Please sign in to comment.