Refactor workflow code to be more UI-friendly. (#2240)

The main goal of this change is to make not started and finished workflows
visible in the UI. Without this it was possible to create a workflow with
-skip_start option, and then it won't be visible in the UI thus not giving any
ability to actually start it. Also once workflow finishes it was disappearing
from the UI thus not giving any ability to see how it finished and whether it
existed at all or not. Besides this UI problem also workflows were never deleted
from the topo server and were accumulating indefinitely. This change makes
everything from topo server visible in the UI thus giving ability for the user
to delete already finished workflows.

This UI improvement allows the validator workflow to become less awkward and
actually finish once all fixers are done working. I'm modifying the validator
workflow to do that here as well.
This commit is contained in:
Pavel Ivanov 2016-11-11 11:06:47 -08:00 коммит произвёл GitHub
Родитель ed54a93a25
Коммит 30ee782478
16 изменённых файлов: 195 добавлений и 152 удалений

Просмотреть файл

@ -156,12 +156,18 @@ func (*SwapWorkflowFactory) Init(workflowProto *workflowpb.Workflow, args []stri
// Instantiate is a part of workflow.Factory interface. It instantiates workflow.Workflow object from
// workflowpb.Workflow protobuf object.
func (*SwapWorkflowFactory) Instantiate(workflowProto *workflowpb.Workflow) (workflow.Workflow, error) {
func (*SwapWorkflowFactory) Instantiate(workflowProto *workflowpb.Workflow, rootNode *workflow.Node) (workflow.Workflow, error) {
data := &swapWorkflowData{}
if err := json.Unmarshal(workflowProto.Data, data); err != nil {
return nil, err
}
return &Swap{keyspace: data.Keyspace, sql: data.SQL}, nil
rootNode.Message = fmt.Sprintf("Schema swap is executed on the keyspace %s", data.Keyspace)
return &Swap{
keyspace: data.Keyspace,
sql: data.SQL,
rootUINode: rootNode,
}, nil
}
// Run is a part of workflow.Workflow interface. This is the main entrance point of the schema swap workflow.
@ -171,17 +177,9 @@ func (schemaSwap *Swap) Run(ctx context.Context, manager *workflow.Manager, work
schemaSwap.topoServer = topo.GetServer()
schemaSwap.tabletClient = tmclient.NewTabletManagerClient()
rootUINode := workflow.NewNode()
rootUINode.PopulateFromWorkflow(workflowInfo)
rootUINode.State = workflowpb.WorkflowState_Running
rootUINode.Display = workflow.NodeDisplayIndeterminate
rootUINode.Message = fmt.Sprintf("Schema swap is executed on the keyspace %s", schemaSwap.keyspace)
if err := manager.NodeManager().AddRootNode(rootUINode); err != nil {
return err
}
defer manager.NodeManager().RemoveRootNode(rootUINode)
schemaSwap.rootUINode.Display = workflow.NodeDisplayIndeterminate
schemaSwap.rootUINode.BroadcastChanges(false /* updateChildren */)
schemaSwap.rootUINode = rootUINode
return schemaSwap.executeSwap()
}

Просмотреть файл

@ -44,6 +44,11 @@ func init() {
commandWorkflowStop,
"<uuid>",
"Stops the workflow."})
addCommand(workflowsGroupName, command{
"WorkflowDelete",
commandWorkflowDelete,
"<uuid>",
"Deletes the finished or not started workflow."})
addCommand(workflowsGroupName, command{
"WorkflowWait",
commandWorkflowWait,
@ -119,6 +124,21 @@ func commandWorkflowStop(ctx context.Context, wr *wrangler.Wrangler, subFlags *f
return WorkflowManager.Stop(ctx, uuid)
}
func commandWorkflowDelete(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error {
if WorkflowManager == nil {
return fmt.Errorf("no workflow.Manager registered")
}
if err := subFlags.Parse(args); err != nil {
return err
}
if subFlags.NArg() != 1 {
return fmt.Errorf("the <uuid> argument is required for the WorkflowDelete command")
}
uuid := subFlags.Arg(0)
return WorkflowManager.Delete(ctx, uuid)
}
func commandWorkflowWait(ctx context.Context, wr *wrangler.Wrangler, subFlags *flag.FlagSet, args []string) error {
if WorkflowManager == nil {
return fmt.Errorf("no workflow.Manager registered")

Просмотреть файл

@ -42,8 +42,9 @@ type Factory interface {
Init(w *workflowpb.Workflow, args []string) error
// Instantiate loads a workflow from the proto representation
// into an in-memory Workflow object.
Instantiate(w *workflowpb.Workflow) (Workflow, error)
// into an in-memory Workflow object. rootNode is the root UI node
// representing the workflow.
Instantiate(w *workflowpb.Workflow, rootNode *Node) (Workflow, error)
}
// Manager is the main Workflow manager object.
@ -78,6 +79,9 @@ type runningWorkflow struct {
// topo server.
wi *topo.WorkflowInfo
// rootNode is the root UI node corresponding to this workflow.
rootNode *Node
// cancel is the method associated with the context that runs
// the workflow.
cancel context.CancelFunc
@ -178,34 +182,17 @@ func (m *Manager) loadAndStartJobsLocked() {
log.Errorf("Failed to load workflow %v, will not start it: %v", uuid, err)
continue
}
if wi.State != workflowpb.WorkflowState_Running {
continue
}
// Ask the factory to create the running workflow.
factory, ok := factories[wi.FactoryName]
if !ok {
log.Errorf("Saved workflow %v is using factory name %v but no such factory registered, will not start it", uuid, wi.FactoryName)
continue
}
w, err := factory.Instantiate(wi.Workflow)
rw, err := m.instantiateWorkflow(wi.Workflow)
if err != nil {
log.Errorf("Failed to instantiate workflow %v from factory %v, will not start it: %v", uuid, wi.FactoryName, err)
continue
}
rw.wi = wi
// Create a context to run it, and save the runningWorkflow.
ctx, cancel := context.WithCancel(m.ctx)
rw := &runningWorkflow{
wi: wi,
cancel: cancel,
workflow: w,
done: make(chan struct{}),
if rw.wi.State == workflowpb.WorkflowState_Running {
m.runWorkflow(rw)
}
m.workflows[uuid] = rw
// And run it in the background.
go m.runWorkflow(ctx, uuid, rw)
}
}
@ -235,17 +222,53 @@ func (m *Manager) Create(ctx context.Context, factoryName string, args []string)
if err := factory.Init(w, args); err != nil {
return "", err
}
rw, err := m.instantiateWorkflow(w)
if err != nil {
return "", err
}
// Now save the workflow in the topo server.
_, err := m.ts.CreateWorkflow(ctx, w)
rw.wi, err = m.ts.CreateWorkflow(ctx, w)
if err != nil {
return "", err
}
// And we're done.
log.Infof("Created workflow %s (%s, %s)", w.Uuid, factoryName, w.Name)
return w.Uuid, nil
}
func (m *Manager) instantiateWorkflow(w *workflowpb.Workflow) (*runningWorkflow, error) {
rw := &runningWorkflow{
wi: &topo.WorkflowInfo{
Workflow: w,
},
rootNode: NewNode(),
done: make(chan struct{}),
}
rw.rootNode.Name = w.Name
rw.rootNode.PathName = w.Uuid
rw.rootNode.Path = "/" + rw.rootNode.PathName
rw.rootNode.State = w.State
factory, ok := factories[w.FactoryName]
if !ok {
return nil, fmt.Errorf("no factory named %v is registered", w.FactoryName)
}
var err error
rw.workflow, err = factory.Instantiate(w, rw.rootNode)
if err != nil {
return nil, err
}
m.workflows[w.Uuid] = rw
if err := m.nodeManager.AddRootNode(rw.rootNode); err != nil {
return nil, err
}
return rw, nil
}
// Start will start a Workflow. It will load it in memory, update its
// status to Running, and call its Run() method.
func (m *Manager) Start(ctx context.Context, uuid string) error {
@ -257,55 +280,40 @@ func (m *Manager) Start(ctx context.Context, uuid string) error {
return fmt.Errorf("manager not running")
}
// Make sure it is not running already.
if _, ok := m.workflows[uuid]; ok {
return fmt.Errorf("workflow %v is already running", uuid)
rw, ok := m.workflows[uuid]
if !ok {
return fmt.Errorf("Cannot find workflow %v in the workflow list", uuid)
}
// Load it from the topo server, make sure it has the right state.
wi, err := m.ts.GetWorkflow(ctx, uuid)
if err != nil {
return err
}
if wi.State != workflowpb.WorkflowState_NotStarted {
return fmt.Errorf("workflow with uuid %v is in state %v", uuid, wi.State)
}
factory, ok := factories[wi.FactoryName]
if !ok {
return fmt.Errorf("workflow %v is using factory name %v but no such factory registered", uuid, wi.FactoryName)
if rw.wi.State != workflowpb.WorkflowState_NotStarted {
return fmt.Errorf("workflow with uuid %v is in state %v", uuid, rw.wi.State)
}
// Change its state in the topo server. Note we do that first,
// so if the running part fails, we will retry next time.
wi.State = workflowpb.WorkflowState_Running
wi.StartTime = time.Now().Unix()
if err := m.ts.SaveWorkflow(ctx, wi); err != nil {
rw.wi.State = workflowpb.WorkflowState_Running
rw.wi.StartTime = time.Now().Unix()
if err := m.ts.SaveWorkflow(ctx, rw.wi); err != nil {
return err
}
// Ask the factory to create the running workflow.
w, err := factory.Instantiate(wi.Workflow)
if err != nil {
return err
}
// Create a context to run it, and save the runningWorkflow.
ctx, cancel := context.WithCancel(m.ctx)
rw := &runningWorkflow{
wi: wi,
cancel: cancel,
workflow: w,
done: make(chan struct{}),
}
m.workflows[uuid] = rw
// And run it in the background.
go m.runWorkflow(ctx, uuid, rw)
rw.rootNode.State = workflowpb.WorkflowState_Running
rw.rootNode.BroadcastChanges(false /* updateChildren */)
m.runWorkflow(rw)
return nil
}
func (m *Manager) runWorkflow(ctx context.Context, uuid string, rw *runningWorkflow) {
func (m *Manager) runWorkflow(rw *runningWorkflow) {
// Create a context to run it.
var ctx context.Context
ctx, rw.cancel = context.WithCancel(m.ctx)
// And run it in the background.
go m.executeWorkflowRun(ctx, rw)
}
func (m *Manager) executeWorkflowRun(ctx context.Context, rw *runningWorkflow) {
defer close(rw.done)
// Run will block until one of three things happen:
@ -344,9 +352,7 @@ func (m *Manager) runWorkflow(ctx context.Context, uuid string, rw *runningWorkf
}
// We are not shutting down, but this workflow is done, or
// canceled. In any case, delete it from the running
// workflows, and change its topo Server state.
delete(m.workflows, uuid)
// canceled. In any case, change its topo Server state.
rw.wi.State = workflowpb.WorkflowState_Done
if err != nil {
rw.wi.Error = err.Error()
@ -355,6 +361,9 @@ func (m *Manager) runWorkflow(ctx context.Context, uuid string, rw *runningWorkf
if err := m.ts.SaveWorkflow(m.ctx, rw.wi); err != nil {
log.Errorf("Could not save workflow %v after completion: %v", rw.wi, err)
}
rw.rootNode.State = workflowpb.WorkflowState_Done
rw.rootNode.BroadcastChanges(false /* updateChildren */)
}
// Stop stops the running workflow. It will cancel its context and
@ -381,6 +390,26 @@ func (m *Manager) Stop(ctx context.Context, uuid string) error {
return nil
}
// Delete deletes the finished or not started workflow.
func (m *Manager) Delete(ctx context.Context, uuid string) error {
m.mu.Lock()
defer m.mu.Unlock()
rw, ok := m.workflows[uuid]
if !ok {
return fmt.Errorf("No workflow with uuid %v", uuid)
}
if rw.wi.State == workflowpb.WorkflowState_Running {
return fmt.Errorf("Cannot delete running workflow")
}
if err := m.ts.DeleteWorkflow(m.ctx, rw.wi); err != nil {
log.Errorf("Could not delete workflow %v: %v", rw.wi, err)
}
m.nodeManager.RemoveRootNode(rw.rootNode)
delete(m.workflows, uuid)
return nil
}
// Wait waits for the provided workflow to end.
func (m *Manager) Wait(ctx context.Context, uuid string) error {
// Find the workflow.

Просмотреть файл

@ -94,6 +94,8 @@ func TestManagerRestart(t *testing.T) {
// Stop the manager.
cancel()
wg.Wait()
// Recreate the manager immitating restart.
m = NewManager(ts)
// Make sure the workflow is still in the topo server. This
// validates that interrupting the Manager leaves the jobs in

Просмотреть файл

@ -11,8 +11,6 @@ import (
log "github.com/golang/glog"
"golang.org/x/net/context"
"github.com/youtube/vitess/go/vt/topo"
workflowpb "github.com/youtube/vitess/go/vt/proto/workflow"
)
@ -173,13 +171,6 @@ func NewNode() *Node {
}
}
// PopulateFromWorkflow populates node's path and name from the workflow information.
func (n *Node) PopulateFromWorkflow(wi *topo.WorkflowInfo) {
n.Name = wi.Name
n.PathName = wi.Uuid
n.Path = "/" + n.PathName
}
// BroadcastChanges sends the new contents of the node to the watchers.
func (n *Node) BroadcastChanges(updateChildren bool) error {
n.nodeManager.mu.Lock()

Просмотреть файл

@ -69,12 +69,9 @@ func (sw *SleepWorkflow) Run(ctx context.Context, manager *Manager, wi *topo.Wor
sw.mu.Lock()
sw.manager = manager
sw.wi = wi
sw.node = NewNode()
sw.node.PopulateFromWorkflow(wi)
sw.node.Listener = sw
sw.node.State = workflowpb.WorkflowState_Running
sw.node.Display = NodeDisplayDeterminate
sw.node.Message = "This workflow is a test workflow that just sleeps for the provided amount of time."
sw.node.Actions = []*Action{
{
Name: pauseAction,
@ -88,10 +85,7 @@ func (sw *SleepWorkflow) Run(ctx context.Context, manager *Manager, wi *topo.Wor
},
}
sw.uiUpdateLocked()
if err := manager.NodeManager().AddRootNode(sw.node); err != nil {
return err
}
defer manager.NodeManager().RemoveRootNode(sw.node)
sw.node.BroadcastChanges(false /* updateChildren */)
sw.mu.Unlock()
for {
@ -221,13 +215,16 @@ func (f *SleepWorkflowFactory) Init(w *workflowpb.Workflow, args []string) error
}
// Instantiate is part of the workflow.Factory interface.
func (f *SleepWorkflowFactory) Instantiate(w *workflowpb.Workflow) (Workflow, error) {
func (f *SleepWorkflowFactory) Instantiate(w *workflowpb.Workflow, rootNode *Node) (Workflow, error) {
rootNode.Message = "This workflow is a test workflow that just sleeps for the provided amount of time."
data := &SleepWorkflowData{}
if err := json.Unmarshal(w.Data, data); err != nil {
return nil, err
}
return &SleepWorkflow{
data: data,
node: rootNode,
logger: logutil.NewMemoryLogger(),
}, nil
}

Просмотреть файл

@ -10,6 +10,7 @@ package topovalidator
import (
"fmt"
"sync"
log "github.com/golang/glog"
"golang.org/x/net/context"
@ -68,6 +69,12 @@ type Workflow struct {
// logger is the logger we export UI logs from.
logger *logutil.MemoryLogger
// rootUINode is the root node representing the workflow in the UI.
rootUINode *workflow.Node
// wg is a wait group that will be used to wait for all fixers to be done.
wg sync.WaitGroup
}
// workflowFixer contains all the information about a fixer.
@ -77,6 +84,7 @@ type workflowFixer struct {
fixer Fixer
actions []string
node *workflow.Node
wg *sync.WaitGroup
}
// AddFixer adds a Fixer to the Workflow. It will end up displaying a
@ -88,28 +96,21 @@ func (w *Workflow) AddFixer(name, message string, fixer Fixer, actions []string)
message: message,
fixer: fixer,
actions: actions,
wg: &w.wg,
})
}
// Run is part of the workflow.Workflow interface.
func (w *Workflow) Run(ctx context.Context, manager *workflow.Manager, wi *topo.WorkflowInfo) error {
// Create a UI Node.
node := workflow.NewNode()
node.PopulateFromWorkflow(wi)
node.State = workflowpb.WorkflowState_Running
node.Display = workflow.NodeDisplayDeterminate
node.Message = "Validates the Topology and proposes fixes for known issues."
w.uiUpdate(node)
if err := manager.NodeManager().AddRootNode(node); err != nil {
return err
}
defer manager.NodeManager().RemoveRootNode(node)
w.uiUpdate()
w.rootUINode.Display = workflow.NodeDisplayDeterminate
w.rootUINode.BroadcastChanges(false /* updateChildren */)
// Run all the validators. They may add fixers.
for name, v := range validators {
w.logger.Infof("Running validator: %v", name)
w.uiUpdate(node)
node.BroadcastChanges(false /* updateChildren */)
w.uiUpdate()
w.rootUINode.BroadcastChanges(false /* updateChildren */)
err := v.Audit(ctx, manager.TopoServer(), w)
if err != nil {
w.logger.Errorf("Validator %v failed: %v", name, err)
@ -124,8 +125,9 @@ func (w *Workflow) Run(ctx context.Context, manager *workflow.Manager, wi *topo.
w.logger.Errorf("No problem found")
}
for i, f := range w.fixers {
w.wg.Add(1)
f.node = workflow.NewNode()
node.Children = append(node.Children, f.node)
w.rootUINode.Children = append(w.rootUINode.Children, f.node)
f.node.PathName = fmt.Sprintf("%v", i)
f.node.Name = f.name
f.node.Message = f.message
@ -137,23 +139,30 @@ func (w *Workflow) Run(ctx context.Context, manager *workflow.Manager, wi *topo.
}
f.node.Listener = f
}
w.uiUpdate(node)
node.BroadcastChanges(true /* updateChildren */)
w.uiUpdate()
w.rootUINode.BroadcastChanges(true /* updateChildren */)
// And wait for the workflow to be terminated.
// And wait for the workflow to be done.
fixersChan := make(chan struct{})
go func(wg *sync.WaitGroup) {
wg.Wait()
close(fixersChan)
}(&w.wg)
select {
case <-ctx.Done():
return ctx.Err()
case <-fixersChan:
return nil
}
}
// uiUpdate updates the computed parts of the Node, based on the
// current state. Needs to be called from inside node.Modify.
func (w *Workflow) uiUpdate(node *workflow.Node) {
// current state.
func (w *Workflow) uiUpdate() {
c := len(validators)
node.Progress = 100 * w.runCount / c
node.ProgressMessage = fmt.Sprintf("%v/%v", w.runCount, c)
node.Log = w.logger.String()
w.rootUINode.Progress = 100 * w.runCount / c
w.rootUINode.ProgressMessage = fmt.Sprintf("%v/%v", w.runCount, c)
w.rootUINode.Log = w.logger.String()
}
// Action is part of the workflow.ActionListener interface.
@ -173,6 +182,7 @@ func (f *workflowFixer) Action(ctx context.Context, path, name string) error {
f.node.Message = "Addressed(" + name + "): " + f.node.Message
f.node.Display = workflow.NodeDisplayNone
f.node.BroadcastChanges(false /* updateChildren */)
f.wg.Done()
return nil
}
@ -191,8 +201,11 @@ func (f *WorkflowFactory) Init(w *workflowpb.Workflow, args []string) error {
}
// Instantiate is part of the workflow.Factory interface.
func (f *WorkflowFactory) Instantiate(w *workflowpb.Workflow) (workflow.Workflow, error) {
func (f *WorkflowFactory) Instantiate(w *workflowpb.Workflow, rootNode *workflow.Node) (workflow.Workflow, error) {
rootNode.Message = "Validates the Topology and proposes fixes for known issues."
return &Workflow{
logger: logutil.NewMemoryLogger(),
logger: logutil.NewMemoryLogger(),
rootUINode: rootNode,
}, nil
}

Просмотреть файл

@ -28,5 +28,5 @@
</head>
<body class="flex-column">
<vt-app-root class="flex-column flex-grow">Loading...</vt-app-root>
<script type="text/javascript" src="inline.js"></script><script type="text/javascript" src="styles.07f8743f5392cfdfbcb5.bundle.js"></script><script type="text/javascript" src="main.8c655da3f055985098e2.bundle.js"></script></body>
<script type="text/javascript" src="inline.js"></script><script type="text/javascript" src="styles.07f8743f5392cfdfbcb5.bundle.js"></script><script type="text/javascript" src="main.173f8ea8a9d98ab9592e.bundle.js"></script></body>
</html>

Просмотреть файл

@ -1 +1 @@
!function(e){function __webpack_require__(r){if(t[r])return t[r].exports;var n=t[r]={i:r,l:!1,exports:{}};return e[r].call(n.exports,n,n.exports,__webpack_require__),n.l=!0,n.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,o,c){for(var _,a,i,u=0,p=[];u<t.length;u++)a=t[u],n[a]&&p.push(n[a][0]),n[a]=0;for(_ in o)if(Object.prototype.hasOwnProperty.call(o,_)){var f=o[_];switch(typeof f){case"object":e[_]=function(r){var t=r.slice(1),n=r[0];return function(r,o,c){e[n].apply(this,[r,o,c].concat(t))}}(f);break;case"function":e[_]=f;break;default:e[_]=e[f]}}for(r&&r(t,o,c);p.length;)p.shift()();if(c)for(u=0;u<c.length;u++)i=__webpack_require__(__webpack_require__.s=c[u]);return i};var t={},n={2:0};__webpack_require__.e=function(e){function onScriptComplete(){t.onerror=t.onload=null,clearTimeout(o);var r=n[e];0!==r&&(r&&r[1](new Error("Loading chunk "+e+" failed.")),n[e]=void 0)}if(0===n[e])return Promise.resolve();if(n[e])return n[e][2];var r=document.getElementsByTagName("head")[0],t=document.createElement("script");t.type="text/javascript",t.charset="utf-8",t.async=!0,t.timeout=12e4,t.src=__webpack_require__.p+""+e+"."+{0:"8c655da3f055985098e2",1:"07f8743f5392cfdfbcb5"}[e]+".chunk.js";var o=setTimeout(onScriptComplete,12e4);t.onerror=t.onload=onScriptComplete,r.appendChild(t);var c=new Promise(function(r,t){n[e]=[r,t]});return n[e][2]=c},__webpack_require__.m=e,__webpack_require__.c=t,__webpack_require__.i=function(e){return e},__webpack_require__.d=function(e,r,t){Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},__webpack_require__.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return __webpack_require__.d(r,"a",r),r},__webpack_require__.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},__webpack_require__.p="",__webpack_require__.oe=function(e){throw console.error(e),e}}(function(e){for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r))switch(typeof e[r]){case"function":break;case"object":e[r]=function(r){var t=r.slice(1),n=e[r[0]];return function(e,r,o){n.apply(this,[e,r,o].concat(t))}}(e[r]);break;default:e[r]=e[e[r]]}return e}([]));
!function(e){function __webpack_require__(r){if(t[r])return t[r].exports;var n=t[r]={i:r,l:!1,exports:{}};return e[r].call(n.exports,n,n.exports,__webpack_require__),n.l=!0,n.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,o,c){for(var _,a,i,u=0,p=[];u<t.length;u++)a=t[u],n[a]&&p.push(n[a][0]),n[a]=0;for(_ in o)if(Object.prototype.hasOwnProperty.call(o,_)){var f=o[_];switch(typeof f){case"object":e[_]=function(r){var t=r.slice(1),n=r[0];return function(r,o,c){e[n].apply(this,[r,o,c].concat(t))}}(f);break;case"function":e[_]=f;break;default:e[_]=e[f]}}for(r&&r(t,o,c);p.length;)p.shift()();if(c)for(u=0;u<c.length;u++)i=__webpack_require__(__webpack_require__.s=c[u]);return i};var t={},n={2:0};__webpack_require__.e=function(e){function onScriptComplete(){t.onerror=t.onload=null,clearTimeout(o);var r=n[e];0!==r&&(r&&r[1](new Error("Loading chunk "+e+" failed.")),n[e]=void 0)}if(0===n[e])return Promise.resolve();if(n[e])return n[e][2];var r=document.getElementsByTagName("head")[0],t=document.createElement("script");t.type="text/javascript",t.charset="utf-8",t.async=!0,t.timeout=12e4,t.src=__webpack_require__.p+""+e+"."+{0:"173f8ea8a9d98ab9592e",1:"07f8743f5392cfdfbcb5"}[e]+".chunk.js";var o=setTimeout(onScriptComplete,12e4);t.onerror=t.onload=onScriptComplete,r.appendChild(t);var c=new Promise(function(r,t){n[e]=[r,t]});return n[e][2]=c},__webpack_require__.m=e,__webpack_require__.c=t,__webpack_require__.i=function(e){return e},__webpack_require__.d=function(e,r,t){Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},__webpack_require__.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return __webpack_require__.d(r,"a",r),r},__webpack_require__.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},__webpack_require__.p="",__webpack_require__.oe=function(e){throw console.error(e),e}}(function(e){for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r))switch(typeof e[r]){case"function":break;case"object":e[r]=function(r){var t=r.slice(1),n=e[r[0]];return function(e,r,o){n.apply(this,[e,r,o].concat(t))}}(e[r]);break;default:e[r]=e[e[r]]}return e}([]));

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Двоичные данные
web/vtctld2/app/main.173f8ea8a9d98ab9592e.bundle.js.gz Normal file

Двоичный файл не отображается.

Двоичный файл не отображается.

Просмотреть файл

@ -1,6 +1,4 @@
<div *ngIf="redirect === ''" class="vt-toolbar vt-padding">
<md-icon class="vt-menu" (click)="menu.toggle($event)">menu</md-icon>
<p-menu #menu popup="popup" [model]="actions"></p-menu>
<h1 class="vt-title">{{title}}</h1>
</div>
<div *ngIf="redirect !== ''" class="vt-toolbar vt-padding">

Просмотреть файл

@ -11,8 +11,6 @@ import { DialogSettings } from '../shared/dialog/dialog-settings';
import { PrepareResponse } from '../shared/prepare-response';
import { NewWorkflowFlags } from '../shared/flags/workflow.flags';
import { MenuItem } from 'primeng/primeng';
@Component({
selector: 'vt-tasks',
templateUrl: './workflow-list.component.html',
@ -25,7 +23,7 @@ import { MenuItem } from 'primeng/primeng';
})
export class WorkflowListComponent implements OnDestroy, OnInit {
title = 'Running Workflows';
title = 'Workflows';
redirect = '';
workflows = [
new Node('Horizontal Resharding Workflow', '/UU130429', [
@ -60,8 +58,6 @@ export class WorkflowListComponent implements OnDestroy, OnInit {
];
dialogSettings: DialogSettings;
dialogContent: DialogContent;
private actions: MenuItem[];
private running = true;
constructor(private workflowService: WorkflowService) {}
@ -71,7 +67,6 @@ export class WorkflowListComponent implements OnDestroy, OnInit {
});
this.dialogContent = new DialogContent();
this.dialogSettings = new DialogSettings();
this.actions = [{label: 'Toggle Running / Non-running Workflows', command: (event) => {this.toggleRunning(); }}];
// Resharding Workflow Example
this.updateWorkFlow('/UU130429', {
@ -398,15 +393,5 @@ dolore magnam aliquam quaerat voluptatem.'});
canDeactivate(): Observable<boolean> | boolean {
return !this.dialogSettings.pending;
}
toggleRunning() {
if (this.running) {
this.running = false;
this.title = 'Stopped Workflows';
} else {
this.running = true;
this.title = 'Running Workflows';
}
}
}

Просмотреть файл

@ -24,7 +24,7 @@
<span class="vt-workflow-action">
<button md-raised-button *ngIf="workflow.isNotStarted()" (click)="startClicked($event); false">Start</button>
<button md-raised-button *ngIf="workflow.isRunning()" (click)="stopClicked($event); false">Stop</button>
<button md-raised-button *ngIf="workflow.isDone()" (click)="deleteClicked($event); false">Delete</button>
<button md-raised-button *ngIf="!workflow.isRunning()" (click)="deleteClicked($event); false">Delete</button>
</span>
</div>
</header>

Просмотреть файл

@ -80,7 +80,12 @@ export class WorkflowComponent implements OnInit {
// (click)="startClicked($event); false".
startClicked(event) {
event.stopPropagation();
console.log('startClicked(%s)', this.workflow.path);
this.dialogSettings = new DialogSettings('Start', `Start ${this.workflow.name}`,
`Are you sure you want to start ${this.workflow.name}?`,
`There was a problem starting ${this.workflow.name}:`);
let flags = new WorkflowFlags(this.workflow.getId()).flags;
this.dialogContent = new DialogContent('workflow_uuid', flags, {}, undefined, 'WorkflowStart');
this.dialogSettings.toggleModal();
}
stopClicked(event) {
@ -95,6 +100,11 @@ export class WorkflowComponent implements OnInit {
deleteClicked(event) {
event.stopPropagation();
console.log('deleteClicked(%s)', this.workflow.path);
this.dialogSettings = new DialogSettings('Delete', `Delete ${this.workflow.name}`,
`Are you sure you want to delete ${this.workflow.name}?`,
`There was a problem deleting ${this.workflow.name}:`);
let flags = new WorkflowFlags(this.workflow.getId()).flags;
this.dialogContent = new DialogContent('workflow_uuid', flags, {}, undefined, 'WorkflowDelete');
this.dialogSettings.toggleModal();
}
}