This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-10-22
Channels
- # announcements (21)
- # aws (7)
- # beginners (105)
- # berlin (1)
- # calva (14)
- # cider (20)
- # clj-kondo (62)
- # cljdoc (7)
- # cljsrn (1)
- # clojure (206)
- # clojure-dev (2)
- # clojure-europe (11)
- # clojure-france (2)
- # clojure-italy (2)
- # clojure-nl (1)
- # clojure-uk (34)
- # clojured (1)
- # clojurescript (52)
- # copenhagen-clojurians (2)
- # core-async (1)
- # cryogen (3)
- # cursive (36)
- # data-science (27)
- # datomic (48)
- # emacs (1)
- # events (1)
- # fulcro (27)
- # hoplon (51)
- # jobs-discuss (1)
- # leiningen (1)
- # nrepl (2)
- # off-topic (52)
- # pathom (43)
- # quil (10)
- # re-frame (11)
- # reitit (28)
- # remote-jobs (2)
- # shadow-cljs (36)
- # sql (12)
- # tools-deps (7)
- # vim (32)
- # xtdb (17)
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?@henrik hello, one question, who is performing the actual search, the graphql API or something you are writing on pathom?
It can basically be considered external, like your Youtube and SpaceX examples from the video.
because, in this case, you may want to do that directly, make the components queries in a way that already aligns with them
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:
(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]}])
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
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?the params part you need to fill during hte load!
trigger (so you use the search-query
fn as described before)
this is the example to run the sub-query internally:
(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
...))
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
)
makes sense?
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.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 🙂
what about this?
(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)))
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?
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.
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.
@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
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
there is another way to trigger that load, maybe this:
(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}}]}]}}}))
(I'm not testing any of this, so some details may need adjust)
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"}}]}]}})]
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.(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)
nice! I'll be glad to have a beer with you! do you have plans to visit here soon?
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.
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?
@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)
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.
no built-in helper, but you can make your own 🙂
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