Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatyczne tworzenie Pull Requesta z nową organizacją #34

Merged
merged 9 commits into from
Dec 19, 2024
16 changes: 8 additions & 8 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: Usuwanie produktu
url: https://github.com/wyslijco/wyslijco.github.io/wiki
about: Zobacz, jak możesz usunąć produkt ze strony swojej organizacji w Wyślij.co
- name: Usuwanie organizacji
url: https://github.com/wyslijco/wyslijco.github.io/wiki
about: Zobacz, jak usunąć swoją organizację z Wyślij.co
blank_issues_enabled: true
contact_links:
- name: Usuwanie produktu
url: https://github.com/wyslijco/wyslijco.github.io/wiki
about: Zobacz, jak możesz usunąć produkt ze strony swojej organizacji w Wyślij.co
- name: Usuwanie organizacji
url: https://github.com/wyslijco/wyslijco.github.io/wiki
about: Zobacz, jak usunąć swoją organizację z Wyślij.co
76 changes: 38 additions & 38 deletions .github/ISSUE_TEMPLATE/dodanieproduktu.yaml
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
name: Dodanie produktu
description: Dodaj produkt do strony swojej organizacji.
title: "[Dodanie produktu]"
labels: ["organizacje", "nowy-produkt"]
assignees:
- ivellios
body:
- type: markdown
attributes:
value: |
Ten formularz pomoże Ci w dodaniu produktu do strony Twojej organizacji w Wyślij.co
- type: dropdown
id: nazwa_organizacji
attributes:
label: Twoja organizacja
options:
- Testowa
validations:
required: true
- type: input
id: nazwa
attributes:
label: Nazwa produktu
validations:
required: true
- type: input
id: opis
attributes:
label: Krótki opis produktu
validations:
required: false
- type: input
id: link
attributes:
label: Link do produktu w sklepie internetowym
validations:
required: true
name: Dodanie produktu
description: Dodaj produkt do strony swojej organizacji.
title: "[Dodanie produktu]"
labels: ["organizacje", "nowy-produkt"]
assignees:
- ivellios

body:
- type: markdown
attributes:
value: |
Ten formularz pomoże Ci w dodaniu produktu do strony Twojej organizacji w Wyślij.co
- type: dropdown
id: nazwa_organizacji
attributes:
label: Twoja organizacja
options:
- Testowa
validations:
required: true
- type: input
id: nazwa
attributes:
label: Nazwa produktu
validations:
required: true
- type: input
id: opis
attributes:
label: Krótki opis produktu
validations:
required: false
- type: input
id: link
attributes:
label: Link do produktu w sklepie internetowym
validations:
required: true
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/nowa.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,8 @@ body:
placeholder: 000 000 000
validations:
required: true
- type: markdown
attributes:
value: |
# Następne kroki
Po wypełnieniu formularza, Twój wniosek zostanie zweryfikowany przez nasz zespół. W celu weryfikacji poprawności danych skontaktujemy się z Twoją organizacją poprzez oficjalne dane kontaktowe dostępne na stronie internetowej organizacji lub w rejestrze KRS. W przypadku pozytywnej weryfikacji, otrzymasz od nas informację o dalszych krokach w komentarzu do tego zgłoszenia (zakładka Issues).
nekeal marked this conversation as resolved.
Show resolved Hide resolved
97 changes: 97 additions & 0 deletions .github/scripts/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import json
import logging
import os

from github import Auth, Github, Issue

from consts import (
OrgFormSchemaIds,
NEW_ORG_ISSUE_DEFAULT_TITLE,
NEW_ORG_FORM_SCHEMA_FILENAME,
)
from exceptions import BranchModifiedError
from git_managers import create_organization_yaml_pr
from labels import Label
from parsers import GithubIssueFormDataParser
from pullers import KRSDataPuller
from utils import has_label
from validators import OrgValidator
from renderers import render_organization_yaml

logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__file__)

GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
GITHUB_REPOSITORY = os.getenv("GITHUB_REPOSITORY")

auth = Auth.Token(GITHUB_TOKEN)
g = Github(auth=auth)
repo = g.get_repo(GITHUB_REPOSITORY)


def process_new_org_issue(issue: Issue, data: GithubIssueFormDataParser):
validation_warnings = []

org_name = data.get(OrgFormSchemaIds.name)

if has_label(issue, Label.AUTO_VERIFIED):
issue.remove_from_labels(Label.AUTO_VERIFIED)

validator = OrgValidator(data, issue)
if not validator.validate():
logger.error("Validation failed - not continuing")
return

if not (org := KRSDataPuller.get_org_by_krs(issue, data.get(OrgFormSchemaIds.krs))):
logger.error("KRS db validation failed")
validation_warnings.append("Nie można zweryfikować KRS")
else:
org_name = org.name

# Update issue title
if issue.title == NEW_ORG_ISSUE_DEFAULT_TITLE:
logger.info("Updating issue title")
issue.edit(
title=f"{NEW_ORG_ISSUE_DEFAULT_TITLE} {org_name}"
)

logger.info("Adding auto-verified label")
if not validation_warnings:
issue.add_to_labels(Label.AUTO_VERIFIED)

if not has_label(issue, Label.WAITING):
issue.add_to_labels(Label.WAITING)
issue.create_comment(
f"@{issue.user.login}, dziękujemy za podanie informacji. "
f"Przyjęliśmy zgłoszenie dodania nowej organizacji. "
f"Wkrótce skontaktujemy się celem weryfikacji zgłoszenia."
)

# create organization yaml file and add to the Pull Request
yaml_string = render_organization_yaml(data)

try:
create_organization_yaml_pr(issue, yaml_string, data)
except BranchModifiedError:
logger.error("Branch was modified by someone else")
issue.create_comment(
f"Aktualizacja pliku organizacji na podstawie opisu zgłoszenia niemożliwa. "
f"Plik organizacji został już zmodyfikowany przez innego użytkownika."
)


def main():
github_form_json = os.getenv("GITHUB_FORM_JSON")
github_issue_number = int(os.getenv("GITHUB_ISSUE_NUMBER"))

issue = repo.get_issue(github_issue_number)
data = GithubIssueFormDataParser(
json.loads(github_form_json), NEW_ORG_FORM_SCHEMA_FILENAME
)
process_new_org_issue(issue, data)


if __name__ == "__main__":
main()
7 changes: 4 additions & 3 deletions .github/scripts/consts.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import enum


class OrgSchemaIds(enum.StrEnum):
class OrgFormSchemaIds(enum.StrEnum):
name = "nazwa"
www = "www"
website = "www"
krs = "krs"
slug = "nazwa_strony"
street = "ulica"
postal_code = "kod_pocztowy"
city = "miasto"
phone_number = "telefon"


NEW_ORG_ISSUE_DEFAULT_TITLE = "[Nowa Organizacja]"
NEW_ORG_SCHEMA_FILENAME = "nowa.yaml"
NEW_ORG_FORM_SCHEMA_FILENAME = "nowa.yaml"


ORG_SCHEMA_SLUG_FIELD = "adres"
6 changes: 6 additions & 0 deletions .github/scripts/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class BranchModifiedError(ValueError):
pass


class KRSMaintenanceError(Exception):
pass
nekeal marked this conversation as resolved.
Show resolved Hide resolved
142 changes: 142 additions & 0 deletions .github/scripts/git_managers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import logging
from multiprocessing.managers import Value

from github import GithubException, InputGitTreeElement
from github.GitCommit import GitCommit
from github.GitRef import GitRef
from github.Issue import Issue
from github.PullRequest import PullRequest
from github.Repository import Repository

from consts import OrgFormSchemaIds
from exceptions import BranchModifiedError
from parsers import GithubIssueFormDataParser


logger = logging.getLogger(__file__)


class GitManager:
"""Manager for creating a new branch and pull request with a file commit in the repo."""

def __init__(self, repo: Repository):
self.repo = repo
ivellios marked this conversation as resolved.
Show resolved Hide resolved

def commit_file_contents_to_branch(
self, branch_ref: GitRef, file_path: str, contents: str, commit_message: str
) -> GitCommit:
latest_commit = self.repo.get_commit(branch_ref.object.sha)
blob = self.repo.create_git_blob(contents, "utf-8")
tree_element = InputGitTreeElement(
path=file_path, mode="100644", type="blob", sha=blob.sha
)
base_tree = latest_commit.commit.tree
new_tree = self.repo.create_git_tree([tree_element], base_tree)
new_commit = self.repo.create_git_commit(
message=commit_message, tree=new_tree, parents=[latest_commit.commit]
)
return new_commit

def get_or_create_branch(self, source_branch: str, new_branch_name: str) -> GitRef:
source = self.repo.get_branch(source_branch)
try:
branch_ref = self.repo.get_git_ref(f"heads/{new_branch_name}")
print(f"Branch '{new_branch_name}' already exists.")
nekeal marked this conversation as resolved.
Show resolved Hide resolved
except GithubException as e:
if e.status == 404:
# Branch does not exist, create it from the source branch
self.repo.create_git_ref(
ref=f"refs/heads/{new_branch_name}", sha=source.commit.sha
)
branch_ref = self.repo.get_git_ref(f"heads/{new_branch_name}")
print(f"Branch '{new_branch_name}' created from '{source_branch}'.")
ivellios marked this conversation as resolved.
Show resolved Hide resolved
else:
raise e

latest_commit = self.repo.get_commit(branch_ref.object.sha)
if latest_commit.sha != source.commit.sha and not latest_commit.commit.message.startswith("[auto] "):
logger.error(f"Branch was modified: {latest_commit.commit.message}")
raise BranchModifiedError()

return branch_ref

def get_or_create_pr(
self, target_branch: str, new_branch_name: str, pr_title: str, pr_body: str
) -> PullRequest:
pulls = self.repo.get_pulls(
state="open",
head=f"{self.repo.owner.login}:{new_branch_name}",
base=target_branch,
)
if pulls.totalCount > 0:
logger.warning(
f"Pull request already exists for branch '{new_branch_name}': {pulls[0].html_url}"
)
return pulls[0]
else:
logger.info(f"Creating pull request for branch '{new_branch_name}'")
return self.repo.create_pull(
title=pr_title, body=pr_body, head=new_branch_name, base=target_branch
)
ivellios marked this conversation as resolved.
Show resolved Hide resolved

def was_branch_modified(self, source_branch, branch: GitRef) -> bool:
ivellios marked this conversation as resolved.
Show resolved Hide resolved
latest_commit = self.repo.get_commit(branch.object.sha)
source = self.repo.get_branch(source_branch)
ivellios marked this conversation as resolved.
Show resolved Hide resolved


def create_or_update_remote_branch_with_file_commit(
self,
source_branch: str,
new_branch: str,
file_path: str,
file_contents: str,
commit_message: str,
) -> GitRef:
"""Create or update a remote branch with a file commit."""
branch = self.get_or_create_branch(source_branch, new_branch)
commit = self.commit_file_contents_to_branch(
branch, file_path, file_contents, commit_message
)
branch.edit(commit.sha)
return branch

def create_or_update_pr_with_file(
self,
source_branch: str,
new_branch: str,
pr_title: str,
pr_body: str,
file_path: str,
file_contents: str,
commit_message: str,
) -> PullRequest:
self.create_or_update_remote_branch_with_file_commit(
source_branch, new_branch, file_path, file_contents, commit_message
)
return self.get_or_create_pr(source_branch, new_branch, pr_title, pr_body)


def create_organization_yaml_pr(
issue: Issue, yaml_string: str, data: GithubIssueFormDataParser
):
repo = issue.repository

source_branch = "main"
new_branch_name = f"nowa-organizacja-zgloszenie-{issue.number}"

commit_message = f"[auto] Dodana nowa organizacja: {data.get(OrgFormSchemaIds.name)} | Zgłoszenie: #{issue.number}"
pr_title = f"Dodana nowa organizacja: {data.get(OrgFormSchemaIds.name)} | Zgłoszenie: #{issue.number}"
pr_body = f"Automatycznie dodana nowa organizacja na podstawie zgłoszenia z issue #{issue.number}.\n\n Closes #{issue.number}"

file_path = "organizations/organization.yaml"

manager = GitManager(repo)
manager.create_or_update_pr_with_file(
source_branch=source_branch,
new_branch=new_branch_name,
pr_title=pr_title,
pr_body=pr_body,
file_path=file_path,
file_contents=yaml_string,
commit_message=commit_message,
)
11 changes: 6 additions & 5 deletions .github/scripts/labels.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import enum

from consts import OrgSchemaIds
from consts import OrgFormSchemaIds


class Label(enum.StrEnum):
Expand All @@ -9,11 +9,12 @@ class Label(enum.StrEnum):
INVALID_PHONE = "niepoprawny numer telefonu"
INVALID_SLUG = "niepoprawna nazwa strony"
AUTO_VERIFIED = "zweryfikowana automatycznie"
WAITING = "oczekuje na akceptację"


INVALID_FIELD_TO_LABEL = {
OrgSchemaIds.krs: Label.INVALID_KRS,
OrgSchemaIds.postal_code: Label.INVALID_POSTAL_CODE,
OrgSchemaIds.phone_number: Label.INVALID_PHONE,
OrgSchemaIds.slug: Label.INVALID_SLUG,
OrgFormSchemaIds.krs: Label.INVALID_KRS,
OrgFormSchemaIds.postal_code: Label.INVALID_POSTAL_CODE,
OrgFormSchemaIds.phone_number: Label.INVALID_PHONE,
OrgFormSchemaIds.slug: Label.INVALID_SLUG,
}
Loading