Import tasks from Notion
milesfrain opened this issue · comments
Miles Frain commented
Here's a hacky python script for converting a Notion export (combined CSV and .md files) to a compatible Linear CSV format.
You're free to reuse/modify/incorporate the code however you'd like. I imagine you'll want to rewrite in TypeScript.
import csv
import os
import glob
def extract_md_data(md_file_path):
print(f"Extracting data from {md_file_path}")
with open(md_file_path, 'r') as f:
content = f.read()
lines = content.split('\n')
# The first line is the title
title = lines[0].replace('# ', '').strip()
data = {
'Title': title,
}
# Followed by an empty line
i = 2
# Followed by metadata key-value pairs
while True:
if i >= len(lines):
return data
line = lines[i]
if line is None or line == '':
break
print(f"Converting line {i} to metadata: {line}")
key, value = line.split(':', 1)
data[key.strip()] = value.strip()
i += 1
# Followed by an empty line
# Followed by the description
data['Description'] = '\n'.join(lines[i:]).strip()
return data
# Read the Notion CSV
notion_csv_file = 'notion_tasks_export.csv'
linear_csv_file = 'linear_import.csv'
with open(notion_csv_file, 'r', newline='', encoding='utf-8-sig') as fin, \
open(linear_csv_file, 'w', newline='', encoding='utf-8') as fout:
reader = csv.DictReader(fin)
# Define the Linear headers
headers = ["ID","Team","Title","Description","Status","Estimate","Priority","Project ID","Project","Creator","Assignee","Labels",
"Cycle Number","Cycle Name","Cycle Start","Cycle End","Created","Updated","Started","Triaged","Completed","Canceled",
"Archived","Due Date","Parent issue","Roadmaps","Project Milestone ID","Project Milestone","SLA Status"]
writer = csv.DictWriter(fout, fieldnames=headers)
writer.writeheader()
for row in reader:
print(f"Processing row: {row}")
task_name = row['Name']
# Find the markdown file corresponding to this task.
# Note that md file titles are truncated after ~40 characters
# Re-attempt with a shorter title after failure
md_file = None
for i in range(40, 1, -1):
md_file = glob.glob(f"*{task_name[:i]}*.md")
if len(md_file) > 1:
print(f"Error: Found multiple files for {task_name[:i]}: {md_file}")
exit(1)
if md_file:
break
if not md_file:
print(f"Error: Could not find MD file for {task_name}")
exit(1)
md_data = extract_md_data(md_file[0])
# Create the Linear CSV entry
entry = {
'Title': md_data.get('Title', ''),
'Description': md_data.get('Description', ''),
'Status': md_data.get('Status', ''),
'Assignee': md_data.get('Assignee', ''),
'Created': md_data.get('Date Created', ''),
'Due Date': row.get('Date', ''),
'Estimate': row.get('Estimate', ''),
'Labels': row.get('Tag', ''),
# You can fill in more fields here as required...
}
writer.writerow(entry)
print(f"Conversion completed. Check {linear_csv_file} for the Linear-compatible tasks.")
Tom Moor commented
Thanks @milesfrain – closing this out as it isn't an issue per-say. Theres's also another issue tracking Notion integration here: