Issue #1

Gathererin laajennettu haku näyttää olevan rikki, ja se on ollut rikki jo jonkin aikaa (veikkaisin ainakin puoli vuotta). En löytänyt Googlen kautta ketään asiasta puhuvaa. Tämä sai minut miettimään, teinkö jotain väärin, tai eikö kukaan muu ole huomannut, tai en vain etsinyt tarpeeksi hyvin löytääkseni aiempia keskusteluja. Päätin tutkia asiaa.

Kaksi pääongelmaa Gathererissa nähdäkseni:

  1. Tee tarkennettu haku kahdella tai useammalla kriteerillä (esim. haku kaikille sinisille, mustille ja punaisille korteille. Napsauta sitten x yhden näistä kriteereistä vieressä hakutulossivulla. Kriteeri katoaa luettelosta, mutta korttitulokset eivät päivity.
  2. Tee tarkennettu haku yhdellä tai useammalla kriteerillä (esim. Väri: sisältää sinistä). Napsauta sitten ”Tarkenna hakua”. Edelliset parametrit eivät näy tarkennetun haun kriteeriruudussa.

Muistaakseni olen aiemmin törmännyt kolmanteen ongelmaan, joka koski kriteerien täyttämistä laajennetun haun sivulla, mutta nyt en muista, mikä se oli.

Olen testannut tätä useilla selaimilla (Google Chrome, Mozilla Firefox ja Microsoft Edge). Olen testannut tätä useilla laitteilla (kannettava tietokone, kaksi pöytäkonetta ja puhelin). Olen testannut tätä useilla käyttöjärjestelmillä (Windows 10, Ubuntu 16.04, Ubuntu 18.04, Android 8, Android 9).

Sikäli kuin pystyn sanomaan katsomalla vain asiakaspuolen koodia, uskon, että tämä on ongelma siinä, miten Gatherer-käyttöliittymä käsittelee palvelimen vastauksen (ja mahdollisesti ongelma myös siinä, miten palvelin vastaa).

Kohdassa AdvancedSearch.js::AdvSearchConditionCallback, rivi 872, näemme, miten Gatherer UI odottaa käsittelevänsä palvelimen vastausta:

var results = eval("(" + transport.responseText + ")");

Palvelimen tässä lähettämä vastaus on JSON. Jos esimerkiksi teen haun kaikille sinisille, mustille ja punaisille korteille ja napsautan sitten x poistaakseni kriteerin ”DOES contain Red” (sisältää punaista), saamani vastaus on:

{ "LCID": 0, "CommandName": null, "CommandListPath": null, "SearchTerms": null, "SubQueryDelimiter": "Union", "SearchTermDelimiter": "And", "EscapeInputDelimiters": true, "EncodedSerializedQuery": "YwBvAGwAbwByAD0AKwBbAFUAXQArAFsAQgBdAA==", "ExtraSafeKeys": , "IncludeSpecial": false, "Filters": { "color": { "Identifier": "fe329dcb-d64a-4939-bfd6-7af5c102d655", "LogicalOperator": "Intersect", "From": "color", "Terms": ", "LogicalOperator": "And", "State": "+" }, { "Identifier": "b622e49d-87de-439f-ac75-0e99ffee07c6", "FormatString": "color", "Value": "", "LogicalOperator": "And", "State": "+" } ], "State": null, "FormatString": "color", "Value": ", "LogicalOperator": "And", "State": "+" }, { "Identifier": "b622e49d-87de-439f-ac75-0e99ffee07c6", "FormatString": "color", "Value": "", "LogicalOperator": "And", "State": "+" } ] } }, "State": "color=++"} 

Huomaa, että JSON ei näytä sisältävän mitään tietoa siitä, mitkä kortit pitäisi näyttää, enkä näe mitään koodia kohdassa AdvancedSearch.js, joka käsittelisi korttien luetteloa.

Kutsuminen eval("(" + transport.responseText + ")") tähän toimii siis hyvin ja muuntaa JSON-merkkijonon JSON-olioksi. AdvancedSearch.js käyttää sitten tuota JSON-objektia päivittääkseen hakuehtoruudun näytön. Se ei kuitenkaan päivitä nykyistä URL-osoitetta (esimerkiksi siirtymällä ”?action=advanced&color=+++”-osoitteesta ”?action=advanced&color=++”-osoitteeseen).

Nyt Prototype.js on konfiguroitu siten, että se yrittää myös arvioida vastauksen. Mutta Prototype.js:ää ei ole kirjoitettu olettamaan, että vastaus on JSON. Niinpä kohdassa Prototype.js::respondToReadyState (rivi 1498) Prototype.js yrittää eval tätä JSON-merkkijonoa JavaScriptinä (kohdassa evalResponse (rivi 1533)), ja koska JSON-merkkijonoa ei ole suljettu sulkuihin, eval epäonnistuu:

SyntaxError: Unexpected token : at klass.evalResponse (Prototype.js:1533) at klass.respondToReadyState (Prototype.js:1501) at klass.onStateChange (Prototype.js:1436) at XMLHttpRequest.<anonymous> (Prototype.js:291)

On kuitenkin syytä huomata, että vaikka muuttaisit Prototype.js niin, että merkkijono suljetaan sulkuihin, se vain muuntaa merkkijonon JSON-objektiksi eikä tee sillä mitään. Myös jos muokkaat AdvancedSearch.js niin, että määrität Prototype.js:n olemaan arvioimatta vastausta, se vain jatkaa ja lopputulos on sama.

Sikäli kuin voin sanoa, on olemassa kaksi mahdollista ongelmaa:

  1. Gatherer UI:n (ja erityisesti AdvancedSearch.js:n) on tarkoitus muuttaa nykyistä URL-osoitetta ja navigoida uusiin hakutuloksiin.
  2. Gatherer UI:n on tarkoitus kysyä palautettua URL-osoitetta taustalla (tai ehkä palvelimen on tarkoitus sisällyttää kaikki kortit vastaukseen) ja päivittää sitten korttiluettelo dynaamisesti.

Jos näin on #1, niin kaikki logiikka hakukriteeriluettelon dynaamiseksi päivittämiseksi olisi turhaa; heti kun selain siirtyy uuteen URL-osoitteeseen, se lataa kokonaan uuden sivun, jossa hakukriteeriluettelo on jo täytetty ja uudet kortit näytetään. Tämä tekisi myös ensimmäisestä kyselystä (ja palvelimen vastauksesta) turhan, koska käyttöliittymällä on jo palvelimen palauttamat tiedot, ja se voisi vain siirtyä suoraan kyseiseen URL-osoitteeseen.

Jos #2 on kyseessä, uskon, että palvelimella on todennäköisesti ongelma, jossa sen pitäisi palauttaa tuloksena oleva korttiluettelo ylipäätään. Jos näin ei ole, taas tämä ensimmäinen kysely vaikuttaa turhalta, koska vastaus ei sisällä mitään sellaista, mitä käyttöliittymällä ei jo ollut. Joka tapauksessa käyttöliittymässä on ongelma, jossa sillä ei (ilmeisesti) ole mitään koodia korttiluettelon dynaamista päivittämistä varten.

Tämä on vielä mutkikkaampi, koska se ei aina tapahdu. Joskus kun klikkaa ”Tarkenna hakua”, se toimii oikein. Useimmiten se ei kuitenkaan toimi. Niille, jotka eivät ole ohjelmistokehitysalalla, tämä on todennäköisesti niin sanottu ”Race Condition”: kaksi asiaa tapahtuu samanaikaisesti, ja lopputulos riippuu siitä, missä järjestyksessä ne päättyvät. (Se on vähän kuin Magicin pino, jossa kahden efektin järjestys voi johtaa hyvin erilaisiin tuloksiin.)

Tässä on mielestäni ongelman ydin.

Käytetään taas yllä olevaa esimerkkiämme, jossa etsitään kaikki siniset, mustat ja punaiset kortit. Jos tarkastat ”Tarkenna hakua” -painikkeen, näet tämän:

<a href="/Pages/Advanced.aspx?action=advanced&amp;color=+++" class="refineSearchLink">Refine Search</a>

Tämän pitäisi siis olla melko suoraviivaista. Siirry takaisin tarkennetun haun sivulle samoilla hakuehdoilla. Ongelmana on se, mitä tässä oikeastaan tapahtuu. Jos siirryt linkitetylle sivulle, napsautitpa sitten ”Tarkenna hakua” -painiketta tai tätä tässä viestissä lisättyä linkkiä tai kopioit ja liität URL-osoitteen osoiteriville, tämä ei (yleensä) toimi; sivu palaa takaisin ilman hakuehtoja. Minusta on mielenkiintoista, että ongelma näyttää taas olevan joko palvelimessa tai käyttöliittymässä (mahdollisesti molemmissa).

Jos menet selaimen kehittäjätyökalujen Verkko-välilehdelle ja siirryt kyseiselle sivulle, ensimmäinen aktiviteettiluettelossa näkyvä merkintä on pyyntö ”Pages/Advanced.aspx?action=advanced&color=+++”. Jos tarkastelet vastausta napsauttamalla sitä, näet sivun koko HTML:n sellaisena kuin se näkyi, kun se palautettiin ensimmäisen kerran. Siellä, missä hakukriteerit pitäisi täyttää, on

<div class="filters" ><span style="display: none;">None yet.</span>

Sen alla on kutsu komentosarjalle, joka näyttää olevan tarkoitettu täyttämään kyseinen luettelo:

<script type="text/javascript">RetrieveCurrentSearchConditions();</script>

Katsomalla RetrieveCurrentSearchConditions kohdassa AdvancedSearch.js, se yrittää tehdä Ajax-pyynnön seuraavilla parametreilla (rivi 406):

parameters: { cacheBust: new Date().getTime(), encodedSerializedPrevious: $(ClientIDs.encodedSerializedParameters).value }

Ongelmana on se, että jos katkaiset pisteen tältä riviltä (tai vain luet palvelimen raa’an HTML-vastauksen), elementillä, johon encodedSerializedParameters viittaa, ei ole arvoa tässä vastauksessa. Teemme siis tämän pyynnön encodedSerializedParameters: "":llä, ja odotetusti saamme takaisin tyhjät hakusuodattimet. Miksi näin tapahtuu? No, jos palaat takaisin hakutuloksiin ja tarkastelet lähdettä, huomaat, että encodedSerializedParameters:llä on arvo: YwBvAGwAbwByAD0AKwBbAFUAXQArAFsAQgBdACsAWwBSAF0A (base 64 -koodaus sanalle ”color=+++”).

Näyttää siis siltä, että tämä koodi on kirjoitettu odottaen, että sitä kutsutaan, kun se on vielä hakutulossivulla, mutta sitä kutsutaan vasta, kun selain on jo siirtynyt takaisin laajennetun haun sivulle.

Näyttäisi siis siltä, että palvelimen pitäisi vastata siten, että encodedSerializedParameters-elementin value-kenttä on jo täytetty, mutta näin ei ole. Tai sitten käyttöliittymän pitäisi jotenkin kerätä tuo arvo ennen navigointia pois ja siirtää se navigoinnin kautta käytettäväksi seuraavalla sivulla. Tai mahdollisesti käyttöliittymässä pitäisi olla toiminto, joka poimii hakutiedot URL-osoitteesta (jossa kyselytiedot on täytetty) ja lisää ne hakukriteerikenttään.

Tiedän, että kaikki tämä toimi jossain vaiheessa, joten minulla ei ole aavistustakaan, miten näin rikkinäinen koodi julkaistiin ja miten se on pysynyt rikkinäisenä näin pitkään. Juuri ennen tämän lähettämistä päätin tehdä haun nimenomaan r/magicTCG:ssä, ja löysin viestejä, jotka saattavat viitata näihin samoihin ongelmiin (esim. Gathererin ’Advanced Search’ hakukriteerit nollautuvat puoliksi satunnaisesti. Help?, Trouble with Gatherer Advanced Search), ja olin yllättynyt nähdessäni, että ne ovat niin vanhoja (1 vuosi, 3 vuotta vastaavasti edellä mainittujen esimerkkien osalta). Jos ne siis viittaavat samoihin ongelmiin, ne ovat olleet saatavilla jo pitkään.

Kysymys kuuluu siis, onko Wizards of the Coast vain hylännyt Gathererin? (Tai ainakin sen laajennetun haun?)

Leave a Reply