Blog ·
Een snelkoppeling is een verwijzing, geen recht
Zes kaarten voor het dashboard, en snelkoppelingen die alleen renderen als de kijker het doel vandaag nog mag zien. Twee bevindingen uit een echte proef.
Door Provibr
Het dashboard van een hostingpaneel is het scherm dat iedereen als eerste ziet en waar bijna niemand een mening over heeft tot het voor hem verkeerd staat. Het verzoek was bescheiden: laat mensen kaarten toevoegen, en laat ze snelkoppelingen neerzetten naar een server of een integratie waar ze steeds naar terugkeren.
Bescheiden verzoeken zijn waar de interessante besluiten zich verstoppen, want een snelkoppeling is een opgeslagen voorkeur die naar een echt object met echte rechten wijst. Die verhouding verkeerd leggen is hoe een paneel iemand een machine laat zien waar hij drie maanden geleden van afgehaald is.
Zes kaarten, geen migratie, en niets komt ongevraagd
Er zijn zes toevoegbare kaarten: een lijst met agents, een lijst met servers, een lijst met integraties, een lijst met tickets, het tegoed, en een snelkoppeling. Alle zes zijn halfbreed, want het zijn overzichten naast elkaar en niet iets dat de hele rij nodig heeft.
De indeling staat als JSON opgeslagen, en deze ronde heeft geen migratie toegevoegd. Dat is een besluit en geen verzuim. Een regel was een id en een verborgen-vlag; hij draagt nu ook een instantie-id en een configuratie per kaart. Een oude rij leest schoon in de nieuwe vorm in, met het instantie-id ingevuld vanuit het kaart-id en de configuratie leeg, en een test toetst precies dat: een oude indeling leest in als dezelfde kaarten in dezelfde volgorde. Een migratie zou hier alleen een tijdstempel in het journaal zetten, en een tijdstempel dat later te hoog blijkt is een echt probleem voor de volgende ronde.
De tweede regel is dat een optionele kaart nooit vanzelf verschijnt. Er is een lus die kaarten toevoegt die een indeling niet noemt, zodat een nieuwe vaste kaart bij iedereen opduikt, en die lus dekt bewust alleen de vaste kaarten. Zou een optionele kaart meedoen, dan is "toevoegen" geen handeling meer en groeit elk dashboard in het product bij elke release met een kaart die niemand vroeg.
Welke kaarten er niet in zitten is net zo goed een besluit als welke er wel in zitten. Monitoring en backups bestaan in de codebase achter rolloutvlaggen die uit staan, dus een kaart ervoor zou een kaart zonder gebruikers zijn. Recente activiteit viel af omdat die al als vaste kaart bestaat.
Twee helften van één regel
In de opgeslagen indeling van een gebruiker is een snelkoppeling niets dan een soort en een id. Alles wat op het scherm komt, de naam, de status, de link, wordt bij elke paginalading opnieuw opgehaald met de rechten van wie er op dát moment kijkt. Die regel heeft twee helften en allebei zijn ze nodig.
De eerste is de module. Een lid dat de module agents verliest ziet zijn snelkoppeling naar een agent niet meer, ook al staat hij nog in zijn indeling. Deze poort kent óók de oppervlakken, dus op het eigen merkdomein van een klant, waar facturatie helemaal niet bestaat, verdwijnt de snelkoppeling naar het tegoed per constructie en niet door een tweede regel die er speciaal voor geschreven is.
De tweede is de objectquery. Het organisatie-id staat in elke WHERE, het filter op zachte verwijdering staat ernaast, en bij een server staat de serverbegrenzing van dat lid daar weer naast. Een server die nooit aan een lid is toegewezen bestaat voor dat lid niet, en dat moet ook gelden voor een snelkoppeling die hij maakte toen hij hem nog wel mocht zien.
De snelkoppelingskaart is ook de enige kaart zonder module en de enige die meer dan één keer mag bestaan. Dat is juist en niet slordig: een snelkoppeling is geen eigen scherm maar een verwijzing naar een scherm dat zijn eigen recht al draagt. Geef de kaartsoort een module en een kijker met die module kan een snelkoppeling zien naar iets dat hij niet mag openen.
Module weg en object weg zijn verschillende gebeurtenissen
Twee dingen die er op het scherm hetzelfde uitzien worden als tegenpolen behandeld.
| Wat er gebeurde | Wat de kijker ziet |
|---|---|
| De module is afgenomen | De kaart verdwijnt zonder een woord, en komt terug met de module |
| Het doel is niet gevonden | Een kaart die zegt dat het niet meer bestaat, met een knop om hem weg te halen |
Het eerste is stil omdat de snelkoppeling niet het verhaal is. Is iemands rol veranderd, dan is de ontbrekende snelkoppeling een symptoom, en hem op het dashboard aankondigen zou ruis zijn boven op een wijziging die hij al kent. Hij komt ook ongeschonden terug zodra de module terugkomt, en dat kan alleen omdat er niets verwijderd is.
Het tweede is luid omdat de gebruiker niets fout deed en er iets van hem is afgenomen. Het is bewust geen 404 en geen foutmelding: gewoon een kaart die de situatie benoemt en aanbiedt zichzelf op te ruimen.
Tolerant renderen, niet opruimen bij verwijderen
Het voor de hand liggende alternatief is snelkoppelingen weghalen zodra hun doel verwijderd wordt. Dat hebben we niet gedaan, en de redenering is het opschrijven waard omdat de tegenovergestelde keuze elders in dezelfde codebase wél gemaakt is: verlaat een lid een account, dan wordt zijn opgeslagen dashboardindeling werkelijk vergeten, want die rij raakt geen enkele cascade en zou anders voorgoed blijven staan in een account waar hij nooit terugkomt.
Hier valt de weging de andere kant op, om drie redenen.
Verdwijnen heeft meer oorzaken dan verwijderen. Een lid verliest een module, zijn serverbegrenzing verandert, hij kijkt op een merkdomein. Geen enkele opruimroutine vangt die drie, dus het tolerante renderpad moet er toch al zijn, en een tweede mechanisme is dan alleen een plek die uit de pas kan lopen.
De indeling staat per gebruiker opgeslagen. Eén verwijderde server zou betekenen dat de JSON van élk lid van de organisatie herschreven wordt, in een transactie op het verwijderpad. Dat is schrijfversterking op een warm pad in ruil voor een schermvoorkeur.
En er is niets te beschermen. De opgeslagen rij is een uuid van een object van de eigen organisatie en verder niets. Hij lekt niets, hij groeit niet, en de gebruiker ziet meteen dat hij weg kan.
De proef van begin tot eind daarvoor is met opzet bot. Nadat een server zacht verwijderd is komt hij niet terug, en staat zijn naam nergens in de renderuitvoer. Een lid zonder de module agents krijgt agent noch integratie, en de kiezer biedt ze niet aan. Een lege serverbegrenzing levert nul rijen op en niet alles.
Twee bevindingen die alleen een echte proef opleverde
De twee echte defecten van deze ronde waren allebei onzichtbaar voor unittests, en allebei kostten ze ongeveer tien seconden zodra er iets echts draaide.
De kiezer heeft een zoekveld, en zoeken op integraties brak de hele kiezer met een databasefout. De reden is dat de kolom met het integratietype een Postgres-enum is, en dat er voor een enum geen hoofdletterongevoelige patroonoperator bestaat: de query gaf 42883 operator does not exist, en omdat die query de hele kiezer voedde rendeerde er helemaal niets. De kolom naar tekst casten lost het op. Geen enkele unittest met een nagebootste executor zou dit gezien hebben, want een nabootsing heeft geen types.
De tweede kwam uit de toegankelijkheidsveeg. Een snelkoppelingstegel is een naam, een status en een soort, en het geheel is aanklikbaar. De eerste versie zette dat allemaal in één link, en de controle meldde een verschil tussen de toegankelijke naam en het zichtbare opschrift, want verborgen tekst telt mee voor de toegankelijke naam. De tegel is nu een gewone omhulling met één link die alleen de naam draagt en een leeg absoluut klikvlak dat de klik opvangt. Dat bracht een tweede, kleiner probleem mee: de afkapklasse op de link klipte het klikvlak tot de breedte van de tekst, waardoor het grootste deel van de tegel niet meer aanklikbaar was. Het afkappen verhuisde naar een binnenste element en de dekking is gemeten in plaats van aangenomen.
Wat wij zouden opschrijven
- Sla een verwijzing op, los een recht op. Wat in de voorkeur staat hoort een id te zijn. Wat op het scherm komt hoort elke keer opgehaald te worden met de rechten van wie er kijkt.
- Een kolom zonder schema is een migratie die je niet hoeft te schrijven. Maar alleen als je kunt bewijzen dat de oude vorm identiek terugleest, en dat is een test.
- Optioneel betekent voorgoed op verzoek. Een kaart die vanzelf verschijnt maakt van "toevoegen" een woord zonder betekenis.
- Onderscheid verwijderd van onbereikbaar. Ze zien er op het scherm hetzelfde uit en ze verdienen een tegengestelde behandeling: stilte voor de een, uitleg voor de ander.
- Niet opruimen kan de sterkere keuze zijn. Heeft hetzelfde symptoom oorzaken die je opruiming niet ziet, dan moet het tolerante pad er hoe dan ook zijn, en is de opruiming alleen een tweede ding om gelijk te houden.
- Draai het ding. Een enum zonder patroonoperator en een link die zijn eigen klikvlak klipt zijn allebei triviaal zichtbaar in een browser, en allebei onzichtbaar voor een testsuite zonder database.
Voor de duidelijkheid over wat dit is: de kaarten die hier beschreven staan zijn live voor elk account. Meerdere dingen zijn onbewezen. Herordenen met de muis is niet aangedreven, de gevlagde monitoring- en backupkaarten bestaan niet dus hun codepad is nooit genomen, het merkdomein is in een query nagelopen en niet in een browser, en een net toegevoegde kaart verschijnt pas na de volgende serverronde.
Uit het platformwerk van Provibr. Wat een klant van onze klant op een merkpaneel wel en niet ziet staat in whitelabelpanelen: wat de klant ziet, en waarom een overzichtsscherm nooit terugschrijft in de tabellen die het leest staat in projecteren, nooit vervangen.