-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add update-notion-database composite action
- Loading branch information
Showing
4 changed files
with
286 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,3 +69,4 @@ runs: | |
set -x | ||
git tag ${TAG} | ||
git push origin ${TAG} | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
# update Notion database | ||
|
||
To use it: | ||
|
||
Set up the secrets in your GitHub repository: | ||
|
||
* NOTION_TOKEN | ||
* NOTION_DATABASE_ID | ||
|
||
|
||
Trigger the workflow manually and provide the fields as a JSON string. Examples: | ||
|
||
``` json | ||
// Example 1 - Version update | ||
{ | ||
"Date": "today", | ||
"Component": "MyApp", | ||
"Old Version": "1.0.0", | ||
"New Version": "1.1.0" | ||
} | ||
|
||
// Example 2 - Task tracking | ||
{ | ||
"Title": "New Feature", | ||
"Status": "In Progress", | ||
"Priority": "High", | ||
"Due Date": "2024-12-31" | ||
} | ||
``` | ||
|
||
The script will: | ||
|
||
Parse the JSON input | ||
Convert values to appropriate types | ||
Add a new row to your Notion database, if row with the same values already exists, it will not be added - the check is based on the unique_fields comma seprated list | ||
|
||
# Example workflow using the composite action | ||
```yaml | ||
name: Update Notion | ||
on: | ||
workflow_dispatch: | ||
inputs: | ||
fields_json: | ||
description: 'Fields to update' | ||
required: true | ||
type: string | ||
unique_fields: | ||
description: 'Unique fields to cjeck for duplicates' | ||
required: true | ||
type: string | ||
|
||
jobs: | ||
update-notion: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: comet-ml/gha-tools/notion-update@main # If publishing as a separate repo | ||
with: | ||
notion_token: ${{ secrets.NOTION_TOKEN }} | ||
database_id: ${{ secrets.NOTION_DATABASE_ID }} | ||
fields_json: ${{ inputs.unique_fields }} | ||
|
||
``` | ||
|
||
For your Notion page you want to update - https://developers.notion.com/docs/create-a-notion-integration#give-your-integration-page-permissions | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
# action.yml | ||
name: 'Update Notion Database' | ||
description: 'Add a new row to a Notion database with field validation and duplicate checking' | ||
|
||
inputs: | ||
notion_token: | ||
description: 'Notion API token' | ||
required: true | ||
database_id: | ||
description: 'Notion database ID' | ||
required: true | ||
fields_json: | ||
description: 'JSON string containing field names and values' | ||
required: true | ||
unique_fields: | ||
description: 'Comma-separated list of fields to check for duplicates' | ||
required: true | ||
|
||
runs: | ||
using: "composite" | ||
steps: | ||
- name: Set up Python | ||
uses: actions/setup-python@v5 | ||
with: | ||
python-version: '3.x' | ||
|
||
- name: Install dependencies | ||
shell: bash | ||
run: | | ||
python -m pip install --upgrade pip | ||
pip install notion-client pandas python-dateutil | ||
- name: Update Notion Database | ||
shell: python | ||
env: | ||
NOTION_TOKEN: ${{ inputs.notion_token }} | ||
DATABASE_ID: ${{ inputs.database_id }} | ||
FIELDS_JSON: ${{ inputs.fields_json }} | ||
UNIQUE_FIELDS: ${{ inputs.unique_fields }} | ||
run: | | ||
import os | ||
import json | ||
import sys | ||
from notion_client import Client | ||
import pandas as pd | ||
from datetime import datetime | ||
from dateutil import parser | ||
def format_date(date_str): | ||
"""Convert various date formats to Notion's expected ISO format""" | ||
try: | ||
parsed_date = parser.parse(date_str) | ||
return parsed_date.isoformat() | ||
except Exception as e: | ||
print(f"Error parsing date {date_str}: {str(e)}") | ||
return None | ||
def get_property_value(property_item): | ||
property_type = property_item['type'] | ||
if property_type == 'title': | ||
return property_item['title'][0]['plain_text'] if property_item['title'] else '' | ||
elif property_type == 'rich_text': | ||
return property_item['rich_text'][0]['plain_text'] if property_item['rich_text'] else '' | ||
elif property_type == 'number': | ||
return property_item['number'] | ||
elif property_type == 'select': | ||
return property_item['select']['name'] if property_item['select'] else '' | ||
elif property_type == 'multi_select': | ||
return [option['name'] for option in property_item['multi_select']] | ||
elif property_type == 'date': | ||
if property_item['date']: | ||
return property_item['date']['start'] | ||
return None | ||
elif property_type == 'checkbox': | ||
return property_item['checkbox'] | ||
return None | ||
def create_property_value(value, property_type): | ||
print(f"Creating property value for type {property_type} with value {value}") | ||
if property_type == 'title': | ||
return {'title': [{'text': {'content': str(value)}}]} | ||
elif property_type == 'rich_text': | ||
return {'rich_text': [{'text': {'content': str(value)}}]} | ||
elif property_type == 'number': | ||
try: | ||
return {'number': float(value)} | ||
except (TypeError, ValueError): | ||
return {'number': None} | ||
elif property_type == 'select': | ||
return {'select': {'name': str(value)}} | ||
elif property_type == 'multi_select': | ||
if isinstance(value, list): | ||
return {'multi_select': [{'name': str(v)} for v in value]} | ||
return {'multi_select': [{'name': str(value)}]} | ||
elif property_type == 'date': | ||
formatted_date = format_date(value) | ||
print(f"Formatted date: {formatted_date}") | ||
if formatted_date: | ||
return { | ||
'date': { | ||
'start': formatted_date | ||
} | ||
} | ||
return {'date': None} | ||
elif property_type == 'checkbox': | ||
return {'checkbox': bool(value)} | ||
return {'rich_text': [{'text': {'content': str(value)}}]} | ||
def main(): | ||
try: | ||
# Get environment variables | ||
notion_token = os.environ['NOTION_TOKEN'] | ||
database_id = os.environ['DATABASE_ID'] | ||
fields_json = os.environ['FIELDS_JSON'] | ||
unique_fields = os.environ['UNIQUE_FIELDS'].split(',') | ||
# Initialize Notion client | ||
notion = Client(auth=notion_token) | ||
# Parse fields JSON | ||
try: | ||
fields = json.loads(fields_json) | ||
except json.JSONDecodeError: | ||
print("Error: Invalid JSON format in fields_json") | ||
sys.exit(1) | ||
print(f"Processing fields: {fields}") | ||
# Get database schema | ||
database = notion.databases.retrieve(database_id) | ||
properties = database['properties'] | ||
print("Database properties:") | ||
for prop_name, prop_data in properties.items(): | ||
print(f"- {prop_name}: {prop_data['type']}") | ||
# Query existing entries | ||
print("\nQuerying existing entries...") | ||
results = [] | ||
query = notion.databases.query(database_id) | ||
results.extend(query['results']) | ||
while query.get('has_more'): | ||
query = notion.databases.query( | ||
database_id, | ||
start_cursor=query['next_cursor'] | ||
) | ||
results.extend(query['results']) | ||
# Check for duplicates | ||
print("\nChecking for duplicates...") | ||
duplicate_found = False | ||
for result in results: | ||
matches = 0 | ||
needed_matches = len(unique_fields) | ||
for unique_field in unique_fields: | ||
# Handle the space in "Date " field name | ||
field_name = unique_field.strip() | ||
if field_name not in fields: | ||
continue | ||
if field_name not in result['properties']: | ||
continue | ||
existing_value = get_property_value(result['properties'][field_name]) | ||
new_value = fields[field_name] | ||
if str(existing_value).strip().lower() == str(new_value).strip().lower(): | ||
matches += 1 | ||
if matches == needed_matches: | ||
duplicate_found = True | ||
print(f"Duplicate entry found!") | ||
break | ||
if duplicate_found: | ||
print("Skipping upload due to duplicate entry") | ||
sys.exit(0) | ||
# Prepare properties for new page | ||
new_properties = {} | ||
for field_name, field_value in fields.items(): | ||
# Handle the space in field names | ||
matching_field = next((prop for prop in properties.keys() | ||
if prop.strip() == field_name.strip()), None) | ||
if matching_field: | ||
prop_type = properties[matching_field]['type'] | ||
print(f"\nProcessing field: {matching_field}") | ||
print(f"Type: {prop_type}") | ||
print(f"Value: {field_value}") | ||
new_properties[matching_field] = create_property_value(field_value, prop_type) | ||
print(f"Converted to: {new_properties[matching_field]}") | ||
# Create new page | ||
print("\nCreating new page with properties:") | ||
print(json.dumps(new_properties, indent=2)) | ||
response = notion.pages.create( | ||
parent={'database_id': database_id}, | ||
properties=new_properties | ||
) | ||
print("Successfully updated Notion database") | ||
except Exception as e: | ||
print(f"Error: {str(e)}") | ||
print(f"Error type: {type(e)}") | ||
import traceback | ||
print(traceback.format_exc()) | ||
sys.exit(1) | ||
if __name__ == "__main__": | ||
main() |