commit 118ffd908f26fd206b9b89ea648c4edcf0164a8f Author: Mason Payne Date: Sun May 23 22:15:30 2021 -0600 inital working metl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab959e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +metl.exe diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/metl.iml b/.idea/metl.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/metl.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..4b655fe --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/watcherTasks.xml b/.idea/watcherTasks.xml new file mode 100644 index 0000000..97ad6d2 --- /dev/null +++ b/.idea/watcherTasks.xml @@ -0,0 +1,29 @@ + + + + + + + + \ No newline at end of file diff --git a/action-modifycolumn.go b/action-modifycolumn.go new file mode 100644 index 0000000..51cf4b2 --- /dev/null +++ b/action-modifycolumn.go @@ -0,0 +1,102 @@ +package main + +import ( + "errors" + "fmt" + "strings" + + "github.com/google/uuid" +) + +type ActionModifyColumn struct { + ActionID uuid.UUID + OutputSchema *Schema + InputSchema *Schema + Config ModifyColumnConfig + status ActionStatus + bus *Bus +} + +type ModifyColumnConfig struct { + ActionConfig +} + +func NewModifyColumn(config ModifyColumnConfig, b *Bus) (ActionModifyColumn, error) { + as := ActionModifyColumn{} + err := as.init(config, b) + if err != nil { + return ActionModifyColumn{}, err + } + return as, nil +} + +func (s *ActionModifyColumn) init(config ModifyColumnConfig, b *Bus) error { + if len(config.Inputs) <= 0 { + return errors.New(fmt.Sprintf("error no input schema defined. ActionID: %v", config.ActionID.String())) + } + s.InputSchema = config.Inputs[0].InputSchema + s.OutputSchema = config.OutputSchema + s.ActionID = config.ActionID + s.Config = config + s.status = ActionStatus{ + State: NotStarted, + HandledRows: 0, + ExpectedRows: -1, + } + s.bus = b + return nil +} + +func (s *ActionModifyColumn) Run(contentRow ContentRow) error { + + // convert the schema to the new one + newContentRow := ContentRow{ + Schema: s.Config.OutputSchema, + Values: make(map[uuid.UUID]string), + Raw: "", + Source: &s.ActionID, + } + + for _, col := range newContentRow.Schema.Columns { + value, ok := contentRow.Values[col.ID] + if !ok { + value = col.DefaultValue + } + newContentRow.Values[col.ID] = value + if strings.EqualFold(newContentRow.Raw, "") { + newContentRow.Raw = value + } else { + newContentRow.Raw = newContentRow.Raw + "," + value + } + } + return s.publishRow(newContentRow) +} + +func (s *ActionModifyColumn) publishRow(row ContentRow) error { + // TODO: decide on the method of transporting data to the next action + return s.bus.Run(s.ActionID, row) +} + +func (s *ActionModifyColumn) publishErrorRow(row ContentRow) error { + // TODO: decide on the method of transporting data to the next action + return nil +} + +func (s *ActionModifyColumn) Validate() error { + if len(s.Config.Inputs) <= 0 { + return errors.New(fmt.Sprintf("error no input schema defined. ActionID: %v", s.ActionID.String())) + } + return nil +} + +func (s *ActionModifyColumn) GetActionID() uuid.UUID { + return s.ActionID +} + +func (s *ActionModifyColumn) Status() ActionStatus { + return s.status +} + +func (s *ActionModifyColumn) GetActionConfig() ActionConfig { + return s.Config.ActionConfig +} diff --git a/action-stdin.go b/action-stdin.go new file mode 100644 index 0000000..fb34cac --- /dev/null +++ b/action-stdin.go @@ -0,0 +1,122 @@ +package main + +import ( + "bufio" + "errors" + "fmt" + "os" + "strings" + + "github.com/google/uuid" +) + +type ActionStdIn struct { + ActionID uuid.UUID + OutputSchema *Schema + Config StdInConfig + status ActionStatus + bus *Bus +} + +type StdInConfig struct { + ActionConfig + HasHeader bool +} + +func NewStdIn(config StdInConfig, b *Bus) (ActionStdIn, error) { + as := ActionStdIn{} + err := as.init(config, b) + if err != nil { + return ActionStdIn{}, err + } + return as, nil +} + +func (s *ActionStdIn) init(config StdInConfig, b *Bus) error { + s.OutputSchema = config.OutputSchema + s.ActionID = config.ActionID + s.Config = config + s.status = ActionStatus{ + State: NotStarted, + HandledRows: 0, + ExpectedRows: -1, + } + s.bus = b + return nil +} + +func (s *ActionStdIn) Run(_ ContentRow) error { + s.status.State = Running + rowsProcessed := int64(0) + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + potentialRow := scanner.Text() + // TODO: handle headers + if rowsProcessed <= 0 && s.Config.HasHeader { + // currently ignoring the first row always + rowsProcessed = rowsProcessed + 1 + continue + } + potentialColumns := strings.Split(potentialRow, ",") + contentRow := ContentRow{ + Schema: s.OutputSchema, + Values: make(map[uuid.UUID]string), + Raw: potentialRow, + Source: &s.ActionID, + } + rowValid := true + for i, c := range potentialColumns { + value := strings.TrimSpace(c) + valid, err := ValidateDataType(value, s.OutputSchema.Columns[i].Type) + if err != nil { + rowValid = false + } + rowValid = rowValid && valid + contentRow.Values[s.OutputSchema.Columns[i].ID] = value + } + if !rowValid { + _ = s.publishErrorRow(contentRow) + } + err := s.publishRow(contentRow) + if err != nil { + return errors.New(fmt.Sprintf("error publishing row. ActionID: %v | %v", s.ActionID, err.Error())) + } + rowsProcessed = rowsProcessed + 1 + s.status.HandledRows = rowsProcessed + } + s.status.State = Finished + + if err := scanner.Err(); err != nil { + return errors.New(fmt.Sprintf("error scanning input. ActionID: %v | %v", s.ActionID, err.Error())) + } + return nil +} + +func (s *ActionStdIn) publishRow(row ContentRow) error { + // TODO: decide on the method of transporting data to the next action + return s.bus.Run(s.ActionID, row) +} + +func (s *ActionStdIn) publishErrorRow(row ContentRow) error { + // TODO: decide on the method of transporting data to the next action + return nil +} + +func (s *ActionStdIn) Validate() error { + if len(s.Config.Inputs) <= 0 { + return errors.New(fmt.Sprintf("error no input schema defined. ActionID: %v", s.ActionID.String())) + } + return nil +} + +func (s *ActionStdIn) GetActionID() uuid.UUID { + return s.ActionID +} + +func (s *ActionStdIn) Status() ActionStatus { + return s.status +} + +func (s *ActionStdIn) GetActionConfig() ActionConfig { + return s.Config.ActionConfig +} diff --git a/action-stdout.go b/action-stdout.go new file mode 100644 index 0000000..65b3799 --- /dev/null +++ b/action-stdout.go @@ -0,0 +1,99 @@ +package main + +import ( + "errors" + "fmt" + "strings" + + "github.com/google/uuid" +) + +type ActionStdOut struct { + ActionID uuid.UUID + OutputSchema *Schema + InputSchema *Schema + Config StdOutConfig + status ActionStatus + bus *Bus +} + +type StdOutConfig struct { + ActionConfig + AddHeader bool +} + +func NewStdOut(config StdOutConfig, b *Bus) (ActionStdOut, error) { + as := ActionStdOut{} + err := as.init(config, b) + if err != nil { + return ActionStdOut{}, err + } + return as, nil +} + +func (s *ActionStdOut) init(config StdOutConfig, b *Bus) error { + if len(config.Inputs) <= 0 { + return errors.New(fmt.Sprintf("error no input schema defined. ActionID: %v", config.ActionID.String())) + } + s.InputSchema = config.Inputs[0].InputSchema + s.OutputSchema = config.OutputSchema + s.ActionID = config.ActionID + s.Config = config + s.status = ActionStatus{ + State: NotStarted, + HandledRows: 0, + ExpectedRows: -1, + } + s.bus = b + return nil +} + +func (s *ActionStdOut) Run(contentRow ContentRow) error { + s.status.State = Running + if s.status.HandledRows == 0 && s.Config.AddHeader { + headerRowRaw := "" + for _, h := range s.Config.OutputSchema.Columns { + if strings.EqualFold(headerRowRaw, "") { + headerRowRaw = h.Name + } else { + headerRowRaw = headerRowRaw + "," + h.Name + } + } + fmt.Printf("%v\n", headerRowRaw) + } + fmt.Printf("%v\n", contentRow.Raw) + s.status.HandledRows = s.status.HandledRows + 1 + if s.status.HandledRows == s.status.ExpectedRows { + s.status.State = Finished + } + return nil +} + +func (s *ActionStdOut) publishRow(row ContentRow) error { + // TODO: decide on the method of transporting data to the next action + return s.bus.Run(s.ActionID, row) +} + +func (s *ActionStdOut) publishErrorRow(row ContentRow) error { + // TODO: decide on the method of transporting data to the next action + return nil +} + +func (s *ActionStdOut) Validate() error { + if len(s.Config.Inputs) <= 0 { + return errors.New(fmt.Sprintf("error no input schema defined. ActionID: %v", s.ActionID.String())) + } + return nil +} + +func (s *ActionStdOut) GetActionID() uuid.UUID { + return s.ActionID +} + +func (s *ActionStdOut) Status() ActionStatus { + return s.status +} + +func (s *ActionStdOut) GetActionConfig() ActionConfig { + return s.Config.ActionConfig +} diff --git a/action.go b/action.go new file mode 100644 index 0000000..7c64651 --- /dev/null +++ b/action.go @@ -0,0 +1,247 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/google/uuid" +) + +type Action interface { + Run(row ContentRow) error + //publishRow(row ContentRow) error + //publishErrorRow(row ContentRow) error + //Start() + //Finish() + Status() ActionStatus + Validate() error + GetActionID() uuid.UUID + GetActionConfig() ActionConfig +} + +type Schema struct { + Columns []ColumnDefinition +} + +type ColumnDefinition struct { + Name string + ID uuid.UUID + Type DataType + DefaultValue string +} + +type ContentRow struct { + Schema *Schema + Values map[uuid.UUID]string + Raw string + Source *uuid.UUID +} + +type ActionConfig struct { + ActionID uuid.UUID + Type ActionType + OutputSchema *Schema + Inputs []InputDefinition +} + +type InputDefinition struct { + InputSchema *Schema + ActionID *uuid.UUID + // TODO: decide if input types exist +} + +type ActionStatus struct { + State StateValue + ExpectedRows int64 + HandledRows int64 +} + +// DataType - Custom type to hold value for data type ranging from 1-5 +type DataType int + +// Declare related constants for each datatype starting with index 1 +const ( + String DataType = iota + 1 + Integer + Float + Date + DateTime +) + +// String - Creating common behavior give the type a String function +func (d DataType) String() string { + return [...]string{"String", "Integer", "Float", "Date", "DateTime"}[d-1] +} + +// EnumIndex - Creating common behavior give the type a EnumIndex function +func (d DataType) EnumIndex() int { + return int(d) +} + +// MarshalJSON marshals the enum as a quoted json string +func (s DataType) MarshalJSON() ([]byte, error) { + buffer := bytes.NewBufferString(`"`) + buffer.WriteString(s.String()) + buffer.WriteString(`"`) + return buffer.Bytes(), nil +} + +// UnmarshalJSON unmashals a quoted json string to the enum value +func (s *DataType) UnmarshalJSON(b []byte) error { + var j string + err := json.Unmarshal(b, &j) + if err != nil { + return err + } + // Note that if the string cannot be found then it will be set to the zero value, 'String' in this case. + var toID = map[string]DataType{ + "String": String, + "Integer": Integer, + "Float": Float, + "Date": Date, + "DateTime": DateTime, + } + *s = toID[j] + return nil +} + +func ValidateDataType(in string, dataType DataType) (bool, error) { + switch dataType { + case String: + return ValidStringDataType(in), nil + case Integer: + return ValidIntegerDataType(in) + case Float: + return ValidFloatDataType(in) + case Date: + return ValidDateDataType(in) + case DateTime: + return ValidDateTimeDataType(in) + default: + return false, nil + } +} + +func ValidStringDataType(in string) bool { + return true +} + +func ValidIntegerDataType(in string) (bool, error) { + i, err := strconv.ParseInt(in, 10, 64) + if err != nil { + return false, err + } + return strings.EqualFold(in, fmt.Sprintf("%v", i)), nil +} + +func ValidFloatDataType(in string) (bool, error) { + i, err := strconv.ParseFloat(in, 64) + if err != nil { + return false, err + } + return strings.EqualFold(in, fmt.Sprintf("%v", i)), nil +} + +func ValidDateDataType(in string) (bool, error) { + // TODO: implement + return false, nil +} + +func ValidDateTimeDataType(in string) (bool, error) { + // TODO: implement + return false, nil +} + +// ActionType - Custom type to hold value for data type ranging from 1-5 +type ActionType int + +const ( + StdIn ActionType = iota + 1 + ModifyColumn + StdOut +) + +// String - Creating common behavior give the type a String function +func (d ActionType) String() string { + return [...]string{"StdIn", "ModifyColumn", "StdOut"}[d-1] +} + +// EnumIndex - Creating common behavior give the type a EnumIndex function +func (d ActionType) EnumIndex() int { + return int(d) +} + +// MarshalJSON marshals the enum as a quoted json string +func (s ActionType) MarshalJSON() ([]byte, error) { + buffer := bytes.NewBufferString(`"`) + buffer.WriteString(s.String()) + buffer.WriteString(`"`) + return buffer.Bytes(), nil +} + +// UnmarshalJSON unmashals a quoted json string to the enum value +func (s *ActionType) UnmarshalJSON(b []byte) error { + var j string + err := json.Unmarshal(b, &j) + if err != nil { + return err + } + // Note that if the string cannot be found then it will be set to the zero value, 'String' in this case. + var toID = map[string]ActionType{ + "StdIn": StdIn, + "ModifyColumn": ModifyColumn, + "StdOut": StdOut, + } + *s = toID[j] + return nil +} + +// StateValue - Custom type to hold value for data type ranging from 1-5 +type StateValue int + +// Declare related constants for each datatype starting with index 1 +const ( + NotStarted StateValue = iota + 1 + Paused + Running + Finished +) + +// String - Creating common behavior give the type a String function +func (d StateValue) String() string { + return [...]string{"NotStarted", "Paused", "Running", "Finished"}[d-1] +} + +// EnumIndex - Creating common behavior give the type a EnumIndex function +func (d StateValue) EnumIndex() int { + return int(d) +} + +// MarshalJSON marshals the enum as a quoted json string +func (s StateValue) MarshalJSON() ([]byte, error) { + buffer := bytes.NewBufferString(`"`) + buffer.WriteString(s.String()) + buffer.WriteString(`"`) + return buffer.Bytes(), nil +} + +// UnmarshalJSON unmashals a quoted json string to the enum value +func (s *StateValue) UnmarshalJSON(b []byte) error { + var j string + err := json.Unmarshal(b, &j) + if err != nil { + return err + } + // Note that if the string cannot be found then it will be set to the zero value, 'String' in this case. + var toID = map[string]StateValue{ + "NotStarted": NotStarted, + "Paused": Paused, + "Running": Running, + "Finished": Finished, + } + *s = toID[j] + return nil +} diff --git a/bus.go b/bus.go new file mode 100644 index 0000000..1fc3493 --- /dev/null +++ b/bus.go @@ -0,0 +1,154 @@ +package main + +import ( + "errors" + "fmt" + "strings" + + "github.com/google/uuid" +) + +type MetlConfig struct { + ActionConfigs []parallelActionConfig `json:"actionConfigs"` +} + +type parallelActionConfig struct { + ActionConfig + StdInConfig + ModifyColumnConfig + StdOutConfig +} + +type Bus struct { + actions map[uuid.UUID]Action + events map[uuid.UUID][]func(contentRow ContentRow) error +} + +func NewBus(config MetlConfig) (Bus, error) { + b := Bus{} + err := b.init(config) + if err != nil { + return Bus{}, err + } + return b, nil +} + +func wrap(err error, message string) error { + return errors.New(fmt.Sprintf("%v | %v", message, err.Error())) +} + +func (b *Bus) init(config MetlConfig) error { + b.events = make(map[uuid.UUID][]func(contentRow ContentRow) error) + b.actions = make(map[uuid.UUID]Action) + for _, ac := range config.ActionConfigs { + // setup the event list for this action (it will contain all the Run funcs for actions that depend on this action + _, ok := b.events[ac.ActionID] + if !ok { + b.events[ac.ActionID] = make([]func(contentRow ContentRow) error, 0) + } + + var action Action + switch ac.Type { + case StdIn: + stdIn, err := NewStdIn(StdInConfig{ + ActionConfig: ac.ActionConfig, + HasHeader: ac.HasHeader, + }, b) + if err != nil { + return errors.New(fmt.Sprintf("error initializing StdIn Action. ActionID: %v | %v", ac.ActionID, err.Error())) + } + action = &stdIn + case ModifyColumn: + modCol, err := NewModifyColumn(ModifyColumnConfig{ + ActionConfig: ac.ActionConfig, + }, b) + if err != nil { + return errors.New(fmt.Sprintf("error initializing ModifyColumn Action. ActionID: %v | %v", ac.ActionID, err.Error())) + } + action = &modCol + case StdOut: + stdOut, err := NewStdOut(StdOutConfig{ + ActionConfig: ac.ActionConfig, + AddHeader: ac.AddHeader, + }, b) + if err != nil { + return errors.New(fmt.Sprintf("error initializing StdOut Action. ActionID: %v | %v", ac.ActionID, err.Error())) + } + action = &stdOut + // TODO: add other actions here + } + b.actions[ac.ActionID] = action + + // add this action's Run func to all events it is dependent on + for _, input := range ac.Inputs { + _, ok := b.events[*input.ActionID] + if !ok { + b.events[*input.ActionID] = make([]func(contentRow ContentRow) error, 0) + } + if input.ActionID != nil { + b.events[*input.ActionID] = append(b.events[*input.ActionID], action.Run) + } + } + } + return nil +} + +func (b *Bus) ValidateAll() (bool, error) { + problems := make(map[uuid.UUID]string) + for key, a := range b.actions { + err := a.Validate() + if err != nil { + problems[key] = err.Error() + } + + // TODO: check the input schemas against the actions they are coming from + } + if len(problems) != 0 { + errorOut := "" + for key, prob := range problems { + if !strings.EqualFold("", errorOut) { + errorOut = fmt.Sprintf("%v | %v: %v", errorOut, key, prob) + } else { + errorOut = fmt.Sprintf("%v: %v", key, prob) + } + } + return false, errors.New(errorOut) + } + return true, nil +} + +func (b *Bus) Start() error { + for _, a := range b.actions { + aConfig := a.GetActionConfig() + if len(aConfig.Inputs) <= 0 { + err := a.Run(ContentRow{}) + if err != nil { + return errors.New(fmt.Sprintf("error running Action. ActionID: %v | %v", a.GetActionID(), err.Error())) + } + } + } + return nil +} + +func (b *Bus) Run(actionID uuid.UUID, row ContentRow) error { + for _, e := range b.events[actionID] { + err := e(row) + if err != nil { + return errors.New(fmt.Sprintf("error running Action. ActionID: %v | %v", actionID, err.Error())) + } + } + return nil +} + +/* + +Bus will manage the transporting of the data from an actions output to all actions that are dependent upon that data. + +If an action does not have a defined action it depends on it will be executed right away using the Run() func and will be +passed a zeroed ContentRow. + +Otherwise, all actions will have their run functions stored in arrays within a map of action uuids. When a certain action +publishes a row then we loop through the array of stored functions that are waiting for data from that action. + + +*/ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ad3e887 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/payne8/metl + +go 1.14 + +require ( + github.com/google/uuid v1.2.0 + github.com/mitchellh/mapstructure v1.4.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fca2f61 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= diff --git a/metl.go b/metl.go new file mode 100644 index 0000000..3261907 --- /dev/null +++ b/metl.go @@ -0,0 +1,95 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" +) + +func main() { + + jsonFile, err := os.Open("metlTestConfig.json") + // if we os.Open returns an error then handle it + if err != nil { + fmt.Println(err) + } + + fmt.Println("Successfully Opened metlTestConfig.json") + // defer the closing of our jsonFile so that we can parse it later on + defer jsonFile.Close() + + // read our opened xmlFile as a byte array. + byteValue, _ := ioutil.ReadAll(jsonFile) + + // we initialize our Users array + var metlConfig MetlConfig + + // we unmarshal our byteArray which contains our + // jsonFile's content into 'users' which we defined above + err = json.Unmarshal(byteValue, &metlConfig) + if err != nil { + panic("error unmarshalling JSON into MetlConfig struct") + } + + bus, err := NewBus(metlConfig) + if err != nil { + fmt.Println(err.Error()) + return + } + + err = bus.Start() + if err != nil { + fmt.Println(err.Error()) + return + } + + //stdInID, _ := uuid.Parse("a64b47b0-8ecb-409e-8fbc-5e2f5b93442b") + //stdInID, _ := uuid.Parse("a64b47b0-8ecb-409e-8fbc-5e2f5b93442b") + // + //testConfig := MetlConfig{ + // ActionConfigs: []interface{}{ + // StdInConfig{ + // ActionConfig: ActionConfig{ + // ActionID: stdInID, + // Type: StdIn, + // OutputSchema: &Schema{ + // Columns: []ColumnDefinition{ + // { + // Name: "id", + // ID: + // }, + // }, + // }, + // }, + // HasHeader: true, + // }, + // ModifyColumnConfig{ + // + // }, + // StdOutConfig{ + // + // }, + // }, + //} + + //fmt.Print("\033[H\033[2J") + //for i := 0; i < 100; i++ { + // fmt.Printf("\033[0;0H") + // fmt.Printf("On %d/100\nfarts %d ", i, i) + // //if isTerminal() { + // //} + // //else { + // // fmt.Printf("On %d/100 ", i) + // //} + // time.Sleep(200 * time.Millisecond) + //} +} + +func isTerminal() bool { + if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) != 0 { + return true + } else { + return false + } +} diff --git a/metlTestConfig.json b/metlTestConfig.json new file mode 100644 index 0000000..7260f0c --- /dev/null +++ b/metlTestConfig.json @@ -0,0 +1,276 @@ +{ + "actionConfigs": [ + { + "ActionID": "a64b47b0-8ecb-409e-8fbc-5e2f5b93442b", + "Type": "StdIn", + "OutputSchema": { + "Columns": [ + { + "Name": "id", + "ID": "af19ecb0-cb48-497f-ada0-d349accb0e2f", + "Type": "Integer", + "DefaultValue": "0" + }, + { + "Name": "first_name", + "ID": "478b9c5a-a31b-411d-9e4c-47bb0cf86213", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "last_name", + "ID": "1b21193f-57da-4060-afa2-7b213d99e62a", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "email", + "ID": "7b636cdd-e131-4a3e-88bc-cfb43d676bc0", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "gender", + "ID": "378729da-de80-45b7-b675-bfa4e92d4e22", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "car_make", + "ID": "d35dc745-3d41-4076-8cc5-9cb7e93ab802", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "car_model", + "ID": "50e3013e-dc43-400c-9582-a3db9de23c0f", + "Type": "String", + "DefaultValue": "" + } + ] + }, + "Inputs": [], + "hasHeader": true + }, + { + "ActionID": "30f7390c-c1cf-401b-aa81-d7156ddc5e1d", + "Type": "ModifyColumn", + "OutputSchema": { + "Columns": [ + { + "Name": "id", + "ID": "af19ecb0-cb48-497f-ada0-d349accb0e2f", + "Type": "Integer", + "DefaultValue": "0" + }, + { + "Name": "first_name", + "ID": "478b9c5a-a31b-411d-9e4c-47bb0cf86213", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "last_name", + "ID": "1b21193f-57da-4060-afa2-7b213d99e62a", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "email", + "ID": "7b636cdd-e131-4a3e-88bc-cfb43d676bc0", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "gender", + "ID": "378729da-de80-45b7-b675-bfa4e92d4e22", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "car_make", + "ID": "d35dc745-3d41-4076-8cc5-9cb7e93ab802", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "car_model", + "ID": "50e3013e-dc43-400c-9582-a3db9de23c0f", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "new_column", + "ID": "e9cff198-2ebd-425d-be59-9b5f834718df", + "Type": "String", + "DefaultValue": "x" + } + ] + }, + "Inputs": [ + { + "InputSchema": { + "Columns": [ + { + "Name": "id", + "ID": "af19ecb0-cb48-497f-ada0-d349accb0e2f", + "Type": "Integer", + "DefaultValue": "0" + }, + { + "Name": "first_name", + "ID": "478b9c5a-a31b-411d-9e4c-47bb0cf86213", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "last_name", + "ID": "1b21193f-57da-4060-afa2-7b213d99e62a", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "email", + "ID": "7b636cdd-e131-4a3e-88bc-cfb43d676bc0", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "gender", + "ID": "378729da-de80-45b7-b675-bfa4e92d4e22", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "car_make", + "ID": "d35dc745-3d41-4076-8cc5-9cb7e93ab802", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "car_model", + "ID": "50e3013e-dc43-400c-9582-a3db9de23c0f", + "Type": "String", + "DefaultValue": "" + } + ] + }, + "ActionID": "a64b47b0-8ecb-409e-8fbc-5e2f5b93442b" + } + ] + }, + { + "ActionID": "c004eb92-24d7-453b-b474-bd49ac467f00", + "Type": "StdOut", + "OutputSchema": { + "Columns": [ + { + "Name": "id", + "ID": "af19ecb0-cb48-497f-ada0-d349accb0e2f", + "Type": "Integer", + "DefaultValue": "0" + }, + { + "Name": "first_name", + "ID": "478b9c5a-a31b-411d-9e4c-47bb0cf86213", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "last_name", + "ID": "1b21193f-57da-4060-afa2-7b213d99e62a", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "email", + "ID": "7b636cdd-e131-4a3e-88bc-cfb43d676bc0", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "gender", + "ID": "378729da-de80-45b7-b675-bfa4e92d4e22", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "car_make", + "ID": "d35dc745-3d41-4076-8cc5-9cb7e93ab802", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "car_model", + "ID": "50e3013e-dc43-400c-9582-a3db9de23c0f", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "new_column", + "ID": "e9cff198-2ebd-425d-be59-9b5f834718df", + "Type": "String", + "DefaultValue": "x" + } + ] + }, + "Inputs": [ + { + "InputSchema": { + "Columns": [ + { + "Name": "id", + "ID": "af19ecb0-cb48-497f-ada0-d349accb0e2f", + "Type": "Integer", + "DefaultValue": "0" + }, + { + "Name": "first_name", + "ID": "478b9c5a-a31b-411d-9e4c-47bb0cf86213", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "last_name", + "ID": "1b21193f-57da-4060-afa2-7b213d99e62a", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "email", + "ID": "7b636cdd-e131-4a3e-88bc-cfb43d676bc0", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "gender", + "ID": "378729da-de80-45b7-b675-bfa4e92d4e22", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "car_make", + "ID": "d35dc745-3d41-4076-8cc5-9cb7e93ab802", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "car_model", + "ID": "50e3013e-dc43-400c-9582-a3db9de23c0f", + "Type": "String", + "DefaultValue": "" + }, + { + "Name": "new_column", + "ID": "e9cff198-2ebd-425d-be59-9b5f834718df", + "Type": "String", + "DefaultValue": "x" + } + ] + }, + "ActionID": "30f7390c-c1cf-401b-aa81-d7156ddc5e1d" + } + ] + } + ] +} \ No newline at end of file