-
Notifications
You must be signed in to change notification settings - Fork 25
/
tabularvars.go
178 lines (151 loc) · 5.01 KB
/
tabularvars.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package main
import (
"math"
"os"
"encoding/csv"
"errors"
"strings"
"log"
"github.com/rainforestapp/rainforest-cli/rainforest"
"github.com/urfave/cli"
)
// tabularVariablesAPI is part of the API connected to the tabular variables
type tabularVariablesAPI interface {
GetGenerators(params ...string) ([]rainforest.Generator, error)
DeleteGenerator(genID int) error
CreateTabularVar(name, description string,
columns []string, singleUse bool) (*rainforest.Generator, error)
AddGeneratorRowsFromTable(targetGenerator *rainforest.Generator,
targetColumns []string, rowData [][]string) error
}
// uploadTabularVar takes a path to csv file and creates tabular variable generator from it.
func uploadTabularVar(api tabularVariablesAPI, pathToCSV, name string, overwrite, singleUse bool) error {
// Open up the CSV file and parse it, return early with an error if we fail to get to the file
f, err := os.Open(pathToCSV)
if err != nil {
return err
}
defer f.Close()
r := csv.NewReader(f)
records, err := r.ReadAll()
if err != nil {
return err
}
// Check if the variable exists in RF
var existingGenID int
var description string
generators, err := api.GetGenerators("generator_type=tabular", "name="+name)
if err != nil {
return err
}
for _, gen := range generators {
if gen.Name == name {
existingGenID = gen.ID
description = gen.Description
}
}
if existingGenID != 0 {
if overwrite {
// if variable exists and we want to override it with new one delete it here
log.Printf("Tabular var %v exists, overwriting it with new data.\n", name)
err = api.DeleteGenerator(existingGenID)
if err != nil {
return err
}
} else {
// if variable exists but we didn't specify to override it then return with error
return errors.New("Tabular variable: " + name +
" already exists, use different name or choose an option to override it")
}
} else {
description = "Uploaded via the CLI"
}
// prepare input data
columnNames, rows := records[0], records[1:]
parsedColumnNames := make([]string, len(columnNames))
for i, colName := range columnNames {
formattedColName := strings.TrimSpace(strings.ToLower(colName))
parsedColName := strings.Replace(formattedColName, " ", "_", -1)
parsedColumnNames[i] = parsedColName
}
// create new generator for the tabular variable
newGenerator, err := api.CreateTabularVar(name, description, parsedColumnNames, singleUse)
if err != nil {
return err
}
// batch the rows and put them into a channel
numOfBatches := int(math.Ceil(float64(len(rows)) / float64(tabularBatchSize)))
rowsToUpload := make(chan [][]string, numOfBatches)
for i := 0; i < len(rows); i += tabularBatchSize {
batch := rows[i:min(i+tabularBatchSize, len(rows))]
rowsToUpload <- batch
}
close(rowsToUpload)
// chan to gather errors from workers
errors := make(chan error, numOfBatches)
log.Println("Beginning batch upload of csv file...")
// spawn workers to upload the rows
for i := 0; i < tabularConcurrency; i++ {
go rowUploadWorker(api, newGenerator, parsedColumnNames, rowsToUpload, errors)
}
for i := 0; i < numOfBatches; i++ {
if err := <-errors; err != nil {
return err
}
log.Printf("Tabular variable '%v' batch %v of %v uploaded.", name, i+1, numOfBatches)
}
return nil
}
// Well... yeah...
func min(a, b int) int {
if a <= b {
return a
}
return b
}
// rowUploadWorker is a helper worker which reads batch of rows to upload from rows chan
// and pushes potential errors through errorsChan
func rowUploadWorker(api tabularVariablesAPI, generator *rainforest.Generator,
columns []string, rowsChan <-chan [][]string, errorsChan chan<- error) {
for rows := range rowsChan {
error := api.AddGeneratorRowsFromTable(generator, columns, rows)
errorsChan <- error
}
}
// csvUpload is a wrapper around uploadTabularVar to function with csv-upload cli command
func csvUpload(c cliContext, api tabularVariablesAPI) error {
// Get the csv file path either from the option or command argument
filePath := c.Args().First()
if filePath == "" {
filePath = c.String("csv-file")
}
if filePath == "" {
return cli.NewExitError("CSV filename not specified", 1)
}
name := c.String("name")
if name == "" {
return cli.NewExitError("Tabular variable name not specified", 1)
}
overwrite := c.Bool("overwrite-variable")
singleUse := c.Bool("single-use")
err := uploadTabularVar(api, filePath, name, overwrite, singleUse)
if err != nil {
return cli.NewExitError(err.Error(), 1)
}
return nil
}
// preRunCSVUpload is a wrapper around uploadTabularVar to be ran before starting a new run
func preRunCSVUpload(c cliContext, api tabularVariablesAPI) error {
// Get the csv file path either and skip uploading if it's not present
filePath := c.String("import-variable-csv-file")
if filePath == "" {
return nil
}
name := c.String("import-variable-name")
if name == "" {
return errors.New("Tabular variable name not specified")
}
overwrite := c.Bool("overwrite-variable")
singleUse := c.Bool("single-use")
return uploadTabularVar(api, filePath, name, overwrite, singleUse)
}