diff --git a/.github/workflows/post-agenda-sofa-dev-meeting.yml b/.github/workflows/post-agenda-sofa-dev-meeting.yml new file mode 100644 index 00000000000..d5ceca44022 --- /dev/null +++ b/.github/workflows/post-agenda-sofa-dev-meeting.yml @@ -0,0 +1,49 @@ +name: Post - SOFA dev meeting agenda/reminder + +on: + schedule: + - cron: '30 7 * * 1' # 9:30 am CET every Monday + - cron: '0 6 * * 3' # 8:00 am CET on Wednesdays +jobs: + run: + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Install dependencies + run: | + pip install python-graphql-client + pip install python-dateutil + pip install requests + working-directory: ${{ github.workspace }} + + # Monday message : agenda + - name: Run script post-discord-message.py + if: ${{ github.event_name == 'schedule' && github.event.scheduled_time | date('%-u') == '1' }} + run: | + python scripts/discord/post-discord-message.py + working-directory: ${{ github.workspace }} + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_MAIN_WEBHOOK_URL }} + MESSAGE : ":sunrise: Morning, dear SOFA community! Next Wednesday takes place the weekly SOFA dev meeting:\n**_Any specific topic to share or to discuss?_** If so, please reply in this thread :speech_balloon: *Remember you can use the tag \"pr: dev meeting topic\" for your PRs*" + BOT_NAME: "Meeting reminder" + EMBEDS_TITLE: "Label \"pr: dev-meeting topic\"" + EMBEDS_URL: "https://github.com/sofa-framework/sofa/labels/pr%3A%20dev%20meeting%20topic" + EMBEDS_DESCRIPTION: "" + + # Wednesday message : get ready + - name: Run script post-discord-message.py + if: ${{ github.event_name == 'schedule' && github.event.scheduled_time | date('%-u') == '3' }} + run: | + python scripts/discord/post-discord-message.py + working-directory: ${{ github.workspace }} + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_MAIN_WEBHOOK_URL }} + MESSAGE: "SOFA dev meeting is about to get started :tv: See you online!" + BOT_NAME: "Meeting reminder" + EMBEDS_TITLE: "SOFA dev visio link" + EMBEDS_URL: "https://www.sofa-framework.org/sofa-dev-meeting" + EMBEDS_DESCRIPTION: "" + diff --git a/.github/workflows/post-announcements.yml b/.github/workflows/post-announcements.yml new file mode 100644 index 00000000000..d4bfe2ba2b7 --- /dev/null +++ b/.github/workflows/post-announcements.yml @@ -0,0 +1,34 @@ +name: Post - Announcement GHD topics + +on: + discussion: + types: [created] + +jobs: + run: + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Install dependencies + if: ( context.payload.discussion.category.name == 'Announcement' ) || ( context.payload.discussion.category.name == 'Share your achievements' ) + run: | + pip install python-graphql-client + pip install python-dateutil + pip install requests + working-directory: ${{ github.workspace }} + + - name: Run script post-discord-message.py + if: ( context.payload.discussion.category.name == 'Announcement' ) || ( context.payload.discussion.category.name == 'Share your achievements' ) + run: | + python scripts/discord/post-discord-message.py + working-directory: ${{ github.workspace }} + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ANNOUNCEMENTS_WEBHOOK_URL }} + MESSAGE: ":loudspeaker: New post \"${{context.payload.discussion.category.name}}\" :arrow_right: **[${{context.payload.discussion.title}}](https://github.com/sofa-framework/sofa/discussions/${{context.payload.discussion.number}})** by [${{context.payload.discussion.author.login}}](https://github.com/${{context.payload.discussion.author.login}})" + BOT_NAME: "Discussion announcement" + EMBEDS_TITLE: ${{context.payload.discussion.title}} + EMBEDS_URL: "https://github.com/sofa-framework/sofa/discussions/${{context.payload.discussion.number}}" + diff --git a/.github/workflows/post-github-activity.yml b/.github/workflows/post-github-activity.yml new file mode 100644 index 00000000000..4971420e3ac --- /dev/null +++ b/.github/workflows/post-github-activity.yml @@ -0,0 +1,95 @@ +name: Post - Github activity (PRs, issues, stars) + +on: + watch: + types: [started] + issues: + types: [opened] + pull_request: + types: [opened,review_requested,closed] + +jobs: + run: + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Install dependencies + run: | + pip install python-graphql-client + pip install python-dateutil + pip install requests + working-directory: ${{ github.workspace }} + + # PR opened + - name: Run script post-discord-message.py for PR opened (main) + if: (github.event_name == 'pull_request') && (github.pull_request.action == 'opened') + run: | + python scripts/discord/post-discord-message.py + working-directory: ${{ github.workspace }} + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_MAIN_WEBHOOK_URL }} + MESSAGE: ":new: PR opened:" + BOT_NAME: "SOFA Github bot" + EMBEDS_TITLE: "#${{context.payload.pull_request.number}} ${{context.payload.pull_request.title}}" + EMBEDS_URL: "https://github.com/sofa-framework/sofa/pull/${{context.payload.pull_request.number}}" + EMBEDS_DESCRIPTION: "Authored by @${{context.payload.pull_request.author.login}}\nLabels: ${{context.payload.pull_request.labels.name}}" + + # PR merged + - name: Run script post-discord-message.py for PR merged + if: (github.event_name == 'pull_request') && (github.pull_request.action == 'closed') && (github.event.pull_request.merged == true) + run: | + python scripts/discord/post-discord-message.py + working-directory: ${{ github.workspace }} + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_MAIN_WEBHOOK_URL }} + MESSAGE: ":raised_hands: Thanks @${{context.payload.pull_request.merged_by.login}} for merging PR [#${{context.payload.pull_request.number}}](https://github.com/sofa-framework/sofa/pull/${{context.payload.pull_request.number}}) ${{context.payload.pull_request.title}} (author: @${{context.payload.pull_request.author.login}})" + BOT_NAME: "SOFA Github bot" + EMBEDS_TITLE: "" + EMBEDS_URL: "" + EMBEDS_DESCRIPTION: "" + + # PR review requested event + - name: Run script post-discord-message.py for PR review request + if: (github.event_name == 'pull_request') && (github.pull_request.action == 'review_requested') + run: | + python scripts/discord/post-discord-message.py + working-directory: ${{ github.workspace }} + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_MAIN_WEBHOOK_URL }} + MESSAGE: "" + BOT_NAME: "SOFA Github bot" + EMBEDS_TITLE: "" + EMBEDS_URL: "" + EMBEDS_DESCRIPTION: ":eyeglasses: Review requested: ${{context.payload.pull_request.requested_reviewers}} would you please review [#${{context.payload.pull_request.number}}](https://github.com/sofa-framework/sofa/pull/${{context.payload.pull_request.number}})?" + + # Issue related event + - name: Run script post-discord-message.py for Issue opened + if: github.event_name == 'issues' + run: | + python scripts/discord/post-discord-message.py + working-directory: ${{ github.workspace }} + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_MAIN_WEBHOOK_URL }} + MESSAGE: ":space_invader: New issue raised" + BOT_NAME: "SOFA Github bot" + EMBEDS_TITLE: "\"${{context.payload.issue.title}}\" by @${{context.payload.issue.user.login}}" + EMBEDS_URL: "https://github.com/sofa-framework/sofa/issues/${{context.payload.issue.number}}" + EMBEDS_DESCRIPTION: "" + + # Star/watch related event + - name: Run script post-discord-message.py for stars + if: github.event_name == 'watch' + run: | + python scripts/discord/post-discord-message.py + working-directory: ${{ github.workspace }} + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_MAIN_WEBHOOK_URL }} + MESSAGE: ":fire: :fire: Come ON! :fire: :fire: \nOne new :star: for SOFA on Github" + BOT_NAME: "SOFA Github bot" + EMBEDS_TITLE: "" + EMBEDS_URL: "" + EMBEDS_DESCRIPTION: "" + diff --git a/.github/workflows/post-pending-discussions.yml b/.github/workflows/post-pending-discussions.yml new file mode 100644 index 00000000000..e76ca3825a5 --- /dev/null +++ b/.github/workflows/post-pending-discussions.yml @@ -0,0 +1,34 @@ +name: Post - List pending GHD + +on: + workflow_dispatch: + schedule: + - cron: '0 6 * * 2,5' # 8 am CET on Tuesday and Friday + +jobs: + run: + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.10' # Use the desired Python version + + - name: Install dependencies + run: | + pip install python-graphql-client + pip install python-dateutil + pip install requests + working-directory: ${{ github.workspace }} + + - name: Run script post-pending-discussions.py + run: | + python scripts/discord/post-pending-discussions.py + working-directory: ${{ github.workspace }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DISCORD_MAIN_WEBHOOK_URL: ${{ secrets.DISCORD_MAIN_WEBHOOK_URL }} diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml new file mode 100644 index 00000000000..8f60d904517 --- /dev/null +++ b/.github/workflows/post-release.yml @@ -0,0 +1,35 @@ +name: Post - Release highlight + +on: + release: + types: + - created + +jobs: + run: + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Install dependencies + run: | + pip install python-graphql-client + pip install python-dateutil + pip install requests + working-directory: ${{ github.workspace }} + + - name: Run script post-discord-message.py + run: | + python scripts/discord/post-discord-message.py + working-directory: ${{ github.workspace }} + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_ANNOUNCEMENTS_WEBHOOK_URL }} + MESSAGE: ":loudspeaker: **New SOFA release** :loudspeaker: \n > SOFA ${{context.payload.release.name}} is now available, [check it out](https://github.com/sofa-framework/sofa/releases/tag/${{context.payload.release.tag_name}})! \n > For more, see the [ChangeLog](https://github.com/sofa-framework/sofa/blob/${{context.payload.release.tag_name}}/CHANGELOG.md) \n \n Thanks to all contributors!" + BOT_NAME: "Release announcement" + EMBEDS_TITLE: "SOFA ${{context.payload.release.name}}" + EMBEDS_URL: "https://github.com/sofa-framework/sofa/releases/tag/${{context.payload.release.tag_name}}" + EMBEDS_DESCRIPTION: "New source and binary release of SOFA" + + diff --git a/.github/workflows/pr-label-checker.yml b/.github/workflows/pr-label-checker.yml index 966efef6e69..695f67784b1 100644 --- a/.github/workflows/pr-label-checker.yml +++ b/.github/workflows/pr-label-checker.yml @@ -1,5 +1,5 @@ --- -name: Label Checker +name: PR check - Labels on: pull_request: types: diff --git a/.github/workflows/pr-timing-checker.yml b/.github/workflows/pr-timing-checker.yml index 555d88ac2d8..3cc46eb89fc 100644 --- a/.github/workflows/pr-timing-checker.yml +++ b/.github/workflows/pr-timing-checker.yml @@ -1,4 +1,4 @@ -name: Check PR Age +name: PR check - Age on: pull_request: types: diff --git a/.github/workflows/pr-title-checker.yml b/.github/workflows/pr-title-checker.yml index 4a07fc661b9..fb106a0b1b8 100644 --- a/.github/workflows/pr-title-checker.yml +++ b/.github/workflows/pr-title-checker.yml @@ -1,4 +1,4 @@ -name: Pull Request Title Check +name: PR check - Title on: pull_request: types: [opened, synchronize, edited, reopened] diff --git a/scripts/discord/post-discord-message.py b/scripts/discord/post-discord-message.py new file mode 100644 index 00000000000..10eddd218e1 --- /dev/null +++ b/scripts/discord/post-discord-message.py @@ -0,0 +1,35 @@ +#!python +import os +import requests + + +# Recover all info as env var +discord_token = os.environ['DISCORD_WEBHOOK_URL'] +message = os.environ['MESSAGE'] +bot_name = os.environ['BOT_NAME'] +embeds_title = os.environ['EMBEDS_TITLE'] +embeds_url = os.environ['EMBEDS_URL'] +embeds_description = os.environ['EMBEDS_DESCRIPTION'] + + +# Format message +data = { + "content" : message, + "username" : bot_name +} + +#leave this out if you dont want an embed +data["embeds"] = [ + { + "description" : embeds_description, + "title" : embeds_title, + "type" : "rich", + "url" : embeds_url, + "color" : "15224347" + } +] + +# Send message to Discord +response = requests.post(discord_token, json=data) +print("Status: "+str(response.status_code)+"\nReason: "+str(response.reason)+"\nText: "+str(response.text)) + diff --git a/scripts/discord/post-pending-discussions.py b/scripts/discord/post-pending-discussions.py new file mode 100644 index 00000000000..0b32228a43e --- /dev/null +++ b/scripts/discord/post-pending-discussions.py @@ -0,0 +1,170 @@ +#!python + + +import os +import requests +from python_graphql_client import GraphqlClient + +client = GraphqlClient(endpoint="https://api.github.com/graphql") +github_token = os.environ['GITHUB_TOKEN'] +discord_token = os.environ['DISCORD_MAIN_WEBHOOK_URL'] + +# List of the repository to scan +repos=[['sofa-framework','sofa']] + +# List of reviewers on GitHub Discussions +reviewer_logins=[["alxbilger"],["hugtalbot"],["bakpaul"],["fredroy"],["epernod"],["damienmarchal"],["VannesteFelix"],["EulalieCoevoet"],["adagolodjo"],["github-actions"]] + + +def computeListOfOpenDiscussionsPerCategory(): + for repo in repos: + + owner = repo[0] + name = repo[1] + + has_next_page = True + after_cursor = None + + categories = [] + discussions_numbers = [] + + + while has_next_page: + # Trigger the query on discussions + data = client.execute( + query = make_query_discussions(owner, name, after_cursor), + headers = {"Authorization": "Bearer {}".format(github_token)}, + ) + + # Process each discussion + for discussion in data["data"]["repository"]["discussions"]["nodes"]: + + # Exit if discussion is closed or answered + if discussion["closed"] == True or discussion["isAnswered"] == True : + continue + + ############################## + # Detect the last comment + lastCommentId = len(discussion["comments"]["nodes"]) - 1 + + # No comment at all + if(lastCommentId < 0): + categories.append(discussion["category"]["name"]) + discussions_numbers.append(discussion["number"]) + continue + + lastReplyOnLastComment = len(discussion["comments"]["nodes"][lastCommentId]["replies"]["nodes"]) - 1 + + # No replies on the last comment + if(lastReplyOnLastComment < 0): + author = discussion["comments"]["nodes"][lastCommentId]["author"]["login"] + # Select the last reply of the last comment + else: + author = discussion["comments"]["nodes"][lastCommentId]["replies"]["nodes"][lastReplyOnLastComment]["author"]["login"] + + authorAsList = [author] + + # Check if author is indeed a reviewer + if authorAsList in reviewer_logins: + continue + else: + categories.append(discussion["category"]["name"]) + discussions_numbers.append(discussion["number"]) + + # save if request has another page to browse and its cursor pointers + has_next_page = data["data"]["repository"]["discussions"]["pageInfo"]["hasNextPage"] + after_cursor = data["data"]["repository"]["discussions"]["pageInfo"]["endCursor"] + return categories, discussions_numbers + + +def printDiscussionsPerCategory(categories, discussions_numbers): + Message = ":speech_balloon: GitHub pending discussion topics :point_down: " + postOnDiscord(Message) + + categoryDone = [] + + for category in categories: + tempVecID = [] + + if category in categoryDone: + continue + + for i,number in enumerate(discussionsNumbers): + if categories[i] == category: + tempVecID.append(number) + tempMessage = "- Category "+str(category)+":" + + for id in tempVecID: + tempMessage = tempMessage + " [#"+ str(id) +"](https://github.com/sofa-framework/sofa/discussions/"+ str(id) +") " + + # Category has been covered + postOnDiscord(tempMessage) + Message = Message + tempMessage + "\n" + categoryDone.append(category) + + #print(Message) + postOnDiscord(":fire: SOFA community appreciates all your support :fire: \n") + + return + + + +# Function posting a message on Discord +def postOnDiscord(message): + payload = {'content': message} + response = requests.post(discord_token, json=payload) + print(response) + return + + + +# Query to access all discussions +def make_query_discussions(owner, name, after_cursor=None): + query = """ + query { + repository(owner: "%s" name: "%s") { + discussions(answered: false, first: 10, after:AFTER) { + totalCount + pageInfo { + hasNextPage + endCursor + } + nodes { + number + isAnswered + closed + category { + name + } + comments (first: 100) { + nodes { + author { + login + } + replies (first: 100) { + nodes { + author { + login + } + } + } + } + } + } + } + } + }""" % (owner, name) + return query.replace("AFTER", '"{}"'.format(after_cursor) if after_cursor else "null") + + + +#========================================================== +# STEPS computed by the script +#========================================================== +# 1 - get the discussion to be warned and closed +result = computeListOfOpenDiscussionsPerCategory() +categories = result[0] +discussionsNumbers = result[1] + +printDiscussionsPerCategory(categories, discussionsNumbers) +#========================================================== \ No newline at end of file