diff options
Diffstat (limited to '')
| -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): | 
