smon/trigger.go

275 lines
5.2 KiB
Go

package main
import (
// External
werr "git.gibonuddevalla.se/go/wrappederror"
"github.com/expr-lang/expr"
"github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/parser"
"github.com/lib/pq"
// Standard
"database/sql"
"encoding/json"
"fmt"
"strings"
)
type Trigger struct {
ID int
Name string
SectionID int `db:"section_id"`
Expression string
Datapoints []string
DatapointValues map[string]any
}
type ExprRenamePatcher struct {
OldName string
NewName string
}
func (p ExprRenamePatcher) Visit(node *ast.Node) {
if n, ok := (*node).(*ast.IdentifierNode); ok && n.Value == p.OldName {
ast.Patch(node, &ast.IdentifierNode{Value: p.NewName})
}
}
func TriggerCreate(sectionID int, name string) (t Trigger, err error) { // {{{
t.SectionID = sectionID
t.Name = name
t.Expression = "false"
err = t.Update()
return
} // }}}
func TriggersRetrieve() (areas []Area, err error) { // {{{
areas = []Area{}
row := service.Db.Conn.QueryRow(`
WITH section_triggers AS (
SELECT
s.id AS id,
s.area_id,
s.name AS name,
jsonb_agg(
to_jsonb(t.*)
) AS triggers
FROM section s
LEFT JOIN "trigger" t ON t.section_id = s.id
GROUP BY
s.id, s.name)
SELECT
jsonb_agg(jsonsections)
FROM (
SELECT
a.id,
a.name,
jsonb_agg(
to_jsonb(
s.*
)
) AS sections
FROM area a
LEFT JOIN section_triggers s ON s.area_id = a.id
GROUP BY
a.id, a.name
) jsonsections
`,
)
var jsonData []byte
err = row.Scan(&jsonData)
if err != nil {
err = werr.Wrap(err)
return
}
if jsonData == nil {
return
}
err = json.Unmarshal(jsonData, &areas)
if err != nil {
err = werr.Wrap(err)
return
}
return
} // }}}
func TriggersRetrieveByDatapoint(datapointName string) (triggers []Trigger, err error) { // {{{
triggers = []Trigger{}
row := service.Db.Conn.QueryRow(`
SELECT jsonb_agg(t.*)
FROM public."trigger" t
WHERE
datapoints @> $1
`,
fmt.Sprintf(`["%s"]`, datapointName),
)
var data []byte
err = row.Scan(&data)
if err != nil {
err = werr.Wrap(err).WithData(datapointName)
return
}
// no triggers found for this datapoint.
if data == nil {
return
}
err = json.Unmarshal(data, &triggers)
if err != nil {
err = werr.Wrap(err).WithData(datapointName)
return
}
return
} // }}}
func TriggerRetrieve(id int) (trigger Trigger, err error) { // {{{
row := service.Db.Conn.QueryRow(`SELECT to_jsonb(t.*) FROM "trigger" t WHERE id=$1`, id)
var jsonData []byte
err = row.Scan(&jsonData)
if err != nil {
err = werr.Wrap(err)
return
}
err = json.Unmarshal(jsonData, &trigger)
return
} // }}}
func TriggerDelete(id int) (err error) { // {{{
_, err = service.Db.Conn.Exec(`DELETE FROM public.trigger WHERE id=$1`, id)
if err != nil {
return werr.Wrap(err).WithData(id)
}
return
} // }}}
func (t *Trigger) Validate() (ok bool, err error) { // {{{
if strings.TrimSpace(t.Name) == "" {
err = fmt.Errorf("Name can't be empty")
return
}
if strings.TrimSpace(t.Expression) == "" {
err = fmt.Errorf("Expression can't be empty")
return
}
return true, nil
} // }}}
func (t *Trigger) Update() (err error) { // {{{
var ok bool
if ok, err = t.Validate(); !ok {
return
}
if t.Datapoints == nil {
t.Datapoints = []string{}
}
jsonDatapoints, _ := json.Marshal(t.Datapoints)
if t.ID == 0 {
var row *sql.Row
row = service.Db.Conn.QueryRow(`
INSERT INTO "trigger"(name, section_id, expression, datapoints)
VALUES($1, $2, $3, $4)
RETURNING id
`,
t.Name,
t.SectionID,
t.Expression,
jsonDatapoints,
)
err = row.Scan(&t.ID)
if err != nil {
err = werr.Wrap(err).WithData(
struct {
SectionID int
Name string
Expression string
JsonDataPoints []byte
}{
t.SectionID,
t.Name,
t.Expression,
jsonDatapoints,
},
)
return
}
} else {
_, err = service.Db.Conn.Exec(`
UPDATE "trigger"
SET
name=$2,
expression=$3,
datapoints=$4
WHERE
id=$1
`,
t.ID,
t.Name,
t.Expression,
jsonDatapoints,
)
}
if pqErr, ok := err.(*pq.Error); ok {
err = werr.Wrap(err).WithData(
struct {
Trigger *Trigger
PostgresCode pq.ErrorCode
PostgresMsg string
}{
t,
pqErr.Code,
pqErr.Code.Name(),
})
} else if err != nil {
err = werr.Wrap(err).WithData(t)
}
return
} // }}}
func (t *Trigger) Run() (output any, err error) { // {{{
datapoints := make(map[string]Datapoint)
for _, dpname := range t.Datapoints {
var dp Datapoint
dp, err = DatapointRetrieve(0, dpname)
if err != nil {
err = werr.Wrap(err)
return
}
datapoints[dpname] = dp
}
t.DatapointValues = make(map[string]any)
for dpName, dp := range datapoints {
t.DatapointValues[dpName] = dp.LastDatapointValue.Value()
}
program, err := expr.Compile(t.Expression)
if err != nil {
return
}
output, err = expr.Run(program, t.DatapointValues)
if err != nil {
return
}
return
} // }}}
func (t *Trigger) RenameDatapoint(from, to string) error { // {{{
tree, err := parser.Parse(t.Expression)
if err != nil {
return werr.Wrap(err).WithData(t.Expression)
}
ast.Walk(&tree.Node, ExprRenamePatcher{from, to})
t.Expression = tree.Node.String()
return nil
} // }}}