Skip to content

Filter Chip yang Diam-Diam Membuang Baris

Adityo Guni Waluyo

Filter sisi klien di atas endpoint terpaginasi menjatuhkan baris yang tak pernah di-fetch. Limit 500, aria-pressed, dan sort yang sengaja tidak ada.

Ringkasan

Filter di halaman daftar organisasi CMS bermasalah. Filter hanya melihat satu halaman dan menghilangkan baris yang tidak terlihat. Batas fetch dinaikkan menjadi 500 baris untuk mengatasi masalah ini.

Chip filter "SK habis" diklik di halaman daftar organisasi CMS. Daftar menyusut dari ratusan baris menjadi belasan, dan beberapa organisasi yang seharusnya muncul justru hilang. Melepas chip memunculkannya kembali.

Kecurigaan pertama mengarah ke data. Anggapan awalnya status organisasi di database tidak konsisten, sebagian baris masih menyimpan nilai lama setelah proses migrasi. Pemeriksaan ke endpoint membantah dugaan itu. Nilai status benar semua. Yang bermasalah bukan isinya, melainkan jumlah baris yang dipakai untuk menyaring.

Filter yang hanya melihat satu halaman

Endpoint daftar organisasi mengembalikan satu halaman per request. Tidak ada parameter status SK habis di API saat itu, sehingga chip bekerja di sisi klien: baris yang sudah di-fetch disaring dengan Array.prototype.filter. Metode itu hanya membuat salinan dangkal dari array masukan dan mempertahankan elemen yang lolos callback [6]. Panjang array masukan menentukan hasil. Kalau halaman pertama berisi 50 baris, filter hanya pernah melihat 50 baris itu.

Di sinilah baris hilang. Organisasi yang cocok tapi berada di halaman ketiga tidak pernah masuk ke array, jadi tidak pernah lolos filter juga. Antarmuka tidak menampilkan error, tidak menandai ada data terpotong. Daftar yang lebih pendek terbaca sebagai hasil penyaringan yang sah.

Filter berbohong lewat penghilangan.

Kesalahan yang sama mudah terulang karena secara tampilan semuanya wajar. Chip menyala, daftar berubah, tidak ada pesan di console. Pengujian manual di halaman pertama selalu lulus.

Batas 500 dan komentar yang menyebut jalan keluarnya

Perbaikan yang diambil bukan menambah parameter server. Commit menaikkan limit fetch menjadi 500 baris dalam satu halaman, cukup untuk menampung seluruh organisasi pada saat audit per Oktober 2026, sehingga filter sisi klien tidak lagi punya halaman lain untuk kehilangan baris. Batas itu ditulis sebagai ceiling di dalam kode, lengkap dengan nama jalan keluarnya: parameter server.

// Client-side filter over a paginated endpoint needs a fetch ceiling.
// Ceiling: 500 rows in one page. Upgrade path: a server-side sk_habis param.
const PAGE_LIMIT = 500;

const res = await fetch(`/api/admin/organizations?limit=${PAGE_LIMIT}`);
const rows = await res.json();
return rows.filter((row) => row.sk_habis === true);

Arah upgrade-nya jelas dan sengaja tidak dikerjakan sekarang: tambahkan sk_habis sebagai parameter query, biarkan database yang menyaring, lalu hapus filter sisi klien. Itu pekerjaan backend dengan uji sendiri. Menaikkan limit adalah perubahan satu konstanta yang menutup kebocoran baris hari itu juga. Angka 500 bukan angka keramat, ia konstanta dengan masa berlaku.

Bagian lain dari chip adalah statusnya. aria-pressed mengubah tombol menjadi toggle button dan merepresentasikan keadaan pressed-nya ke assistive technology [9]. Tanpa atribut itu, chip yang menyala hanya terlihat berbeda secara visual, sementara pembaca layar tidak mendapat informasi apa pun tentang keadaannya.

Commit yang sama juga memberi badge destruktif pada organisasi berstatus SK habis. Jumlahnya lima baris saat audit, dan angka itu akan berubah mengikuti data.

Umur pengajuan, dan sort yang tidak dibuat

Umur pengajuan ditambahkan di sebelah tiket: label "hari ini" untuk pengajuan hari berjalan, dan "X hari lalu" untuk sisanya. Perhitungannya memakai selisih hari manual, mengikuti pola timeAgo yang sudah ada di codebase. Alternatif standar platform tersedia, karena Intl.RelativeTimeFormat memformat waktu relatif mengikuti locale tanpa merangkai string sendiri [7]. Commit tidak memakainya. Dua label bahasa Indonesia tidak membutuhkan tabel plural seluruh locale, dan pola yang sudah ada tetap konsisten dengan bagian lain antarmuka.

Yang sengaja tidak ditambahkan justru sort berdasarkan umur. Urutan baris dimiliki server. Menambahkan sort di klien akan mengurutkan satu halaman saja, lalu halaman berikutnya memulai urutan baru dari awal. Hasilnya daftar yang tampak terurut tapi tidak pernah benar secara keseluruhan. Absennya tombol sort adalah keputusan, bukan kekurangan.

Dua endpoint statistik yang lupa pada bidang

Audit statistik yang menyertai commit menemukan persoalan terpisah. Dua endpoint dashboard di area admin, yang menghitung tampilan dan yang menghitung konten populer, mengembalikan angka lintas-bidang untuk akun yang seharusnya ter-scope ke satu bidang. Akun dengan akses terbatas bisa membaca agregat di luar wilayahnya.

Temuan itu dicatat dan dilaporkan, tidak diperbaiki di commit yang sama. Perbaikannya menyentuh otorisasi di backend dan butuh uji akses per peran untuk memastikan scope benar-benar menempel di query, bukan hanya di lapisan tampilan. Menumpangkannya pada commit ergonomi CMS akan membuat satu perubahan berisi dua risiko berbeda.

Batas 500 dipertahankan sebagai penanda utang yang disadari. Angka itu akan terasa cukup sampai suatu hari jumlah organisasi melewatinya, dan pada titik itu parameter server berhenti menjadi komentar dan mulai menjadi pekerjaan.

Sumber:

  1. [6] MDN Web Docs: Array.prototype.filter
  2. [7] MDN Web Docs: Intl.RelativeTimeFormat
  3. [9] MDN Web Docs: aria-pressed

Artikel terkait