Skip to content

Commit

Permalink
Merge ec232c9 into 045f0f4
Browse files Browse the repository at this point in the history
  • Loading branch information
TimJentzsch committed Apr 22, 2022
2 parents 045f0f4 + ec232c9 commit 0b7bff5
Show file tree
Hide file tree
Showing 5 changed files with 407 additions and 85 deletions.
8 changes: 6 additions & 2 deletions api/slack/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from api.slack import client
from api.slack.commands.blacklist import blacklist_cmd
from api.slack.commands.check import check_cmd
from api.slack.commands.checkstats import checkstats_cmd
from api.slack.commands.dadjoke import dadjoke_cmd
from api.slack.commands.help import help_cmd
from api.slack.commands.info import info_cmd
Expand Down Expand Up @@ -36,13 +37,15 @@ def process_command(data: Dict) -> None:

if not message and not actions:
client.chat_postMessage(
channel=channel, text=i18n["slack"]["errors"]["message_parse_error"],
channel=channel,
text=i18n["slack"]["errors"]["message_parse_error"],
)
return

if not message:
client.chat_postMessage(
channel=channel, text=i18n["slack"]["errors"]["empty_message_error"],
channel=channel,
text=i18n["slack"]["errors"]["empty_message_error"],
)
return

Expand All @@ -51,6 +54,7 @@ def process_command(data: Dict) -> None:
options = {
"blacklist": blacklist_cmd,
"check": check_cmd,
"checkstats": checkstats_cmd,
"dadjoke": dadjoke_cmd,
"help": help_cmd,
"info": info_cmd,
Expand Down
223 changes: 223 additions & 0 deletions api/slack/commands/checkstats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
from datetime import timedelta
from typing import Dict

from django.db.models import Q, QuerySet
from django.utils import timezone

from api.models import TranscriptionCheck
from api.slack import client
from api.slack.commands.utils import format_stats_section, format_time
from api.slack.utils import parse_user
from authentication.models import BlossomUser
from blossom.strings import translation

i18n = translation()

CheckStatus = TranscriptionCheck.TranscriptionCheckStatus

# Timedelta for all "recent" queries
RECENT_DELTA = timedelta(weeks=2)


def checkstats_cmd(channel: str, message: str) -> None:
"""Get the check stats for a specific mod.
Notably this shows how many transcriptions the given mod checked,
NOT how many checks were done for the given user.
"""
parsed_message = message.split()

if len(parsed_message) == 1:
# they didn't give a username
msg = i18n["slack"]["errors"]["missing_username"]
elif len(parsed_message) == 2:
user, username = parse_user(parsed_message[1])
if user:
msg = check_stats_msg(user)
else:
msg = i18n["slack"]["errors"]["unknown_username"].format(username=username)

else:
msg = i18n["slack"]["errors"]["too_many_params"]

client.chat_postMessage(channel=channel, text=msg)


def check_stats_msg(mod: BlossomUser) -> str:
"""Get the message showing the check stats for the given mod."""
recent_date = timezone.now() - RECENT_DELTA

server_checks = TranscriptionCheck.objects.filter(complete_time__isnull=False)
mod_checks = server_checks.filter(moderator=mod)

recent_server_checks = server_checks.filter(complete_time__gte=recent_date)
recent_mod_checks = mod_checks.filter(complete_time__gte=recent_date)

name_link = f"<https://reddit.com/u/{mod.username}|u/{mod.username}>"
title = f"Mod check stats for *{name_link}*:"

all_stats = format_stats_section(
"Completed Checks",
_all_check_stats(
server_checks, mod_checks, recent_server_checks, recent_mod_checks
),
)
warning_stats = format_stats_section(
"Completed Warnings",
_warning_check_stats(
server_checks, mod_checks, recent_server_checks, recent_mod_checks
),
)
comment_stats = format_stats_section(
"Completed Comments",
_comment_check_stats(
server_checks, mod_checks, recent_server_checks, recent_mod_checks
),
)

return f"{title}\n\n{all_stats}\n\n{warning_stats}\n\n{comment_stats}"


def _all_check_stats(
server_checks: QuerySet,
mod_checks: QuerySet,
recent_server_checks: QuerySet,
recent_mod_checks: QuerySet,
) -> Dict:
"""Get the stats for all checks."""
# All time checks
server_check_count = server_checks.count()
mod_check_count = mod_checks.count()
check_ratio = _get_ratio(mod_check_count, server_check_count)
check_msg = f"{mod_check_count} ({check_ratio:.1%} of all checks)"

# Recent checks
recent_server_check_count = recent_server_checks.count()
recent_mod_check_count = recent_mod_checks.count()
recent_check_ratio = _get_ratio(recent_mod_check_count, recent_server_check_count)
recent_check_msg = (
f"{recent_mod_check_count} ({recent_check_ratio:.1%} of all recent checks)"
)

# Last check
last_check = mod_checks.order_by("-complete_time").first()
last_check_date = last_check.complete_time if last_check else None
last_check_msg = format_time(last_check_date)

return {
"All-time": check_msg,
"Last 2 weeks": recent_check_msg,
"Last completed": last_check_msg,
}


def _warning_check_stats(
server_checks: QuerySet,
mod_checks: QuerySet,
recent_server_checks: QuerySet,
recent_mod_checks: QuerySet,
) -> Dict:
"""Get the stats for the warning checks."""
warning_filter = (
Q(status=CheckStatus.WARNING_PENDING)
| Q(status=CheckStatus.WARNING_RESOLVED)
| Q(status=CheckStatus.WARNING_UNFIXED)
)
server_warnings = server_checks.filter(warning_filter)
mod_warnings = mod_checks.filter(warning_filter)

# All time warnings
server_warning_count = server_warnings.count()
mod_warning_count = mod_warnings.count()
warning_ratio_checks = _get_ratio(mod_warning_count, mod_checks.count())
warning_ratio_all = _get_ratio(mod_warning_count, server_warning_count)
warning_msg = (
f"{mod_warning_count} ({warning_ratio_checks:.1%} of checks, "
f"{warning_ratio_all:.1%} of all warnings)"
)

# Recent warnings
recent_server_warning_count = recent_server_checks.filter(warning_filter).count()
recent_mod_warning_count = recent_mod_checks.filter(warning_filter).count()
recent_warning_ratio_checks = _get_ratio(
recent_mod_warning_count, recent_mod_checks.count()
)
recent_warning_ratio_all = _get_ratio(
recent_mod_warning_count, recent_server_warning_count
)
recent_warning_msg = (
f"{recent_mod_warning_count} "
f"({recent_warning_ratio_checks:.1%} of recent checks, "
f"{recent_warning_ratio_all:.1%} of all recent warnings)"
)

# Last warning
last_warning = mod_warnings.order_by("-complete_time").first()
last_warning_date = last_warning.complete_time if last_warning else None
last_warning_msg = format_time(last_warning_date)

return {
"All-time": warning_msg,
"Last 2 weeks": recent_warning_msg,
"Last completed": last_warning_msg,
}


def _comment_check_stats(
server_checks: QuerySet,
mod_checks: QuerySet,
recent_server_checks: QuerySet,
recent_mod_checks: QuerySet,
) -> Dict:
"""Get the stats for the comment checks."""
comment_filter = (
Q(status=CheckStatus.COMMENT_PENDING)
| Q(status=CheckStatus.COMMENT_RESOLVED)
| Q(status=CheckStatus.COMMENT_UNFIXED)
)
server_comments = server_checks.filter(comment_filter)
mod_comments = mod_checks.filter(comment_filter)

# All time comments
server_comment_count = server_comments.count()
mod_comment_count = mod_comments.count()
comment_ratio_checks = _get_ratio(mod_comment_count, mod_checks.count())
comment_ratio_all = _get_ratio(mod_comment_count, server_comment_count)
comment_msg = (
f"{mod_comment_count} ({comment_ratio_checks:.1%} of checks, "
f"{comment_ratio_all:.1%} of all comments)"
)

# Recent comments
recent_server_comment_count = recent_server_checks.filter(comment_filter).count()
recent_mod_comment_count = recent_mod_checks.filter(comment_filter).count()
recent_comment_ratio_checks = _get_ratio(
recent_mod_comment_count, recent_mod_checks.count()
)
recent_comment_ratio_all = _get_ratio(
recent_mod_comment_count, recent_server_comment_count
)
recent_comment_msg = (
f"{recent_mod_comment_count} "
f"({recent_comment_ratio_checks:.1%} of recent checks, "
f"{recent_comment_ratio_all:.1%} of all recent comments)"
)

# Last comment
last_comment = mod_comments.order_by("-complete_time").first()
last_comment_date = last_comment.complete_time if last_comment else None
last_comment_msg = format_time(last_comment_date)

return {
"All-time": comment_msg,
"Last 2 weeks": recent_comment_msg,
"Last completed": last_comment_msg,
}


def _get_ratio(value: float, total: float) -> float:
"""Get the ratio of the two values.
Returns 0.0 if the total is 0.0.
"""
return value / total if total > 0 else 0.0
91 changes: 8 additions & 83 deletions api/slack/commands/info.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from datetime import datetime, timedelta
from typing import Dict, Optional
from datetime import timedelta
from typing import Dict

from django.utils import timezone

from api.models import TranscriptionCheck
from api.slack import client
from api.slack.commands.utils import bool_str, format_stats_section, format_time
from api.slack.utils import dict_to_table, parse_user
from api.views.misc import Summary
from authentication.models import BlossomUser
Expand Down Expand Up @@ -42,35 +43,22 @@ def user_info_text(user: BlossomUser) -> str:
name_link = f"<https://reddit.com/u/{user.username}|u/{user.username}>"
title = f"Info about *{name_link}*:"

general = _format_info_section("General", user_general_info(user))
transcription_quality = _format_info_section(
general = format_stats_section("General", user_general_info(user))
transcription_quality = format_stats_section(
"Transcription Quality", user_transcription_quality_info(user)
)
debug = _format_info_section("Debug Info", user_debug_info(user))
debug = format_stats_section("Debug Info", user_debug_info(user))

return f"{title}\n\n{general}\n\n{transcription_quality}\n\n{debug}"


def _format_info_section(name: str, section: Dict) -> str:
"""Format a given info section to a readable string.
Example:
*Section name*:
- Key 1: Value 1
- Key 2: Value 2
"""
section_items = "\n".join([f"- {key}: {value}" for key, value in section.items()])

return f"*{name}*:\n{section_items}"


def user_general_info(user: BlossomUser) -> Dict:
"""Get general info for the given user."""
total_gamma = user.gamma
recent_gamma = user.gamma_at_time(start_time=timezone.now() - timedelta(weeks=2))
gamma = f"{total_gamma} Γ ({recent_gamma} Γ in last 2 weeks)"
joined_on = _format_time(user.date_joined)
last_active = _format_time(user.date_last_active()) or "Never"
joined_on = format_time(user.date_joined)
last_active = format_time(user.date_last_active()) or "Never"

return {
"Gamma": gamma,
Expand Down Expand Up @@ -138,66 +126,3 @@ def user_debug_info(user: BlossomUser) -> Dict:
"Bot": bot,
"Accepted CoC": accepted_coc,
}


def bool_str(bl: bool) -> str:
"""Convert a bool to a Yes/No string."""
return "Yes" if bl else "No"


def _format_time(time: Optional[datetime]) -> Optional[str]:
"""Format the given time in absolute and relative strings."""
if time is None:
return None

now = timezone.now()
absolute = time.date().isoformat()

relative_delta = now - time
relative = _relative_duration(relative_delta)

if now >= time:
return f"{absolute} ({relative} ago)"
else:
return f"{absolute} (in {relative})"


def _relative_duration(delta: timedelta) -> str:
"""Format the delta into a relative time string."""
seconds = abs(delta.total_seconds())
minutes = seconds / 60
hours = minutes / 60
days = hours / 24
weeks = days / 7
months = days / 30
years = days / 365

# Determine major time unit
if years >= 1:
value, unit = years, "year"
elif months >= 1:
value, unit = months, "month"
elif weeks >= 1:
value, unit = weeks, "week"
elif days >= 1:
value, unit = days, "day"
elif hours >= 1:
value, unit = hours, "hour"
elif minutes >= 1:
value, unit = minutes, "min"
elif seconds > 5:
value, unit = seconds, "sec"
else:
duration_ms = seconds / 1000
value, unit = duration_ms, "ms"

if unit == "ms":
duration_str = f"{value:0.0f} ms"
else:
# Add plural s if necessary
if value != 1:
unit += "s"

duration_str = f"{value:.1f} {unit}"

return duration_str
Loading

0 comments on commit 0b7bff5

Please sign in to comment.