UI plugins using redux?

Hi,

I’ve saw an interesting blog about using redux as a registry as placeholders for plugins to add their content to the screen, might be a long term replacement to using deface…

Ohad

Could be nice if they share the code that links the plugins together.

I like this approach, @amirfefer, @Shimon_Shtein and I talked about it for a while.

Lately, I thought about a bit different approach, let plugins to import components and manipulate them easily:

import { Notifications } from 'foremanReact';

// override propTypes
Notifications.propTypes = { ...Notifications.propTypes, customProp: PropTypes.string };

// override defaultProps
Notifications.defaultProps = { ...Notifications.defaultProps, name: 'change-default-prop' };

// maybe?
Notifications.styles['some-style'] = 'new style';

// Insert component, at the end of the Notification component
Notifications.append(<MyComponent />);
// should result
<Notifications>
  {children}
  <MyComponent {...propsFromNotifications} />
</Notifications>

// Insert component, at the beginning of the Notification component
Notifications.prepend(<MyComponent />);
// should result
<Notifications>
  <MyComponent {...propsFromNotifications} />
  {children}
</Notifications>

// Insert component, before the Notification component
Notifications.before(<MyComponent />);
// should result
<MyComponent {...propsFromNotifications} />
<Notifications>{children}</Notifications>

// Insert component, after the Notification component
Notifications.after(<MyComponent />);
// should result
<Notifications>{children}</Notifications>
<MyComponent {...propsFromNotifications} />

We can deliver all those functionalities as a base class:

class ForemanComponent extends React.Component {
  append(component) {}
  prepend(component) {}
  brfore(componnent) {}
  after(component) {}
  replace(component) {}
}

class Notifications extends ForemanComponent {
  render() {}
}

At the end, each component will manage a state with linked components.

Another API we can make is about overriding the mapStateToProps functionality so plugins can replace the data the floating into the core components.

I like the concept of generic placeholders, like append, prepend, before. I think in addition to the generic ones, we should add an ability to add custom placeholders too, so plugin developers would have even more control where their code is embedded.

Another API we can make is about overriding the mapStateToProps functionality so plugins can replace the data the floating into the core components.

Isn’t it solved better on the redux side? Just add custom data in a predefined format to a predefined place, and we’re done.

Isn’t it solved better on the redux side? Just add custom data in a predefined format to a predefined place, and we’re done.

Well, it depends, usually, you attach a reducer to a specific key in your store so you are not able to manipulate data from other reducers.
You can decide to create general/common reducers if you want to but it is not that common.
We had a small chat about it here:
https://github.com/theforeman/foreman/pull/5240#issuecomment-364138778

About the dom:

I have start thinking about an API that looks like:

import foremanDom from 'foremanDom';
import { ComponentName } from 'foremanReact';

ComponentName.find('input#my-input-id').append(<Something />);
// get a very spesific instance of ComponentName
foremanDom.find('ComponentName#component-id.component-class').find('input#my-input-id').append(<Something />);

I have start writing this document to make it clear, what should plugins be able to do?

Thanks for sharing, it’s interesting approach.
I shared a blog long time ago which shows similar approach with flux.

@sharvit
This placeholders approach reminds me Deface plugin a bit, which could be easier for plugins developers.
though, we still need to manage mounting components, we probably need a component register mechanism for expanded components, and I’m not sure if those placeholders callbacks should be managed in the basic components.

I like the repl.it approach with the expendable layout, looks like redux taking care for expanding behavior, with this receiver mechanism. while the components remain unaware. seems cleaner solution, it’s too bad they don’t fully expose it.

I like the repl.it approach too.

I’d like to collect list of current use-cases to find out what extension points we really need. It’s probably not many. Typically plugins need to add more tabs to forms, action buttons and table columns. I think that starting from a list of real needs can lead us to better defined extension points.

Allowing to modify the UI only at certain places is less error prone compared to hooking on ids and modifying any component. The core developers will be aware about the extension points and can take care about not breaking them. In the other case you have no idea if somebody is using your id to extend a page.

I created a basic list of current extensions that plugins make. @sharvit do you think I could add it into your document? Can you make it editable, please?

Would this also work for removing components? For example, if I wanted to
remove provisioning features and replace with a self-service portal. Or if
I wanted to remove the display of subscriptions completely.

Sure @Tomas_Strachota, I had to make it editable from the beginning.
Let’s try to put everything there together.

About the extension-points, I agree it will be the easiest to maintain in case you don’t have too many of them.

Thanks for this update :+1:

Added those use-cases to the document:
https://docs.google.com/document/d/1q6n3fH5ivcYXZaN-QM_D6GNUbWbCGlSLZ7DMb3AhsMQ/edit?userstoinvite=tstracho@redhat.com&ts=5a8c1f68

Feel free update the document

:+1: to the repl.it approach. For adding columns to tables we should just use configurable columns:

See also https://github.com/theforeman/foreman/pull/5262

Correct, but we must make sure that plugins can add their own columns in addition to what is defined in the core.

Oh I see, so if katello, for example, wanted to add content related columns to the hosts table. That makes sense as a use case.