Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions docs/swagger/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,61 @@ paths:
description: Success
default:
description: ""
/namespaces/{ns}/charts/histogram/{collection}:
get:
description: 'TODO: Description'
operationId: getChartHistogram
parameters:
- description: 'TODO: Description'
in: path
name: ns
required: true
schema:
example: default
type: string
- description: 'TODO: Description'
in: path
name: collection
required: true
schema:
type: string
- description: Start time of the data to be fetched
in: query
name: startTime
schema:
type: string
- description: End time of the data to be fetched
in: query
name: endTime
schema:
type: string
- description: Number of buckets between start time and end time
in: query
name: buckets
schema:
type: string
- description: Server-side request timeout (millseconds, or set a custom suffix
like 10s)
in: header
name: Request-Timeout
schema:
default: 120s
type: string
responses:
"200":
content:
application/json:
schema:
items:
properties:
count:
type: string
timestamp: {}
type: object
type: array
description: Success
default:
description: ""
/namespaces/{ns}/data:
get:
description: 'TODO: Description'
Expand Down
63 changes: 63 additions & 0 deletions internal/apiserver/route_get_chart_histogram.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright © 2021 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package apiserver

import (
"net/http"
"strconv"

"github.com/hyperledger/firefly/internal/config"
"github.com/hyperledger/firefly/internal/i18n"
"github.com/hyperledger/firefly/internal/oapispec"
"github.com/hyperledger/firefly/pkg/database"
"github.com/hyperledger/firefly/pkg/fftypes"
)

var getChartHistogram = &oapispec.Route{
Name: "getChartHistogram",
Path: "namespaces/{ns}/charts/histogram/{collection}",
Method: http.MethodGet,
PathParams: []*oapispec.PathParam{
{Name: "ns", ExampleFromConf: config.NamespacesDefault, Description: i18n.MsgTBD},
{Name: "collection", Description: i18n.MsgTBD},
},
QueryParams: []*oapispec.QueryParam{
{Name: "startTime", Description: i18n.MsgHistogramStartTimeParam, IsBool: false},
{Name: "endTime", Description: i18n.MsgHistogramEndTimeParam, IsBool: false},
{Name: "buckets", Description: i18n.MsgHistogramBucketsParam, IsBool: false},
},
FilterFactory: nil,
Description: i18n.MsgTBD,
JSONInputValue: nil,
JSONOutputValue: func() interface{} { return []*fftypes.ChartHistogram{} },
JSONOutputCodes: []int{http.StatusOK},
JSONHandler: func(r *oapispec.APIRequest) (output interface{}, err error) {
startTime, err := fftypes.ParseString(r.QP["startTime"])
if err != nil {
return nil, i18n.NewError(r.Ctx, i18n.MsgInvalidChartNumberParam, "startTime")
}
endTime, err := fftypes.ParseString(r.QP["endTime"])
if err != nil {
return nil, i18n.NewError(r.Ctx, i18n.MsgInvalidChartNumberParam, "endTime")
}
buckets, err := strconv.ParseInt(r.QP["buckets"], 10, 64)
if err != nil {
return nil, i18n.NewError(r.Ctx, i18n.MsgInvalidChartNumberParam, "buckets")
}
return r.Or.GetChartHistogram(r.Ctx, r.PP["ns"], startTime.UnixNano(), endTime.UnixNano(), buckets, database.CollectionName(r.PP["collection"]))
},
}
76 changes: 76 additions & 0 deletions internal/apiserver/route_get_chart_histogram_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright © 2021 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package apiserver

import (
"net/http/httptest"
"testing"

"github.com/hyperledger/firefly/pkg/database"
"github.com/hyperledger/firefly/pkg/fftypes"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func TestGetChartHistogramBadStartTime(t *testing.T) {
_, r := newTestAPIServer()
req := httptest.NewRequest("GET", "/api/v1/namespaces/mynamespace/charts/histogram/test?startTime=abc&endTime=456&buckets=30", nil)
req.Header.Set("Content-Type", "application/json; charset=utf-8")
res := httptest.NewRecorder()

r.ServeHTTP(res, req)

assert.Equal(t, 400, res.Result().StatusCode)
}

func TestGetChartHistogramBadEndTime(t *testing.T) {
_, r := newTestAPIServer()
req := httptest.NewRequest("GET", "/api/v1/namespaces/mynamespace/charts/histogram/test?startTime=123&endTime=abc&buckets=30", nil)
req.Header.Set("Content-Type", "application/json; charset=utf-8")
res := httptest.NewRecorder()

r.ServeHTTP(res, req)

assert.Equal(t, 400, res.Result().StatusCode)
}

func TestGetChartHistogramBadBuckets(t *testing.T) {
_, r := newTestAPIServer()
req := httptest.NewRequest("GET", "/api/v1/namespaces/mynamespace/charts/histogram/test?startTime=123&endTime=456&buckets=abc", nil)
req.Header.Set("Content-Type", "application/json; charset=utf-8")
res := httptest.NewRecorder()

r.ServeHTTP(res, req)

assert.Equal(t, 400, res.Result().StatusCode)
}

func TestGetChartHistogramSuccess(t *testing.T) {
o, r := newTestAPIServer()
req := httptest.NewRequest("GET", "/api/v1/namespaces/mynamespace/charts/histogram/test?startTime=1234567890&endTime=1234567891&buckets=30", nil)
req.Header.Set("Content-Type", "application/json; charset=utf-8")
res := httptest.NewRecorder()

startTime, _ := fftypes.ParseString("1234567890")
endtime, _ := fftypes.ParseString("1234567891")

o.On("GetChartHistogram", mock.Anything, "mynamespace", startTime.UnixNano(), endtime.UnixNano(), int64(30), database.CollectionName("test")).
Return([]*fftypes.ChartHistogram{}, nil)
r.ServeHTTP(res, req)

assert.Equal(t, 200, res.Result().StatusCode)
}
2 changes: 2 additions & 0 deletions internal/apiserver/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ var routes = []*oapispec.Route{
getTxnOps,
getTxns,

getChartHistogram,

postTokenPool,
postTokenPoolByType,
getTokenPools,
Expand Down
106 changes: 106 additions & 0 deletions internal/database/sqlcommon/chart_sql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright © 2021 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sqlcommon

import (
"context"
"database/sql"
"fmt"

sq "github.com/Masterminds/squirrel"
"github.com/hyperledger/firefly/internal/i18n"
"github.com/hyperledger/firefly/pkg/database"
"github.com/hyperledger/firefly/pkg/fftypes"
)

func (s *SQLCommon) getCaseQueries(ns string, intervals []fftypes.ChartHistogramInterval) (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{"namespace": ns},
},
"1",
).
Else("0"))
}

return caseQueries
}

func (s *SQLCommon) getTableNameFromCollection(ctx context.Context, collection database.CollectionName) (tableName string, err error) {
switch collection {
case database.CollectionName(database.CollectionMessages):
return "messages", nil
case database.CollectionName(database.CollectionTransactions):
return "transactions", nil
case database.CollectionName(database.CollectionOperations):
return "operations", nil
case database.CollectionName(database.CollectionEvents):
return "events", nil
default:
return "", i18n.NewError(ctx, i18n.MsgUnsupportedCollection, collection)
}
}

func (s *SQLCommon) histogramResult(ctx context.Context, rows *sql.Rows, cols []*fftypes.ChartHistogram) ([]*fftypes.ChartHistogram, error) {
results := []interface{}{}

for i := range cols {
results = append(results, &cols[i].Count)
}
err := rows.Scan(results...)
if err != nil {
return nil, i18n.NewError(ctx, i18n.MsgDBReadErr, "histogram")
}

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)
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))
if err != nil {
return nil, err
}
defer rows.Close()

if !rows.Next() {
return []*fftypes.ChartHistogram{}, nil
}

return s.histogramResult(ctx, rows, histogram)
}
Loading