linear / linear

Tools, SDK's and plugins for Linear

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Import tasks from Notion

milesfrain opened this issue · comments

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.")

Thanks @milesfrain – closing this out as it isn't an issue per-say. Theres's also another issue tracking Notion integration here:

#168