Skip to content

Commit

Permalink
Merge pull request #3 from StanGirard/feat/gsheet
Browse files Browse the repository at this point in the history
Feat/gsheet
  • Loading branch information
Stan Girard authored Aug 2, 2022
2 parents 9cfc1ae + 1220106 commit d7b3b8e
Show file tree
Hide file tree
Showing 13 changed files with 732 additions and 50 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@ There are a couple of parameters that you can use
- `--granularity`: Granularity of the report, can be `daily`, `monthly`
- `--html`: Output the report as html to `pricy.html`
- `--prometheus`: Outputs as prometheus metrics on `http://localhost:2112/metrics` that can be scraped by prometheus
- `--gsheets`: Outputs the report to a google sheets spreadsheet

## Example

### Generate a Google Spreadsheet


You need to export the variable `GOOGLE_APPLICATION_CREDENTIALS` to the path of your json file which contains your OAuth2 credentials.
In oder to get the credentials, you follow this guide:
- [Google Cloud Platform](https://developers.google.com/sheets/api/quickstart/go)
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.6.0
0.7.0
Binary file added docs/gsheets.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 20 additions & 17 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,46 @@ module github.com/stangirard/pricy
go 1.18

require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/costmanagement/armcostmanagement v1.0.0
github.com/Azure/go-autorest/autorest/to v0.4.0
github.com/aws/aws-sdk-go v1.44.66
github.com/prometheus/client_golang v1.12.2
golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c
google.golang.org/api v0.90.0
)

require (
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible // indirect
cloud.google.com/go/compute v1.7.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/costmanagement/armcostmanagement v1.0.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/golang-jwt/jwt v3.2.1+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.7 // indirect
github.com/google/uuid v1.1.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect
github.com/googleapis/gax-go/v2 v2.4.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/yuin/goldmark v1.4.13 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220728211354-c7608f3a8462 // indirect
golang.org/x/sys v0.0.0-20220731174439-a90be440212d // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f // indirect
google.golang.org/grpc v1.48.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
)
321 changes: 291 additions & 30 deletions go.sum

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions internal/cmd/pricy.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func Execute() error {
Days: *days,
Interval: *interval,
}

services := make([]format.Service, 0)
getServices(&services, &m, configuration)

Expand Down
84 changes: 84 additions & 0 deletions internal/gsheet/charts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package gsheet

import (
"google.golang.org/api/sheets/v4"
)

func createBasicChartSeries(values [][]interface{}) []*sheets.BasicChartSeries {
var BasicChartSeries []*sheets.BasicChartSeries
for i := range values {
if i == 0 {
continue
}
BasicChartSeries = append(BasicChartSeries, &sheets.BasicChartSeries{
Series: &sheets.ChartData{
SourceRange: &sheets.ChartSourceRange{
Sources: []*sheets.GridRange{
{ //A2:O2
SheetId: 1023,
StartRowIndex: int64(i),
EndRowIndex: int64(i + 1),
StartColumnIndex: 0,
EndColumnIndex: int64(len(values[i])),
},
},
},
},
})
}
return BasicChartSeries
}

func createHistogramSeries(values [][]interface{}) []*sheets.HistogramSeries {
var HistogramSeries []*sheets.HistogramSeries
for i := range values {
if i == 0 {
continue
}
HistogramSeries = append(HistogramSeries, &sheets.HistogramSeries{
Data: &sheets.ChartData{
SourceRange: &sheets.ChartSourceRange{
Sources: []*sheets.GridRange{
{ //A2:O2
SheetId: 1023,
StartRowIndex: int64(i),
EndRowIndex: int64(i + 1),
StartColumnIndex: 0,
EndColumnIndex: int64(len(values[i])),
},
},
},
},
})
}
return HistogramSeries
}

func createBasicChartSpec(legendPosition, chartype, stackedType string, sourceSheetId int64, values [][]interface{}, basicChartSeries []*sheets.BasicChartSeries) *sheets.BasicChartSpec {
var BasicChartSpec *sheets.BasicChartSpec
BasicChartSpec = &sheets.BasicChartSpec{
HeaderCount: 1,
Series: basicChartSeries,
LegendPosition: "BOTTOM_LEGEND",
ChartType: "COLUMN",
StackedType: "STACKED",
Domains: []*sheets.BasicChartDomain{
{
Domain: &sheets.ChartData{
SourceRange: &sheets.ChartSourceRange{
Sources: []*sheets.GridRange{
{
SheetId: 1023,
StartRowIndex: 0,
EndRowIndex: 1,
StartColumnIndex: 0,
EndColumnIndex: int64(len(values[0])),
},
},
},
},
},
},
}
return BasicChartSpec
}
46 changes: 46 additions & 0 deletions internal/gsheet/gsheet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package gsheet

import (
"flag"
"fmt"

"github.com/stangirard/pricy/internal/helpers"
)

var (
spreadsheet = flag.String("spreadsheet", "", "The ID of the spreadsheet to write to.")
)

func Execute(value [][]string) {
flag.Parse()

dataSpreadsheet := helpers.ConvertStringToInterface(value)
srv, ctx := NewSheetService()

// Create a new spreadsheet.
rb := createSpreadSheetConfig()

BasicChartSeries := createBasicChartSeries(dataSpreadsheet)

if *spreadsheet == "" {
*spreadsheet = createSpreadsheet(srv, rb, ctx)
// Add a sheet
addSheet(srv, *spreadsheet, "Reports", 1023)
deleteSheet(srv, *spreadsheet, 0)

}

//Delete the Chart sheet if it exists
deleteSheet(srv, *spreadsheet, 1024)
addSheet(srv, *spreadsheet, "Charts", 1024)

// Write the value variable to the new spreadsheet.
writeCSVToSheet(ctx, srv, *spreadsheet, dataSpreadsheet)

// Create BasicChart Series
chartSpecs := createBasicChartSpec("BOTTOM_LEGEND", "COLUMN", "STACKED", 1023, dataSpreadsheet, BasicChartSeries)
addChart(srv, *spreadsheet, 1024, chartSpecs)

// Print the spreadsheet url
fmt.Printf("Spreadsheet URL: https://docs.google.com/spreadsheets/d/%s\n", *spreadsheet)
}
121 changes: 121 additions & 0 deletions internal/gsheet/populate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package gsheet

import (
"context"
"fmt"
"log"
"time"

"google.golang.org/api/sheets/v4"
)

func createSpreadSheetConfig() *sheets.Spreadsheet {
var timeNowString = time.Now().Format("2006-01-02-15-04-05")
title := "Pricy-Report-" + timeNowString
rb := &sheets.Spreadsheet{
Properties: &sheets.SpreadsheetProperties{
Title: title,
},
}
return rb
}

func addSheet(service *sheets.Service, spreadsheetId, name string, idSheet int64) {
_, error := service.Spreadsheets.BatchUpdate(*spreadsheet, &sheets.BatchUpdateSpreadsheetRequest{
Requests: []*sheets.Request{
{
AddSheet: &sheets.AddSheetRequest{
Properties: &sheets.SheetProperties{
Title: name,
SheetId: idSheet,
},
},
},
},
}).Do()

if error != nil {
log.Fatalf("Unable to add sheet: %v", error)
}
fmt.Println("Sheet added: ", name)
}

func addChart(service *sheets.Service, spreadsheetId string, idSheetToAddTo int64, specs *sheets.BasicChartSpec) {
_, error := service.Spreadsheets.BatchUpdate(*spreadsheet, &sheets.BatchUpdateSpreadsheetRequest{
Requests: []*sheets.Request{
{
AddChart: &sheets.AddChartRequest{
Chart: &sheets.EmbeddedChart{
Position: &sheets.EmbeddedObjectPosition{
NewSheet: false,
OverlayPosition: &sheets.OverlayPosition{
AnchorCell: &sheets.GridCoordinate{
SheetId: 1024,
RowIndex: 0,
ColumnIndex: 0,
},
WidthPixels: 1200,
HeightPixels: 600,
},
},
Spec: &sheets.ChartSpec{
BasicChart: specs,
},
},
},
},
},
}).Do()

if error != nil {
log.Fatalf("Unable to add sheet: %v", error)
}
fmt.Println("Chart added")
}

func deleteSheet(service *sheets.Service, spreadsheetId string, sheetId int64) {
_, error := service.Spreadsheets.BatchUpdate(*spreadsheet, &sheets.BatchUpdateSpreadsheetRequest{
Requests: []*sheets.Request{
{
DeleteSheet: &sheets.DeleteSheetRequest{
SheetId: sheetId,
},
},
},
}).Do()

if error != nil {
fmt.Printf("Sheet not deleted because it does not exist (Id:%v)\n", sheetId)
}
fmt.Println("Default sheet deleted")
}

func writeCSVToSheet(context context.Context, service *sheets.Service, spreadsheetId string, data [][]interface{}) {
valuesRanges := make([]*sheets.ValueRange, len(data))
valuesRanges = append(valuesRanges, &sheets.ValueRange{
MajorDimension: "ROWS",
Values: data,
Range: "A1:ZZ",
},
)
rb2 := &sheets.BatchUpdateValuesRequest{
ValueInputOption: "USER_ENTERED",
Data: valuesRanges,
}

_, err := service.Spreadsheets.Values.BatchUpdate(*spreadsheet, rb2).Context(context).Do()

if err != nil {
log.Fatal(err)
}

fmt.Println("Sheet populated")
}

func createSpreadsheet(service *sheets.Service, config *sheets.Spreadsheet, ctx context.Context) string {
resp, err := service.Spreadsheets.Create(config).Context(ctx).Do()
if err != nil {
log.Fatalf("Unable to create spreadsheet: %v", err)
}
return resp.SpreadsheetId
}
36 changes: 36 additions & 0 deletions internal/gsheet/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package gsheet

import (
"context"
"io/ioutil"
"log"
"os"

"golang.org/x/oauth2/google"
"google.golang.org/api/option"
"google.golang.org/api/sheets/v4"
)

func NewSheetService() (*sheets.Service, context.Context) {

ctx := context.Background()
//Os get env variable GOOGLE_APPLICATION_CREDENTIALS
GOOGLE_APPLICATION_CREDENTIALS := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")

b, err := ioutil.ReadFile(GOOGLE_APPLICATION_CREDENTIALS)
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
// If modifying these scopes, delete your previously saved token.json.
config, err := google.ConfigFromJSON(b, "https://www.googleapis.com/auth/spreadsheets")
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := getClient(config)
srv, err := sheets.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
log.Fatalf("Unable to retrieve Sheets client: %v", err)
}

return srv, ctx
}
Loading

0 comments on commit d7b3b8e

Please sign in to comment.