planedrop/README.md
2026-03-02 15:41:59 +03:00

149 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# notion2plank
One-shot Python script that imports tasks from a **Notion CSV export** into a **self-hosted Plane** instance via its REST API.
Plane's free/community tier has no built-in import UI, but the API is fully open. This script bridges the gap.
---
## Requirements
- Python 3.10+
- A Plane API key — _Settings → API Tokens_
- A Notion database exported as CSV — _··· → Export → Markdown & CSV → without subpages_
```
pip -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
---
## Setup
```bash
cp config.yaml.example config.yaml
```
Edit `config.yaml`:
```yaml
plane_url: https://your-plane-instance.com
workspace_slug: your-workspace
project_id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
api_key: your-api-key-here
# Optional — needed to import estimate points (вес column).
# Get it from browser DevTools → Application → Cookies → session-id.
# If omitted, the вес column is silently skipped.
plane_session: your-session-id-cookie-value
# Map Notion status values (exact text + emoji) → Plane state names.
# Find your state names: GET /api/v1/workspaces/<slug>/projects/<id>/states/
status_mapping:
"в работе 🔨": "In Progress"
"Готово ✅": "Done"
"На проверке 👀": "Review"
"Утверждённые задачи 📝": "Todo"
"Общие идеи (задачи) 📋": "Backlog"
```
---
## Usage
**Dry run first** — parses every row and shows what would be created, no API calls:
```bash
python import.py --dry-run
```
**Real import:**
```bash
python import.py
```
**Custom paths:**
```bash
python import.py --csv path/to/export.csv --config path/to/config.yaml
```
### Output
```
Fetching project states…
Found 6 states: ['Backlog', 'Todo', 'In Progress', 'Done', 'Cancelled', 'Review']
Fetching project labels…
Found 7 labels: ['Sound', '2d', 'Game Design', 'Org', '3d', 'Level Design', 'Devel']
Fetching estimate points…
Found 6 estimate points: ['1', '2', '3', '5', '8', '13']
Ensuring labels for disciplines: ['Game Design', 'Sound', ...]
Processing 37 rows…
[ 1] Created: #31 — 'Create Kanban Board'
[ 2] Created: #32 — 'Add Milestones'
...
Summary: 37 created, 0 failed.
```
---
## Field Mapping
| Notion column | Plane field | Notes |
| ---------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------- |
| `Name` | `name` | Required |
| `Status` | `state` (UUID) | Mapped via `status_mapping` in config |
| `priority` | `priority` | Emoji stripped: 🔥 Critical→`urgent`, ⭐ High→`high`, 📌 Medium→`medium`, 💤 Low→`low`, empty→`none` |
| `discipline` | `label_ids` | Auto-created as a Plane label if it doesn't exist yet |
| `Date` (single) | `target_date` | Parsed with dateutil; output `YYYY-MM-DD` |
| `Date` (range `A → B`) | `start_date` + `target_date` | Split on the `→` Unicode arrow |
| `Description` | `description_html` | Wrapped in `<p>` |
| `результат` | appended to `description_html` | Added as `<p><em>Result: N</em></p>` if non-zero |
| `вес` | `estimate_point` (UUID) | Matched by value (e.g. `"5"`) then by key position; requires `plane_session` |
| `исполнитель` | — | Skipped (no user ID mapping) |
| `Автор задачи` | — | Skipped |
| `Attachments` | — | Skipped |
| `Person` | — | Skipped |
---
## Notes
### Estimate points (`вес`)
Plane's public API (`/api/v1/`) does not expose an endpoint to list estimate point UUIDs.
The script works around this by calling an internal frontend endpoint (`/api/workspaces/…/estimates/`) using your browser session cookie.
Resolution order:
1. **Exact value match**`вес="5"` maps to the point labeled "5" (Fibonacci-style: 1, 2, 3, 5, 8, 13)
2. **Ordinal key fallback**`вес="4"` maps to the 4th point in the estimate scale
If `plane_session` is not set or the cookie has expired, the `вес` column is skipped and everything else still imports normally.
### Custom fields (work item properties)
Plane's custom property API (`/work-item-types/…/work-item-properties/`) requires the `is_issue_type_enabled` project flag, which is a paid-tier feature. It is not available in the community/self-hosted free edition.
### Discipline labels
All unique `discipline` values in the CSV are pre-fetched and compared against existing project labels before the import starts. Missing labels are created automatically via `POST /labels/`. Existing labels are reused by UUID.
---
## Files
```
import.py — main script
config.yaml.example — annotated config template
config.yaml — your local config (do not commit)
requirements.txt — pip dependencies
```