github.com/rainforestapp/rainforest-cli@v2.12.0+incompatible/tabularvars.go (about)

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