├── .github └── workflows │ └── notion.yml ├── .gitignore ├── Dockerfile ├── README.md ├── action.yml ├── body.md ├── main.py └── requirements.txt /.github/workflows/notion.yml: -------------------------------------------------------------------------------- 1 | name: issue_to_notion_card 2 | on: 3 | issues: 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - name: Notion Card Creator 10 | uses: dodo4114/issue-to-notion@v1.1.1 11 | env: 12 | NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }} 13 | DATABASE_URL: https://www.notion.so/dodo4114/aebe312a066c465494fb1eb6997060b0?v=95652d72244a44bd97d39b6057c51dc0 14 | PROPERTY_NAME : state 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | main_test.py 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | Pipfile 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7-slim 2 | 3 | LABEL "com.github.actions.name"="Issue to Notion" 4 | LABEL "com.github.actions.description"="From github Issue Make Notion Card" 5 | LABEL "com.github.actions.icon" = "activity" 6 | LABEL "com.github.actions.color" = "yellow" 7 | 8 | LABEL "repository"="https://github.com/dodo4114/issue-to-notion" 9 | LABEL "maintainer"="Dohyeon Park " 10 | 11 | WORKDIR /usr/src/app 12 | COPY requirements.txt ./ 13 | COPY main.py ./ 14 | RUN ls 15 | RUN pip install --no-cache-dir -r requirements.txt 16 | 17 | ENTRYPOINT ["python", "/usr/src/app/main.py"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # issue-to-notion 2 | This action make **ISSUEs affect TO NOTION cards**. 3 | Issue OPEN/CLOSE/EDIT/REOPEN event will make a corresponding action to your Notion DB 4 | 5 | ## :wink: Pre-order of Notion-to-Github and get discount! 6 | We provide issue-to-notion for free! But it's hard to make Notion-to-Github... 7 | So we need to check its demand is enough. If you want it, plz answer below typeform. 8 | If you answer, you will get 10% discount a year. If you pre-pay 9.9$, you will get 30% discount a year. 9 | (Surely it can be free if it doesn't use any paid resource) 10 | Answer HERE : [typeform Link](https://dodo41142727.typeform.com/to/mPP7d1hV) 11 | 12 | ## :rocket: WHAT's NEW? (v1.1.1) 13 | 14 | ### 1. Custom property & status 15 | you can choose which DB property set to which status, when issue OPEN/CLOSE 16 | ### 2. Edit/Close/Reopen will work nicely 17 | Edit/Close/Reopen will work even though there isn't a corresponding card. 18 | It will create notion card when there isn't! 19 | 20 | ## :upside_down_face: Usage 21 | ### Your DB should have "name" property 22 | 23 | Create `.github/workflows/issue-to-notion.yml` in your repository. 24 | And copy&paste following, and edit appropriately. 25 | 26 | ``` 27 | name: issue_to_notion 28 | on: 29 | issues: 30 | jobs: 31 | build: 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v2 35 | - name: Notion Card Creator 36 | uses: dodo4114/issue-to-notion@v1.1.1 37 | env: 38 | NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }} 39 | DATABASE_URL: {YOUR DB URL} 40 | PROPERTY_NAME : {YOUR PROPERTY NAME} 41 | STATE_OPEN : {YOUR OPEN STATE NAME} 42 | STATE_CLOSED : {YOUR CLOSED STATE NAME} 43 | ``` 44 | 45 | | Input Key | Required | Default value | Description | 46 | |:-----:|:-----:|:-----:|-----| 47 | | NOTION_TOKEN | O | X | Auth token called token_v2 from cookie in your browser. Put it your repository secret, and name it NOTION_TOKEN(Recommended) | 48 | | DATABASE_URL | O | X | The url of the database you are trying to access | 49 | | PROPERTY_NAME | X | status | Name of your PROPERTY which will be changed | 50 | | STATE_OPEN | X | open | Your OPEN state name | 51 | | STATE_CLOSED | X | closed | Your CLOSED state name | 52 | 53 | ## Example 54 | this repo and [this notion DB](https://www.notion.so/dodo4114/aebe312a066c465494fb1eb6997060b0?v=95652d72244a44bd97d39b6057c51dc0) 55 | 56 | 57 | ## Dependency 58 | This action uses 59 | + [notion-py](https://github.com/jamalex/notion-py) 60 | + [md2notion](https://github.com/Cobertos/md2notion) 61 | 62 | ## Reference 63 | https://developer.github.com/webhooks/event-payloads/#issues 64 | 65 | ## HELP? 66 | dodo4114@naver.com 67 | 68 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fmarketplace%2Factions%2Fissue-to-notion&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 69 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name : issue-to-notion 2 | description : Make github Issue Event affect Notion Card 3 | author : Dohyeon Park 4 | branding: 5 | icon : activity 6 | color : yellow 7 | runs: 8 | using: 'docker' 9 | image: 'Dockerfile' 10 | -------------------------------------------------------------------------------- /body.md: -------------------------------------------------------------------------------- 1 | # this file will be charged by your issue body -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import json 4 | from notion.client import NotionClient 5 | from notion.block import PageBlock, BookmarkBlock 6 | from md2notion.upload import upload, convert, uploadBlock 7 | 8 | # Get data from github environment 9 | path = os.environ.get("GITHUB_EVENT_PATH") 10 | token = os.environ.get("NOTION_TOKEN") 11 | database_url = os.environ.get("DATABASE_URL") 12 | property_name = os.environ.get("PROPERTY_NAME","status") 13 | state_open = os.environ.get("STATE_OPEN","open") 14 | state_closed = os.environ.get("STATE_CLOSED","closed") 15 | 16 | 17 | # Get the event string from github 18 | with open(path,"r") as f: 19 | github_event_str = f.read() 20 | 21 | # Convert event string to json 22 | github_event_json = json.loads(github_event_str) 23 | 24 | # Login and go into collection 25 | client = NotionClient(token_v2=token) 26 | cv = client.get_collection_view(database_url) 27 | 28 | def main(): 29 | print("main() is excuted") 30 | 31 | global github_event_json 32 | global cv 33 | 34 | # Get issue title, body and link 35 | action_type = github_event_json["action"] 36 | issue_number = github_event_json["issue"]["number"] 37 | issue_title = github_event_json["issue"]["title"] 38 | issue_link = github_event_json["issue"]["html_url"] 39 | 40 | print("action_type is",action_type) 41 | 42 | # Check action type 43 | if action_type == "opened": 44 | 45 | row = createRow(cv,issue_number,issue_title) 46 | 47 | # Add Bookmark for issue 48 | row.children.add_new(BookmarkBlock, title=issue_title, link=issue_link) 49 | upload_body_with_markdown(row) 50 | else: 51 | row = get_or_create_row(cv,issue_number,issue_title) 52 | 53 | if action_type == "edited": 54 | clear_page(row) 55 | row.children.add_new(BookmarkBlock, title=issue_title, link=issue_link) 56 | upload_body_with_markdown(row) 57 | 58 | elif action_type == "closed": 59 | setattr(row,property_name,state_closed) 60 | 61 | elif action_type == "deleted": 62 | pass 63 | # TODO 64 | elif action_type == "reopened": 65 | setattr(row,property_name,state_open) 66 | 67 | elif action_type == "labeled" or action_type == "unlabeled": 68 | pass 69 | # TODO 70 | 71 | 72 | def upload_body_with_markdown(row): 73 | global github_event_json 74 | 75 | body = github_event_json["issue"]["body"] 76 | 77 | # Make markdown file from issue body 78 | f= open("body.md","w+") 79 | f.write(body) 80 | f.close() 81 | 82 | # Upload issue body markdown file to row 83 | with open("body.md", "r", encoding="utf-8") as mdFile: 84 | upload(mdFile,row) 85 | 86 | def clear_page(row): 87 | for child in row.children: 88 | child.remove() 89 | 90 | def get_row_with_IssueNumber(number): 91 | global cv 92 | inputNumber ="[#"+str(number)+"]" 93 | print('issue number is',inputNumber) 94 | 95 | exact_ID_filter_params = { 96 | 'filters': [{'property': "title", 'filter': {'operator': "string_starts_with", 'value': {'type': "exact", 'value': inputNumber}}}], 97 | 'operator': "and" 98 | } 99 | rows = list(filter(lambda row : row.title.startswith(inputNumber),cv.build_query(filter=exact_ID_filter_params).execute())) 100 | print('filtered rows :',rows) 101 | if len(rows) == 0: 102 | return None 103 | return rows[0] 104 | 105 | def createRow(cv, issue_number, issue_title): 106 | # Add row to notion collection 107 | row = cv.collection.add_row() 108 | row.name = "[#"+str(issue_number)+"] "+issue_title 109 | setattr(row,property_name,state_open) 110 | 111 | return row 112 | 113 | def get_or_create_row(cv, issue_number, issue_title): 114 | row = get_row_with_IssueNumber(issue_number) 115 | if not row: 116 | row = createRow(cv, issue_number, issue_title) 117 | return row 118 | 119 | main() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | notion==0.0.25 2 | md2notion==2.1.1 --------------------------------------------------------------------------------