Lompat ke konten
dokumentasi.id
Commit a2522f5
v1
Star645

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

6.1

Status, Prioritas, dan Epic

Setiap ticket berdiri di atas tiga entitas konfigurasi: TicketStatus dan Epic yang selalu terikat pada satu project, serta TicketPriority yang berlaku global untuk seluruh project. Halaman ini membahas kolom yang disimpan ketiganya, aturan is_completed pada status, dan cara epic menghitung progres dari status ticket-nya. Pembuatan ticket dan perpindahan antar status dibahas di Manajemen Ticket; tata letak kolom status di board dibahas di Papan Kanban dan Timeline.

Bentuk data ketiga entitas

TicketStatus menyimpan project_id, name, sort_order, color, dan is_completed, dengan is_completed di-cast ke boolean app/Models/TicketStatus.php:10-24. Model ini memiliki project() sebagai BelongsTo dan tickets() sebagai HasMany app/Models/TicketStatus.php:26-34, sehingga satu status hanya melayani satu project.

TicketPriority hanya menyimpan name dan color; relasi tickets() memakai foreign key priority_id app/Models/TicketPriority.php:9-21. Karena tidak ada kolom project_id, satu baris prioritas dipakai bersama oleh semua project.

Epic menyimpan project_id, name, description, start_date, end_date, dan sort_order, dengan kedua kolom tanggal di-cast ke date app/Models/Epic.php:10-36. Relasinya adalah project() dan tickets(), jadi satu epic menaungi banyak ticket.

erDiagram
    PROJECTS ||--o{ TICKET_STATUSES : "memiliki status"
    PROJECTS ||--o{ EPICS : "memiliki epic"
    TICKET_STATUSES ||--o{ TICKETS : "ticket_status_id"
    TICKET_PRIORITIES ||--o{ TICKETS : "priority_id"
    EPICS ||--o{ TICKETS : "epic_id"
    TICKET_STATUSES {
        bigint id PK
        bigint project_id FK
        string name
        int sort_order
        string color
        boolean is_completed
    }
    TICKET_PRIORITIES {
        bigint id PK
        string name UK
        string color
    }
    EPICS {
        bigint id PK
        bigint project_id FK
        string name
        text description
        date start_date
        date end_date
        int sort_order
    }
    TICKETS {
        bigint id PK
        bigint project_id FK
        bigint ticket_status_id FK
        bigint priority_id FK
        bigint epic_id FK
    }
%% fig: Relasi status, prioritas, dan epic terhadap ticket
%% desc: Status dan epic dibatasi per project, prioritas dipakai lintas project, dan ketiganya bertemu di tabel tickets lewat foreign key masing-masing.
Fig. 1: Relasi status, prioritas, dan epic terhadap ticketStatus dan epic dibatasi per project, prioritas dipakai lintas project, dan ketiganya bertemu di tabel tickets lewat foreign key masing-masing.

Kolom-kolom itu tidak lahir sekaligus; tabel ticket_statuses tumbuh lewat empat migration.

TahapBerkas migrationPerubahan
Tabel awaldatabase/migrations/2025_03_02_200213_create_ticket_statuses_table.php:14-19id, project_id (cascade on delete), name, timestamps
Urutandatabase/migrations/2025_05_06_220233_add_sort_order_to_ticket_statuses_table.php:14-16sort_order integer, default 0
Warnadatabase/migrations/2025_05_06_221002_add_sort_color_to_ticket_statuses_table.php:14-16color string, default #3490dc
Penanda selesaidatabase/migrations/2025_08_23_214007_add_is_completed_to_ticket_statuses_table.php:11-13is_completed boolean, default false, diletakkan setelah color

Status berwarna dan penanda is_completed

Status dikelola dari relation manager milik project. Formnya berisi TextInput untuk name, ColorPicker untuk color dengan default #3490dc, TextInput numerik untuk sort_order dengan helper Determines display order in the project board (lower values appear first), dan Toggle berlabel Mark as Completed Status app/Filament/Resources/Projects/RelationManagers/TicketStatusesRelationManager.php:38-72. Helper pada toggle menuliskan batasannya secara eksplisit: Only one status per project can be marked as completed.

Batas satu status completed itu ditegakkan di tiga tempat sekaligus. afterStateUpdated pada toggle memeriksa TicketStatus::where('project_id', $projectId)->where('is_completed', true) dan, bila sudah ada, mengembalikan toggle ke false serta mengirim notifikasi berjudul Cannot mark as completed app/Filament/Resources/Projects/RelationManagers/TicketStatusesRelationManager.php:49-72. Pemeriksaan yang sama diulang pada CreateAction lewat mutateDataUsing, yang juga menetapkan sort_order baru sebesar max('sort_order') + 1 app/Filament/Resources/Projects/RelationManagers/TicketStatusesRelationManager.php:97-121. Varian ketiga ada di EditAction, dengan tambahan where('id', '!=', $record->id) agar record yang sedang diedit tidak dianggap bentrok app/Filament/Resources/Projects/RelationManagers/TicketStatusesRelationManager.php:124-145.

stateDiagram-v2
    classDef ok fill:#2d7d46,color:#fff
    classDef warn fill:#f59e0b,color:#000
    [*] --> BelumCompleted
    BelumCompleted --> Completed: Toggle is_completed = true dan belum ada status completed lain
    BelumCompleted --> Ditolak: Toggle is_completed = true tetapi project sudah punya status completed
    Ditolak --> BelumCompleted: set is_completed = false
    Completed --> BelumCompleted: Toggle is_completed = false
    class Completed ok
    class Ditolak warn
%% fig: Siklus penanda is_completed pada satu status
%% desc: Toggle hanya bisa menjadi completed bila belum ada status lain di project yang sama, dan penolakan mengembalikan nilainya ke false disertai notifikasi.
Fig. 2: Siklus penanda is_completed pada satu statusToggle hanya bisa menjadi completed bila belum ada status lain di project yang sama, dan penolakan mengembalikan nilainya ke false disertai notifikasi.

Di tabel relation manager, color tampil lewat ColorColumn dan is_completed lewat IconColumn boolean dengan ikon heroicon-o-check-circle untuk benar dan heroicon-o-x-circle untuk salah app/Filament/Resources/Projects/RelationManagers/TicketStatusesRelationManager.php:80-93. Tabel yang sama memakai defaultSort('sort_order') dan reorderable('sort_order'), sehingga urutan kolom bisa diubah dengan drag.

Warna dan penanda itu dipakai langsung oleh board: header kolom diberi background-color dari $status->color, dan sebuah badge centang muncul bila $status->is_completed bernilai benar resources/views/filament/pages/project-board.blade.php:539-554. Query board mengambil is_completed dan mengurutkan kolom dengan orderBy('sort_order') app/Filament/Pages/ProjectBoard.php:151-187.

is_completed juga menjadi dasar progres project. Project::getProgressPercentageAttribute menghitung ticket selesai lewat whereHas('status', fn ($query) => $query->where('is_completed', true)), lalu membulatkan (completedTickets / totalTickets) * 100 sampai satu desimal app/Models/Project.php:95-110.

Project baru mendapat lima status bawaan, dan hanya Done yang ditandai selesai app/Filament/Resources/Projects/Pages/CreateProject.php:12-34:

sort_ordernamecoloris_completed
0Backlog#6B7280false
1To Do#F59E0Bfalse
2In Progress#3B82F6false
3Review#8B5CF6false
4Done#10B981true

⚠️ Batas "satu status completed per project" hanya dijaga di lapisan Filament. Migration menambahkan index biasa idx_ticket_statuses_project_completed pada ['project_id', 'is_completed'], bukan unique index database/migrations/2025_09_16_051002_add_performance_indexes.php:40-48. Penulisan langsung ke tabel di luar form, misalnya lewat import atau seeder, tidak akan ditolak oleh database.

Prioritas ticket

Prioritas adalah data referensi global. Migration-nya membuat ticket_priorities dengan name unik dan color sepanjang 7 karakter berdefault #6B7280, lalu langsung menyisipkan tiga baris awal: Low (#10B981), Medium (#F59E0B), dan High (#EF4444) database/migrations/2025_07_04_164429_create_ticket_priorities_table.php:15-42.

Ticket terhubung ke prioritas lewat kolom priority_id yang nullable, diletakkan setelah ticket_status_id, dan memakai onDelete('set null') database/migrations/2025_07_04_164558_add_priority_id_to_tickets_table.php:14-20. Konsekuensinya, menghapus satu prioritas tidak menghapus ticket; hanya kolomnya yang dikosongkan sehingga ticket tampil tanpa prioritas.

Pengelolaannya ada di resource tersendiri. Form TicketPriorityResource memakai TextInput name yang wajib dan unik dengan unique(ignoreRecord: true), ditambah ColorPicker color berdefault #6B7280 app/Filament/Resources/TicketPriorities/TicketPriorityResource.php:39-51. Pada form ticket, prioritas dipilih lewat Select::make('priority_id') yang mengambil opsi dari TicketPriority::pluck('name', 'id') dan bersifat nullable; select epic di sebelahnya justru difilter per project app/Filament/Resources/Tickets/TicketResource.php:110-133.

Epic

Epic dikelompokkan per project. Migration epics membuat id, project_id dengan cascadeOnDelete, name, description nullable, start_date dan end_date bertipe date nullable, lalu menambahkan epic_id nullable pada tickets setelah ticket_status_id dengan nullOnDelete database/migrations/2025_03_27_065113_create_epics_table.php:14-30. Kolom sort_order integer berdefault 0 ditambahkan belakangan, diletakkan setelah end_date database/migrations/2025_10_17_215303_add_sort_order_to_epics_table.php:14-16. Sama seperti priority_id, menghapus epic mengosongkan epic_id pada ticket alih-alih ikut menghapus ticket.

Relation manager epic menyediakan Epic Name wajib dengan maxLength(255), Sort Order numerik berdefault 0 dengan helper Lower numbers appear first, dua DatePicker nullable, dan RichEditor deskripsi yang menyimpan lampiran ke disk public di direktori attachments app/Filament/Resources/Projects/RelationManagers/EpicsRelationManager.php:30-56. Tabelnya menampilkan sort_order dengan label Order, start_date, end_date, dan kolom tickets_count hasil counts('tickets') berlabel Tickets, lalu mengurutkan baris dengan defaultSort('sort_order', 'asc') app/Filament/Resources/Projects/RelationManagers/EpicsRelationManager.php:58-99.

Progres epic dihitung di halaman EpicsOverview. Method getEpicStats(Epic $epic) mengembalikan total, completed, in_progress, todo, dan progress_percentage; bila epic belum punya ticket, seluruh angkanya 0 app/Filament/Pages/EpicsOverview.php:141-175. Pengelompokan dilakukan dengan mencocokkan $ticket->status?->name terhadap tiga daftar nama: ['Done', 'Completed', 'Closed'] untuk completed, ['In Progress', 'Review'] untuk in_progress, dan ['To Do', 'Open', 'New'] untuk todo. Persentasenya adalah round(($completed / $totalTickets) * 100).

graph TD
    A["Epic::tickets"] --> B["TicketStatus.name"]
    B --> C{"in_array nama status"}
    C -->|"Done, Completed, Closed"| D["completed"]
    C -->|"In Progress, Review"| P["in_progress"]
    C -->|"To Do, Open, New"| T["todo"]
    D --> E["progress_percentage = round(completed / total * 100)"]
    E --> F["EpicsOverview view"]
    style A fill:#336791,color:#fff
    style B fill:#336791,color:#fff
    style D fill:#2d7d46,color:#fff
    style P fill:#8b5cf6,color:#fff
    style T fill:#8b5cf6,color:#fff
    style E fill:#f59e0b,color:#000
    style F fill:#2d7d46,color:#fff
%% fig: Klasifikasi ticket epic menjadi tiga kelompok progres
%% desc: Nama status ticket dicocokkan ke tiga daftar literal, lalu jumlah kelompok completed dibagi total ticket untuk menghasilkan progress_percentage.
Fig. 3: Klasifikasi ticket epic menjadi tiga kelompok progresNama status ticket dicocokkan ke tiga daftar literal, lalu jumlah kelompok completed dibagi total ticket untuk menghasilkan progress_percentage.

⚠️ Ada dua definisi "selesai" yang berbeda di aplikasi ini. Project::getProgressPercentageAttribute memakai kolom is_completed pada status app/Models/Project.php:95-110, sedangkan EpicsOverview::getEpicStats memakai nama status literal Done, Completed, atau Closed app/Filament/Pages/EpicsOverview.php:141-175. Mengganti nama status Done, atau menandai status lain sebagai is_completed, membuat progres epic dan progres project tidak lagi sejalan.