From e20ef1d1a1a25c6894bb6dc691abe59de911c7f3 Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Wed, 23 Feb 2022 17:45:49 -0500 Subject: [PATCH 1/3] [charts-by-type] histogram broken down by type Signed-off-by: David Echelberger --- docs/swagger/swagger.yaml | 11 +- internal/database/sqlcommon/chart_sql.go | 105 +++++++++++++----- internal/database/sqlcommon/chart_sql_test.go | 56 +++++++++- pkg/fftypes/charthistogram.go | 12 +- 4 files changed, 147 insertions(+), 37 deletions(-) diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 4cb40a91d5..f479cd2213 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -1506,9 +1506,16 @@ paths: application/json: schema: properties: - count: + buckets: + items: + properties: + count: + type: string + timestamp: {} + type: object + type: array + type: type: string - timestamp: {} type: object description: Success default: diff --git a/internal/database/sqlcommon/chart_sql.go b/internal/database/sqlcommon/chart_sql.go index a93f946552..e005479f8f 100644 --- a/internal/database/sqlcommon/chart_sql.go +++ b/internal/database/sqlcommon/chart_sql.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -27,13 +27,14 @@ import ( "github.com/hyperledger/firefly/pkg/fftypes" ) -func (s *SQLCommon) getCaseQueries(ns string, intervals []fftypes.ChartHistogramInterval) (caseQueries []sq.CaseBuilder) { +func (s *SQLCommon) getCaseQueries(ns string, dataType string, intervals []fftypes.ChartHistogramInterval, typeColName string) (caseQueries []sq.CaseBuilder) { for _, interval := range intervals { caseQueries = append(caseQueries, sq.Case(). When( sq.And{ sq.GtOrEq{"created": interval.StartTime}, sq.Lt{"created": interval.EndTime}, + sq.Eq{typeColName: dataType}, sq.Eq{"namespace": ns}, }, "1", @@ -44,22 +45,45 @@ func (s *SQLCommon) getCaseQueries(ns string, intervals []fftypes.ChartHistogram return caseQueries } -func (s *SQLCommon) getTableNameFromCollection(ctx context.Context, collection database.CollectionName) (tableName string, err error) { +func (s *SQLCommon) getTableNameFromCollection(ctx context.Context, collection database.CollectionName) (tableName string, fieldMap map[string]string, err error) { switch collection { case database.CollectionName(database.CollectionMessages): - return "messages", nil + return "messages", msgFilterFieldMap, nil case database.CollectionName(database.CollectionTransactions): - return "transactions", nil + return "transactions", transactionFilterFieldMap, nil case database.CollectionName(database.CollectionOperations): - return "operations", nil + return "operations", opFilterFieldMap, nil case database.CollectionName(database.CollectionEvents): - return "events", nil + return "events", eventFilterFieldMap, nil default: - return "", i18n.NewError(ctx, i18n.MsgUnsupportedCollection, collection) + return "", nil, i18n.NewError(ctx, i18n.MsgUnsupportedCollection, collection) } } -func (s *SQLCommon) histogramResult(ctx context.Context, rows *sql.Rows, cols []*fftypes.ChartHistogram) ([]*fftypes.ChartHistogram, error) { +func (s *SQLCommon) getDistinctTypesFromTable(ctx context.Context, tableName string, fieldMap map[string]string) ([]string, error) { + qb := sq.Select(fieldMap["type"]).Distinct().From(tableName) + + rows, _, err := s.query(ctx, qb.From(tableName)) + if err != nil { + return nil, err + } + defer rows.Close() + + var dataTypes []string + for rows.Next() { + var dataType string + err := rows.Scan(&dataType) + if err != nil { + return []string{}, i18n.WrapError(ctx, err, i18n.MsgDBReadErr, tableName) + } + dataTypes = append(dataTypes, dataType) + } + rows.Close() + + return dataTypes, nil +} + +func (s *SQLCommon) histogramResult(ctx context.Context, rows *sql.Rows, cols []*fftypes.ChartHistogramBucket, tableName string) ([]*fftypes.ChartHistogramBucket, error) { results := []interface{}{} for i := range cols { @@ -67,40 +91,61 @@ func (s *SQLCommon) histogramResult(ctx context.Context, rows *sql.Rows, cols [] } err := rows.Scan(results...) if err != nil { - return nil, i18n.NewError(ctx, i18n.MsgDBReadErr, "histogram") + return nil, i18n.NewError(ctx, i18n.MsgDBReadErr, tableName) } return cols, nil } -func (s *SQLCommon) GetChartHistogram(ctx context.Context, ns string, intervals []fftypes.ChartHistogramInterval, collection database.CollectionName) (histogram []*fftypes.ChartHistogram, err error) { - tableName, err := s.getTableNameFromCollection(ctx, collection) +func (s *SQLCommon) GetChartHistogram(ctx context.Context, ns string, intervals []fftypes.ChartHistogramInterval, collection database.CollectionName) (histogramList []*fftypes.ChartHistogram, err error) { + tableName, fieldMap, err := s.getTableNameFromCollection(ctx, collection) if err != nil { return nil, err } - qb := sq.Select() - - for i, caseQuery := range s.getCaseQueries(ns, intervals) { - query, args, _ := caseQuery.ToSql() - - histogram = append(histogram, &fftypes.ChartHistogram{ - Count: "", - Timestamp: intervals[i].StartTime, - }) - - qb = qb.Column(sq.Alias(sq.Expr("SUM("+query+")", args...), fmt.Sprintf("case_%d", i))) - } - - rows, _, err := s.query(ctx, qb.From(tableName)) + dataTypes, err := s.getDistinctTypesFromTable(ctx, tableName, fieldMap) if err != nil { return nil, err } - defer rows.Close() - if !rows.Next() { - return []*fftypes.ChartHistogram{}, nil + for _, dataType := range dataTypes { + qb := sq.Select() + histogram := make([]*fftypes.ChartHistogramBucket, 0) + for i, caseQuery := range s.getCaseQueries(ns, dataType, intervals, fieldMap["type"]) { + query, args, _ := caseQuery.ToSql() + + histogram = append(histogram, &fftypes.ChartHistogramBucket{ + Count: "", + Timestamp: intervals[i].StartTime, + }) + + qb = qb.Column(sq.Alias(sq.Expr("SUM("+query+")", args...), fmt.Sprintf("case_%d", i))) + } + + rows, _, err := s.query(ctx, qb.From(tableName)) + if err != nil { + return nil, err + } + defer rows.Close() + + if rows.Next() { + hist, err := s.histogramResult(ctx, rows, histogram, tableName) + rows.Close() + if err != nil { + return nil, err + } + + histogramList = append(histogramList, &fftypes.ChartHistogram{ + Buckets: hist, + Type: dataType, + }) + } else { + histogramList = append(histogramList, &fftypes.ChartHistogram{ + Buckets: make([]*fftypes.ChartHistogramBucket, 0), + Type: dataType, + }) + } } - return s.histogramResult(ctx, rows, histogram) + return histogramList, nil } diff --git a/internal/database/sqlcommon/chart_sql_test.go b/internal/database/sqlcommon/chart_sql_test.go index e598d91a5f..d62935a2ed 100644 --- a/internal/database/sqlcommon/chart_sql_test.go +++ b/internal/database/sqlcommon/chart_sql_test.go @@ -28,13 +28,37 @@ import ( ) var ( - emptyHistogramResult = make([]*fftypes.ChartHistogram, 0) + emptyHistogramResult = []*fftypes.ChartHistogram{ + { + Buckets: make([]*fftypes.ChartHistogramBucket, 0), + Type: "typeA", + }, + { + Buckets: make([]*fftypes.ChartHistogramBucket, 0), + Type: "typeB", + }, + } expectedHistogramResult = []*fftypes.ChartHistogram{ { - Count: "123", - Timestamp: fftypes.UnixTime(1000000000), + Buckets: []*fftypes.ChartHistogramBucket{ + { + Count: "123", + Timestamp: fftypes.UnixTime(1000000000), + }, + }, + Type: "typeA", + }, + { + Buckets: []*fftypes.ChartHistogramBucket{ + { + Count: "123", + Timestamp: fftypes.UnixTime(1000000000), + }, + }, + Type: "typeB", }, } + mockHistogramInterval = []fftypes.ChartHistogramInterval{ { StartTime: fftypes.UnixTime(1000000000), @@ -59,6 +83,8 @@ func TestGetChartHistogramInvalidCollectionName(t *testing.T) { func TestGetChartHistogramValidCollectionName(t *testing.T) { for i := range validCollections { s, mock := newMockProvider().init() + mock.ExpectQuery("SELECT DISTINCT .*").WillReturnRows(sqlmock.NewRows([]string{"type"}).AddRow("typeA").AddRow("typeB")) + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0"}).AddRow("123")) mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0"}).AddRow("123")) histogram, err := s.GetChartHistogram(context.Background(), "ns1", mockHistogramInterval, database.CollectionName(validCollections[i])) @@ -71,6 +97,7 @@ func TestGetChartHistogramValidCollectionName(t *testing.T) { func TestGetChartHistogramsQueryFail(t *testing.T) { s, mock := newMockProvider().init() + mock.ExpectQuery("SELECT DISTINCT .*").WillReturnRows(sqlmock.NewRows([]string{"type"}).AddRow("typeA").AddRow("typeB")) mock.ExpectQuery("SELECT *").WillReturnError(fmt.Errorf("pop")) _, err := s.GetChartHistogram(context.Background(), "ns1", mockHistogramInterval, database.CollectionName("messages")) @@ -78,8 +105,27 @@ func TestGetChartHistogramsQueryFail(t *testing.T) { assert.NoError(t, mock.ExpectationsWereMet()) } +func TestGetChartHistogramQueryFailBadDistinctTypes(t *testing.T) { + s, mock := newMockProvider().init() + mock.ExpectQuery("SELECT DISTINCT .*").WillReturnError(fmt.Errorf("pop")) + + _, err := s.GetChartHistogram(context.Background(), "ns1", mockHistogramInterval, database.CollectionName("messages")) + assert.Regexp(t, "FF10115", err) + assert.NoError(t, mock.ExpectationsWereMet()) +} + +func TestGetChartHistogramScanFailInvalidRowType(t *testing.T) { + s, mock := newMockProvider().init() + mock.ExpectQuery("SELECT DISTINCT .*").WillReturnRows(sqlmock.NewRows([]string{"type"}).AddRow(nil).AddRow("typeB")) + + _, err := s.GetChartHistogram(context.Background(), "ns1", mockHistogramInterval, database.CollectionName("messages")) + assert.Regexp(t, "FF10121", err) + assert.NoError(t, mock.ExpectationsWereMet()) +} + func TestGetChartHistogramScanFailTooManyCols(t *testing.T) { s, mock := newMockProvider().init() + mock.ExpectQuery("SELECT DISTINCT .*").WillReturnRows(sqlmock.NewRows([]string{"type"}).AddRow("typeA").AddRow("typeB")) mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0", "unexpected_column"}).AddRow("one", "two")) _, err := s.GetChartHistogram(context.Background(), "ns1", mockHistogramInterval, database.CollectionName("messages")) @@ -89,6 +135,8 @@ func TestGetChartHistogramScanFailTooManyCols(t *testing.T) { func TestGetChartHistogramSuccessNoRows(t *testing.T) { s, mock := newMockProvider().init() + mock.ExpectQuery("SELECT DISTINCT .*").WillReturnRows(sqlmock.NewRows([]string{"type"}).AddRow("typeA").AddRow("typeB")) + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0"})) mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0"})) histogram, err := s.GetChartHistogram(context.Background(), "ns1", mockHistogramInterval, database.CollectionName("messages")) @@ -99,6 +147,8 @@ func TestGetChartHistogramSuccessNoRows(t *testing.T) { func TestGetChartHistogramSuccess(t *testing.T) { s, mock := newMockProvider().init() + mock.ExpectQuery("SELECT DISTINCT .*").WillReturnRows(sqlmock.NewRows([]string{"type"}).AddRow("typeA").AddRow("typeB")) + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0"}).AddRow("123")) mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0"}).AddRow("123")) histogram, err := s.GetChartHistogram(context.Background(), "ns1", mockHistogramInterval, database.CollectionName("messages")) diff --git a/pkg/fftypes/charthistogram.go b/pkg/fftypes/charthistogram.go index 218501a567..e9018e1dad 100644 --- a/pkg/fftypes/charthistogram.go +++ b/pkg/fftypes/charthistogram.go @@ -1,4 +1,4 @@ -// Copyright © 2021 Kaleido, Inc. +// Copyright © 2022 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -23,8 +23,16 @@ const ( ChartHistogramMinBuckets = 1 ) -// ChartHistogram is a timestamp and count +// ChartHistogram is a list of buckets and a type type ChartHistogram struct { + // Buckets list of histogram buckets + Buckets []*ChartHistogramBucket `json:"buckets"` + // Type type of histogram buckets + Type string `json:"type"` +} + +// ChartHistogramBucket is a timestamp and count +type ChartHistogramBucket struct { // Timestamp of bucket in histogram Timestamp *FFTime `json:"timestamp"` // Count for timestamp in histogram From 31f630884d3ea82e0637a0bb2bc5cc7e34385ed7 Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Thu, 24 Feb 2022 15:53:11 -0500 Subject: [PATCH 2/3] [charts-by-type] support for token transfer metrics Signed-off-by: David Echelberger --- internal/database/sqlcommon/chart_sql.go | 2 ++ internal/database/sqlcommon/tokentransfer_sql.go | 1 + 2 files changed, 3 insertions(+) diff --git a/internal/database/sqlcommon/chart_sql.go b/internal/database/sqlcommon/chart_sql.go index e005479f8f..6617ca2998 100644 --- a/internal/database/sqlcommon/chart_sql.go +++ b/internal/database/sqlcommon/chart_sql.go @@ -55,6 +55,8 @@ func (s *SQLCommon) getTableNameFromCollection(ctx context.Context, collection d return "operations", opFilterFieldMap, nil case database.CollectionName(database.CollectionEvents): return "events", eventFilterFieldMap, nil + case database.CollectionName(database.CollectionTokenTransfers): + return "tokentransfer", tokenTransferFilterFieldMap, nil default: return "", nil, i18n.NewError(ctx, i18n.MsgUnsupportedCollection, collection) } diff --git a/internal/database/sqlcommon/tokentransfer_sql.go b/internal/database/sqlcommon/tokentransfer_sql.go index 279b20b0b1..813558b4b6 100644 --- a/internal/database/sqlcommon/tokentransfer_sql.go +++ b/internal/database/sqlcommon/tokentransfer_sql.go @@ -49,6 +49,7 @@ var ( "created", } tokenTransferFilterFieldMap = map[string]string{ + "type": "type", "localid": "local_id", "pool": "pool_id", "tokenindex": "token_index", From 9cffb808812567e77a2771af3bb2ad22c78de6e4 Mon Sep 17 00:00:00 2001 From: David Echelberger Date: Fri, 4 Mar 2022 16:47:59 -0500 Subject: [PATCH 3/3] [charts-by-type] reformatting response Signed-off-by: David Echelberger --- docs/swagger/swagger.yaml | 10 ++-- internal/database/sqlcommon/chart_sql.go | 52 +++++++++++++------ internal/database/sqlcommon/chart_sql_test.go | 49 ++++++++--------- pkg/fftypes/charthistogram.go | 22 ++++---- 4 files changed, 79 insertions(+), 54 deletions(-) diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 6b03318d0e..67325ce7b0 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -1417,16 +1417,18 @@ paths: application/json: schema: properties: - buckets: + count: + type: string + timestamp: {} + types: items: properties: count: type: string - timestamp: {} + type: + type: string type: object type: array - type: - type: string type: object description: Success default: diff --git a/internal/database/sqlcommon/chart_sql.go b/internal/database/sqlcommon/chart_sql.go index 6617ca2998..17c1bf29a8 100644 --- a/internal/database/sqlcommon/chart_sql.go +++ b/internal/database/sqlcommon/chart_sql.go @@ -20,6 +20,7 @@ import ( "context" "database/sql" "fmt" + "strconv" sq "github.com/Masterminds/squirrel" "github.com/hyperledger/firefly/internal/i18n" @@ -27,8 +28,8 @@ import ( "github.com/hyperledger/firefly/pkg/fftypes" ) -func (s *SQLCommon) getCaseQueries(ns string, dataType string, intervals []fftypes.ChartHistogramInterval, typeColName string) (caseQueries []sq.CaseBuilder) { - for _, interval := range intervals { +func (s *SQLCommon) getCaseQueries(ns string, dataTypes []string, interval fftypes.ChartHistogramInterval, typeColName string) (caseQueries []sq.CaseBuilder) { + for _, dataType := range dataTypes { caseQueries = append(caseQueries, sq.Case(). When( sq.And{ @@ -56,7 +57,7 @@ func (s *SQLCommon) getTableNameFromCollection(ctx context.Context, collection d case database.CollectionName(database.CollectionEvents): return "events", eventFilterFieldMap, nil case database.CollectionName(database.CollectionTokenTransfers): - return "tokentransfer", tokenTransferFilterFieldMap, nil + return "tokentransfers", tokenTransferFilterFieldMap, nil default: return "", nil, i18n.NewError(ctx, i18n.MsgUnsupportedCollection, collection) } @@ -85,7 +86,7 @@ func (s *SQLCommon) getDistinctTypesFromTable(ctx context.Context, tableName str return dataTypes, nil } -func (s *SQLCommon) histogramResult(ctx context.Context, rows *sql.Rows, cols []*fftypes.ChartHistogramBucket, tableName string) ([]*fftypes.ChartHistogramBucket, error) { +func (s *SQLCommon) histogramResult(ctx context.Context, rows *sql.Rows, cols []*fftypes.ChartHistogramType, tableName string) ([]*fftypes.ChartHistogramType, error) { results := []interface{}{} for i := range cols { @@ -99,6 +100,18 @@ func (s *SQLCommon) histogramResult(ctx context.Context, rows *sql.Rows, cols [] return cols, nil } +func (s *SQLCommon) getBucketTotal(typeBuckets []*fftypes.ChartHistogramType) (string, error) { + total := 0 + for _, typeBucket := range typeBuckets { + typeBucketInt, err := strconv.Atoi(typeBucket.Count) + if err != nil { + return "", err + } + total += typeBucketInt + } + return strconv.Itoa(total), nil +} + func (s *SQLCommon) GetChartHistogram(ctx context.Context, ns string, intervals []fftypes.ChartHistogramInterval, collection database.CollectionName) (histogramList []*fftypes.ChartHistogram, err error) { tableName, fieldMap, err := s.getTableNameFromCollection(ctx, collection) if err != nil { @@ -110,15 +123,15 @@ func (s *SQLCommon) GetChartHistogram(ctx context.Context, ns string, intervals return nil, err } - for _, dataType := range dataTypes { + for _, interval := range intervals { qb := sq.Select() - histogram := make([]*fftypes.ChartHistogramBucket, 0) - for i, caseQuery := range s.getCaseQueries(ns, dataType, intervals, fieldMap["type"]) { - query, args, _ := caseQuery.ToSql() + histogramTypes := make([]*fftypes.ChartHistogramType, 0) - histogram = append(histogram, &fftypes.ChartHistogramBucket{ - Count: "", - Timestamp: intervals[i].StartTime, + for i, caseQuery := range s.getCaseQueries(ns, dataTypes, interval, fieldMap["type"]) { + query, args, _ := caseQuery.ToSql() + histogramTypes = append(histogramTypes, &fftypes.ChartHistogramType{ + Count: "", + Type: dataTypes[i], }) qb = qb.Column(sq.Alias(sq.Expr("SUM("+query+")", args...), fmt.Sprintf("case_%d", i))) @@ -131,20 +144,27 @@ func (s *SQLCommon) GetChartHistogram(ctx context.Context, ns string, intervals defer rows.Close() if rows.Next() { - hist, err := s.histogramResult(ctx, rows, histogram, tableName) + hist, err := s.histogramResult(ctx, rows, histogramTypes, tableName) rows.Close() if err != nil { return nil, err } + total, err := s.getBucketTotal(hist) + if err != nil { + return nil, err + } + histogramList = append(histogramList, &fftypes.ChartHistogram{ - Buckets: hist, - Type: dataType, + Count: total, + Timestamp: interval.StartTime, + Types: hist, }) } else { histogramList = append(histogramList, &fftypes.ChartHistogram{ - Buckets: make([]*fftypes.ChartHistogramBucket, 0), - Type: dataType, + Count: "0", + Timestamp: interval.StartTime, + Types: make([]*fftypes.ChartHistogramType, 0), }) } } diff --git a/internal/database/sqlcommon/chart_sql_test.go b/internal/database/sqlcommon/chart_sql_test.go index d62935a2ed..3a790fb758 100644 --- a/internal/database/sqlcommon/chart_sql_test.go +++ b/internal/database/sqlcommon/chart_sql_test.go @@ -30,32 +30,25 @@ import ( var ( emptyHistogramResult = []*fftypes.ChartHistogram{ { - Buckets: make([]*fftypes.ChartHistogramBucket, 0), - Type: "typeA", - }, - { - Buckets: make([]*fftypes.ChartHistogramBucket, 0), - Type: "typeB", + Count: "0", + Timestamp: fftypes.UnixTime(1000000000), + Types: make([]*fftypes.ChartHistogramType, 0), }, } expectedHistogramResult = []*fftypes.ChartHistogram{ { - Buckets: []*fftypes.ChartHistogramBucket{ + Count: "10", + Timestamp: fftypes.UnixTime(1000000000), + Types: []*fftypes.ChartHistogramType{ { - Count: "123", - Timestamp: fftypes.UnixTime(1000000000), + Count: "5", + Type: "typeA", }, - }, - Type: "typeA", - }, - { - Buckets: []*fftypes.ChartHistogramBucket{ { - Count: "123", - Timestamp: fftypes.UnixTime(1000000000), + Count: "5", + Type: "typeB", }, }, - Type: "typeB", }, } @@ -70,6 +63,7 @@ var ( "messages", "operations", "transactions", + "tokentransfers", } ) @@ -84,8 +78,7 @@ func TestGetChartHistogramValidCollectionName(t *testing.T) { for i := range validCollections { s, mock := newMockProvider().init() mock.ExpectQuery("SELECT DISTINCT .*").WillReturnRows(sqlmock.NewRows([]string{"type"}).AddRow("typeA").AddRow("typeB")) - mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0"}).AddRow("123")) - mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0"}).AddRow("123")) + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0", "case_1"}).AddRow("5", "5")) histogram, err := s.GetChartHistogram(context.Background(), "ns1", mockHistogramInterval, database.CollectionName(validCollections[i])) @@ -126,18 +119,27 @@ func TestGetChartHistogramScanFailInvalidRowType(t *testing.T) { func TestGetChartHistogramScanFailTooManyCols(t *testing.T) { s, mock := newMockProvider().init() mock.ExpectQuery("SELECT DISTINCT .*").WillReturnRows(sqlmock.NewRows([]string{"type"}).AddRow("typeA").AddRow("typeB")) - mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0", "unexpected_column"}).AddRow("one", "two")) + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0", "case_1", "unexpected_col"}).AddRow("one", "two", "three")) _, err := s.GetChartHistogram(context.Background(), "ns1", mockHistogramInterval, database.CollectionName("messages")) assert.Regexp(t, "FF10121", err) assert.NoError(t, mock.ExpectationsWereMet()) } +func TestGetChartHistogramFailStringToIntConversion(t *testing.T) { + s, mock := newMockProvider().init() + mock.ExpectQuery("SELECT DISTINCT .*").WillReturnRows(sqlmock.NewRows([]string{"type"}).AddRow("typeA").AddRow("typeB")) + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0", "case_1"}).AddRow("5", "NotInt")) + + _, err := s.GetChartHistogram(context.Background(), "ns1", mockHistogramInterval, database.CollectionName("messages")) + assert.Error(t, err) + assert.NoError(t, mock.ExpectationsWereMet()) +} + func TestGetChartHistogramSuccessNoRows(t *testing.T) { s, mock := newMockProvider().init() mock.ExpectQuery("SELECT DISTINCT .*").WillReturnRows(sqlmock.NewRows([]string{"type"}).AddRow("typeA").AddRow("typeB")) - mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0"})) - mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0"})) + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0", "case_1"})) histogram, err := s.GetChartHistogram(context.Background(), "ns1", mockHistogramInterval, database.CollectionName("messages")) assert.NoError(t, err) @@ -148,8 +150,7 @@ func TestGetChartHistogramSuccessNoRows(t *testing.T) { func TestGetChartHistogramSuccess(t *testing.T) { s, mock := newMockProvider().init() mock.ExpectQuery("SELECT DISTINCT .*").WillReturnRows(sqlmock.NewRows([]string{"type"}).AddRow("typeA").AddRow("typeB")) - mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0"}).AddRow("123")) - mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0"}).AddRow("123")) + mock.ExpectQuery("SELECT .*").WillReturnRows(sqlmock.NewRows([]string{"case_0", "case_1"}).AddRow("5", "5")) histogram, err := s.GetChartHistogram(context.Background(), "ns1", mockHistogramInterval, database.CollectionName("messages")) diff --git a/pkg/fftypes/charthistogram.go b/pkg/fftypes/charthistogram.go index e9018e1dad..19716f08cc 100644 --- a/pkg/fftypes/charthistogram.go +++ b/pkg/fftypes/charthistogram.go @@ -23,20 +23,22 @@ const ( ChartHistogramMinBuckets = 1 ) -// ChartHistogram is a list of buckets and a type +// ChartHistogram is a list of buckets with types type ChartHistogram struct { - // Buckets list of histogram buckets - Buckets []*ChartHistogramBucket `json:"buckets"` - // Type type of histogram buckets - Type string `json:"type"` + // Count for entire timestamp in histogram + Count string `json:"count"` + // Timestamp of bucket + Timestamp *FFTime `json:"timestamp"` + // Types list of histogram types and their count + Types []*ChartHistogramType `json:"types"` } -// ChartHistogramBucket is a timestamp and count -type ChartHistogramBucket struct { - // Timestamp of bucket in histogram - Timestamp *FFTime `json:"timestamp"` - // Count for timestamp in histogram +// ChartHistogramType is a type and count +type ChartHistogramType struct { + // Count for type in histogram bucket Count string `json:"count"` + // Type of bucket in histogram + Type string `json:"type"` } // ChartHistogramInterval specifies lower and upper timestamps for histogram bucket