diff options
author | 2023-03-03 16:34:52 +0000 | |
---|---|---|
committer | 2023-03-03 16:34:52 +0000 | |
commit | f2e009d7a793d15d32812cfb44a4c1f7706190a3 (patch) | |
tree | 557a6cb33bde19f0c7c450483a60c249f1816653 | |
parent | Edit comment to match wookie's review (diff) | |
parent | Merge pull request #2430 from python-discord/dependabot/pip/pydis-core-9.5.1 (diff) |
Merge branch 'main' into snekbox/handling-input-error-message
-rw-r--r-- | .github/dependabot.yml | 12 | ||||
-rw-r--r-- | .github/workflows/build-deploy.yml | 80 | ||||
-rw-r--r-- | .github/workflows/build.yml | 63 | ||||
-rw-r--r-- | .github/workflows/deploy.yml | 46 | ||||
-rw-r--r-- | .github/workflows/lint-test.yml | 15 | ||||
-rw-r--r-- | .github/workflows/main.yml | 47 | ||||
-rw-r--r-- | .github/workflows/sentry_release.yml | 9 | ||||
-rw-r--r-- | .github/workflows/status_embed.yaml | 4 | ||||
-rw-r--r-- | bot/exts/info/help.py | 11 | ||||
-rw-r--r-- | bot/exts/recruitment/talentpool/_review.py | 29 | ||||
-rw-r--r-- | poetry.lock | 16 | ||||
-rw-r--r-- | pyproject.toml | 2 | ||||
-rw-r--r-- | tests/bot/exts/recruitment/talentpool/test_review.py | 14 |
13 files changed, 194 insertions, 154 deletions
diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..f60e94af8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + reviewers: + - "python-discord/devops" diff --git a/.github/workflows/build-deploy.yml b/.github/workflows/build-deploy.yml new file mode 100644 index 000000000..2582a4113 --- /dev/null +++ b/.github/workflows/build-deploy.yml @@ -0,0 +1,80 @@ +name: Build & Deploy + +on: + workflow_call: + inputs: + sha-tag: + description: "A short-form SHA tag for the commit that triggered this workflow" + required: true + type: string + + +jobs: + build: + name: Build & Push + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + # The current version (v2) of Docker's build-push action uses + # buildx, which comes with BuildKit features that help us speed + # up our builds using additional cache features. Buildx also + # has a lot of other features that are not as relevant to us. + # + # See https://github.com/docker/build-push-action + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Github Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Build and push the container to the GitHub Container + # Repository. The container will be tagged as "latest" + # and with the short SHA of the commit. + + - name: Build and push + uses: docker/build-push-action@v4 + with: + context: . + file: ./Dockerfile + push: true + cache-from: type=registry,ref=ghcr.io/python-discord/bot:latest + cache-to: type=inline + tags: | + ghcr.io/python-discord/bot:latest + ghcr.io/python-discord/bot:${{ inputs.sha-tag }} + build-args: | + git_sha=${{ github.sha }} + + deploy: + name: Deploy + needs: build + runs-on: ubuntu-latest + environment: production + steps: + - name: Checkout Kubernetes repository + uses: actions/checkout@v3 + with: + repository: python-discord/kubernetes + + - uses: azure/setup-kubectl@v3 + + - name: Authenticate with Kubernetes + uses: azure/k8s-set-context@v3 + with: + method: kubeconfig + kubeconfig: ${{ secrets.KUBECONFIG }} + + - name: Deploy to Kubernetes + uses: azure/k8s-deploy@v4 + with: + manifests: | + namespaces/default/bot/deployment.yaml + images: 'ghcr.io/python-discord/bot:${{ inputs.sha-tag }}' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index f8f2c8888..000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Build - -on: - workflow_run: - workflows: ["Lint & Test"] - branches: - - main - types: - - completed - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' - name: Build & Push - runs-on: ubuntu-latest - - steps: - # Create a commit SHA-based tag for the container repositories - - name: Create SHA Container Tag - id: sha_tag - run: | - tag=$(cut -c 1-7 <<< $GITHUB_SHA) - echo "::set-output name=tag::$tag" - - - name: Checkout code - uses: actions/checkout@v2 - - # The current version (v2) of Docker's build-push action uses - # buildx, which comes with BuildKit features that help us speed - # up our builds using additional cache features. Buildx also - # has a lot of other features that are not as relevant to us. - # - # See https://github.com/docker/build-push-action - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - - name: Login to Github Container Registry - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - # Build and push the container to the GitHub Container - # Repository. The container will be tagged as "latest" - # and with the short SHA of the commit. - - name: Build and push - uses: docker/build-push-action@v2 - with: - context: . - file: ./Dockerfile - push: true - cache-from: type=registry,ref=ghcr.io/python-discord/bot:latest - cache-to: type=inline - tags: | - ghcr.io/python-discord/bot:latest - ghcr.io/python-discord/bot:${{ steps.sha_tag.outputs.tag }} - build-args: | - git_sha=${{ github.sha }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 79eef8821..000000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Deploy - -on: - workflow_run: - workflows: ["Build"] - branches: - - main - types: - - completed - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - environment: production - if: github.event.workflow_run.conclusion == 'success' - name: Build & Push - runs-on: ubuntu-latest - - steps: - - name: Create SHA Container Tag - id: sha_tag - run: | - tag=$(cut -c 1-7 <<< $GITHUB_SHA) - echo "::set-output name=tag::$tag" - - - name: Checkout code - uses: actions/checkout@v2 - with: - repository: python-discord/kubernetes - - - name: Authenticate with Kubernetes - uses: azure/k8s-set-context@v1 - with: - method: kubeconfig - kubeconfig: ${{ secrets.KUBECONFIG }} - - - name: Deploy to Kubernetes - uses: Azure/k8s-deploy@v1 - with: - manifests: | - namespaces/default/bot/deployment.yaml - images: 'ghcr.io/python-discord/bot:${{ steps.sha_tag.outputs.tag }}' - kubectl-version: 'latest' diff --git a/.github/workflows/lint-test.yml b/.github/workflows/lint-test.yml index a331659e6..051ca2265 100644 --- a/.github/workflows/lint-test.yml +++ b/.github/workflows/lint-test.yml @@ -1,14 +1,7 @@ name: Lint & Test on: - push: - branches: - - main - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + workflow_call jobs: lint-test: @@ -35,13 +28,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Install Python Dependencies - uses: HassanAbouelela/actions/setup-python@setup-python_v1.3.1 + uses: HassanAbouelela/actions/setup-python@setup-python_v1.4.0 with: - # Set dev=true to install flake8 extensions, which are dev dependencies - dev: true python_version: '3.10' # Check all of our non-dev dependencies are compatible with the MIT license. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..0f972b16f --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,47 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + + +jobs: + lint-test: + uses: ./.github/workflows/lint-test.yml + + + generate-sha-tag: + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + outputs: + sha-tag: ${{ steps.sha-tag.outputs.sha-tag }} + steps: + - name: Create SHA Container tag + id: sha-tag + run: | + tag=$(cut -c 1-7 <<< $GITHUB_SHA) + echo "sha-tag=$tag" >> $GITHUB_OUTPUT + + + build-deploy: + if: github.ref == 'refs/heads/main' + uses: ./.github/workflows/build-deploy.yml + needs: + - lint-test + - generate-sha-tag + with: + sha-tag: ${{ needs.generate-sha-tag.outputs.sha-tag }} + secrets: inherit + + sentry-release: + if: github.ref == 'refs/heads/main' + uses: ./.github/workflows/sentry_release.yml + needs: build-deploy + secrets: inherit diff --git a/.github/workflows/sentry_release.yml b/.github/workflows/sentry_release.yml index 48f5e50f4..cdc8f37d5 100644 --- a/.github/workflows/sentry_release.yml +++ b/.github/workflows/sentry_release.yml @@ -1,20 +1,15 @@ name: Create Sentry release on: - push: - branches: - - main + workflow_call -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true jobs: create_sentry_release: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@main + uses: actions/checkout@v3 - name: Create a Sentry.io release uses: tclindner/[email protected] diff --git a/.github/workflows/status_embed.yaml b/.github/workflows/status_embed.yaml index 4178c366d..1923965ab 100644 --- a/.github/workflows/status_embed.yaml +++ b/.github/workflows/status_embed.yaml @@ -3,9 +3,7 @@ name: Status Embed on: workflow_run: workflows: - - Lint & Test - - Build - - Deploy + - CI types: - completed diff --git a/bot/exts/info/help.py b/bot/exts/info/help.py index 48f840e51..8950f4936 100644 --- a/bot/exts/info/help.py +++ b/bot/exts/info/help.py @@ -244,15 +244,20 @@ class CustomHelpCommand(HelpCommand): choices.update(cog.category for cog in self.context.bot.cogs.values() if hasattr(cog, "category")) return choices - async def command_not_found(self, string: str) -> "HelpQueryNotFound": + async def command_not_found(self, query: str) -> "HelpQueryNotFound": """ Handles when a query does not match a valid command, group, cog or category. Will return an instance of the `HelpQueryNotFound` exception with the error message and possible matches. """ choices = list(await self.get_all_help_choices()) - result = process.extract(default_process(string), choices, scorer=fuzz.ratio, score_cutoff=60, processor=None) - return HelpQueryNotFound(f'Query "{string}" not found.', {choice[0]: choice[1] for choice in result}) + result = process.extract(default_process(query), choices, scorer=fuzz.ratio, score_cutoff=60, processor=None) + + # Trim query to avoid embed limits when sending the error. + if len(query) >= 100: + query = query[:100] + "..." + + return HelpQueryNotFound(f'Query "{query}" not found.', {choice[0]: choice[1] for choice in result}) async def subcommand_not_found(self, command: Command, string: str) -> "HelpQueryNotFound": """ diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py index f41e08fe1..b9e76b60f 100644 --- a/bot/exts/recruitment/talentpool/_review.py +++ b/bot/exts/recruitment/talentpool/_review.py @@ -9,6 +9,7 @@ from datetime import datetime, timedelta, timezone from typing import List, Optional, Union import discord +from async_rediscache import RedisCache from discord import Embed, Emoji, Member, Message, NotFound, PartialMessage, TextChannel from pydis_core.site_api import ResponseCodeError @@ -52,6 +53,11 @@ NOMINATION_MESSAGE_REGEX = re.compile( class Reviewer: """Manages, formats, and publishes reviews of helper nominees.""" + # RedisCache[ + # "last_vote_date": float | POSIX UTC timestamp. + # ] + status_cache = RedisCache() + def __init__(self, bot: Bot, nomination_api: NominationAPI): self.bot = bot self.api = nomination_api @@ -82,8 +88,18 @@ class Reviewer: """ voting_channel = self.bot.get_channel(Channels.nomination_voting) + last_vote_timestamp = await self.status_cache.get("last_vote_date") + if last_vote_timestamp: + last_vote_date = datetime.fromtimestamp(last_vote_timestamp, tz=timezone.utc) + time_since_last_vote = datetime.now(timezone.utc) - last_vote_date + + if time_since_last_vote < MIN_REVIEW_INTERVAL: + log.debug("Most recent review was less than %s ago, cancelling check", MIN_REVIEW_INTERVAL) + return False + else: + log.info("Date of last vote not found in cache, a vote may be sent early") + review_count = 0 - is_first_message = True async for msg in voting_channel.history(): # Try and filter out any non-review messages. We also only want to count # one message from reviews split over multiple messages. We use fixed text @@ -91,14 +107,6 @@ class Reviewer: if not msg.author.bot or "for Helper!" not in msg.content: continue - if is_first_message: - time_since_message_created = datetime.now(timezone.utc) - msg.created_at - if time_since_message_created < MIN_REVIEW_INTERVAL: - log.debug("Most recent review was less than %s ago, cancelling check", MIN_REVIEW_INTERVAL) - return False - - is_first_message = False - review_count += 1 if review_count >= MAX_ONGOING_REVIEWS: @@ -182,6 +190,9 @@ class Reviewer: ) message = await thread.send(f"<@&{Roles.mod_team}> <@&{Roles.admins}>") + now = datetime.now(tz=timezone.utc) + await self.status_cache.set("last_vote_date", now.timestamp()) + await self.api.edit_nomination(nomination.id, reviewed=True, thread_id=thread.id) bump_cog: ThreadBumper = self.bot.get_cog("ThreadBumper") diff --git a/poetry.lock b/poetry.lock index db694b8f9..924f1645e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -454,14 +454,14 @@ cli = ["clevercsv (==0.7.4)", "click (==8.1.3)", "pyyaml (==6.0)", "toml (==0.10 [[package]] name = "discord-py" -version = "2.2.0" +version = "2.2.2" description = "A Python wrapper for the Discord API" category = "main" optional = false python-versions = ">=3.8.0" files = [ - {file = "discord.py-2.2.0-py3-none-any.whl", hash = "sha256:012e98571af6847467e81f9501bbe4c6ebfe292c842f5ef8e951908839ee1cd0"}, - {file = "discord.py-2.2.0.tar.gz", hash = "sha256:a92d69ab6f982998693d0c371ea19235fa0f9900b50068fc461086d02c33e6bb"}, + {file = "discord.py-2.2.2-py3-none-any.whl", hash = "sha256:38fc52a784727b8e5e5749267089400035b187a009028eddfabeb182abcc6d52"}, + {file = "discord.py-2.2.2.tar.gz", hash = "sha256:b9944056bcb5711b2d04088848fd004466cf117c15c84fa798bf55470f28275f"}, ] [package.dependencies] @@ -1479,20 +1479,20 @@ email = ["email-validator (>=1.0.3)"] [[package]] name = "pydis-core" -version = "9.5.0" +version = "9.5.1" description = "PyDis core provides core functionality and utility to the bots of the Python Discord community." category = "main" optional = false python-versions = ">=3.10.0,<3.12.0" files = [ - {file = "pydis_core-9.5.0-py3-none-any.whl", hash = "sha256:35834274a80b86a5426f27cb546b3fada8a5711bbf01bbcf1b0a8860a2afee94"}, - {file = "pydis_core-9.5.0.tar.gz", hash = "sha256:1cf9c223af9b5377e08cc0eb046a12130518e4743afbdd4f052d5556b7dae805"}, + {file = "pydis_core-9.5.1-py3-none-any.whl", hash = "sha256:50bbf1800fe228dd60ba6624615815f45139c105512ef701c556ee7dedaa91eb"}, + {file = "pydis_core-9.5.1.tar.gz", hash = "sha256:83b89117def529c8b130f22c9f8cd46211df6329b039eedd5020098e656aa198"}, ] [package.dependencies] aiodns = "3.0.0" async-rediscache = {version = "1.0.0rc2", extras = ["fakeredis"], optional = true, markers = "extra == \"async-rediscache\""} -"discord.py" = "2.2.0" +"discord.py" = "2.2.2" statsd = "4.0.1" [package.extras] @@ -2314,4 +2314,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "3.10.*" -content-hash = "95d9251abd0a6125c99cc0c290fd8f97e9003cfe95cde3666ccc1817f751e814" +content-hash = "9e554d23838cde31200630eda42ce0f1ead91a0f521d10362037f6d4290d3529" diff --git a/pyproject.toml b/pyproject.toml index 13efe1f34..8950bcbc8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ license = "MIT" python = "3.10.*" # See https://bot-core.pythondiscord.com/ for docs. -pydis_core = { version = "9.5.0", extras = ["async-rediscache"] } +pydis_core = { version = "9.5.1", extras = ["async-rediscache"] } redis = "4.3.5" fakeredis = { version = "2.0.0", extras = ["lua"] } diff --git a/tests/bot/exts/recruitment/talentpool/test_review.py b/tests/bot/exts/recruitment/talentpool/test_review.py index 03c78ab08..1ddb73ab0 100644 --- a/tests/bot/exts/recruitment/talentpool/test_review.py +++ b/tests/bot/exts/recruitment/talentpool/test_review.py @@ -67,6 +67,7 @@ class ReviewerTests(unittest.IsolatedAsyncioTestCase): MockMessage(author=self.bot_user, content="Not a review", created_at=not_too_recent), MockMessage(author=self.bot_user, content="Not a review", created_at=not_too_recent), ], + not_too_recent.timestamp(), True, ), @@ -77,6 +78,7 @@ class ReviewerTests(unittest.IsolatedAsyncioTestCase): MockMessage(author=self.bot_user, content="Zig for Helper!", created_at=not_too_recent), MockMessage(author=self.bot_user, content="Scaleios for Helper!", created_at=not_too_recent), ], + not_too_recent.timestamp(), False, ), @@ -85,6 +87,7 @@ class ReviewerTests(unittest.IsolatedAsyncioTestCase): [ MockMessage(author=self.bot_user, content="Chrisjl for Helper!", created_at=too_recent), ], + too_recent.timestamp(), False, ), @@ -96,18 +99,25 @@ class ReviewerTests(unittest.IsolatedAsyncioTestCase): MockMessage(author=self.bot_user, content="wookie for Helper!", created_at=not_too_recent), MockMessage(author=self.bot_user, content="Not a review", created_at=not_too_recent), ], + not_too_recent.timestamp(), True, ), # No messages, so ready. - ([], True), + ([], None, True), ) - for messages, expected in cases: + for messages, last_review_timestamp, expected in cases: with self.subTest(messages=messages, expected=expected): self.voting_channel.history = AsyncIterator(messages) + + cache_get_mock = AsyncMock(return_value=last_review_timestamp) + self.reviewer.status_cache.get = cache_get_mock + res = await self.reviewer.is_ready_for_review() + self.assertIs(res, expected) + cache_get_mock.assert_called_with("last_vote_date") @patch("bot.exts.recruitment.talentpool._review.MIN_NOMINATION_TIME", timedelta(days=7)) async def test_get_nomination_to_review(self): |