Fork me on GitHub
#fulcro
<
2021-03-29
>
tony.kay02:03:11

Some more experiments for upcoming Fulcro 3.5 with respect to raw React usage, and just using hooks and lightweight components in Fulcro in general. The SHA 34789fb993fdfd121d3ffa3551b106b7ae374f8b has the support, and the demo cards. Here’s an overview: • Refined use-tree so that it is easier to use with I/O and such. See https://github.com/fulcrologic/fulcro/blob/feature/fulcro-3.5/src/workspaces/com/fulcrologic/fulcro/cards/composition4_cards.cljs#L190 • Made a helper that generates a form-state compatible component from nearly no code: https://github.com/fulcrologic/fulcro/blob/feature/fulcro-3.5/src/workspaces/com/fulcrologic/fulcro/cards/composition4_cards.cljs#L154 • Made a use-uism that lets a state machine set up actors, and returns the current props for all actors. I think this is the most useful way to do this one, but it sort of implies the UISM will be a parent-like thing to all actors in UI: https://github.com/fulcrologic/fulcro/blob/feature/fulcro-3.5/src/workspaces/com/fulcrologic/fulcro/cards/composition4_cards.cljs#L321

❤️ 27
Henry16:03:21

Thanks so much Tony for sharing your innovation with us through Fulcro. The experiments are cool, can't wait to try out the demos. Since fulcro-3.4.21-SNAPSHOT does not contain alpha.raw-components3.cljs, I have cloned the branch feature/fulcro-3.5 from the Fulcro repository. However, after spending hours trying to run the workspaces cards, I still haven't figured out the workflow to launch the workspaces (like the way we do it with Fulcro Template). It would be appreciated if anyone could give me a pointer on what is the best way to run these demo cards.

tony.kay16:03:11

Shadow, build workspaces, then you have to use /workspaces.html on the non test url

Henry02:03:08

Many thanks @U0CKQ19AQ for your help! The cards are working perfectly now.

tony.kay02:03:20

The sc and formsc functions are actually a pretty cool idea for super lightweight components (that have no render, only compatible with component-options)…thus you can get query/ident/initial state AND component registry (if you need it) with almost nothing more than a raw EQL query.

😻 3
thosmos07:03:18

I’m attempting to add a RAD report and form to an existing fulcro app. I have a basic table and editor working. Now I’m needing to add a “hidden” query-param to filter the report’s query, but the only way I’m seeing to add query-params is via ro/controls. Am I missing it? Maybe something like fo/triggers but for reports …? If I add my own custom key to the defsc-report map, where’s the best place to process that?

tony.kay11:03:34

Controls, just don't add it to the layout.

thosmos16:03:10

ok, I have added a control with :type :stringbut how do I set the value? This doesn’t work

ro/controls {:agency {:type :string :value "SYRCL"}}

thosmos17:03:32

this worked:

:componentDidMount (fn [this]
                        (control/set-parameter! this :agency "SYRCL"))

Jakub Holý (HolyJak)18:03:02

You can set it when routing to the report, the fn takes a map of extra params, look into the demo

👍 3
Timofey Sitnikov11:03:07

Good morning Clojurians, I am trying to use https://material-ui.com/ in my Fulcro project. After some research, I found the following approach:

(ns app.ui.material-ui
  (:require
    ["@material-ui/core" :as mui]
    ["@material-ui/core/Button" :default material-button]
    [com.fulcrologic.fulcro.algorithms.react-interop :as interop]))

(def button        (interop/react-factory material-button))
(def css-base-line (interop/react-factory mui/CssBaseline))
(def app-bar       (interop/react-factory mui/AppBar))
Looking at the https://github.com/MrEbbinghaus/fulcro-material-ui-wrapper, the files are not .cljs but they are .cljc. Why are they .cljc? Seems like this is only for the cljs side of things.

Björn Ebbinghaus11:03:52

Yes. It only works for CLJS. BUT. With fulcro itself, you can use components on the server-side as well. (for SSR or initial-state, for example). I figured, it would be nice to provide a cljc so that the user don't have to add reader conditionals in their code, if they wish to use their components on the server-side.

3
❤️ 3
Timofey Sitnikov11:03:27

OK, that makes sense.

Timofey Sitnikov11:03:25

@U4VT24ZM3, I do have on more question, how do you turn this example:

<Typography variant="h6" color="inherit" noWrap className={classes.toolbarTitle} >
into CLJS. I tried this:
(material-ui.data-display/typography {:variant "h6" :color "inherit" :className "{classes.toolbarTitle}"}
But I am not sure how to include the noWrap since it does not have a pair.

Björn Ebbinghaus11:03:56

{:noWrap true 
 :className "just-a-string"}

Björn Ebbinghaus11:03:10

:className ["foo" "bar"] works as well.

3
cjsauer11:03:17

{classes.toolbarTitle} is JSX syntax for string interpolation. The example you’re looking at must have an object called classes with a property toolbarTitle in scope somewhere.

Timofey Sitnikov12:03:38

@U6GFE9HS7, I believe the classes are generated by material UI, but I am not sure.

Timofey Sitnikov12:03:41

@U4VT24ZM3 I added that sample to your REAME.md, see if it makes sense to you. It may make it easier for someone like myself to get started. Also, I recommend removing the Warning statement from readme, warning reads to me that the repo can harm something, but it is pretty benign, clean, and nice repo, nothing to warn about.

Björn Ebbinghaus12:03:07

classes comes from the custom styling method used in that material-ui example See: https://material-ui.com/styles/basics/#hook-api

Björn Ebbinghaus12:03:47

The warning is there because I created the repo with no one in mind but myself. 🙂 Because of this not all components are included and there are some "breaking changes" (renames/moves).

3
cjsauer21:03:28

Hey all 👋 I wanted to quick share an idea I’m toying with that builds on top of Fulcro RAD’s authorization support. It’s mentioned in code comments that the hope is for community members to step up and try to tackle this, so here goes 🙂 https://gist.github.com/cjsauer/9ab075ca995d7ee855040271c766db27 This idea is implemented in two pieces: - Reads: A pathom plugin that wraps each reader call and checks whether the logged in user has :read access for this attribute of https://gist.github.com/cjsauer/9ab075ca995d7ee855040271c766db27#file-authorization-clj-L28-L32. If not, that value is replaced with a :redacted value, similar to how RAD’s current impl works. Unlike RAD’s current impl tho, which elides unreadable keys as a post-process, this strategy leverages the rich amount of state available during the pathom parse, which for the common cases I tested seems to allow deriving “this entity” automatically. Reads are allowed by default, but this could be configurable. - Writes: Fulcro form save/delete middleware that scans the form diff to check if the logged in user has :update/`:create`/`:delete` access for every attribute on every entity in the diff. A save payload is allowed or denied as a unit, rather than succeeding partially. Writes/delete are denied by default, but this could be configured. With this in place, one can https://gist.github.com/cjsauer/9ab075ca995d7ee855040271c766db27#file-account-cljc-L15-L18`::auth`https://gist.github.com/cjsauer/9ab075ca995d7ee855040271c766db27#file-account-cljc-L15-L18 to configure access controls; it’s a function of (fn [env entity-ident user-ident]) to a set of permissions #{:create :read :update :delete}. A lot of the “magic” is possible thanks to metadata on attributes like ao/identities and ao/identity? , however I’m certain there are some edge cases that I’m not aware of. Feedback very welcome. Thanks for taking a look.

tony.kay22:03:34

What I’ve ended up doing in my projects is making defresolver/mutation macros that wrap pathoms, and allow the security declaration on the server pathom unit as a function that receives the pathom env and the inputs. That makes for a fully general permissions system where the middleware can augment the env with things like user permissions, policies, etc…and then the endpoint itself can look basically like this:

(defn admin-only [env input] ...)

(defresolver x [env input]
  {:policy admin-only
   ...})
I don’t have time to look over code right now, but perhaps there is a useful idea in there.

tony.kay22:03:18

Thanks for working on it. I look forward to seeing what you end up with. Integrating with the attributes is of course the right path, and having resolvers generate from defattr with a similar approach is, of course, pretty simple

tony.kay00:03:37

Just looked through it some more. I would say: 1. The env has things like identity/session/ring request (if you add it), so that should be leveraged to do permissions/identity. 2. The input of the resolver/mutation tells you the “what” of the access So, the main feedback is essentially what I already gave you: I’d use a (fn [env input] ), where input for a read is assumed to be one of the ao/identities or possibly a sequence if you’re supporting batch.

tony.kay00:03:42

and input is mutation params if it is a write…that perhaps makes it a bit tough. Forms can be composed in an arbitrary way. I agree with you that a partial auth failure on save should prob be treated as an attack and denied, but the fact remains that individual fields will have granular permissions, so for writes you really need to analyze things. For example, my write protections in my systems put an ownership link on every major entity (e.g. :item/owner _ref-to-acct-entity )_ which allows for the most important and common write permission check: “do you own it (or have write access to that other owner) already”? A write of something with a tempid needs the ownership added, and a write with a real ID need to be verified. This makes something like save-form! easy to augment, because that creates an easy-to-verify pattern in general

tony.kay00:03:16

So, my form save auth middleware is pretty trivial, and general.

cjsauer01:03:39

Thanks for the response. As for 1 above, my ad hoc strategy was to have the developer https://gist.github.com/cjsauer/9ab075ca995d7ee855040271c766db27#file-authorization-clj-L14 when creating the pathom plugin. In reality tho this is just a convenience. As for 2, here’s my thinking on the interface being (fn [env entity-ident user-ident]) hung on defattr. I want an interface that lets me surgically select an edge from the graph and answer the question “can this specific user perform c|r|u|d on this exact edge leading from this exact entity?“. The “this exact entity” part is what is difficult (for me at least) to generalize. With reads, it’s easy to just follow pathom’s parse, because it keeps track of the current entity for you. For form saves, it’s also easy, because the diff is in the perfect shape to make this trivial (https://gist.github.com/cjsauer/9ab075ca995d7ee855040271c766db27#file-authorization-clj-L60 over the [k v] tuples of that delta, calling the auth function on every key that is attempting to be modified. But for arbitrary mutations, just being handed an input alone is a bit tough like you said…I can’t quite see how I’d bend that to obtain entity-ident for every attribute in that input. === Here’s a thought that might help us ground this discussion: do you think that the ideal authorization interface would be (fn [env user e a v]) -> #{:c :r :u :d}? In other words, the most general access control would check every single datom that is attempting to be read, or that would result from a successful mutation. This is what’s been guiding my experiments. Can I reason about access at the fact level?

cjsauer01:03:08

As a complex example, you could imagine an access control scheme that depends on all three e a v. Imagine a system where admins can schedule patients at any time of day, but “schedulers” are only allowed to schedule appointments between the hours of A and B. This would be access control that involves • e (which schedule we’re affecting) • a (the :appointment/time) • v (must be between hours A and B)

tony.kay01:03:10

Remember that saves happen thorugh form diffs, which are normalized. There’s an ident, and then there are before/after values. Often for permissions you need all of the input/params to make your decision. I would not go for an EAV concept at all. The UI layer might just need a different option key, because those decisions are likely to be UI-centric, and less narrowly focused on data security. The server layer is what I’m speaking about, and at that layer you want the general env (which needs to be customizable) and then you always need every bit of context that is needed for the resolution of the request: The input (for resolver) or params (for mutation).

tony.kay01:03:32

IMO, that is the most general way to go about it for the I/O layer at least…but the UI layer may in fact need a different mechanism.

tony.kay01:03:20

for you example, imagine that the security of your scheduling system also depends on which doctor you’re seeing, and which insurance you have.

tony.kay01:03:26

EAV is simply insufficient

cjsauer01:03:42

> Often for permissions you need all of the input/params to make your decision. I would not go for an EAV concept at all. Hm yea, that’s a good point. EAV is too local, and not able to interact with other tuples. Very good point.

cjsauer01:03:31

Back pedaling a bit, have you attempted to use your custom defresolver/mutation macros with RAD at all? How would they interact with the auto-resolvers for example?

cjsauer02:03:12

Thinking more, EAV seems general enough to cover access control of reads, as I can’t think of interdependencies between the viewing of facts. Things like “you can only read X if you also read Y” just don’t really happen. However they fail when it comes to writes, which often have invariants that need to be upheld. Ignoring my flawed save/delete middleware, the wrap-read pathom plug-in solution I shared might still be viable.

tony.kay04:03:20

Batching N+1 queries. Reads need a generalize input that is probably always a sequence so that you can resolve N things. Pathom started out with that morphing, but always making it a sequence turns out to be the right thing.

tony.kay04:03:05

but there can be more than one fact required per input. A natural key, for example, might be two incoming facts to resolve one

tony.kay04:03:16

this is why pathom inputs are sets

cjsauer14:03:30

So, back to the drawing board. I oversimplified things chasing a fact based model. Thanks for your feedback @U0CKQ19AQ I think I’ll switch gears to something closer to your resolver/mutation level solution. I’m thinking I could use pathom :transform function to sprinkle in auth checks, and that those could potentially integrate with RAD’s generated resolvers quite easily.