js-ojus / flow Goto Github PK
View Code? Open in Web Editor NEWA tiny open source workflow engine written in Go (golang)
License: Apache License 2.0
A tiny open source workflow engine written in Go (golang)
License: Apache License 2.0
Hi, I have found that the following code is a bug:
var tx *sql.Tx
if otx == nil {
tx, err := db.Begin()
if err != nil {
return 0, err
}
defer tx.Rollback()
} else {
tx = otx
}
here tx, err := db.Begin()
will make a temporary variable tx
, so out of the if statement, tx also will be a nil value. Thus, when you call the interface which need parameter *sql.Tx
and you give a nil to it, it will cause a panic. For example:
gid, err := flow.Groups.New(nil, "nike", "G")
I give nil
to Groups.New
because I have called RegisterDB to make db is not null.
We can use this code to fix it:
var tx *sql.Tx
if otx == nil {
var err error
tx, err = db.Begin()
if err != nil {
return 0, err
}
defer tx.Rollback()
} else {
tx = otx
}
Do you have plans to provide a version that supports sqlite databases? I tried to change your code to support sqlite. The original SQL language is a bit different for mysql and sqlite. If you are considering using a golang orm library like xorm, the flow may have extensive database support.
Hi, I have read your code and has a little confused. When creating a new event without check the group id is singleton, that may be will cause we do not know who create this event if the group is type of 'G' and has more than one user. Yeah, the workflow also do not check the group type, so the event can be applied. Is there a problem?
How can I Use it , can you give me a demo ?
It would be good to include event triggered time in the messages.
Spurious column reference.
It will be good to add another method to list all the access contexts of a user.
Also src/github.com/js-ojus/flow/document.go:536
-- doc_id
to be replaced with id
.
I am trying like these:
dtID1, err := flow.DocTypes.New(tx, "图纸设计") //dtID1
if err != nil {
fmt.Println(err)
}
// dtID2, err := flow.DocTypes.New(tx, "图纸校核") //dtID1
// if err != nil {
// fmt.Println(err)
// }
_, err = flow.DocTypes.New(tx, "变更立项") //dtID2
if err != nil {
fmt.Println(err)
}
dsID1, err := flow.DocStates.New(tx, "设计中...") //初始化
if err != nil {
fmt.Println(err)
}
dsID2, err := flow.DocStates.New(tx, "校核中...") //委托创建
if err != nil {
fmt.Println(err)
}
_, err = flow.DocStates.New(tx, "审查中...")
if err != nil {
fmt.Println(err)
}
flow.DocStates.New(tx, "批注中...")
flow.DocStates.New(tx, "申报中...")
flow.DocStates.New(tx, "评估中...")
flow.DocStates.New(tx, "审批中...")
// flow.DocStates.New(tx, "DataApproved")
// flow.DocStates.New(tx, "ReportGen")
// flow.DocStates.New(tx, "ReportApproved")
daID1, err := flow.DocActions.New(tx, "提交设计", false) //改变状态设计中...为校核中...
if err != nil {
fmt.Println(err)
}
daID2, err := flow.DocActions.New(tx, "校核", false)
if err != nil {
fmt.Println(err)
}
daID3, err := flow.DocActions.New(tx, "审查", false)
if err != nil {
fmt.Println(err)
}
daID4, err := flow.DocActions.New(tx, "核定", true)
if err != nil {
fmt.Println(err)
}
daID5, err := flow.DocActions.New(tx, "评估", true)
if err != nil {
fmt.Println(err)
}
daID6, err := flow.DocActions.New(tx, "审批", false)
if err != nil {
fmt.Println(err)
}
daID7, err := flow.DocActions.New(tx, "立项", false)
if err != nil {
fmt.Println(err)
}
workflowID1, err := flow.Workflows.New(tx, "图纸设计流程", dtID1, dsID1) //初始状态是“设计中...”
if err != nil {
fmt.Println(err)
}
// workflowID2, err := flow.Workflows.New(tx, "图纸校核流程", dtID2, dsID2) //初始状态是“设计中...”
// if err != nil {
// fmt.Println(err)
// }
accessContextID1, err := flow.AccessContexts.New(tx, "Context")
if err != nil {
beego.Error(err)
}
// AddNode maps the given document state to the specified node. This
// map is consulted by the workflow when performing a state transition
// of the system.nodeID1
// _, err = flow.Workflows.AddNode(tx, dtID1, dsID1, accessContextID1, workflowID1, "图纸三角校审流程-设计", flow.NodeTypeLinear)
// if err != nil {
// fmt.Println(err)
// }
_, err = flow.Workflows.AddNode(tx, dtID1, dsID2, accessContextID1, workflowID1, "图纸三角校审流程-校核", flow.NodeTypeEnd)
if err != nil {
fmt.Println(err)
}
res, err := tx.Exec(`INSERT INTO users_master(first_name, last_name, email, active)
VALUES('秦', '晓川-1', '[email protected]', 1)`)
if err != nil {
log.Fatalf("%v\n", err)
}
uid, _ := res.LastInsertId()
uID1 := flow.UserID(uid)
_, err = flow.Groups.NewSingleton(tx, uID1)
res, err = tx.Exec(`INSERT INTO users_master(first_name, last_name, email, active)
VALUES('秦', '晓川-2', '[email protected]', 1)`)
if err != nil {
log.Fatalf("%v\n", err)
}
uid, _ = res.LastInsertId()
uID2 := flow.UserID(uid)
_, err = flow.Groups.NewSingleton(tx, uID2)
res, err = tx.Exec(`INSERT INTO users_master(first_name, last_name, email, active)
VALUES('秦', '晓川-3', '[email protected]', 1)`)
if err != nil {
log.Fatalf("%v\n", err)
}
uid, _ = res.LastInsertId()
uID3 := flow.UserID(uid)
_, err = flow.Groups.NewSingleton(tx, uID3)
res, err = tx.Exec(`INSERT INTO users_master(first_name, last_name, email, active)
VALUES('秦', '晓川-4', '[email protected]', 1)`)
if err != nil {
log.Fatalf("%v\n", err)
}
uid, _ = res.LastInsertId()
uID4 := flow.UserID(uid)
_, err = flow.Groups.NewSingleton(tx, uID4)
gID1 := fatal1(flow.Groups.New(tx, "设计人员组", "G")).(flow.GroupID)
gID2 := fatal1(flow.Groups.New(tx, "校核人员组", "G")).(flow.GroupID)
fatal0(flow.Groups.AddUser(tx, gID1, uID1))
fatal0(flow.Groups.AddUser(tx, gID1, uID2))
fatal0(flow.Groups.AddUser(tx, gID1, uID3))
fatal0(flow.Groups.AddUser(tx, gID2, uID2))
fatal0(flow.Groups.AddUser(tx, gID2, uID3))
fatal0(flow.Groups.AddUser(tx, gID2, uID4))
roleID1 := fatal1(flow.Roles.New(tx, "设计人员角色")).(flow.RoleID)
roleID2 := fatal1(flow.Roles.New(tx, "校核人员角色")).(flow.RoleID)
//给角色赋予action权限
fatal0(flow.Roles.AddPermissions(tx, roleID1, dtID1, []flow.DocActionID{daID1, daID2, daID3, daID4}))
fatal0(flow.Roles.AddPermissions(tx, roleID2, dtID1, []flow.DocActionID{daID1, daID2, daID3, daID4, daID5, daID6, daID7}))
//给用户组赋予角色
err = flow.AccessContexts.AddGroupRole(tx, accessContextID1, gID1, roleID1)
if err != nil {
beego.Error(err)
}
err = flow.AccessContexts.AddGroupRole(tx, accessContextID1, gID2, roleID2)
if err != nil {
beego.Error(err)
}
tx.Commit()
and then:
dtID1, err := flow.DocTypes.GetByName("图纸设计")
if err != nil {
beego.Error(err)
}
beego.Info(dtID1)
//查询context
accessContextID1, err := flow.AccessContexts.List("Context", 0, 0)
if err != nil {
beego.Error(err)
}
beego.Info(accessContextID1[0].ID)
beego.Info(flow.GroupID(1))
docNewInput := flow.DocumentsNewInput{
DocTypeID: dtID1.ID,
AccessContextID: accessContextID1[0].ID,
GroupID: 5, //groupId,
Title: "厂房布置图",
Data: "设计、制图: 秦晓川1, 校核: 秦晓川2",
}
// flow.Documents.New(tx, &docNewInput)
DocumentID1, err := flow.Documents.New(tx, &docNewInput)
if err != nil {
beego.Error(err)
}
// tx.Commit()
beego.Info(DocumentID1)
dsID2, err := flow.DocStates.GetByName("校核中...")
if err != nil {
fmt.Println(err)
}
beego.Info(dsID2)
daID2, err := flow.DocActions.GetByName("提交设计")
if err != nil {
fmt.Println(err)
}
beego.Info(daID2)
beego.Info(flow.GroupID(2))
docEventInput := flow.DocEventsNewInput{
DocTypeID: dtID1.ID, //flow.DocTypeID(1),
DocumentID: DocumentID1,
DocStateID: dsID2.ID,
DocActionID: daID2.ID, //flow.DocActionID(2),
GroupID: 6,
Text: "校核",
}
docEventID1, err := flow.DocEvents.New(tx, &docEventInput)
if err != nil {
beego.Error(err)
}
tx.Commit()
beego.Info(docEventID1)
myDocEvent, err := flow.DocEvents.Get(docEventID1)
if err != nil {
beego.Error(err)
}
beego.Info(myDocEvent)
// myWorkflow, err := flow.Workflows.Get(workflowId.ID)
// if err != nil {
// beego.Error(err)
// }
myWorkflow, err := flow.Workflows.GetByName("图纸设计流程")
if err != nil {
beego.Error(err)
}
beego.Info(myWorkflow)
//给出接受的组groupids
groupIds := []flow.GroupID{flow.GroupID(6)}
beego.Info(groupIds)
newDocStateId, err := myWorkflow.ApplyEvent(tx, myDocEvent, groupIds)
if err != nil {
beego.Error(err)
}
tx.Commit()
fmt.Println("newDocStateId=", newDocStateId, err)
I don't know how to use the flow. Can you give me some advice?
Currently we can fetch roles only by group. For a user, we have to first fetch the corresponding singleton group.
If a group received messages and a mailbox created for it, then an error would be raised if you try to delete it.
update or delete on table "wf_groups_master" violates foreign key constraint "wf_mailboxes_group_id_fkey" on table "wf_mailboxes"
Maybe Groups.Delete
should also delete the Group mailbox, or Mailboxes should provide a Delete
method?
We might need to list documents of multiple document types in single Accesscontext.
This workflow is what I need, and I'm going to make it in beego.Thanks.
While a new document is being added Instead of listing all the available transitions list only the applicable transitions. At least introduce a new method which does that.
When a document action is performed, the message generated by the event fails a uniqueness constraint test in wf_mailboxes
. There is an attempted duplicate delivery.
Hi, I think the node should refuse the request to apply event if it is in NodeTypeEnd. How do you think so?
the code:
func (n *Node) applyEvent(otx *sql.Tx, event *DocEvent, recipients []GroupID) (DocStateID, error) {
...
switch tnode.NodeType {
...
case NodeTypeBegin, NodeTypeEnd, NodeTypeLinear, NodeTypeBranch:
....
}
}
When I need to query the history, do I need to write another method by myself?#105
Documents should be accessed by a temporary access id which gets invalid when the user tries bookmarking the documents.
Its good to have a method to show up the root document even when requested for any child document
When a group is added to an access context it would be good to have a method which returns all the group members of that access context.
In Documents.Get
, there is a mismatch between the number of dynamically-bound parameters in the query string and the number of actual arguments given in QueryRow
.
How would I get all the accesscontexts of the user(not group)?
How would i check whether a user is member of the accesscontext?
Hi the information of flow is too little。can you make a turtorial that a blog or a website? i think is very useful for some people who is new beginner of flow.
I use "vue.js element vue-element-extends beego and flow" to make a very simple flow-ui(interface), which is still in the preliminary stage.
3xxx/engineercms#17
Originally posted by @yuedefeng in #101 (comment)
Hi, I have some question about workflow node after reading wf_workflow_nodes.sql. If I want to create two workflows for the same doctype, for example, A -> B -> C and E -> B -> F. Now, since the constraints of table wf_workflow_nodes.sql, the two flows on the node B will has the same ac_id and will have the same access context which means the same permission. So, why not change to unique(doctype_id, docstate_id, ac_id), so we can give the different permission to node B in the two or more wrokflows?
q := INSERT INTO wf_workflow_nodes(doctype_id, docstate_id, ac_id, workflow_id, name, type) VALUES(?, ?, ?, ?, ?)
Originally posted by @yuedefeng in #101 (comment)
Currently we can fetch permissions only by group. For a user, we have to first fetch the corresponding singleton group.
When I call myWorkflow.ApplyEvent, received the error ErrWorkflowInvalidAction ?
tstate, ok := ts[event.Action]
if !ok {
return 0, ErrWorkflowInvalidAction
}
Originally posted by @yuedefeng in #101 (comment)
Thank you very much for the previous example.
Can you perfect the example again?
For example, when I create a new documents, do I create their events at the same time? Or create a new event and apply it to workflow when I clicks the action button?
In the list of documents presented to the user, for each document, is it right to list all possible actions and next states according to transition? When the user clicks on the action button, do I just create a new event and then apply it to workflow?
When I create a new event, it returns DocEventID, but what workflow applyevent needs is an DocEvent. Feel that I do is not correct.
Both in listing as well as individual message 'get'.
Is this project stalled.
Are you going to put any examples in the repo.
I ask because it's quite useful code base for BPM
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.