Skip to content

Feat/import focalboard#1

Open
ananastii wants to merge 9 commits intomasterfrom
feat/import-focalboard
Open

Feat/import focalboard#1
ananastii wants to merge 9 commits intomasterfrom
feat/import-focalboard

Conversation

@ananastii
Copy link
Collaborator

@ananastii ananastii commented Dec 17, 2025

Focalboard to Planka Import - Project Summary

Project Overview

Goal: Import boards from Focalboard (JSONL format) into Planka project management system.

Status: Version 5 COMPLETE ✅

  • Basic import working (v1)
  • Lists, cards, and descriptions imported (v1)
  • Labels imported (v2) ✅
  • Due dates imported (v3) ✅
  • Custom fields imported (v4) ✅
  • Two-step import with UI property selection (v5) ✅

Architecture Overview

Import Flow (v5 - Two-Step)

┌─────────────────────────────────────────────────────────────────┐
│                        STEP 1: PREVIEW                          │
├─────────────────────────────────────────────────────────────────┤
│  User selects .jsonl file                                       │
│           ↓                                                     │
│  Frontend calls POST /api/focalboard/preview                    │
│           ↓                                                     │
│  parse-focalboard-file.js (previewOnly=true, fast parsing)      │
│           ↓                                                     │
│  Returns: board info, properties list, views, card count        │
│           ↓                                                     │
│  User selects property mappings in UI:                          │
│    • Columns (required) - select type                           │
│    • Labels (optional) - select/multiSelect type                │
│    • Due Date (optional) - date type                            │
│    • Custom Fields (optional) - text/url/number/etc.            │
│    • Use board title as import name (checkbox)                  │
└─────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────┐
│                        STEP 2: IMPORT                           │
├─────────────────────────────────────────────────────────────────┤
│  User clicks "Create Board" with name + file + mappings         │
│           ↓                                                     │
│  POST /api/projects/:id/boards (multipart form + query params)  │
│           ↓                                                     │
│  create.js → process-uploaded-focalboard-import-file.js         │
│           ↓                                                     │
│  import-from-focalboard.js orchestrates:                        │
│    1. import-focalboard-labels.js                               │
│    2. import-focalboard-lists.js                                │
│    3. import-focalboard-custom-fields.js                        │
│    4. import-focalboard-cards.js                                │
│           ↓                                                     │
│  Board, Lists, Cards, Labels, CustomFields created              │
└─────────────────────────────────────────────────────────────────┘

Key Decisions & Design Choices

1. Two-Step Import Flow

Decision: Preview file first, let user select mappings, then import.

Reasoning:

  • Different Focalboard boards have different property configurations
  • Hardcoded property IDs don't work across boards
  • User needs to see available properties before deciding what to import
  • Allows selective import of custom fields

2. Preview Mode Optimization

Decision: parse-focalboard-file.js has previewOnly mode that only parses board + views, counts cards/textBlocks.

Reasoning:

  • Full parsing of 2000+ line files is slow for just showing a preview
  • Preview only needs: board properties, views (for auto-selecting column), and counts
  • Cards and text blocks are just counted, not stored in memory

3. importMapping as Query Parameter

Decision: Send importMapping JSON as URL query parameter, not in multipart form body.

Reasoning:

  • Sails.js multipart form parser doesn't reliably pass all fields to inputs
  • Query parameters are parsed correctly regardless of form body
  • importMapping is JSON-stringified on frontend, parsed on backend

4. Property Type Support

Focalboard Type Planka Mapping Supported Notes
select Lists/Columns Required, single choice
multiSelect Labels Optional, maps to Planka labels
date card.dueDate Native Planka field
text CustomFieldValue Stored as string
url CustomFieldValue Stored as string
number CustomFieldValue Stored as string
email CustomFieldValue Stored as string
phone CustomFieldValue Stored as string
checkbox CustomFieldValue Stored as string
person Not supported (user mapping complex)
multiPerson Not supported
file Not supported (attachment handling)
createdTime System field, Planka handles
createdBy System field
updatedTime System field
updatedBy System field

File Structure

Backend (Sails.js)

api/
├── controllers/
│   ├── boards/
│   │   └── create.js                    # Board creation endpoint (handles import)
│   └── focalboard/
│       └── preview.js                   # Preview endpoint for property selection
│
└── helpers/
    └── boards/
        ├── parse-focalboard-file.js     # JSONL parser (previewOnly mode)
        ├── process-uploaded-focalboard-import-file.js  # Apply user mappings
        ├── import-from-focalboard.js    # Main orchestrator
        ├── import-focalboard-labels.js  # Create labels
        ├── import-focalboard-lists.js   # Create lists
        ├── import-focalboard-cards.js   # Create cards + custom field values
        ├── import-focalboard-custom-fields.js  # Create custom field group/fields
        ├── group-focalboard-cards-by-list.js   # Group cards by column (sync)
        └── parse-focalboard-date.js     # Date parser utility

config/
└── routes.js # Add: 'POST /api/focalboard/preview': 'focalboard/preview'

Frontend (React)

src/
├── api/
│   └── boards.js                        # API calls (createBoardWithImport, previewFocalboardImport)
│
├── components/boards/AddBoardStep/
│   ├── AddBoardStep.jsx                 # Main form, handles boardTitle from import
│   ├── ImportStep.jsx                   # File picker, routes to mapping step
│   ├── FocalboardMappingStep.jsx        # Property selection UI
│   └── FocalboardMappingStep.module.scss
│
└── sagas/core/services/
    └── boards.js                        # createBoard saga (sends importMapping)

API Reference

POST /api/focalboard/preview

Preview a Focalboard file to get available properties.

Request: multipart/form-data

  • file: JSONL file

Response:

{
  "board": {
    "id": "...",
    "title": "Board Name",
    "description": "..."
  },
  "properties": [
    {
      "id": "property-uuid",
      "name": "Property Name",
      "type": "select|multiSelect|date|text|url|...",
      "options": [...]
    }
  ],
  "views": [
    {
      "id": "...",
      "title": "Kanban View",
      "type": "board",
      "groupById": "column-property-id"
    }
  ],
  "stats": {
    "totalCards": 1065,
    "totalTextBlocks": 584
  }
}

POST /api/projects/:projectId/boards

Create board with optional import.

Request: multipart/form-data + query params

  • Body: name, position, importType, importFile
  • Query: requestId, importMapping (JSON string)

importMapping structure:

{
  "columnPropertyId": "uuid",
  "labelPropertyId": "uuid",
  "dueDatePropertyId": "uuid",
  "customFieldPropertyIds": ["uuid1", "uuid2"]
}

Translation Keys

// Add to translation files
{
     common: {
     cardsCount: "{{count}} cards",
     cardsCount_plural: "{{count}} cards",
     columns: "Columns",
     error: "Error",
     focalboardImport_title: "Focalboard Import",
     fromFocalboard: "From Focalboard",
     none: "None",
     parsingFile: "Parsing file...",
     selectProperty: "Select property...",
     unsupported: 'Unsupported',
  },
  action: {
    back: "Back",
    deselectAll: "Deselect All",
    selectAll: "Select All",
  }
}

@ananastii ananastii force-pushed the feat/import-focalboard branch from 438cf0d to bb65801 Compare January 17, 2026 01:08
@ananastii ananastii requested a review from alexeyqu January 20, 2026 22:51
@ananastii ananastii force-pushed the feat/import-focalboard branch from 47cb6e8 to b5d4008 Compare January 25, 2026 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant