Fork me on GitHub
#pathom
<
2019-10-22
>
henrik13:10:31

Now that I have a connection to the GraphQL API up and running, I’m trying to figure out how to write a load! for the SearchResults component below.

(defsc Article
  [this {:zd.Article/keys [article_doi article_title]}]
  {:ident :zd.Article/article_doi
   :query [:zd.Article/article_doi
		   :zd.Article/article_title]}
  (div article_title))
(defsc SearchResults
  [this {:search-results/keys [id articles]}]
  {:ident :search-results/id
   :query [:search-results/id
		   {:search-results/articles (get-query Article)}]}
  (div
	(map article articles)))
Given that search queries look like this,
[{(:zd/search_articles_advanced_2
   {:n_results 1
	:filter_expr {:should [{:must [{:terminal_exprs {:matches_text "Water"}}]}]}})
  [:zd.Article/article_title
   :zd.Article/article_doi]}] 
And returns stuff like this:
{:zd/search_articles_advanced_2
 [{:zd.Article/article_title "Drinking Water",
   :zd.Article/article_doi "10.1177/1084822313481784"}]}
I suppose I want to write a resolver that is something like this (the only data of interest to the search are params, so I’m guessing it becomes global),
{::pc/output [{:search-results/articles […]}]}
` With being some mysterious join into the world of the GraphQL API, and specifically the search_articles_advanced_2 entry point above (with the filter_expr being supplied as a param, I guess) so that I can ultimately do
(df/load! this [:search-results/id some-id] SearchResults
  {:target [:search-results/id some-id :articles]
   :filter-expr {:should {:must ["Water"]}}}
I’m not sure how to supply this glue—how would one do it?

wilkerlucio14:10:12

@henrik hello, one question, who is performing the actual search, the graphql API or something you are writing on pathom?

henrik14:10:03

Hey, the GraphQL endpoint is performing the search.

henrik14:10:41

It can basically be considered external, like your Youtube and SpaceX examples from the video.

wilkerlucio14:10:49

because, in this case, you may want to do that directly, make the components queries in a way that already aligns with them

wilkerlucio14:10:40

one thing that's not obvious is how to set the parameters, IME I found that writing a fn that returns the query with the params is most straitforward way to do it, something like:

wilkerlucio14:10:09

(defn search-query [text-match]
 [{(:zd/search_articles_advanced_2
     {:n_results   1
      :filter_expr {:should [{:must [{:terminal_exprs {:matches_text text-match}}]}]}})
   [:zd.Article/article_title
    :zd.Article/article_doi]}])

wilkerlucio14:10:28

if you really want to rename the result somehow (instead of using the GraphQL names directly on your components), you can trigger a sub-query in a resolver, let me get you an example

👍 4
henrik14:10:07

I’m not sure how to write the components to line up with the GraphQL API. Is,

[{(:zd/search_articles_advanced_2
   {:n_results 1
    :filter_expr {:should [{:must [{:terminal_exprs {:matches_text "Water"}}]}]}})
  [:zd.Article/article_title
   :zd.Article/article_doi]}] 
even an acceptable query for a component in Fulcro, especially given that n_results and filter_expr are dynamic?

wilkerlucio14:10:14

the params part you need to fill during hte load! trigger (so you use the search-query fn as described before)

wilkerlucio14:10:26

this is the example to run the sub-query internally:

wilkerlucio14:10:27

(pc/defresolver search-articles [env _]
  {::pc/output [{:search-results/articles 
                 […]}]
   ; this ::pc/params is more a documentation thing, doens't affect any runtime properties
   ::pc/params [:my-system/search-query]}
  (let [search-text (-> env :ast :params :my-system/search-query)
        ; if you are using async or parallel parsers, p/entity will return a core.async channel
        ; and you must wait for it
        search-result (p/entity env [{(:zd/search_articles_advanced_2
                                        {:n_results   1
                                         :filter_expr {:should [{:must [{:terminal_exprs {:matches_text search-text}}]}]}})
                                      [:zd.Article/article_title
                                       :zd.Article/article_doi]}])]
    ; write code to re-format search-results in whatever you want
    ...))

wilkerlucio14:10:10

to change the query, on the load! trigger you can use the property :update-query, this way you can replace the query entirely (there is where you would call search-query)

henrik14:10:45

I’m slowly wrapping my head around it. The sub-query solution seems to assume that

[:zd.Article/article_title
 :zd.Article/article_doi]
Are hard-coded, whereas I would very much like them to flow from the fact that they were selected by the Fulcro component.

wilkerlucio14:10:37

yeah, considering that, I would suggest you just use the final GraphQL names on the component, I can do a quick write on how I imagine it, once sec 🙂

🙏 4
wilkerlucio14:10:56

what about this?

wilkerlucio14:10:57

(defsc Article
  [this {:zd.Article/keys [article_doi article_title]}]
  {:ident :zd.Article/article_doi
   :query [:zd.Article/article_doi
           :zd.Article/article_title]}
  (div article_title))

(defn load-search-results [component query-text]
  (df/load-field component :zd/search_articles_advanced_2
    {:params {:n_results   1
              :filter_expr {:should [{:must [{:terminal_exprs {:matches_text query-text}}]}]}}}))

(defsc SearchResults
  [this {:search-results/keys [id articles]}]
  {:ident :search-results/id
   :query [:search-results/id
           {:zd/search_articles_advanced_2 (get-query Article)}]}
  (div
    (dom/button {:onClick #(load-search-results this "search")} "Search!")
    (mapv article articles)))

henrik14:10:36

Processing… Please wait.

henrik14:10:06

Seems far better by the look of it.

henrik14:10:21

Regarding load-field, the search results component won’t own the search function. It must come from a parent of both the results component and the search field/input component. How would you deal with this? Extract it from the component registry?

henrik14:10:53

Other than that conundrum, I like this solution. The component query very explicitly shows directly how it depends on the search API, which is nice. The less impedance mismatch, the better.

henrik14:10:06

I guess that a solution to the above is to just store the results in the top-level component rather than in the sub-component of SearchResults.

wilkerlucio14:10:35

@henrik didn't get the load-field part, the data will live inside of the SearchResults ident in this case, would be good if you have some id for the search results component, that can be a random generated uuid

wilkerlucio14:10:33

its ok for pathom to do that query from inside of an ident, pathom should generate the appropriated GraphQL query in this case, even so it not top level

wilkerlucio14:10:43

there is another way to trigger that load, maybe this:

wilkerlucio14:10:46

(defn load-search-results [component query-text]
  (df/load component :zd/search_articles_advanced_2
    {:target (conj (fp/get-ident component) :zd/search_articles_advanced_2)
     :params {:n_results   1
              :filter_expr {:should [{:must [{:terminal_exprs {:matches_text query-text}}]}]}}}))

wilkerlucio14:10:04

(I'm not testing any of this, so some details may need adjust)

henrik15:10:36

So, looking at the query that load! sends to the Pathom parser, it ends up being:

[({[:search-results/id :an-id]
   [:search-results/id
    {:zd/search_articles_advanced_2
     [:zd.Article/article_doi :zd.Article/article_title]}]}
  {:n_results 1,
   :filter_expr
   {:should [{:must [{:terminal_exprs {:matches_text "hello"}}]}]}})]

henrik15:10:04

(with some dummy data, just to see what it says)

henrik15:10:06

Sorry, I may have done something stupid.

henrik15:10:32

Ah, yes, here’s the missing ticket:

(df/load app :zd/search_articles_advanced_2 Article …)
Give it Article, or otherwise it doesn’t select any fields in the query to GraphQL.

henrik15:10:03

Well, that certainly goes out and loads some data! Awesome!

henrik15:10:45

(Since I haven’t actually wired the components into the UI yet, I wanted to try it out on app before assuming that I’ve understood more than I have)

henrik16:10:12

I’m going to have to buy you a beer next time I’m in Brazil.

😄 4
🍻 4
🍺 4
henrik18:10:16

You think I’m joking, but my wife is a Paulista 😁

wilkerlucio18:10:32

nice! I'll be glad to have a beer with you! do you have plans to visit here soon?

henrik09:10:10

We were going to go at the end of the year, but a surprise happened and all her family is coming to Stockholm instead! So preliminarily, early next year.

twicebaked19:10:30

Silly question, is there any way to rename a resolver with a single input defined with defresolver or is it always auto-named? If so, what's the best practice? I of course can rename the input which changes the resolver name, but it's a bit odd destructuring a param that's really just there to change the name. Maybe using aliases/alias resolvers is possible?

wilkerlucio19:10:11

@twicebaked is worth noticing that those helpers just return maps, so the easiest way to rename is doing: (assoc (pc/single-attr-resolver ...) ::pc/sym 'my/new-name)

twicebaked19:10:06

OK, yeah I figured that manipulating pc/sym waas what would do it. Just wondered if there was a simpler way maybe via the macro itself but didn't see anything obvious in the source right away. Thanks.

wilkerlucio20:10:55

no built-in helper, but you can make your own 🙂

wilkerlucio20:10:33

this project is serious about keeping things as stable as possible (include keywords), so you can count that ::pc/sym is not something that will change