complexdatacollective / Interviewer

Simplifying complex network data collection.
https://networkcanvas.com
GNU General Public License v3.0
46 stars 20 forks source link

Develop protocol format #153

Closed jthrilly closed 7 years ago

wwqrd commented 7 years ago

Gonna write up a description then I'll ping this back into up next

wwqrd commented 7 years ago

What is the protocol?

The protocol's primary purpose is to configure the interview, this includes:

Secondly we might like to include custom react components with the protocol. We intend to release a single app per platform to the app stores to be used by academics -- if we want to enable those users to add custom interfaces then they need to be side-loaded into the app somehow. Currently this seems like the place to do it -- other ideas welcome!

The current situation

The protocol (i.e. configuration) and all interfaces are baked into the app.

We also built a rough sketch that loaded a protocol with bundled interfaces, with the following limitations:

What a protocol might look like

// http://www.northwestern.edu/~jmelville/my-research-protocol.txt

class MyCustomInterface extends React.Component {
   ...
}

return {
  config: {
    stages: [
      {
        name: 'My customised stage',
        interface: MyCustomInterface,
        ...
    ],
    ...
  },
  ...
}

// protocolLoader.js
const protocolData = '...';  // String of data loaded from url
const protocol = new Function('React', 'Redux', 'ReactRedux', 'Containers', 'Components', 'Selectors', 'ducks', protocolData);

const config = protocol(React, Redux, ReactRedux, Containers, Components, Selectors, ducks).config;
rebeccamadsen commented 7 years ago

I am okay with this in theory, but have no practical experience with this approach.

jamieshark commented 7 years ago

The way of requiring the external libraries / directories still feels a bit icky for me. I understand the desire to want to have customizable components, but to me, it feels like this can get messy very quickly if we have too much middle-man dependency management.

I feel like the protocol config should be created by the architect app and then created by the Network Canvas app and really should be kept as close to a JSON object as possible. :|

ronaldpedagat commented 7 years ago

It also feels like an anti-pattern.

I align more with Jamie as that is a normal pattern to follow. It's a bit rough breaking the REST-type of thinking.

Are there any examples/further ideas how they would customise something?

My immediate thoughts still makes me lean towards Higher Order Components(HOC) in the Architect for this type of problem. We could compose each stage/piece of config into it's own component and use them as builders for a component that will be wrapped and passed to props. The resulting wrapped component with the props would then wrap their 'customComponent'.

It would definitely take a bit more pondering to map it out in a more 'elegant' solution, to possibly even create the complete desired separation.

I think one-route my solution could take is it makes protocol a HOC itself, and if it is broken down far enough can remain modular, and the props from it can just be passed to a new wrapped component.

With end result being closer to:

class MyCustomInterface extends React.Component {
   ...(props contains separated configs)
}

const CustomProtocolHOC = Enhance(stage1(stage2(stage3(stage4(MyCustomInterface)))));
jthrilly commented 7 years ago

It also feels like an anti-pattern.

I align more with Jamie as that is a normal pattern to follow. It's a bit rough breaking the REST-type of thinking.

Please remember this is not a 'normal' web app. A lot of the constraints and paradigms you are both used to working with do not apply. REST is a great example -- ideal for certain use-cases, but absolutely not relevant here (there's no server/client model). I am keen to avoid too much discussion of the 'correct' implementation paradigm. These things are more subjective than many developers would admit, and are also subject to changes in fashion (not to mention industry fads). This issue requires a little bit of lateral thought, and the hacker ethos that I have talked with each of you about. Our solution might not be pretty, but the fundamental test here is if it works, is robust, and provides the features we have discussed.

The way of requiring the external libraries / directories still feels a bit icky for me. I understand the desire to want to have customizable components, but to me, it feels like this can get messy very quickly if we have too much middle-man dependency management.

Can you say what you mean by "middle man dependency management"? If we do this correctly, the packaging of the custom protocol (including any specific dependencies) should be taken care of either by the architect app, or by the developer. If someone messes up and produces something that crashes the app when it loads, so be it.

I feel like the protocol config should be created by the architect app and then created by the Network Canvas app and really should be kept as close to a JSON object as possible. :|

We need to separate the two things you're talking about here, because they require different solutions:

For this reason, we simply cannot use JSON. We need people to be able to build custom interfaces for Network Canvas that are as complex as anything we are building as part of the application 'core', and bundle them in an interview protocol. Unless we built an entire UI framework and set of base components, JSON wouldn't let us do that. This is before we get to the shenanigans we would need to be able to support the logic that would normally be executed within simple functions. JSON is definitely out at this point.

Are there any examples/further ideas how they would customise something?

It could be something as trivial as changing the layout of some components on an existing interface, or as complex as implementing an entirely new interface, including its own components, external dependencies, CSS, etc. As above, these could be very complex.

My immediate thoughts still makes me lean towards Higher Order Components(HOC) in the Architect for this type of problem. We could compose each stage/piece of config into it's own component and use them as builders for a component that will be wrapped and passed to props. The resulting wrapped component with the props would then wrap their 'customComponent'.

I've read your description, and I'm still not sure exactly how this would work. Given what I've said above, do you still think this approach would work? Can you elaborate/provide a more thorough example?

Ugh.

Okay, as things stand here is how I see this working conceptually:

wwqrd commented 7 years ago

I'm really curious about this HOC approach too, but I don't quite follow either. Are you suggesting that we could compose the protocol this way? Or is this about how the protocol would be rendered?

ronaldpedagat commented 7 years ago

The principles are similar to composition and currying in functional programming.

For a further explanation on HOCs, View here.

HOCs allow for code reuse, logic and bootstrap abstraction, render highjacking, state abstraction and manipulation, props manipulation

The best helper library currently for composition is Recompose, which have several helper functions, that allow manipulation of HOCs via props or context, render different HOCs via branch function tests. (other common composition patterns)

This takes a lot more architecting to fulfil, but as hopefully most of our components would be reusable if they were converted to HOCs where necessary, it shouldn't be too bad to allow customization via functional capabilities our components have themselves.

There are several ideas that come to mind, a few being:

Be at mind this is pseudocode an example for the power of HOCs

// simple example

function stageHOC (stageData, InterfaceComponent) {
   return(
     class StageHOC extends component {
       //have default actions
     render(){
      return <InterfaceComponent {...default actions} data={stageData} />
     }
   }
  )
}

MyCustomComponent () => {
 do custom stuff with data
}

function giveMeStage1Data () {
 return stage1 Data
}

Stage1Component () => {
 return Stage1Component
}

let customData = customData

const newCustom = stageHOC(customData, MyCustomComponent);
const stage1Only = stageHOC(giveMeStage1Data, Stage1Component);

A more advanced way is passing a function prop test to a HOC... so it would default to our component, but if the test renders true it would use a custom component or vice versa.

I would have to give this extreme thought though to create the proper architecture for the separation we are trying to achieve.

jthrilly commented 7 years ago

Okay, thanks for the example @racpa - that makes things clearer.

From what I have seen, I think we need to go with Steve's approach for now. It strikes the balance I think we are looking for. As much as it would be great to go down the route of completely reusable components, by abstracting it all through HOCs, I think this is way outside the scope of this problem. With Steve's approach, we simply provide the facility for a developer to write a react component. That's all. We aren't creating a completely reusable framework here - just a hook with which people can write custom code should they wish.

@wwqrd do you think you can work on this as your next item? Ideally also loading the protocol file itself as an external resource so we can troubleshoot that.

wwqrd commented 7 years ago

Am I right in saying that using the HOC approach we'd still intend to send that code over the network? So it's really another approach to designing config and that we would still need something like Function to interpret the code? i.e. The two approaches are not incompatible with each other?

If say we have one of our built-in interfaces (by "interface" we mean full-screen view, i.e. one of the stages):

<NameGenerator>
  <ListOfNodes />
  <NewNodeForm />
</NameGenerator>

And our user wants to make an interface with categorical bins to put nodes in, that might look like this:

<Categorizer>
  <PanelOfNodesFromLastScreen />
  <Category label="Is the funniest" attributes={ { funny: true } } />
  <Category label="Is the most hard working" attributes={ { hardworking: true } } />
</Categorizer>

How would HOCs help us or the user to do that? What might it look like?

Or are you suggesting we use HOCs like "reducers" to generate configuration json?

const config = config(meta('my protocol'), stages(stage1(NameGenerator), stage2(Categorizer)))

In which case what makes this advantageous over json?

ronaldpedagat commented 7 years ago

The 2 approaches can definitely be compatible with each other as it's a function in of itself.

  1. Definitely, an interesting problem, I think we'd have a callback - which could serve as a conditional rendering check, and do the conditional rendering based off whether or not we passed a custom react component or not.

  2. It would still need to be stored into a json format though? The difference with the reducer method is we pass it via props, and have more avenues to manipulate the view, and can just expose the methods for reuse and customization.

wwqrd commented 7 years ago

Closed by: https://github.com/codaco/Network-Canvas/pull/167