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

feat: Custom renderer #241

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions api/renderers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package api

import "fmt"

type Renderers struct {
Components []RenderComponent `json:"components,omitempty"`
Properties []RenderComponent `json:"properties,omitempty"`
}

type RenderComponent struct {
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
JSX string `json:"jsx,omitempty"`
}

func (c *RenderComponent) Key(isProp bool) string {
prefix := "component"
if isProp {
prefix = "property"
}

if c.Type != "" {
return fmt.Sprintf("%s_%s_%s", prefix, c.Type, c.Name)
}

return fmt.Sprintf("%s_%s", prefix, c.Name)
}
3 changes: 3 additions & 0 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/flanksource/incident-commander/events"
"github.com/flanksource/incident-commander/jobs"
"github.com/flanksource/incident-commander/snapshot"
"github.com/flanksource/incident-commander/topology"
"github.com/flanksource/incident-commander/utils"
)

Expand Down Expand Up @@ -69,6 +70,8 @@ var Serve = &cobra.Command{
e.GET("/snapshot/incident/:id", snapshot.Incident)
e.GET("/snapshot/config/:id", snapshot.Config)

e.GET("/custom_renderer", topology.GetCustomRenderer)

// Serve openapi schemas
schemaServer, err := utils.HTTPFileserver(openapi.Schemas)
if err != nil {
Expand Down
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/google/cel-go v0.13.0
github.com/google/uuid v1.3.0
github.com/jackc/pgx/v4 v4.18.0
github.com/jvatic/goja-babel v0.0.0-20230204121733-ac82a55cfa50
github.com/labstack/echo/v4 v4.7.2
github.com/microsoft/kiota-authentication-azure-go v0.6.0
github.com/microsoftgraph/msgraph-sdk-go v0.54.0
Expand Down Expand Up @@ -57,6 +58,8 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.17.7 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/cloudflare/circl v1.3.1 // indirect
github.com/dlclark/regexp2 v1.8.0 // indirect
github.com/dop251/goja v0.0.0-20230203172422-5460598cfa32 // indirect
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
github.com/flanksource/gomplate/v3 v3.20.1 // indirect
github.com/go-errors/errors v1.0.1 // indirect
Expand All @@ -65,6 +68,7 @@ require (
github.com/go-git/go-git/v5 v5.5.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/inflect v0.19.0 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
Expand Down Expand Up @@ -187,7 +191,7 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/microsoft/kiota-abstractions-go v0.17.0 // indirect
github.com/microsoft/kiota-abstractions-go v0.17.0
github.com/microsoft/kiota-http-go v0.14.0 // indirect
github.com/microsoft/kiota-serialization-json-go v0.8.0 // indirect
github.com/microsoft/kiota-serialization-text-go v0.7.0 // indirect
Expand Down
16 changes: 16 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,10 @@ github.com/dgryski/go-sip13 v0.0.0-20200911182023-62edffca9245/go.mod h1:vAd38F8
github.com/digitalocean/godo v1.78.0/go.mod h1:GBmu8MkjZmNARE7IXRPmkbbnocNN8+uBm0xbEVw2LCs=
github.com/digitalocean/godo v1.81.0/go.mod h1:BPCqvwbjbGqxuUnIKB4EvS/AX7IDnNmt5fwvIkWo+ew=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.8.0 h1:rJD5HeGIT/2b5CDk63FVCwZA3qgYElfg+oQK7uH5pfE=
github.com/dlclark/regexp2 v1.8.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
Expand All @@ -556,6 +560,11 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja v0.0.0-20230203172422-5460598cfa32 h1:audXtK7nV3y4W9ckAxRBE+eQV5Bljf5Non4NTa9kLVE=
github.com/dop251/goja v0.0.0-20230203172422-5460598cfa32/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
Expand Down Expand Up @@ -717,6 +726,8 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
Expand Down Expand Up @@ -1132,6 +1143,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/jvatic/goja-babel v0.0.0-20230204121733-ac82a55cfa50 h1:ewpo/HSvn4zCct4LvULM3Oc1PB9gDUR2f5RzgONOVIY=
github.com/jvatic/goja-babel v0.0.0-20230204121733-ac82a55cfa50/go.mod h1:e6baxuoF3V4g/q0HMjzOtziMK//6Ui0RjfwbFgPQWB4=
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
Expand Down Expand Up @@ -1605,6 +1618,8 @@ github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stvp/assert v0.0.0-20170616060220-4bc16443988b h1:GlTM/aMVIwU3luIuSN2SIVRuTqGPt1P97YxAi514ulw=
github.com/stvp/assert v0.0.0-20170616060220-4bc16443988b/go.mod h1:CC7OXV9IjEZRA+znA6/Kz5vbSwh69QioernOHeDCatU=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
Expand Down Expand Up @@ -2192,6 +2207,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
Expand Down
138 changes: 138 additions & 0 deletions topology/controllers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package topology

import (
"bytes"
"fmt"
"io"
"net/http"
"text/template"
"time"

"github.com/flanksource/commons/logger"
"github.com/flanksource/incident-commander/api"
babel "github.com/jvatic/goja-babel"
"github.com/labstack/echo/v4"
"github.com/patrickmn/go-cache"
)

var (
jsComponentTpl *template.Template
templateCache *cache.Cache
)

func init() {
tpl, err := template.New("registry").Parse(jsComponentRegistryTpl)
if err != nil {
logger.Fatalf("error parsing template 'jsComponentRegistryTpl'. %v", err)
}
jsComponentTpl = tpl

templateCache = cache.New(time.Hour*24, time.Hour*12)

if err := babel.Init(10); err != nil {
logger.Fatalf("failed to init babel: %v", err)
}
}

type component struct {
Name string
JS string
}

// GetCustomRenderer returns an application/javascript HTTP response
// with custom components and a registry.
// This registry needs to be used to select custom components
// for rendering of properties and cards.
func GetCustomRenderer(ctx echo.Context) error {
id := ctx.QueryParams().Get("id")
results, err := QueryRenderComponents(ctx.Request().Context(), id)
if err != nil {
return errorResponse(ctx, http.StatusBadRequest, err, "failed to query components by id")
}

var components = make(map[string]component)
for _, r := range results {
if err := compileComponents(components, r.Components, false); err != nil {
return errorResponse(ctx, http.StatusInternalServerError, err, "failed to compile components")
}

if err := compileComponents(components, r.Properties, true); err != nil {
return errorResponse(ctx, http.StatusInternalServerError, err, "failed to compile property components")
}
}

registryResp, err := renderComponents(components)
if err != nil {
return errorResponse(ctx, http.StatusInternalServerError, err, "failed to render components")
}

return ctx.Stream(http.StatusOK, "application/javascript", registryResp)
}

func compileComponents(output map[string]component, components []api.RenderComponent, isProp bool) error {
if len(components) == 0 {
return nil
}

for _, c := range components {
res, err := transformJSX(c.JSX)
if err != nil {
return fmt.Errorf("error transforming jsx: %w", err)
}

output[c.Key(isProp)] = component{
Name: c.Name,
JS: res,
}
}

return nil
}

// transformJSX transforms the provided jsx and also
// caches the result.
func transformJSX(jsx string) (string, error) {
if val, ok := templateCache.Get(jsx); ok {
return val.(string), nil
}

res, err := babel.TransformString(jsx, map[string]any{
"plugins": []string{
"transform-react-jsx",
"transform-block-scoping",
},
})
if err != nil {
return "", fmt.Errorf("error transforming jsx: %w", err)
}

templateCache.Set(jsx, res, cache.DefaultExpiration)

return res, nil
}

func renderComponents(components map[string]component) (io.Reader, error) {
var buf bytes.Buffer
if err := jsComponentTpl.Execute(&buf, components); err != nil {
return nil, fmt.Errorf("error generating components: %w", err)
}

return &buf, nil
}

const jsComponentRegistryTpl = `
{{range $k, $v := .}}
const {{$k}} = {{$v.JS}}
{{end}}
const componentRegistry = {
{{range $k, $v := .}}"{{$k}}": {{$k}},
{{end}}
};
`

func errorResponse(c echo.Context, code int, err error, msg string) error {
return c.JSON(code, api.HTTPErrorMessage{
Error: err.Error(),
Message: msg,
})
}
34 changes: 34 additions & 0 deletions topology/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package topology

import (
"context"
"encoding/json"
"fmt"

"github.com/flanksource/incident-commander/api"
"github.com/flanksource/incident-commander/db"
)

func QueryRenderComponents(ctx context.Context, systemTemplateID string) ([]api.Renderers, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@moshloop Should we keep this in db/topology.go as we keep all the database interactions in the db package

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assumed that was the convention I am supposed to follow. I took pkg/snapshot as a reference but looks like pkg/rules also does it.

rows, err := db.Gorm.WithContext(ctx).Table("templates").Select("spec->'renderers'").Where("id = ?", systemTemplateID).Rows()
if err != nil {
return nil, fmt.Errorf("failed to query renderers: %w", err)
}
defer rows.Close()

var results []api.Renderers
for rows.Next() {
var renderers api.Renderers
var s string
if err := rows.Scan(&s); err != nil {
return nil, fmt.Errorf("error scanning row: %w", err)
}

if err := json.Unmarshal([]byte(s), &renderers); err != nil {
return nil, fmt.Errorf("error unmarshalling to renderers: %w", err)
}
results = append(results, renderers)
}

return results, nil
}