entrefine

package module
v1.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 16, 2023 License: GPL-3.0 Imports: 19 Imported by: 0

README

entrefine

Powerful tool that combines the power of two frameworks, Ent(ORM) and Refine(UI).

GitHub contributors GitHub issues GitHub stars GitHub closed issues GitHub pull requests GitHub release (latest by date) GitHub commit activity GitHub license

cover.png

It simplifies the process of generating CRUDs from Ent definitions with customizable views, fields, actions and search features.

main.png

Live Demo and Package Info

Live demo: https://demo.entrefine.dev/

Go.dev Package: https://pkg.go.dev/github.com/diazoxide/entrefine

Roadmap

  • Generates CRUD operations based on Ent definitions
  • Customizable views for each CRUD operation
  • Customizable fields for lists, forms, and show views using annotations
  • Custom actions for items
    • Actions on a list, show, or edit view that trigger a GraphQL mutation.
    • Bulk actions on lists
  • Relational view with nested lists and edges
  • Smart search component to find records by every attribute with a custom operator
  • Uses only a Graphql API with a custom Refine data-provider
  • Generates TypeScript types from Ent definitions
  • Column filters with customizable operators
  • Edges diagram graph view (with gojs or react-force-graph)
  • Nested create/edit
    • Ability to create edges from form
    • Ability to edit edges from form
  • I18n support
  • Keycloak Authentication
  • Keycloak Authorization
  • Filter by edges
  • Sort by edges
  • Godoc: provide comprehensive documentation

entrefine provides a smart search component to easily find records by any attribute with a custom operator. search.gif

Requirements

The platform uses a Graphql API as the data-provider interface and therefore a GQL extension is mandatory.

How to set up?

Extension registration on entc.go

Add extension to your Ent framework entc.go file.

Example
package main
import (
	//...
	"entgo.io/contrib/entgql"
	"github.com/diazoxide/entrefine"
)

func main() {
	gqlEx, err := entgql.NewExtension(
		// Make sure that EntGql configs are wrapped
		entrefine.EntgqlExtensionOptionsWrapper(
			entgql.WithConfigPath("./gqlgen.yml"),
			entgql.WithSchemaGenerator(),
			entgql.WithSchemaPath("./graphql/ent.graphql"),
			entgql.WithWhereInputs(true),
		)...,
	)
	//...
	opts := []entc.Option{
		entc.Extensions(
			// GQL extension is mandatory
			gqlEx,
			// entrefine configuration
			entrefine.NewExtension(
				entrefine.WithAppPath(filepath.Join("..", "refine"),
			),
		),
	}
	err = entc.Generate(schemaPath, config, opts...)
	//...
}
Then Apply search query to your query resolvers WhereInput

This is important for smart-search component

EntityWhereInput.ApplySearchQuery(q)

Example
package graphql

import (
	"ent"
	"context"
	"github.com/google/uuid"
)

func (r *queryResolver) Companies(
	ctx context.Context,
	after *ent.Cursor,
	first *int,
	before *ent.Cursor,
	last *int,
	orderBy *ent.CompanyOrder,
	where *ent.CompanyWhereInput,
	q *string, // Added by entrefine
) (*ent.CompanyConnection, error) {
	return r.client.Company.Query().Paginate(ctx, after, first, before, last,
		ent.WithCompanyOrder(orderBy),
		ent.WithCompanyFilter(
			where.ApplySearchQuery(q).Filter, // Applying query filter
		),
	)
}
Configure your ent schemas with annotations

e.g. entrefine.FilterOperator("contains")

Supporting annotations

For Fields
  • ImageField - mark field as image
  • MainImageField - mark field as main image field (force marking field as a ImageField too)
  • TitleField - mark field as title field
  • CodeField - mark field as code field
  • URLField - mark field as url field
  • RichTextField - mark field as rich text field
  • HideOnList - hide field on list
  • HideOnShow - hide field on show page
  • HideOnForm - hide field on create and edit forms
  • HideOnEdit - hide field on edit form
  • HideOnCreate - hide field on create form
  • FilterOperator entrefine.FilterOperator("contains")
  • View - custom view of field on list and show pages
  • ViewOnList - custom view of field on list page
  • ViewOnShow - custom view of field on show page
  • ViewOnForm - custom view of field on form
For Entities
  • Icon (field/entity) - set icon of entity to show on navigation, breadcrumbs, edges, etc.
    • entrefine.Icon("some-antdesign-icon")
  • Actions - custom actions
  • NoList - disable list and hide entity from navigation
  • NoShow - disable entity show page
  • NoCreate - disable entity creat form
  • NoEdit - disable entity edit form
  • Badge - custom badge view of entity

Getting ready to use

  1. After configuration regenerate Ent.
  2. Your package.json file is changed so run npm install to get deps.
  3. Check directory of refine application. On src directory you can find entrefine folder with ent resources.
  4. Update your App.ts file
    import React from "react";
    import "@pankod/refine-antd/dist/reset.css";
    import {Refine} from "@pankod/refine-core";
    import {ErrorComponent, Layout, notificationProvider, ReadyPage,} from "@pankod/refine-antd";
    import routerProvider from "@pankod/refine-react-router-v6";
    import {GraphQLClient} from "graphql-request";
    import {Resources} from "./entrefine/resources";
    import dataProvider from "./entrefine/data-provider";
    
    // Provide your graphql query endpoint
    const client = new GraphQLClient("http://localhost:8081/query");
    
    function App() {
        return (
            <Refine
                routerProvider={routerProvider}
                dataProvider={dataProvider(client)}
                Layout={Layout}
                ReadyPage={ReadyPage}
                notificationProvider={notificationProvider}
                catchAll={<ErrorComponent/>}
                resources={Resources}
            />
        );
    }
    export default App;
    
  5. Run npm run dev
  6. Ready

Search Component <SearchComponent/>

How it works?

Querying all fields with your defined operator (FilterOperator Annotation) included UUID

Example
Root App
function App() {
    return (
        <Refine
            //...
            Header={Header}
            //...
        />
    );
}
Header component
import {SearchComponent} from "../../entrefine/search-component";

export const Header: React.FC = () => {
    const screens = useBreakpoint();
    return (
        <AntdHeader style={{
            padding: "0 24px",
            background: "white",
        }}>
            <Row align="middle"
                 style={{
                     justifyContent: screens.sm ? "space-between" : "end",
                 }}>
                <Col xs={0} sm={12}>
                    <SearchComponent/>
                </Col>
            </Row>
        </AntdHeader>
    );
};

Customization

File custom.tsx

To customize entrefine components you can find ./entrefine/custom.tsx file on your refine root directory.

Custom Actions

Add entity annotation to your schema

Annotation Example
entrefine.Actions(
   entrefine.ShowAction,
   entrefine.DeleteAction,
   entrefine.EditAction,
   entrefine.Action{
        Operation: "myCustomGqlOperation",
        Fields: []string{
            "id", "title"
        },
        Label:          "Do something",
        FailMessage:    "Something bad happened",
        SuccessMessage: "${ resp.data.length } Pages processed",
        OnList:         true,
        OnShow:         true,
        Bulk:           true,
        Icon:           "RA.Icons.RadarChartOutlined",
   },
),
Implementation Example
  1. First you need to add custom graphql mutation with WhereInput
    # ./mutations.graphql
    myCustomGqlOperation(where: PageWhereInput!): [Page!]
    
  2. Then let's write mutation resolver
    func (r *mutationResolver) MyCustomGqlOperation(ctx context.Context, where generated.PageWhereInput) ([]*generated.Page, error) {
        w, err := where.P()
        p, err := r.client.Page.Query().Where(w).All(ctx)
    
        err = someCustomAction(p...)
        if err != nil {
            return nil, err
        }
        return p, nil
    }
    

Edges diagram graph view

The Edge Graph Diagram is an effective tool for visualizing the relationships between your entities. It presents an interactive representation of the edges, displaying record IDs and their connections to the main record, making it easier to understand and analyze complex data.

edges-diagram.png

How to enable

Important! By default, the Edge Graph Diagram utilizes GoJS technology. Both GoJS and react-force-graph-2d are available options, allowing you to select the best solution for your needs. However, it's important to note that GoJS is a proprietary library and requires a license key purchased from the GoJS official website for commercial use. On the other hand, react-force-graph-2d is an open-source option.

How to switch GoJS to react-force-graph-2d

Customize entrefine extension configs on entc.go file

e.g.

entRefine, err := entrefine.NewExtension(
    ...
    entrefine.WithForceGraph2D(
        entrefine.ForceGraph2DOptions{
            Enabled: true,
        },
    ),
    entrefine.WithDefaultEdgesDiagram('Diagram.ForceGraph2D'),
    ...
)
GoJS license and configuration

e.g.

entRefine, err := entrefine.NewExtension(
    ...
    entrefine.WithGoJs(
        entrefine.GoJSOptions{
            Enabled: true,
            LicenseKey: "xxxxx-xxxxxx-xxxxx-xxxxx",
        },
    ),
    entrefine.WithDefaultEdgesDiagram('Diagram.GoJS'),
    ...
)

Custom views

On entrefine every view of field is customizable for every type of layout.

Special annotations
  1. View - Forcing list and show views
  2. ViewOnList
  3. ViewOnShow
  4. ViewOnForm
How to customize?
  1. First create new React Component on custom.tsx (e.g. MyCustomTitle) with ViewProps type props.

    import {ViewProps} from "./view";
    
    export const MyCustomTitle: React.FC<ViewProps<string>> = ({value}) => {
       return <RA.Typography.Text copyable={true} style={ {color: "red"} }>{ value }</RA.Typography.Text>
    }
    
  2. Define type of field on schema by entrefine.View annotation

    field.String("title").
         Annotations(
             ...
             entrefine.View("MyCustomTitle"),
             ...
         ),
    
  3. Regenerate and check custom-list-field.png

Custom Badge view

What is a badge?

Badge is a public view of entity on other items edge views. e.g. img.png

Badge customization

First you need to create new React component like Custom Views. Then use Badge annotation to connect it with entity.

Example

Check out the documentation for more information and examples.

Both frameworks (Ent and Refine) are configured as described in documentation.

Contacts

logo.svg Linkedin: https://www.linkedin.com/in/aaron-yor/

Discord: aaron․yordanyan#7556

Phone: +374 98 471111

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// EditAction standard edit action
	EditAction = Action{
		Operation: "Edit",
		OnList:    true,
		OnShow:    true,
	}
	// ShowAction standard show action
	ShowAction = Action{
		Operation: "Show",
		OnList:    true,
	}
	// DeleteAction standard delete action
	DeleteAction = Action{
		Operation: "Delete",
		Label:     "Delete",
		Icon:      "RA.Icons.DeleteOutlined",
		OnList:    true,
		OnShow:    true,
		Bulk:      true,
		Props: map[string]any{
			"danger": true,
		},
	}
)
View Source
var (
	Dependencies = JSDeps{
		Deps: map[string]string{
			"pluralize":         "^8.0.0",
			"camelcase":         "^6.2.0",
			"gql-query-builder": "^3.5.5",
			"graphql-request":   "^4.3.0",
			"graphql":           "^15.6.1",
			"lodash":            "^4.17.21",
		},
		DevDeps: map[string]string{
			"@types/pluralize": "^0.0.29",
			"@types/lodash":    "^4.14.171",
		},
	}

	ForceGraph2DDependencies = JSDeps{
		Deps: map[string]string{
			"react-force-graph-2d": "^1.23.17",
		},
	}

	GoJSDependencies = JSDeps{
		Deps: map[string]string{
			"gojs":       "^2.3.1",
			"gojs-react": "^1.1.1",
		},
	}
)

Functions

func EntgqlExtensionOptionsWrapper

func EntgqlExtensionOptionsWrapper(opts ...entgql.ExtensionOption) []entgql.ExtensionOption

EntgqlExtensionOptionsWrapper Wrap options of EntGQL Sometimes entrefine using some custom GraphQL queries and mutations Wrapper can add custom configs/hooks

func GenerateRefineScripts

func GenerateRefineScripts(ex *Extension) gen.Hook

Types

type Action

type Action struct {
	Operation       string         `json:"Operation,omitempty"`       // Operation of graphql
	Fields          []string       `json:"Fields,omitempty"`          // Fields to take after operation
	Props           map[string]any `json:"Props,omitempty"`           // Props are directly passing to react component
	Single          bool           `json:"Single"`                    // Show on single item
	Bulk            bool           `json:"Bulk,omitempty"`            // Show on bulk selected items
	SuccessMessage  string         `json:"SuccessMessage,omitempty"`  // Message on success
	FailMessage     string         `json:"FailMessage,omitempty"`     // Message on fail
	CustomComponent string         `json:"CustomComponent,omitempty"` // Custom component TODO: custom component
	Description     string         `json:"Description,omitempty"`     // Description of action
	Label           string         `json:"Label,omitempty"`           // Label of button
	Icon            string         `json:"Icon,omitempty"`            // Icon of button
	OnList          bool           `json:"OnList,omitempty"`          // Display on list
	OnShow          bool           `json:"OnShow,omitempty"`          // Display on show
	OnEdit          bool           `json:"OnEdit,omitempty"`          // Display on edit
}

Action item related action

type CodeFieldOptions

type CodeFieldOptions struct {
	Language string `json:"Language,omitempty"`
}

CodeFieldOptions code field is configurable

**Example**

```go

CodeFieldOptions{
	Language: "javascript"
}

```

type Extension

type Extension struct {
	entc.DefaultExtension
	AppPath             string         // AppPath JS Application path (packages.json directory path)
	SrcDirName          string         // SrcDirName JS Application source dir name
	Meta                map[string]any // Meta to share with frontend application
	TypeScriptPrefix    string
	GoJs                GoJSOptions
	ForceGraph2D        ForceGraph2DOptions
	DefaultEdgesDiagram string
}

Extension main struct

func NewExtension

func NewExtension(opts ...ExtensionOption) (*Extension, error)

NewExtension initialize extension

func (*Extension) Annotations

func (ex *Extension) Annotations() []entc.Annotation

Annotations Define Ent annotations

func (*Extension) Hooks

func (ex *Extension) Hooks() []gen.Hook

Hooks Define Ent hooks

func (*Extension) Templates

func (ex *Extension) Templates() []*gen.Template

Templates Define Ent templates

type ExtensionOption

type ExtensionOption = func(*Extension) error

func WithAppPath

func WithAppPath(path string) ExtensionOption

WithAppPath define refine-project directory

func WithDefaultEdgesDiagram

func WithDefaultEdgesDiagram(name string) ExtensionOption

WithDefaultEdgesDiagram set default edges graph/diagram view component name

func WithForceGraph2D

func WithForceGraph2D(options ForceGraph2DOptions) ExtensionOption

WithForceGraph2D use react-force-graph-2d for edges diagrams

func WithGoJs

func WithGoJs(options GoJSOptions) ExtensionOption

WithGoJs use gojs for edges diagrams

func WithMeta

func WithMeta(meta map[string]any) ExtensionOption

WithMeta add metadata to `{AppPath}/entrefine.json`

func WithSrcDirName

func WithSrcDirName(name string) ExtensionOption

WithSrcDirName additional option to configure Refine repo code-source directory, default value is `src`

func WithTypeScriptPrefix

func WithTypeScriptPrefix(prefix string) ExtensionOption

WithTypeScriptPrefix define typescript types/vars prefix

type ForceGraph2DOptions

type ForceGraph2DOptions struct {
	Enabled bool `json:"Enabled,omitempty"`
}

type GoJSOptions

type GoJSOptions struct {
	Enabled    bool   `json:"Enabled,omitempty"`
	LicenseKey string `json:"LicenseKey,omitempty"`
}

type JSDeps

type JSDeps struct {
	Deps    map[string]string
	DevDeps map[string]string
}

type RefineAnnotation

type RefineAnnotation struct {
	TitleField     bool              `json:"TitleField,omitempty"`     // Mark field as title of entity
	ImageField     bool              `json:"ImageField,omitempty"`     // Mark field as image
	MainImageField bool              `json:"MainImageField,omitempty"` // Mark field as main image of entity
	CodeField      *CodeFieldOptions `json:"CodeField,omitempty"`      // Mark field as code field
	URLField       bool              `json:"URLField,omitempty"`       // Mark field as url field
	RichTextField  bool              `json:"RichTextField,omitempty"`  // Mark field as rich text field
	NoList         bool              `json:"NoList,omitempty"`
	NoShow         bool              `json:"NoShow,omitempty"`
	NoCreate       bool              `json:"NoCreate,omitempty"`
	NoEdit         bool              `json:"NoEdit,omitempty"`
	HideOnList     bool              `json:"HideOnList,omitempty"`
	HideOnShow     bool              `json:"HideOnShow,omitempty"`
	HideOnForm     bool              `json:"HideOnForm,omitempty"`
	HideOnCreate   bool              `json:"HideOnCreate,omitempty"`
	HideOnUpdate   bool              `json:"HideOnUpdate,omitempty"`
	FilterOperator *string           `json:"FilterOperator,omitempty"`
	Icon           *string           `json:"Icon,omitempty"`
	Label          *string           `json:"Label,omitempty"`
	Description    *string           `json:"Description,omitempty"`
	Prefix         *string           `json:"Prefix,omitempty"`
	Suffix         *string           `json:"Suffix,omitempty"`
	Actions        []Action          `json:"Actions,omitempty"`
	View           *string           `json:"View,omitempty"`

	ViewOnShow *string `json:"ViewOnShow,omitempty"`
	ViewOnList *string `json:"ViewOnList,omitempty"`
	ViewOnForm *string `json:"ViewOnForm,omitempty"`
	Badge      *string `json:"Badge,omitempty"`

	EdgesDiagram *string `json:"EdgesDiagram,omitempty"`
}

RefineAnnotation struct container of all annotations

func Actions

func Actions(actions ...Action) RefineAnnotation

Actions actions/buttons on list items

func Badge

func Badge(name string) RefineAnnotation

Badge define entity badge view

func CodeField

func CodeField(config *CodeFieldOptions) RefineAnnotation

CodeField mark field as a code field

func Description

func Description(description string) RefineAnnotation

Description define description of field/entity todo: implement generator

func FilterOperator

func FilterOperator(operator gen.Op) RefineAnnotation

FilterOperator define entity field filter operator

func HideOnCreate

func HideOnCreate() RefineAnnotation

HideOnCreate hide field on form

func HideOnForm

func HideOnForm() RefineAnnotation

HideOnForm hide field on form

func HideOnList

func HideOnList() RefineAnnotation

HideOnList hide field on list

func HideOnShow

func HideOnShow() RefineAnnotation

HideOnShow hide field on show page

func HideOnUpdate

func HideOnUpdate() RefineAnnotation

HideOnUpdate hide field on form

func Icon

func Icon(icon string) RefineAnnotation

Icon define icon of entity that will be shown on navigations, breadcrumbs e.t.c.

func ImageField

func ImageField() RefineAnnotation

ImageField mark field as an image field

func Label

func Label(label string) RefineAnnotation

Label define label of field todo: implement generator

func MainImageField

func MainImageField() RefineAnnotation

MainImageField mark field as a main image field

func NoCreate

func NoCreate() RefineAnnotation

NoCreate disable entity creation form

func NoEdit

func NoEdit() RefineAnnotation

NoEdit disable entity edit form

func NoList

func NoList() RefineAnnotation

NoList disable entity browse, also hide from navigation

func NoShow

func NoShow() RefineAnnotation

NoShow disable entity show page

func OnlyOnForm

func OnlyOnForm() RefineAnnotation

OnlyOnForm show field only on form

func OnlyOnList

func OnlyOnList() RefineAnnotation

OnlyOnList show field only on list

func OnlyOnShow

func OnlyOnShow() RefineAnnotation

OnlyOnShow show field only on show page

func Prefix

func Prefix(prefix string) RefineAnnotation

Prefix add prefix to value of field todo: implement generator

func RichTextField

func RichTextField() RefineAnnotation

RichTextField mark field as a rich text field (wysiwyg editor)

func Suffix

func Suffix(suffix string) RefineAnnotation

Suffix add suffix to value of field

func TitleField

func TitleField() RefineAnnotation

TitleField mark field as a title field

func URLField

func URLField() RefineAnnotation

URLField mark field as an url field

func View

func View(name string) RefineAnnotation

View define field views on list and show

func ViewOnForm

func ViewOnForm(name string) RefineAnnotation

ViewOnForm define field view on form

func ViewOnList

func ViewOnList(name string) RefineAnnotation

ViewOnList define field view on list

func ViewOnShow

func ViewOnShow(name string) RefineAnnotation

ViewOnShow define field view on show page

func (RefineAnnotation) Merge

Merge implements the schema.Merger interface.

func (RefineAnnotation) Name

func (ra RefineAnnotation) Name() string

Name annotation

type RefineGen

type RefineGen struct {
	Extension *Extension
	Entities  []ent.Interface
	Graph     *gen.Graph
	SkipModes SkipModes
	Ops       []gen.Op
	Prefix    string
}

func NewRefineGen

func NewRefineGen(extension *Extension, graph *gen.Graph) *RefineGen

func (*RefineGen) Generate

func (rg *RefineGen) Generate()

func (*RefineGen) GetJSDependencies

func (rg *RefineGen) GetJSDependencies() JSDeps

type SkipModes

type SkipModes struct {
	SkipAll                 int
	SkipEnumField           entgql.SkipMode
	SkipMutationCreateInput entgql.SkipMode
	SkipMutationUpdateInput entgql.SkipMode
	SkipOrderField          entgql.SkipMode
	SkipWhereInput          entgql.SkipMode
}

func (*SkipModes) Cast

func (sm *SkipModes) Cast(value int) entgql.SkipMode

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL