fix: csv-importer (#10)

* update item fields to support import_ref

* add additional rows to CSV importer

* add CSV import documentation

* update readme

* update readme

* fix failed test
This commit is contained in:
Hayden 2022-09-12 20:54:30 -08:00 committed by GitHub
parent 90813abf76
commit ca36e3b080
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 447 additions and 135 deletions

View file

@ -84,12 +84,10 @@ func TestItemsRepository_Create(t *testing.T) {
assert.NoError(t, err)
assert.NotEmpty(t, result.ID)
// Cleanup
// Cleanup - Also deletes item
err = tRepos.Locations.Delete(context.Background(), location.ID)
assert.NoError(t, err)
err = tRepos.Items.Delete(context.Background(), result.ID)
assert.NoError(t, err)
}
func TestItemsRepository_Create_Location(t *testing.T) {
@ -111,11 +109,9 @@ func TestItemsRepository_Create_Location(t *testing.T) {
assert.Equal(t, result.ID, foundItem.ID)
assert.Equal(t, location.ID, foundItem.Edges.Location.ID)
// Cleanup
// Cleanup - Also deletes item
err = tRepos.Locations.Delete(context.Background(), location.ID)
assert.NoError(t, err)
err = tRepos.Items.Delete(context.Background(), result.ID)
assert.NoError(t, err)
}
func TestItemsRepository_Delete(t *testing.T) {

View file

@ -156,7 +156,8 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
if len(row) == 0 {
continue
}
if len(row) != 14 {
if len(row) != NumOfCols {
return ErrInvalidCsv
}
@ -227,15 +228,16 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
}
log.Info().
Str("name", row.Name).
Str("name", row.Item.Name).
Str("location", row.Location).
Strs("labels", row.getLabels()).
Str("locationId", locationID.String()).
Msgf("Creating Item: %s", row.Name)
Msgf("Creating Item: %s", row.Item.Name)
result, err := svc.repo.Items.Create(ctx, gid, types.ItemCreate{
Name: row.Name,
Description: row.Description,
ImportRef: row.Item.ImportRef,
Name: row.Item.Name,
Description: row.Item.Description,
LabelIDs: labelIDs,
LocationID: locationID,
})
@ -246,21 +248,36 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s
// Update the item with the rest of the data
_, err = svc.repo.Items.Update(ctx, types.ItemUpdate{
ID: result.ID,
Name: result.Name,
LocationID: locationID,
LabelIDs: labelIDs,
Description: result.Description,
SerialNumber: row.SerialNumber,
ModelNumber: row.ModelNumber,
Manufacturer: row.Manufacturer,
Notes: row.Notes,
PurchaseFrom: row.PurchaseFrom,
PurchasePrice: row.parsedPurchasedPrice(),
PurchaseTime: row.parsedPurchasedAt(),
SoldTo: row.SoldTo,
SoldPrice: row.parsedSoldPrice(),
SoldTime: row.parsedSoldAt(),
// Edges
LocationID: locationID,
LabelIDs: labelIDs,
// General Fields
ID: result.ID,
Name: result.Name,
Description: result.Description,
Insured: row.Item.Insured,
Notes: row.Item.Notes,
// Identifies the item as imported
SerialNumber: row.Item.SerialNumber,
ModelNumber: row.Item.ModelNumber,
Manufacturer: row.Item.Manufacturer,
// Purchase
PurchaseFrom: row.Item.PurchaseFrom,
PurchasePrice: row.Item.PurchasePrice,
PurchaseTime: row.Item.PurchaseTime,
// Warranty
LifetimeWarranty: row.Item.LifetimeWarranty,
WarrantyExpires: row.Item.WarrantyExpires,
WarrantyDetails: row.Item.WarrantyDetails,
SoldTo: row.Item.SoldTo,
SoldPrice: row.Item.SoldPrice,
SoldTime: row.Item.SoldTime,
SoldNotes: row.Item.SoldNotes,
})
if err != nil {

View file

@ -5,10 +5,14 @@ import (
"strconv"
"strings"
"time"
"github.com/hay-kot/content/backend/internal/types"
)
var ErrInvalidCsv = errors.New("invalid csv")
const NumOfCols = 21
func parseFloat(s string) float64 {
if s == "" {
return 0
@ -26,60 +30,56 @@ func parseDate(s string) time.Time {
return p
}
func parseBool(s string) bool {
switch strings.ToLower(s) {
case "true", "yes", "1":
return true
default:
return false
}
}
func parseInt(s string) int {
i, _ := strconv.Atoi(s)
return i
}
type csvRow struct {
Location string
Labels string
Name string
Description string
SerialNumber string
ModelNumber string
Manufacturer string
Notes string
PurchaseFrom string
PurchasedPrice string
PurchasedAt string
SoldTo string
SoldPrice string
SoldAt string
Item types.ItemSummary
Location string
LabelStr string
}
func newCsvRow(row []string) csvRow {
return csvRow{
Location: row[0],
Labels: row[1],
Name: row[2],
Description: row[3],
SerialNumber: row[4],
ModelNumber: row[5],
Manufacturer: row[6],
Notes: row[7],
PurchaseFrom: row[8],
PurchasedPrice: row[9],
PurchasedAt: row[10],
SoldTo: row[11],
SoldPrice: row[12],
SoldAt: row[13],
Location: row[1],
LabelStr: row[2],
Item: types.ItemSummary{
ImportRef: row[0],
Quantity: parseInt(row[3]),
Name: row[4],
Description: row[5],
Insured: parseBool(row[6]),
SerialNumber: row[7],
ModelNumber: row[8],
Manufacturer: row[9],
Notes: row[10],
PurchaseFrom: row[11],
PurchasePrice: parseFloat(row[12]),
PurchaseTime: parseDate(row[13]),
LifetimeWarranty: parseBool(row[14]),
WarrantyExpires: parseDate(row[15]),
WarrantyDetails: row[16],
SoldTo: row[17],
SoldPrice: parseFloat(row[18]),
SoldTime: parseDate(row[19]),
SoldNotes: row[20],
},
}
}
func (c csvRow) parsedSoldPrice() float64 {
return parseFloat(c.SoldPrice)
}
func (c csvRow) parsedPurchasedPrice() float64 {
return parseFloat(c.PurchasedPrice)
}
func (c csvRow) parsedPurchasedAt() time.Time {
return parseDate(c.PurchasedAt)
}
func (c csvRow) parsedSoldAt() time.Time {
return parseDate(c.SoldAt)
}
func (c csvRow) getLabels() []string {
split := strings.Split(c.Labels, ";")
split := strings.Split(c.LabelStr, ";")
// Trim each
for i, s := range split {

View file

@ -8,14 +8,13 @@ import (
)
const CSV_DATA = `
Location,Labels,Name,Description,Serial Number,Mode Number,Manufacturer,Notes,Purchase From,Purchased Price,Purchased At,Sold To,Sold Price,Sold At
Garage,IOT;Home Assistant; Z-Wave,Zooz Universal Relay ZEN17,"Zooz 700 Series Z-Wave Universal Relay ZEN17 for Awnings, Garage Doors, Sprinklers, and More | 2 NO-C-NC Relays (20A, 10A) | Signal Repeater | Hub Required (Compatible with SmartThings and Hubitat)",,ZEN17,Zooz,,Amazon,39.95,10/13/2021,,,
Living Room,IOT;Home Assistant; Z-Wave,Zooz Motion Sensor,"Zooz Z-Wave Plus S2 Motion Sensor ZSE18 with Magnetic Mount, Works with Vera and SmartThings",,ZSE18,Zooz,,Amazon,29.95,10/15/2021,,,
Office,IOT;Home Assistant; Z-Wave,Zooz 110v Power Switch,"Zooz Z-Wave Plus Power Switch ZEN15 for 110V AC Units, Sump Pumps, Humidifiers, and More",,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,,
Downstairs,IOT;Home Assistant; Z-Wave,Ecolink Z-Wave PIR Motion Sensor,"Ecolink Z-Wave PIR Motion Detector Pet Immune, White (PIRZWAVE2.5-ECO)",,PIRZWAVE2.5-ECO,Ecolink,,Amazon,35.58,10/21/2020,,,
Entry,IOT;Home Assistant; Z-Wave,Yale Security Touchscreen Deadbolt,"Yale Security YRD226-ZW2-619 YRD226ZW2619 Touchscreen Deadbolt, Satin Nickel",,YRD226ZW2619,Yale,,Amazon,120.39,10/14/2020,,,
Kitchen,IOT;Home Assistant; Z-Wave,Smart Rocker Light Dimmer,"UltraPro Z-Wave Smart Rocker Light Dimmer with QuickFit and SimpleWire, 3-Way Ready, Compatible with Alexa, Google Assistant, ZWave Hub Required, Repeater/Range Extender, White Paddle Only, 39351",,39351,Honeywell,,Amazon,65.98,09/30/0202,,,
`
Import Ref,Location,Labels,Quantity,Name,Description,Insured,Serial Number,Mode Number,Manufacturer,Notes,Purchase From,Purchased Price,Purchased Time,Lifetime Warranty,Warranty Expires,Warranty Details,Sold To,Sold Price,Sold Time,Sold Notes
,Garage,IOT;Home Assistant; Z-Wave,1,Zooz Universal Relay ZEN17,"Zooz 700 Series Z-Wave Universal Relay ZEN17 for Awnings, Garage Doors, Sprinklers, and More | 2 NO-C-NC Relays (20A, 10A) | Signal Repeater | Hub Required (Compatible with SmartThings and Hubitat)",,,ZEN17,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
,Living Room,IOT;Home Assistant; Z-Wave,1,Zooz Motion Sensor,"Zooz Z-Wave Plus S2 Motion Sensor ZSE18 with Magnetic Mount, Works with Vera and SmartThings",,,ZSE18,Zooz,,Amazon,29.95,10/15/2021,,,,,,,
,Office,IOT;Home Assistant; Z-Wave,1,Zooz 110v Power Switch,"Zooz Z-Wave Plus Power Switch ZEN15 for 110V AC Units, Sump Pumps, Humidifiers, and More",,,ZEN15,Zooz,,Amazon,39.95,10/13/2021,,,,,,,
,Downstairs,IOT;Home Assistant; Z-Wave,1,Ecolink Z-Wave PIR Motion Sensor,"Ecolink Z-Wave PIR Motion Detector Pet Immune, White (PIRZWAVE2.5-ECO)",,,PIRZWAVE2.5-ECO,Ecolink,,Amazon,35.58,10/21/2020,,,,,,,
,Entry,IOT;Home Assistant; Z-Wave,1,Yale Security Touchscreen Deadbolt,"Yale Security YRD226-ZW2-619 YRD226ZW2619 Touchscreen Deadbolt, Satin Nickel",,,YRD226ZW2619,Yale,,Amazon,120.39,10/14/2020,,,,,,,
,Kitchen,IOT;Home Assistant; Z-Wave,1,Smart Rocker Light Dimmer,"UltraPro Z-Wave Smart Rocker Light Dimmer with QuickFit and SimpleWire, 3-Way Ready, Compatible with Alexa, Google Assistant, ZWave Hub Required, Repeater/Range Extender, White Paddle Only, 39351",,,39351,Honeywell,,Amazon,65.98,09/30/0202,,,,,,,`
func loadcsv() [][]string {
reader := csv.NewReader(bytes.NewBuffer([]byte(CSV_DATA)))
@ -30,7 +29,7 @@ func loadcsv() [][]string {
func Test_csvRow_getLabels(t *testing.T) {
type fields struct {
Labels string
LabelStr string
}
tests := []struct {
name string
@ -40,28 +39,28 @@ func Test_csvRow_getLabels(t *testing.T) {
{
name: "basic test",
fields: fields{
Labels: "IOT;Home Assistant;Z-Wave",
LabelStr: "IOT;Home Assistant;Z-Wave",
},
want: []string{"IOT", "Home Assistant", "Z-Wave"},
},
{
name: "no labels",
fields: fields{
Labels: "",
LabelStr: "",
},
want: []string{},
},
{
name: "single label",
fields: fields{
Labels: "IOT",
LabelStr: "IOT",
},
want: []string{"IOT"},
},
{
name: "trailing semicolon",
fields: fields{
Labels: "IOT;",
LabelStr: "IOT;",
},
want: []string{"IOT"},
},
@ -69,7 +68,7 @@ func Test_csvRow_getLabels(t *testing.T) {
{
name: "whitespace",
fields: fields{
Labels: " IOT; Home Assistant; Z-Wave ",
LabelStr: " IOT; Home Assistant; Z-Wave ",
},
want: []string{"IOT", "Home Assistant", "Z-Wave"},
},
@ -77,7 +76,7 @@ func Test_csvRow_getLabels(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := csvRow{
Labels: tt.fields.Labels,
LabelStr: tt.fields.LabelStr,
}
if got := c.getLabels(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("csvRow.getLabels() = %v, want %v", got, tt.want)

View file

@ -73,21 +73,21 @@ func TestItemService_CsvImport(t *testing.T) {
}
for _, csvRow := range dataCsv {
if csvRow.Name == item.Name {
assert.Equal(t, csvRow.Description, item.Description)
assert.Equal(t, csvRow.SerialNumber, item.SerialNumber)
assert.Equal(t, csvRow.Manufacturer, item.Manufacturer)
assert.Equal(t, csvRow.Notes, item.Notes)
if csvRow.Item.Name == item.Name {
assert.Equal(t, csvRow.Item.Description, item.Description)
assert.Equal(t, csvRow.Item.SerialNumber, item.SerialNumber)
assert.Equal(t, csvRow.Item.Manufacturer, item.Manufacturer)
assert.Equal(t, csvRow.Item.Notes, item.Notes)
// Purchase Fields
assert.Equal(t, csvRow.parsedPurchasedAt(), item.PurchaseTime)
assert.Equal(t, csvRow.PurchaseFrom, item.PurchaseFrom)
assert.Equal(t, csvRow.parsedPurchasedPrice(), item.PurchasePrice)
assert.Equal(t, csvRow.Item.PurchaseTime, item.PurchaseTime)
assert.Equal(t, csvRow.Item.PurchaseFrom, item.PurchaseFrom)
assert.Equal(t, csvRow.Item.PurchasePrice, item.PurchasePrice)
// Sold Fields
assert.Equal(t, csvRow.parsedSoldAt(), item.SoldTime)
assert.Equal(t, csvRow.SoldTo, item.SoldTo)
assert.Equal(t, csvRow.parsedSoldPrice(), item.SoldPrice)
assert.Equal(t, csvRow.Item.SoldTime, item.SoldTime)
assert.Equal(t, csvRow.Item.SoldTo, item.SoldTo)
assert.Equal(t, csvRow.Item.SoldPrice, item.SoldPrice)
}
}
}

View file

@ -7,6 +7,7 @@ import (
)
type ItemCreate struct {
ImportRef string `json:"-"`
Name string `json:"name"`
Description string `json:"description"`
@ -53,6 +54,7 @@ type ItemUpdate struct {
}
type ItemSummary struct {
ImportRef string `json:"-"`
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`