cesko-digital / app

Komunitní aplikace Česko.Digital
https://app.cesko.digital
BSD 3-Clause "New" or "Revised" License
22 stars 39 forks source link

Zjednodušený nábor lidí na role #1099

Open zoul opened 1 month ago

zoul commented 1 month ago

Jeden z našich nejdůležitějších use casů je nabírání lidí na nějaké role. Nejčastěji to stále děláme přes ad-hoc formuláře v Airtable (příklad), protože lepší variantu nemáme. Ta současná varianta ale trpí velkou řadou problémů – zejména se ptáme lidí pořád dokola na to stejné, vytváříme duplicity a zadaná data nám končí v nějaké random tabulce:

Databáze uživatelů:
    Jan Novák
        jan@novak.cz
        Typescript
        Praha
        …

Databáze projektu:
    Odpovědi z registračního formuláře:
        Jan Novák
        jan@novak.cz
        Typescript
        Praha
        …

Potřebujeme to změnit tak, aby přihlášený uživatel nemusel vyplňovat věci, které už o něm víme, a zároveň data z registračního formuláře zachovala nějakou vazbu na uživatelský profil:

Databáze uživatelů:
    Jan Novák   <---------------------------+
        jan@novak.cz                        |
        Typescript                          |
        Praha                               |
                                            |
Databáze projektu:                          |
    Odpovědi z registračního formuláře:     |
        Jan Novák --------------------------+
        (…dodatečné info z registračního formuláře…)

Technicky se to nabízí udělat tak, že odpověď na dotyčnou pozici bude vyžadovat přihlášení (případně registraci) a následně… nevíme co. Ty formuláře v Airtable jsou super flexibilní, tým s nimi umí dobře zacházet, ale nevím, jestli jde nějak zařídit ta vazba na stávající data. Airtable nabízí nějakou možnost předvyplnit data přes URL, což je zajímavá cesta zhruba naším směrem – obávám se, že to nebude dost dobré, ale mrknu na to.

zoul commented 1 month ago

Rychlý technický test: Založil jsem tabulku Test registrace a udělal odpovídající formulář. V tom formuláři je pole User, ve kterém jde vybrat z naší tabulky uživatelů – což samo o sobě není nic moc, ale jde použít předvyplněné URL, kde toho uživatele vyplníme automaticky a zároveň to pole úplně schováme. Ve výsledku pak můžu do registrační tabulky přidat nový záznam, který se odkazuje na existující uživatelský profil a zároveň obsahuje dodatečné informace (potřebné například pro registraci na danou roli). Je to slibné, ale jsou tam dva háčky – jednak bezpečnost, jednak synchronizace do jiných databází. Ještě si s tím pohraju a napíšu.

zoul commented 1 month ago

Synchronizované databáze

Ty registrační formuláře se obvykle týkají projektů, a ty žijí v samostatných databázích (tedy nikoliv v databázi Users). A vazba z registračního formuláře na existující uživatelský profil nemůže vést napříč databázemi. Teď se často děje to, že databáze projektu (například App) má vlastní synchronizovanou tabulku uživatelských profilů. Takže bychom to chtěli udělat tak, aby byl registrační formulář vázaný na záznam z téhle synchronizované tabulky:

Databáze Users:
    User Profiles
Databáze App:
    User Profiles (synchronizovaná kopie z Users)
        Jan Novák <-----+
    Registration form:  |
        Jan Novák ------+
        (…dodatečné info z registračního formuláře)

Teď bychom tedy chtěli změnit stránku s vypsanou rolí v aplikaci tak, aby hlavní CTA Mám zájem vedlo na nějaký takovýhle formulář a přidalo do URL databázové ID aktuálně přihlášeného uživatele. Tím by se to ID předvyplnilo do formuláře, vznikla by provazba na synchronizovanou kopii tabulky User Profiles a všecko by bylo krásné.

Ovšem ta synchronizovaná kopie tabulky User Profiles má odlišná databázová ID 💩 Takže zatímco v původní databázi User Profiles najdeme pod ID recmU8tKzog4merHh můj profil, v té synchronizované kopii žádný záznam s tímhle ID neexistuje. Aby to automaticky fungovalo, museli bychom na té stránce s rolí vědět, jaké je databázové ID přihlášeného uživatele v databázi, kde žije registrační formulář. Není to jaderná fyzika, ale je to pruda a nejspíš to bude vyžadovat vyplnění nějakých extra informací k dotyčné roli.

PS. Zároveň platí, že když budeme vázat registrační info na záznam ze synchronizované kopie User Profiles, tak ta registrační data nebudou automaticky přístupná z opačné strany v tabulce User Profiles v databázi Users. Ale to je prozatím OK.

Bezpečnost

To předávání ID přihlášeného uživatele v URL pochopitelně není moc bezpečné, protože to ID může kdokoliv změnit. Na jednu stranu to bezvadně odpovídá současné situaci, kdy taky můžu formulář vyplnit jménem kohokoliv jiného, ale na druhou stranu nás to může furt někde nemile překvapit. Jednoduchá možnost by byla poslat společně s ID uživatele i nějaký kontrolní součet (vygenerovaný na serveru), který bychom pak ve formuláři zkontrolovali – tedy například bychom poslali něco à la User_ID + SHA1(User_ID + salt + secret) a následně ve formuláři zkontrolovali, že ta heš sedí. Což ale v Airtable neumíme (nemáme vzorec pro SHA1). Můžem podumat.

anezkamll commented 1 month ago

Přemýšlím, jestli se nedostáváme do situace, kdy by bylo jednodušší přemyslet celou architekturu těch databází tak, aby to co nejlépe sloužilo účelu sjednocování dat, než se snažit napasovat řešení na to stávající rozdělení. Co z toho má lepší poměr vloženého úsilí vs dlouhodobého gainu

zoul commented 1 month ago

Já jsem nad tímhle už trochu přemýšlel dřív a k ničemu kloudnému jsem nedošel. Ten model „každý projekt má vlastní databázi“ je v něčem velmi žádoucí, protože si pak lidi můžou hospodařit hodně nezávisle a nevznikají tím úzká hrdla. Na druhou stranu to „nezávislé hospodaření“ samozřejmě generuje ty nežádoucí divergence.

Proto mně tenhle drobný pragmatický posun ve stylu „nechte si svoje registrační formuláře a my vám je akorát zjednodušíme“ přijde docela dobrý. Z našeho pohledu (tedy celkového ekosystému) je za mě zásadní to, abychom už se nikdy lidí znova neptali, jak se jmenujou nebo jaký je jejich mail.

anezkamll commented 1 month ago

Pročítám si to ještě zpětně a nepochopila jsem přesně, jak by fungovalo toto:

jde použít předvyplněné URL, kde toho uživatele vyplníme automaticky a zároveň to pole úplně schováme

Nechápu ten usecase, kdy by to mohlo být dobré, tedy jak se jako někdo, kdo se hlásí na nějakou roli, dostanu k tomu personalizovanému URL. Bylo by to v rámci toho, že jsem přihlášená v app? Že by se u každé role do přihlašování už rovnou propsal můj základní profil?

anezkamll commented 1 month ago

Mám teď call s Marťou a Romčou, přinesu z toho nějaké vstupy, zároveň předám myšlenky z tohohle flow. A v každém případě tu úplně základní myšlenku:

za mě zásadní to, abychom už se nikdy lidí znova neptali, jak se jmenujou nebo jaký je jejich mail.

zoul commented 1 month ago

Je to myšlené přesně tak, jak píšeš – odpověď na roli by vyžadovala přihlášení do app. Takže když klikám na CTA „Mám zájem“, aplikace už by uměla předat do formuláře ID toho přihlášeného uživatele.

anezkamll commented 1 month ago

:information_source: teď ten reg form Nezsik.Digital je v Users bázi, to by pro nás mělo být výrazně jednodušší celé

zoul commented 1 month ago

Pro pilot jo, ale výhledově nechceme, aby nám kvůli tomu všechny registrační tabulky naskákaly do Users. Zejména pokud tam potřebujou vazby na nějaké další projektové tabulky, které by tím pádem chtěli stěhovat do Users. My chceme ta projektová data z Users spíš vystrnadit než naopak :)

anezkamll commented 1 month ago

:hourglass: Zvládneme za měsíc nasadit tu verzi tak, aby to mohly holky použít na další běh ND?

Jak si to představuju v rámci vhodného flow a předschválily jsme si s Marťou a Romčou:

Je to reálné?

zoul commented 1 month ago

Podle mě je to velmi reálné. V detailech se můžem někde šprajcnout, ale neposíláme raketu do vesmíru, určitě můžem dojít k něčemu zhruba v těchto intencích tak, aby to bylo lepší než současný stav.

anezkamll commented 1 month ago

OK, tak tomu dejme prioritu.

anezkamll commented 1 month ago

Doplňuju z dnešního syncu tři varianty reakce na roli nebo event, když jsem přihlášený: Co se stane po kliknutí na tlačítko Mám zájem?

U eventu ještě možnost přihlásit se jako Guest

zoul commented 1 month ago

Jak si představuju MVP

Odpověď jedním klikem

Máme vlastně tři možnosti, jak tu reakci jedním klikem reprezentovat:

  1. Vazba mezi Events/Opportunities a User Profiles, tedy například event má pole registeredUsers, role má pole interestedUsers a uživatelský profil má příslušná pole registeredEvents a registeredOpportunities. Tohle je nejjednodušší z pohledu úvodní administrace – založím event a jdu od toho. Ale bude se s tím hůř pracovat v Airtable? Kdyby s každou reakcí vznikl/zanikl nový záznam, dá se na to snadno pověsit třeba notifikace. Když vznikne/zanikne vazba, tak se s tím bude dělat blbě. (Takhle je to teď implementované pro eventy, viz #760.)
  2. Samostatná triviální tabulka pro každý event nebo roli. Takže třeba založím event Meetup a k němu tabulku Meetup Registration, kde mně budou naskakovat řádky se sloupci User (vazba do User Profiles) a Timestamp (čas přihlášení). Tohle dobře odpovídá tomu, jak s eventy pracujeme dneska, a uživatelé z naší strany si tam můžou snadno psát poznámky nebo zobrazit další relevantní pole z User Profiles. Na druhou stranu to znamená, že pro každý event dostaneme úplně nový sloupec v tabulce User Profiles, což je trochu pakárna a taky to komplikuje agregace („které všechny eventy uživatel navštívil?“, viz https://github.com/cesko-digital/app/issues/807). Navíc bychom museli u eventu nějak dát vědět, kam mají ty odpovědi v Airtable padat. Tahle varianta ovšem přesně odpovídá tomu, jak celá věc funguje při použití custom formuláře, což může být velká výhoda.
  3. Mezitabulka účasti lidí na eventech. Obsahovala by sloupce User a Event a každý řádek by reprezentoval účast daného uživatele na daném eventu. Oproti (1) se to líp automatizuje v Airtable (nová registrace = nový řádek) a člověk si tam může připsat poznámky a podobně, oproti (2) to je příčetnější databázové schéma a zůstává relativně snadná možnost agregace. Editorům ale nemusí docházet, že je to sdílená tabulka pro všechny eventy, a můžou začít měnit schéma a přidávat si třeba vlastní pole, což může být rychle chaos.

Nemám v tomhle úplně jasno, můžem zkusit probrat s uživateli anebo (zatím preferuju) tu reakci jedním klikem zatím neimplementovat a počkat na zkušenosti z provozu.

Technické komplikace

Jak už jsem psal výše, to ID přihlášeného uživatele se bohužel liší podle cílové databáze, ve které žije synchronizovaná tabulka User Profiles. Takže když to CTA tlačítko odkazuje na cílové URL a přidává do něj ID přihlášeného uživatele, musíme nějak zjistit, jaké ID to je. Navíc na straně formuláře už neběží náš kód (který by mohl být nějak chytrý), ale jen Airtable, pro kterou je to čistě mechanická vazba.

Technicky jednoduchá varianta je přidat do popisu eventu nebo role políčko identifikující, ve které databázi žije ten registrační formulář. Tedy třeba single select s variantami Users, App, Nezisk.Digital kurz a podobně. Pak by ten volající kód našel přihlášeného uživatele (třeba podle registračního mailu) v synchronizované kopii User Profiles z dotyčné databáze a poslal do formuláře správné ID. Tohle je dobré v tom, že je to technicky jednoduché a explicitní. Ale vyžaduje to nějakou spolupráci od uživatelů/editorů.

Pak je ještě možný drobný hack. Ono se totiž dá pro jedno pole předvyplnit víc hodnot. Takže bychom mohli najít přihlášeného uživatele ve všech relevantních databázích a poslat do formuláře seznam všech IDs ze všech databází, přičemž Airtable si z toho vybere to správné ID a ostatní ignoruje (zkoušel jsem). Nevýhoda je v tom, že spálíme víc API dotazů, není to evidentní pro uživatele (například to nebude fungovat v nových databázích, které nejsou explicitně zmíněné v kódu) a to pole User v registračním formuláři musí být (nelogicky) nastavené, aby akceptovalo víc lidí. Zase to ale funguje automaticky.

Mírně preferuju udělat to tou první, explicitní variantou. Dá se sdružit s některým z těch checkboxů zmíněných v předchozí sekci, například místo prefillUser může být místo checkboxu single select s variantami Předvyplnit ID uživatele (App), Předvyplnit ID uživatele (Nezisk.Digital) a podobně. To by nešlo minout, ale zase to nemusí být moc srozumitelné pro editory.

anezkamll commented 4 weeks ago

Od Romči: