From fce922e68cdb6c53936714d4699e3ae54569636b Mon Sep 17 00:00:00 2001 From: Martin-Belton-gov Date: Wed, 6 Dec 2023 14:56:45 +0000 Subject: [PATCH 1/3] Removed unwanted workflows Added Read Me --- .github/workflows/al-test.yml | 17 ----------------- .github/workflows/test.yml | 10 ---------- README.md | 27 +++++++++++++++++++++++++++ 3 files changed, 27 insertions(+), 27 deletions(-) delete mode 100644 .github/workflows/al-test.yml delete mode 100644 .github/workflows/test.yml create mode 100644 README.md diff --git a/.github/workflows/al-test.yml b/.github/workflows/al-test.yml deleted file mode 100644 index c45bdc9..0000000 --- a/.github/workflows/al-test.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: 'Test by Al' - -on: - workflow_dispatch: - -jobs: - al-test: - name: 'Test by Al' - runs-on: ubuntu-latest - - defaults: - run: - shell: bash - - steps: - - name: Say Hello - run: echo "Al says hello" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 728a2ce..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: Manual Test - -on: - workflow_dispatch: - -jobs: - do-something: - runs-on: ubuntu-latest - steps: - - run: echo "Hello I have been triggered Manually" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..23251a3 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# Grafana.Plugin.CostManagement +This is a Grafana datasource plugin to display Azure cost data + +## Requirements + +npm, mage, go, and any supported IDE for DEV running (suggested would be VS Code). + +## Local Running + +This requires a linux environment with npm, mage and go installed + +###Build Steps + +Clone the repos then cd dfe-azurecostbackend-datasource +npm install +npm run dev +mage -v build:linux + +###Sign the Plugin + +export GRAFANA_ACCESS_POLICY_TOKEN=your token created in the grafana cloud site +npx @grafana/sign-plugin@latest --rootUrls http://localhost:3000/ + +###Run Steps + +Call: docker-compose up +Select the Azure Cost Datasource, confugure it then add it to a pannel \ No newline at end of file From e2c7cbf9606032a888a3ac1684adbca7283626cc Mon Sep 17 00:00:00 2001 From: Martin-Belton-gov Date: Thu, 7 Dec 2023 08:55:21 +0000 Subject: [PATCH 2/3] Added Sum Values --- dfe-azurecostbackend-datasource/package.json | 2 +- .../pkg/plugin/datasource.go | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/dfe-azurecostbackend-datasource/package.json b/dfe-azurecostbackend-datasource/package.json index f4c51c2..97ce443 100644 --- a/dfe-azurecostbackend-datasource/package.json +++ b/dfe-azurecostbackend-datasource/package.json @@ -1,7 +1,7 @@ { "id": "1", "name": "azurecost-backend", - "version": "1.0.0", + "version": "1.0.1", "description": "Azure cost backend", "scripts": { "build": "webpack -c ./.config/webpack/webpack.config.ts --env production", diff --git a/dfe-azurecostbackend-datasource/pkg/plugin/datasource.go b/dfe-azurecostbackend-datasource/pkg/plugin/datasource.go index 0a9ca01..b2bd4ad 100644 --- a/dfe-azurecostbackend-datasource/pkg/plugin/datasource.go +++ b/dfe-azurecostbackend-datasource/pkg/plugin/datasource.go @@ -243,6 +243,9 @@ func (d *Datasource) query(_ context.Context, pCtx backend.PluginContext, query // // Add fields for "time" and "values" timeField := data.NewField("time", nil, make([]time.Time, len(datepoints))) valuesField := data.NewField("values", nil, make([]float64, len(datepoints))) + sumField := data.NewField("sum-values", nil, make([]float64, len(datepoints))) + + rollingTotal := 0.0 // Populate the fields with DatePoint values for i, dp := range datepoints { @@ -252,12 +255,16 @@ func (d *Datasource) query(_ context.Context, pCtx backend.PluginContext, query fmt.Println("Error parsing date:", err) continue } + + rollingTotal = rollingTotal + dp.Value + timeField.Set(i, date) valuesField.Set(i, dp.Value) + sumField.Set(i, rollingTotal) } // Add fields to the frame - frame.Fields = append(frame.Fields, timeField, valuesField) + frame.Fields = append(frame.Fields, timeField, valuesField, sumField) // add the frames to the response. response.Frames = append(response.Frames, frame) @@ -380,7 +387,7 @@ func getCosts(token string, config Config) (CostResponse, error) { } requestURL := config.AzureCostSubscriptionUrl + url - if(len(config.TokenURL) > 1){ + if len(config.TokenURL) > 1 { requestURL = config.TokenURL } req, err := http.NewRequest("POST", requestURL, bytes.NewBuffer(body)) From 5cd7d96887e096d49cc741f07a2979b02aba5560 Mon Sep 17 00:00:00 2001 From: Martin-Belton-gov Date: Thu, 7 Dec 2023 10:32:52 +0000 Subject: [PATCH 3/3] Added Time frame Query --- .../pkg/plugin/datasource.go | 35 +++++++++++++++++-- .../pkg/plugin/datasource_test.go | 32 ++++++++++++++++- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/dfe-azurecostbackend-datasource/pkg/plugin/datasource.go b/dfe-azurecostbackend-datasource/pkg/plugin/datasource.go index b2bd4ad..f5026ae 100644 --- a/dfe-azurecostbackend-datasource/pkg/plugin/datasource.go +++ b/dfe-azurecostbackend-datasource/pkg/plugin/datasource.go @@ -225,7 +225,15 @@ func (d *Datasource) query(_ context.Context, pCtx backend.PluginContext, query // Use the token as needed log.Println("Fetched token:", token) - costs, err := getCosts(token, d.config) + start, end := getCurrentYearDates() + + timeRange := query.TimeRange + if !timeRange.From.IsZero() && !timeRange.To.IsZero(){ + start = timeRange.From.Format("2006-01-02") + end = timeRange.To.Format("2006-01-02") + } + + costs, err := getCosts(token, d.config, start, end) if err != nil { log.Println("Error getting costs:", err) return response @@ -365,11 +373,16 @@ func parseAccessToken(body []byte) (string, error) { } // Fetch Costs -func getCosts(token string, config Config) (CostResponse, error) { +func getCosts(token string, config Config, start string, end string) (CostResponse, error) { url := config.SubscriptionID + "/providers/Microsoft.CostManagement/query?api-version=2023-03-01" + bodyParameters := map[string]interface{}{ "type": "Usage", - "timeframe": "MonthToDate", + "timeframe": "Custom", + "timeperiod": map[string]string{ + "from": start, + "to": end, + }, "dataset": map[string]interface{}{ "granularity": "Daily", "aggregation": map[string]interface{}{ @@ -468,3 +481,19 @@ func convertToStandardDateFormat(inputDate string) (string, error) { fmt.Println("Invalid date format. Expected YYYYMMDD.") return inputDate, fmt.Errorf("Invalid date format. Expected YYYYMMDD.") } + +func getCurrentYearDates() (string, string) { + // Get the current year + currentYear := time.Now().Year() + + // Get the 1st of January of the current year + firstOfJanuary := time.Date(currentYear, time.January, 1, 0, 0, 0, 0, time.UTC) + firstOfJanuaryFormatted := firstOfJanuary.Format("2006-01-02") + + // Get the 31st of December of the current year + thirtyFirstOfDecember := time.Date(currentYear, time.December, 31, 0, 0, 0, 0, time.UTC) + thirtyFirstOfDecemberFormatted := thirtyFirstOfDecember.Format("2006-01-02") + + return firstOfJanuaryFormatted, thirtyFirstOfDecemberFormatted +} + diff --git a/dfe-azurecostbackend-datasource/pkg/plugin/datasource_test.go b/dfe-azurecostbackend-datasource/pkg/plugin/datasource_test.go index a982fae..9a27b15 100644 --- a/dfe-azurecostbackend-datasource/pkg/plugin/datasource_test.go +++ b/dfe-azurecostbackend-datasource/pkg/plugin/datasource_test.go @@ -5,6 +5,7 @@ import ( "net/http" "net/http/httptest" "testing" + "time" "encoding/json" "reflect" @@ -143,7 +144,8 @@ func TestGetCosts(t *testing.T) { // Call the getCosts function with the mock token and config token := "your-mock-token" config.TokenURL = server.URL - costs, err := getCosts(token, config) + start, end := getCurrentYearDates() + costs, err := getCosts(token, config, start, end) if err != nil { t.Errorf("Expected no error, got %v", err) } @@ -187,3 +189,31 @@ func createMockCostResponse() CostResponse { return costResponse } + +var timeNow = time.Now + +func TestGetCurrentYearDates(t *testing.T) { + // Mock current date for testing + mockDate := time.Date(2023, time.January, 15, 0, 0, 0, 0, time.UTC) + // Save the original time function and replace it with a mock + originalTimeNow := timeNow + timeNow = func() time.Time { return mockDate } + defer func() { timeNow = originalTimeNow }() + + // Call the function to get the formatted dates + firstOfJanuary, thirtyFirstOfDecember := getCurrentYearDates() + + // Expected results for the mock date + expectedFirstOfJanuary := "2023-01-01" + expectedThirtyFirstOfDecember := "2023-12-31" + + // Check if the actual results match the expected results + if firstOfJanuary != expectedFirstOfJanuary { + t.Errorf("First of January: expected %s, got %s", expectedFirstOfJanuary, firstOfJanuary) + } + + if thirtyFirstOfDecember != expectedThirtyFirstOfDecember { + t.Errorf("Thirty-First of December: expected %s, got %s", expectedThirtyFirstOfDecember, thirtyFirstOfDecember) + } +} +