Fork me on GitHub
#fulcro
<
2019-10-25
>
henrik13:10:59

Not OK (destructured, but not in query):

(defsc Filters
  [this {:zd.ArticlesSelection/keys [articles_sel_top_publisher_name_distribution]}]
  {:query [{'(:zd.ArticlesSelection/articles_sel_top_publisher_name_distribution
              {:n_results 10})
            (get-query FilterItem)}]}
OK (returns the proper data as expected):
(defsc Filters
  [this props]
  {:query [{'(:zd.ArticlesSelection/articles_sel_top_publisher_name_distribution
              {:n_results 10})
            (get-query FilterItem)}]}
  (let [{:zd.ArticlesSelection/keys [articles_sel_top_publisher_name_distribution]} props]
Is this due to passing parameters to the queries?

wilkerlucio14:10:37

@henrik yes, and there are two things you can do about it, one is to convert :query to a fn, this way you disable validation, or just remove the params, adding them back on the load! trigger

đź‘Ť 4
henrik14:10:32

To show you the whole query:

[({:zd/articles_selection_2
   [({:zd.ArticlesSelection/articles_sel_top_subject_categories_distribution
      [:zd.FrequencyCell/frequency_cell_key
       :zd.FrequencyCell/frequency_cell_n]}
     {:n_results 10})
    ({:zd.ArticlesSelection/articles_sel_top_journals_distributions
      [:zd.JournalFrequencyCell/frequency_cell_n
       {:zd.JournalFrequencyCell/journal_fc_journal
        [:zd.Journal/journal_issns :zd.Journal/journal_title]}]}
     {:n_results 10})
    ({:zd.ArticlesSelection/articles_sel_top_crossref_article_types_distribution
      [:zd.FrequencyCell/frequency_cell_key
       :zd.FrequencyCell/frequency_cell_n]}
     {:n_results 10})
    ({:zd.ArticlesSelection/articles_sel_top_publisher_name_distribution
      [:zd.FrequencyCell/frequency_cell_key
       :zd.FrequencyCell/frequency_cell_n]}
     {:n_results 10})]}
  {:filter_expr
   {:should [{:must [{:terminal_exprs {:matches_text "water"}}]}]}})]

henrik14:10:28

I.e., the :zd/articles_selection_2 needs the filter_expr, then each subclause needs :n_results.

henrik14:10:38

How to I wrangle load! to target each subclause with the params? This would be ideal, I’d rather not have :n_results in the query, as it doesn’t belong there. The query is static, but :n_results is a dynamic value.

wilkerlucio14:10:01

when I need something like this I usually isolate the query in a separated fn that can take arguments, and that fn builds the query with the params, them you use that fn as the query with some blank param (to don't fill anything), and them use it with :update-query on the load, calling giving the params, eg:

henrik14:10:12

Aha, compose the query with functions. I’ll have a whack at it, thanks! (Sorry, I’ll await your example 🙂)

wilkerlucio14:10:08

no worries, I think this is it:

wilkerlucio14:10:09

(defn some-component-query [n-results]
  [{'(:zd/articles_selection_2
       {:filter_expr
        {:should [{:must [{:terminal_exprs {:matches_text "water"}}]}]}})
    [{(cond-> :zd.ArticlesSelection/articles_sel_top_subject_categories_distribution
        n-results (p/update-attribute-param assoc :n_results n-results))
      [:zd.FrequencyCell/frequency_cell_key
       :zd.FrequencyCell/frequency_cell_n]}
     {(cond-> :zd.ArticlesSelection/articles_sel_top_journals_distributions
        n-results (p/update-attribute-param assoc :n_results n-results))
      [:zd.JournalFrequencyCell/frequency_cell_n
       {:zd.JournalFrequencyCell/journal_fc_journal
        [:zd.Journal/journal_issns :zd.Journal/journal_title]}]}
     {(cond-> :zd.ArticlesSelection/articles_sel_top_crossref_article_types_distribution
        n-results (p/update-attribute-param assoc :n_results n-results))
      [:zd.FrequencyCell/frequency_cell_key
       :zd.FrequencyCell/frequency_cell_n]}
     {(cond-> :zd.ArticlesSelection/articles_sel_top_publisher_name_distribution
        n-results (p/update-attribute-param assoc :n_results n-results))
      :zd.ArticlesSelection/articles_sel_top_publisher_name_distribution
      [:zd.FrequencyCell/frequency_cell_key
       :zd.FrequencyCell/frequency_cell_n]}]}])

(defsc Filters
  [this {:zd.ArticlesSelection/keys [articles_sel_top_publisher_name_distribution]}]
  {:query (some-component-query nil)})

; later on:

(df/load! component ident Filters {:update-query #(with-meta (some-component-query) {:component Filters})})

wilkerlucio14:10:58

one tip, I see that when you are doing joins with params, you are putting the list around the join, as: ({:join-key [:sub-query]} {:params "here"})

wilkerlucio14:10:19

this is totally valid, but there is another option that I think is more readable, that is putting the params around the join key:

wilkerlucio14:10:30

{(:join-key {:params "here"}) [:sub-query]}

wilkerlucio14:10:33

both are valid, but the later gets the params closer to the keys that will respond for it

henrik14:10:10

Ah, I see. That’s basically what I picked up from the EQL docs, I guess I didn’t spot the alternative syntax.

henrik14:10:04

Ah, sorry, no, I think I have been using the latter syntax. The “params at the end” version seems to be what load! does when it sends it to the server.

henrik14:10:55

So, basically here I’m declaring the entire query in the top-level component, rather than using get-query and composing it from the subcomponent querues. I guess that’s a tradeoff when you need to deal with parameterized subqueries?

henrik14:10:58

Or could I perhaps run get-query in the function in order to get rid of the duplication?

henrik15:10:40

To answer my own question, something like

(defn filters-query
  ([]
   (filters-query 10))
  ([n-results]
   [(p/update-attribute-param (get-query FilterGroupCategories)
       assoc :n_results n-results)
    (p/update-attribute-param (get-query FilterGroupPublisher)
        assoc :n_results n-results)
    (p/update-attribute-param (get-query FilterGroupArticleType)
        assoc :n_results n-results)
    (p/update-attribute-param (get-query FilterGroupJournals)
        assoc :n_results n-results)]))

henrik15:10:09

Seems to work fine, with (fn [] (filters-query)) as the :query in Filters

henrik15:10:32

(With

:query [:filter-bar/id
        {:zd/articles_selection_2 (get-query Filters)}]
in the parent component of Filters)

tony.kay15:10:56

My approach is to use a separate load for each of those joins, instead of a load higher up. That way each load can specify parameters without cluttering the UI component with it. That’s simple enough to then wrap into your own function of (defn load-stuff [this] (df/load ...) (df/load ...) ...).

henrik15:10:36

This hits a GraphQL API in the end, so I guess that is up to that API. In the end, though, I need two levels of params regardless:

[{(:zd/articles_selection_2
   …params)
  [(:zd.ArticlesSelection/articles_sel_top_subject_categories_distribution
    …params)]}]]

henrik15:10:17

The second one always happens in context of the first one.

henrik15:10:39

Yeah, makes it awkward to compose with it. I think it’s details from the underlying ElasticSearch interface slipping through.

tony.kay16:10:13

I never use it this way, but dynamic queries can also be a way to mess with that…i.e. set-query!

tony.kay16:10:10

there could be some bugs when using that way, since I think no one is currently doing it, but I think it should work

henrik16:10:38

Yeah, it’s starting to feel a bit awkward. I’m contemplating whether I should wrap the GraphQL API in higher-level resolvers on the backend, rather than expose it directly. The benefit of having it flow through is that changes in the GraphQL API automatically flow through to the frontend, and are directly visible in the Fulcro Inspector without me having to enumerate them.

henrik16:10:02

Then I get into the business of basically reproducing the GraphQL API as resolvers, and become responsible making sure that the resolvers enumerate the possibilities present in the GraphQL API. Either way is not excellent.

henrik16:10:43

In a way, it’s nice to “force” compliance in the components by aligning them to the API. Takes care of impedance mismatch. On the other hand, there are complexities in it that are hard to express in the components.

tony.kay15:10:26

loads issued this way are subject to combining at the network layer into one request, though it does require that you potentially push in “more context” for the resolver to use.

mruzekw19:10:33

Hi there I just used the lein fulcro template to spin up a new project, but I noticed there isn’t a default index.html file. Is this intended?

mruzekw20:10:24

I’m also getting the following when I add an index.html file and try to load the frontend:

main.js:2176 failed to load app.application.js ReferenceError: fulcro_network_csrf_token is not defined

mruzekw20:10:47

(ns app.application
  (:require [com.fulcrologic.fulcro.networking.http-remote :as net]
            [com.fulcrologic.fulcro.application :as app]
            [com.fulcrologic.fulcro.components :as comp]))

(def secured-request-middleware
  ;; The CSRF token is embedded via server_components/html.clj
  (->
    (net/wrap-csrf-token (or js/fulcro_network_csrf_token "TOKEN-NOT-IN-HTML!"))
    (net/wrap-fulcro-request)))

tony.kay21:10:08

it is intended…don’t load from shadow dev, run server

tony.kay21:10:24

CSRF has to be embedded in page, so server generates index

mruzekw21:10:56

Makes more sense now