Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BO templates scaffolding proposal #820

Open
phgrey opened this issue Jan 12, 2024 · 6 comments
Open

BO templates scaffolding proposal #820

phgrey opened this issue Jan 12, 2024 · 6 comments

Comments

@phgrey
Copy link
Contributor

phgrey commented Jan 12, 2024

version 0.4

Purpose of the Proposal

The aim of this proposal is to streamline the frontend development process for common CRUD (Create, Read, Update, Delete) operations in the BO Templates area. This will be achieved by implementing a mix generator task that facilitates instant prototyping. The intention is to minimize the occurrence of breaking changes while adhering to the development standards of Phoenix.

Workflow of the Proposal

The proposal should:

  • initially be discussed within the MoonDS team (done)
  • and then presented to our library consumers for review and discussion.

Subject of the Proposal

The proposal involves the creation of a generator task like mix moon.scaffold <Your.Data.Type.Module>, which closely resembles the Phoenix's mix phx.gen... tasks.
<Your.Data.Type.Module> refers to a specific struct or Ecto.Schema. Other datatypes processing can be added later as well.

The output of this generator task will be a compound component that encompasses all CRUD operations (including a table for listing) and its corresponding subcomponents. The generated table will have sortable and filterable columns, aligned with the fields in the original struct. Clicking on a row in the table will display a row card/form adjacent to the table. Additionally, a form with the necessary fields will be generated. An additional "{Item|List}Actions" components will encapsulate entity/list actions.

Please refer to the tables in the actual Figma design files for details:

https://www.figma.com/file/sv4LnptKzx5JwgpIVcEenU/Partners.io?type=design&node-id=3135-10499&mode=design&t=YHzkQMOQ7U0VIvkD-0

https://www.figma.com/file/maDYymDYYORfKtrT1nq23n/Yolo-Bo's?type=design&node-id=267-19671&mode=design&t=qgPmpFp8sOTtnaiW-0

https://www.figma.com/file/maDYymDYYORfKtrT1nq23n/Yolo-Bo's?type=design&node-id=85-45474&mode=design&t=I7Sut6mUNgus1SNc-0

The Ecto.Schema provides additional information that can be utilized when generating forms/tables. Depending on the field data type, this may include select options for relations, checkboxes for boolean values, radio buttons for enums, etc.

Potential Drawbacks

The main drawback is a lack of flexibility. The generated component may not be as customizable as other standard Moon components due to its inherent complexity and the deprecation of Surface.Context usage. Anyway, flexibility is not a primary focus inside BO Templates.

Furthermore, these components do not receive automatic updates, which is intentional. When updates are delivered with a new moon package version, a new generator run is necessary to implement them. So, changes will definitely be reviewed by the consumer before being committed. It's a kind of no-breaking-changes for free. However, this process may override or disregard any previous changes.

Example:

There are a lot of Surface templates and a bit of Elixir code below, nothing else.

Let's say we have some struct. Ecto.{Schema, Changeset} should also be applicable here. Not sure about Phoenix.Form.

defmodule MoonWeb.Schema.Link do
  defstruct name: nil,
            key: nil,
            icon: nil
end

so, running mix moon.scaffold MoonWeb.Schema.Link will generate few components:

defmodule MoonWeb.Components.Link do
...

  alias __MODULE__

  def render(assigns) do
    ~F"""
    <Moon.CRUD>
      <Link.Table items=... />
      <Link.Form for=.../>
      <Link.Card for=.../>
      <Link.ItemActions>
      <Link.ListActions>
    </Moon.CRUD>
    """
  end
end

defmodule MoonWeb.Components.Link.Table do
...

  alias Moon.Parts.Table

  def render(assigns) do
    ~F"""
    <Table items={item <- @items}>
      <Table.Col name="name">{item.name}</Table.Col>
      <Table.Col name="key">{item.key}</Table.Col>
      <Table.Col name="icon">{item.icon}</Table.Col>
    </Table>
    """
  end
end

defmodule MoonWeb.Components.Link.Form do
...

  alias Moon.Design.{Form, Button}

  def render(assigns) do

    ~F"""
    <Form {=@for}>
      <Form.Field field="name">
        <Form.Input />
      </Form.Field>
      <Form.Field field="key">
        <Form.Input />
      </Form.Field>
      <Form.Field field="icon">
        <Form.Input />
      </Form.Field>
      <Button type="submit">Submit</Button>
    </Form>
    """

  end
end

defmodule MoonWeb.Components.Link.Card do
...

  def render(assigns) do

    ~F"""
    <pre>{inspect(@for)}</pre>
    """

  end
end
@gfrancischelli
Copy link

gfrancischelli commented Jan 16, 2024

Nicely written thank you 👏

From my experience with mix phx.gen, I'm not a big fan of using generators, since upgrading the code is super difficult after anyone done any changes and to be honest sometimes even without making any customizations. I know the idea of templates is to definitely avoid customization. But even so, it feels bad not to provide any safe escape hatches.

Have you guys considered a similar approach to LiveAdmin ?

The API is super simple and looks like this:

defmodule MyApp.Admin.Accounts do
  use LiveAdmin.Resource, schema: MyApp.Accounts.User

  # callbacks here for escape hatches, like:
  #  - `delete_with`, an atom or MFA function to delete a record (set to false to disable deletes)
  #  - `actions`, list of atoms or MFAs that identify a function that operates on a record
end

....

# router.ex
live_admin "/admin" do
  admin_resource "/accounts", MyApp.Admin.Accounts
end

Pros

  • No need to review patches when applying new generator
  • Easier to carry own simple customizations after upgrades
  • First-class extension points
  • Smaller API surface
  • Even fully-backend applications without moon as a dependecy could easily add a backoffice without adding lots of frontend code to their repo think of same DX as LiveDashboard, fast plug-n-play

Cons

  • Maintainers will have to do more heavy lifting
  • Less customizable than generators (but we don't care that much about allowing a lot of customization any way)

@ViniciusGaiaValente
Copy link

Hey, very nice document!

  • Point 1:

There was any alternative taken in consideration? I believe that in when decisions like that are being documented it's vital to document also the alternatives taken into consideration, what would be the pros and cons for this alternatives and why the selected approach is preferable.

  • Point 2:

I can see the overral discussion been shaped around how much customization we want to provide to the user. Do we have this clearly defined already? If so would be interesting to document it also.

  • Point 3:

The generated table will have sortable and filterable columns, aligned with the fields in the original struct.

We have some examples of tables in balanced that only make sense together with their associations, like invoices and invoice rows, for instance, the data on the this 2 tables are complementary. The invoice_row table only appears inside the invoices show page, so managing this 2 entities on separate pages would provide us no value, even in a back office scenario. Were this use case taken into consideration? Do you believe this is relevant for other teams?

@phgrey
Copy link
Contributor Author

phgrey commented Jan 17, 2024

@gfrancischelli I would greatly appreciate having both of these options. In my opinion, both of them will require the same functionality under the hood - building forms, tables, and more based on the given datatype.

Furthermore, I see several additional advantages in having generated code, especially considering my numerous negative experiences with customer feedback for the auto-updates provided by the Moon library.

@phgrey
Copy link
Contributor Author

phgrey commented Jan 17, 2024

@ViniciusGaiaValente point by point:
1. An alternative perspective has been presented by @gfrancischelli in the initial comment.
2. The issue of flexibility has been raised, although it is not officially recognized as a point for BO Templates. However, despite the lack of design flexibility, functional flexibility is believed to be essential, particularly in terms of implementing features such as table filters and sorting.
3. For me it looks like an additional flexibility point for the table, thanks.

@gfrancischelli
Copy link

What are the several advantages besides putting 1 more command between a potentially explosive upgrade and the user?

Furthermore, I see several additional advantages in having generated code, especially considering my numerous negative experiences with customer feedback for the auto-updates provided by the Moon library.

I don't think a design systems are difficult to upgrade by nature.

Would be more worth to explore what makes Moon upgrades so difficult in the first place and work to fix it

WDYT?

@phgrey
Copy link
Contributor Author

phgrey commented Jan 26, 2024

Let's consider both approaches: using a generator and a single CRUD component (as proposed by @gfrancischelli).
Initially, both methods will appear identical as the generator will create several components, while the single CRUD component will generate the same interface.

Suppose we need to modify the behavior for all *_icon fields. In the provided example, we will introduce the Table.IconColumn component and utilize it to render all *_icon fields instead of the Table.Column component.
Both the generator and single CRUD component need to be updated. However, after updating the Moon library, users of the single CRUD component will immediately receive the update. On the other hand, generator users will need to execute a mix command for each table individually to apply the update. And thanks to CVS, generator users can selectively choose which update to apply to each column in each table.

The same for form, filters, ...

By following this approach, the MoonDS team can avoid encountering many issues regarding accidentally updated components in consuming projects.

Everybody who needs immediate updates is supposed to use a single CRUD component and can easily get the latest updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants