mirror of
https://github.com/absmach/supermq.git
synced 2026-06-23 04:00:27 +00:00
MG-136 - Move reports to a separate service (#152)
* initial implementation Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * initial implementation Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * add remove report from nats handler Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * add license header Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * fix failing linter Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * remove unused code Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * update docker compose Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * address comments Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * fix failing linter Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * move runinfo to pkg Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * update report handler Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * update reports handler Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * update handler in reports Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * update repo method from time to due Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * fix validation methods Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * address comments Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * update reports port to 9017 Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * update nginx to support reports Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * fix reports location in nginx Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * update env variable Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> --------- Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
|
||||
MG_DOCKER_IMAGE_NAME_PREFIX ?= ghcr.io/absmach/magistrala
|
||||
BUILD_DIR = build
|
||||
SERVICES = bootstrap provision re postgres-writer postgres-reader timescale-writer timescale-reader cli alarms
|
||||
SERVICES = bootstrap provision re postgres-writer postgres-reader timescale-writer timescale-reader cli alarms reports
|
||||
DOCKERS = $(addprefix docker_,$(SERVICES))
|
||||
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
|
||||
CGO_ENABLED ?= 0
|
||||
|
||||
@@ -0,0 +1,556 @@
|
||||
# Copyright (c) Abstract Machines
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
openapi: 3.0.1
|
||||
info:
|
||||
title: Magistrala Reports Service API
|
||||
description: |
|
||||
HTTP API for managing reports service.
|
||||
version: 0.15.1
|
||||
servers:
|
||||
- url: http://localhost:9017
|
||||
tags:
|
||||
- name: reports
|
||||
description: Operations related to report configurations and generation
|
||||
paths:
|
||||
/{domainID}/reports:
|
||||
post:
|
||||
operationId: generateReport
|
||||
summary: Generate a report
|
||||
description: Generates a report based on the provided configuration or an existing config. The action determines the response format.
|
||||
tags:
|
||||
- reports
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/DomainID'
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenerateReportRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Report generated successfully (content varies by action)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GenerateReportResponse'
|
||||
application/octet-stream:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
'400':
|
||||
description: Invalid request parameters
|
||||
'401':
|
||||
description: Missing or invalid access token
|
||||
'500':
|
||||
$ref: '#/components/responses/ServiceError'
|
||||
|
||||
/{domainID}/reports/configs:
|
||||
post:
|
||||
operationId: addReportConfig
|
||||
summary: Create a report configuration
|
||||
description: Creates a new report configuration.
|
||||
tags:
|
||||
- reports
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/DomainID'
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AddReportConfigRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: Report configuration created
|
||||
headers:
|
||||
Location:
|
||||
schema:
|
||||
type: string
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReportConfig'
|
||||
'400':
|
||||
description: Invalid request body
|
||||
'401':
|
||||
description: Missing or invalid access token
|
||||
'500':
|
||||
$ref: '#/components/responses/ServiceError'
|
||||
get:
|
||||
operationId: listReportConfigs
|
||||
summary: List report configurations
|
||||
description: Retrieves a paginated list of report configurations.
|
||||
tags:
|
||||
- reports
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/DomainID'
|
||||
- $ref: '#/components/parameters/Offset'
|
||||
- $ref: '#/components/parameters/Limit'
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: List of report configurations
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ListReportsConfigResponse'
|
||||
'400':
|
||||
description: Invalid query parameters
|
||||
'401':
|
||||
description: Missing or invalid access token
|
||||
'500':
|
||||
$ref: '#/components/responses/ServiceError'
|
||||
|
||||
/{domainID}/reports/configs/{reportID}:
|
||||
get:
|
||||
operationId: viewReportConfig
|
||||
summary: View a report configuration
|
||||
description: Retrieves details of a specific report configuration.
|
||||
tags:
|
||||
- reports
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/DomainID'
|
||||
- $ref: '#/components/parameters/ReportID'
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: Report configuration details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReportConfig'
|
||||
'404':
|
||||
description: Report configuration not found
|
||||
'401':
|
||||
description: Missing or invalid access token
|
||||
'500':
|
||||
$ref: '#/components/responses/ServiceError'
|
||||
patch:
|
||||
operationId: updateReportConfig
|
||||
summary: Update a report configuration
|
||||
description: Updates specified fields of a report configuration.
|
||||
tags:
|
||||
- reports
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/DomainID'
|
||||
- $ref: '#/components/parameters/ReportID'
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UpdateReportConfigRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Report configuration updated
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReportConfig'
|
||||
'400':
|
||||
description: Invalid request body
|
||||
'401':
|
||||
description: Missing or invalid access token
|
||||
'404':
|
||||
description: Report configuration not found
|
||||
'500':
|
||||
$ref: '#/components/responses/ServiceError'
|
||||
delete:
|
||||
operationId: deleteReportConfig
|
||||
summary: Delete a report configuration
|
||||
description: Permanently deletes a report configuration.
|
||||
tags:
|
||||
- reports
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/DomainID'
|
||||
- $ref: '#/components/parameters/ReportID'
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
'204':
|
||||
description: Report configuration deleted
|
||||
'401':
|
||||
description: Missing or invalid access token
|
||||
'404':
|
||||
description: Report configuration not found
|
||||
'500':
|
||||
$ref: '#/components/responses/ServiceError'
|
||||
|
||||
/{domainID}/reports/configs/{reportID}/schedule:
|
||||
patch:
|
||||
operationId: updateReportSchedule
|
||||
summary: Update report schedule
|
||||
description: Updates the schedule of a report configuration.
|
||||
tags:
|
||||
- reports
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/DomainID'
|
||||
- $ref: '#/components/parameters/ReportID'
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Schedule'
|
||||
responses:
|
||||
'200':
|
||||
description: Schedule updated
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReportConfig'
|
||||
'400':
|
||||
description: Invalid schedule
|
||||
'401':
|
||||
description: Missing or invalid access token
|
||||
'404':
|
||||
description: Report configuration not found
|
||||
'500':
|
||||
$ref: '#/components/responses/ServiceError'
|
||||
|
||||
/{domainID}/reports/configs/{reportID}/enable:
|
||||
post:
|
||||
operationId: enableReportConfig
|
||||
summary: Enable a report configuration
|
||||
description: Enables a report configuration to generate scheduled reports.
|
||||
tags:
|
||||
- reports
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/DomainID'
|
||||
- $ref: '#/components/parameters/ReportID'
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: Report configuration enabled
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReportConfig'
|
||||
'401':
|
||||
description: Missing or invalid access token
|
||||
'404':
|
||||
description: Report configuration not found
|
||||
'500':
|
||||
$ref: '#/components/responses/ServiceError'
|
||||
|
||||
/{domainID}/reports/configs/{reportID}/disable:
|
||||
post:
|
||||
operationId: disableReportConfig
|
||||
summary: Disable a report configuration
|
||||
description: Disables a report configuration, stopping scheduled reports.
|
||||
tags:
|
||||
- reports
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/DomainID'
|
||||
- $ref: '#/components/parameters/ReportID'
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: Report configuration disabled
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReportConfig'
|
||||
'401':
|
||||
description: Missing or invalid access token
|
||||
'404':
|
||||
description: Report configuration not found
|
||||
'500':
|
||||
$ref: '#/components/responses/ServiceError'
|
||||
|
||||
/health:
|
||||
get:
|
||||
summary: Service health check
|
||||
tags:
|
||||
- health
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/HealthRes'
|
||||
|
||||
components:
|
||||
schemas:
|
||||
ReportConfig:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
readOnly: true
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
domain_id:
|
||||
type: string
|
||||
readOnly: true
|
||||
schedule:
|
||||
$ref: '#/components/schemas/Schedule'
|
||||
config:
|
||||
$ref: '#/components/schemas/MetricConfig'
|
||||
email:
|
||||
$ref: '#/components/schemas/EmailSetting'
|
||||
metrics:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ReqMetric'
|
||||
status:
|
||||
$ref: '#/components/schemas/Status'
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
created_by:
|
||||
type: string
|
||||
readOnly: true
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
updated_by:
|
||||
type: string
|
||||
readOnly: true
|
||||
required:
|
||||
- name
|
||||
- metrics
|
||||
- config
|
||||
|
||||
Schedule:
|
||||
type: object
|
||||
properties:
|
||||
recurring:
|
||||
type: string
|
||||
enum: [None, Daily, Weekly, Monthly]
|
||||
recurring_period:
|
||||
type: integer
|
||||
minimum: 1
|
||||
start_time:
|
||||
type: string
|
||||
format: date-time
|
||||
next_run:
|
||||
type: string
|
||||
format: date-time
|
||||
readOnly: true
|
||||
|
||||
MetricConfig:
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
maxLength: 100
|
||||
format:
|
||||
type: string
|
||||
enum: [pdf, csv, html]
|
||||
aggregation:
|
||||
$ref: '#/components/schemas/AggConfig'
|
||||
|
||||
AggConfig:
|
||||
type: object
|
||||
properties:
|
||||
window:
|
||||
type: string
|
||||
function:
|
||||
type: string
|
||||
enum: [sum, average, max, min]
|
||||
|
||||
EmailSetting:
|
||||
type: object
|
||||
properties:
|
||||
recipients:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: email
|
||||
subject:
|
||||
type: string
|
||||
body_template:
|
||||
type: string
|
||||
required:
|
||||
- recipients
|
||||
- subject
|
||||
|
||||
ReqMetric:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
enum: [gauge, counter, histogram]
|
||||
parameters:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- type
|
||||
|
||||
Status:
|
||||
type: string
|
||||
enum: [enabled, disabled]
|
||||
|
||||
GenerateReportRequest:
|
||||
type: object
|
||||
properties:
|
||||
action:
|
||||
type: string
|
||||
enum: [view, download, email]
|
||||
config_id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
schedule:
|
||||
$ref: '#/components/schemas/Schedule'
|
||||
config:
|
||||
$ref: '#/components/schemas/MetricConfig'
|
||||
email:
|
||||
$ref: '#/components/schemas/EmailSetting'
|
||||
metrics:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ReqMetric'
|
||||
required:
|
||||
- action
|
||||
|
||||
GenerateReportResponse:
|
||||
type: object
|
||||
properties:
|
||||
total:
|
||||
type: integer
|
||||
from:
|
||||
type: string
|
||||
format: date-time
|
||||
to:
|
||||
type: string
|
||||
format: date-time
|
||||
aggregation:
|
||||
$ref: '#/components/schemas/AggConfig'
|
||||
reports:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Report'
|
||||
|
||||
Report:
|
||||
type: object
|
||||
properties:
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
value:
|
||||
type: number
|
||||
metric_name:
|
||||
type: string
|
||||
|
||||
AddReportConfigRequest:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
schedule:
|
||||
$ref: '#/components/schemas/Schedule'
|
||||
config:
|
||||
$ref: '#/components/schemas/MetricConfig'
|
||||
email:
|
||||
$ref: '#/components/schemas/EmailSetting'
|
||||
metrics:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ReqMetric'
|
||||
status:
|
||||
$ref: '#/components/schemas/Status'
|
||||
required:
|
||||
- name
|
||||
- metrics
|
||||
- config
|
||||
|
||||
UpdateReportConfigRequest:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
schedule:
|
||||
$ref: '#/components/schemas/Schedule'
|
||||
config:
|
||||
$ref: '#/components/schemas/MetricConfig'
|
||||
email:
|
||||
$ref: '#/components/schemas/EmailSetting'
|
||||
metrics:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ReqMetric'
|
||||
status:
|
||||
$ref: '#/components/schemas/Status'
|
||||
|
||||
ListReportsConfigResponse:
|
||||
type: object
|
||||
properties:
|
||||
total:
|
||||
type: integer
|
||||
offset:
|
||||
type: integer
|
||||
limit:
|
||||
type: integer
|
||||
report_configs:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ReportConfig'
|
||||
|
||||
parameters:
|
||||
DomainID:
|
||||
name: domainID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
ReportID:
|
||||
name: reportID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
Offset:
|
||||
name: offset
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
minimum: 0
|
||||
Limit:
|
||||
name: limit
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 10
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
|
||||
responses:
|
||||
ServiceError:
|
||||
description: Unexpected server error
|
||||
HealthRes:
|
||||
description: Service health status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
+6
-4
@@ -18,9 +18,11 @@ import (
|
||||
grpcReadersV1 "github.com/absmach/magistrala/api/grpc/readers/v1"
|
||||
"github.com/absmach/magistrala/consumers/writers/brokers"
|
||||
"github.com/absmach/magistrala/internal/email"
|
||||
"github.com/absmach/magistrala/pkg/emailer"
|
||||
pkglog "github.com/absmach/magistrala/pkg/logger"
|
||||
"github.com/absmach/magistrala/pkg/ticker"
|
||||
"github.com/absmach/magistrala/re"
|
||||
httpapi "github.com/absmach/magistrala/re/api"
|
||||
"github.com/absmach/magistrala/re/emailer"
|
||||
"github.com/absmach/magistrala/re/middleware"
|
||||
repg "github.com/absmach/magistrala/re/postgres"
|
||||
grpcClient "github.com/absmach/magistrala/readers/api/grpc"
|
||||
@@ -190,7 +192,7 @@ func main() {
|
||||
}
|
||||
defer authnClient.Close()
|
||||
logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure())
|
||||
runInfo := make(chan re.RunInfo, channBuffer)
|
||||
runInfo := make(chan pkglog.RunInfo, channBuffer)
|
||||
|
||||
domsGrpcCfg := grpcclient.Config{}
|
||||
if err := env.ParseWithOptions(&domsGrpcCfg, env.Options{Prefix: envPrefixDomains}); err != nil {
|
||||
@@ -285,7 +287,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func newService(db pgclient.Database, runInfo chan re.RunInfo, rePubSub messaging.PubSub, writersPub, alarmsPub messaging.Publisher, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient) (re.Service, error) {
|
||||
func newService(db pgclient.Database, runInfo chan pkglog.RunInfo, rePubSub messaging.PubSub, writersPub, alarmsPub messaging.Publisher, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient) (re.Service, error) {
|
||||
repo := repg.NewRepository(db)
|
||||
idp := uuid.New()
|
||||
|
||||
@@ -294,7 +296,7 @@ func newService(db pgclient.Database, runInfo chan re.RunInfo, rePubSub messagin
|
||||
logger.Error(fmt.Sprintf("failed to configure e-mailing util: %s", err.Error()))
|
||||
}
|
||||
|
||||
csvc := re.NewService(repo, runInfo, idp, rePubSub, writersPub, alarmsPub, re.NewTicker(time.Second*30), emailerClient, readersClient)
|
||||
csvc := re.NewService(repo, runInfo, idp, rePubSub, writersPub, alarmsPub, ticker.NewTicker(time.Second*30), emailerClient, readersClient)
|
||||
csvc, err = middleware.AuthorizationMiddleware(csvc, authz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package main contains rule engine main function to start the service.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
chclient "github.com/absmach/callhome/pkg/client"
|
||||
grpcReadersV1 "github.com/absmach/magistrala/api/grpc/readers/v1"
|
||||
"github.com/absmach/magistrala/internal/email"
|
||||
"github.com/absmach/magistrala/pkg/emailer"
|
||||
pkglog "github.com/absmach/magistrala/pkg/logger"
|
||||
"github.com/absmach/magistrala/pkg/ticker"
|
||||
grpcClient "github.com/absmach/magistrala/readers/api/grpc"
|
||||
"github.com/absmach/magistrala/reports"
|
||||
httpapi "github.com/absmach/magistrala/reports/api"
|
||||
"github.com/absmach/magistrala/reports/middleware"
|
||||
repg "github.com/absmach/magistrala/reports/postgres"
|
||||
"github.com/absmach/supermq"
|
||||
smqlog "github.com/absmach/supermq/logger"
|
||||
authnsvc "github.com/absmach/supermq/pkg/authn/authsvc"
|
||||
mgauthz "github.com/absmach/supermq/pkg/authz"
|
||||
authzsvc "github.com/absmach/supermq/pkg/authz/authsvc"
|
||||
domainsAuthz "github.com/absmach/supermq/pkg/domains/grpcclient"
|
||||
"github.com/absmach/supermq/pkg/grpcclient"
|
||||
jaegerclient "github.com/absmach/supermq/pkg/jaeger"
|
||||
pgclient "github.com/absmach/supermq/pkg/postgres"
|
||||
"github.com/absmach/supermq/pkg/server"
|
||||
httpserver "github.com/absmach/supermq/pkg/server/http"
|
||||
"github.com/absmach/supermq/pkg/uuid"
|
||||
"github.com/caarlos0/env/v11"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const (
|
||||
svcName = "reports"
|
||||
envPrefixDB = "MG_REPORTS_DB_"
|
||||
envPrefixHTTP = "MG_REPORTS_HTTP_"
|
||||
envPrefixAuth = "SMQ_AUTH_GRPC_"
|
||||
defDB = "repo"
|
||||
defSvcHTTPPort = "9017"
|
||||
envPrefixGrpc = "MG_TIMESCALE_READER_GRPC_"
|
||||
envPrefixDomains = "SMQ_DOMAINS_GRPC_"
|
||||
)
|
||||
|
||||
// We use a buffered channel to prevent blocking, as logging is an expensive operation.
|
||||
const channBuffer = 256
|
||||
|
||||
type config struct {
|
||||
LogLevel string `env:"MG_REPORTS_LOG_LEVEL" envDefault:"info"`
|
||||
InstanceID string `env:"MG_REPORTS_INSTANCE_ID" envDefault:""`
|
||||
JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"`
|
||||
SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"`
|
||||
ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"`
|
||||
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
cfg := config{}
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
log.Fatalf("failed to load %s configuration : %s", svcName, err)
|
||||
}
|
||||
|
||||
var logger *slog.Logger
|
||||
logger, err := smqlog.New(os.Stdout, cfg.LogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to init logger: %s", err.Error())
|
||||
}
|
||||
|
||||
var exitCode int
|
||||
defer smqlog.ExitWithError(&exitCode)
|
||||
|
||||
if cfg.InstanceID == "" {
|
||||
if cfg.InstanceID, err = uuid.New().ID(); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ec := email.Config{}
|
||||
if err := env.Parse(&ec); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load email configuration : %s", err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
dbConfig := pgclient.Config{Name: defDB}
|
||||
if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
db, err := pgclient.Setup(dbConfig, *repg.Migration())
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := tp.Shutdown(ctx); err != nil {
|
||||
logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err))
|
||||
}
|
||||
}()
|
||||
tracer := tp.Tracer(svcName)
|
||||
|
||||
httpServerConfig := server.Config{Port: defSvcHTTPPort}
|
||||
if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
grpcCfg := grpcclient.Config{}
|
||||
if err := env.ParseWithOptions(&grpcCfg, env.Options{Prefix: envPrefixAuth}); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load auth gRPC client configuration : %s", err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
authn, authnClient, err := authnsvc.NewAuthentication(ctx, grpcCfg)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
defer authnClient.Close()
|
||||
logger.Info("AuthN successfully connected to auth gRPC server " + authnClient.Secure())
|
||||
|
||||
domsGrpcCfg := grpcclient.Config{}
|
||||
if err := env.ParseWithOptions(&domsGrpcCfg, env.Options{Prefix: envPrefixDomains}); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load domains gRPC client configuration : %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
domAuthz, _, domainsHandler, err := domainsAuthz.NewAuthorization(ctx, domsGrpcCfg)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
defer domainsHandler.Close()
|
||||
|
||||
authz, authzClient, err := authzsvc.NewAuthorization(ctx, grpcCfg, domAuthz)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
defer authzClient.Close()
|
||||
logger.Info("AuthZ successfully connected to auth gRPC server " + authnClient.Secure())
|
||||
|
||||
database := pgclient.NewDatabase(db, dbConfig, tracer)
|
||||
regrpcCfg := grpcclient.Config{}
|
||||
if err := env.ParseWithOptions(®rpcCfg, env.Options{Prefix: envPrefixGrpc}); err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to load clients gRPC client configuration : %s", err))
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
|
||||
client, err := grpcclient.NewHandler(regrpcCfg)
|
||||
if err != nil {
|
||||
exitCode = 1
|
||||
return
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
readersClient := grpcClient.NewReadersClient(client.Connection(), regrpcCfg.Timeout)
|
||||
logger.Info("Readers gRPC client successfully connected to readers gRPC server " + client.Secure())
|
||||
|
||||
runInfo := make(chan pkglog.RunInfo, channBuffer)
|
||||
|
||||
svc, err := newService(database, runInfo, authz, ec, logger, readersClient)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to create services: %s", err))
|
||||
exitCode = 1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
for info := range runInfo {
|
||||
logger.LogAttrs(context.Background(), info.Level, info.Message, info.Details...)
|
||||
}
|
||||
}()
|
||||
|
||||
mux := chi.NewRouter()
|
||||
|
||||
httpSvc := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, httpapi.MakeHandler(svc, authn, mux, logger, cfg.InstanceID), logger)
|
||||
|
||||
if cfg.SendTelemetry {
|
||||
chc := chclient.New(svcName, supermq.Version, logger, cancel)
|
||||
go chc.CallHome(ctx)
|
||||
}
|
||||
|
||||
g.Go(func() error {
|
||||
return svc.StartScheduler(ctx)
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
return httpSvc.Start()
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
return server.StopSignalHandler(ctx, cancel, logger, svcName, httpSvc)
|
||||
})
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
logger.Error(fmt.Sprintf("%s service terminated: %s", svcName, err))
|
||||
}
|
||||
}
|
||||
|
||||
func newService(db pgclient.Database, runInfo chan pkglog.RunInfo, authz mgauthz.Authorization, ec email.Config, logger *slog.Logger, readersClient grpcReadersV1.ReadersServiceClient) (reports.Service, error) {
|
||||
repo := repg.NewRepository(db)
|
||||
idp := uuid.New()
|
||||
|
||||
emailerClient, err := emailer.New(&ec)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("failed to configure e-mailing util: %s", err.Error()))
|
||||
}
|
||||
|
||||
csvc := reports.NewService(repo, runInfo, idp, ticker.NewTicker(time.Second*30), emailerClient, readersClient)
|
||||
csvc, err = middleware.AuthorizationMiddleware(csvc, authz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csvc = middleware.LoggingMiddleware(csvc, logger)
|
||||
|
||||
return csvc, nil
|
||||
}
|
||||
+18
@@ -136,6 +136,24 @@ MG_ALARMS_DB_SSL_KEY=
|
||||
MG_ALARMS_DB_SSL_ROOT_CERT=
|
||||
MG_ALARMS_INSTANCE_ID=
|
||||
|
||||
### REPORTS
|
||||
MG_REPORTS_LOG_LEVEL=debug
|
||||
MG_REPORTS_HTTP_HOST=reports
|
||||
MG_REPORTS_HTTP_PORT=9017
|
||||
MG_REPORTS_HTTP_SERVER_CERT=
|
||||
MG_REPORTS_HTTP_SERVER_KEY=
|
||||
MG_REPORTS_DB_HOST=reports-db
|
||||
MG_REPORTS_DB_PORT=5432
|
||||
MG_REPORTS_DB_USER=magistrala
|
||||
MG_REPORTS_DB_PASS=magistrala
|
||||
MG_REPORTS_DB_NAME=reports
|
||||
MG_REPORTS_DB_SSL_MODE=disable
|
||||
MG_REPORTS_DB_SSL_CERT=
|
||||
MG_REPORTS_DB_SSL_KEY=
|
||||
MG_REPORTS_DB_SSL_ROOT_CERT=
|
||||
MG_REPORTS_INSTANCE_ID=
|
||||
MG_REPORTS_EMAIL_TEMPLATE=reports.tmpl
|
||||
|
||||
### Certs
|
||||
SMQ_ADDONS_CERTS_PATH_PREFIX=./
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ volumes:
|
||||
magistrala-re-db-volume:
|
||||
magistrala-auth-redis-volume:
|
||||
magistrala-alarms-db-volume:
|
||||
magistrala-reports-db-volume:
|
||||
|
||||
services:
|
||||
ui:
|
||||
@@ -353,3 +354,93 @@ services:
|
||||
target: /domains-grpc-server-ca${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
reports-db:
|
||||
image: postgres:16.2-alpine
|
||||
container_name: magistrala-reports-db
|
||||
restart: on-failure
|
||||
command: postgres -c "max_connections=${SMQ_POSTGRES_MAX_CONNECTIONS}"
|
||||
environment:
|
||||
POSTGRES_USER: ${MG_REPORTS_DB_USER}
|
||||
POSTGRES_PASSWORD: ${MG_REPORTS_DB_PASS}
|
||||
POSTGRES_DB: ${MG_REPORTS_DB_NAME}
|
||||
ports:
|
||||
- 6020:5432
|
||||
networks:
|
||||
- magistrala-base-net
|
||||
volumes:
|
||||
- magistrala-reports-db-volume:/var/lib/postgresql/data
|
||||
|
||||
reports:
|
||||
image: ghcr.io/absmach/magistrala/reports:${MG_RELEASE_TAG}
|
||||
container_name: magistrala-reports
|
||||
depends_on:
|
||||
- reports-db
|
||||
restart: on-failure
|
||||
environment:
|
||||
MG_REPORTS_LOG_LEVEL: ${MG_REPORTS_LOG_LEVEL}
|
||||
MG_REPORTS_HTTP_PORT: ${MG_REPORTS_HTTP_PORT}
|
||||
MG_REPORTS_HTTP_HOST: ${MG_REPORTS_HTTP_HOST}
|
||||
MG_REPORTS_HTTP_SERVER_CERT: ${MG_REPORTS_HTTP_SERVER_CERT}
|
||||
MG_REPORTS_HTTP_SERVER_KEY: ${MG_REPORTS_HTTP_SERVER_KEY}
|
||||
MG_REPORTS_DB_HOST: ${MG_REPORTS_DB_HOST}
|
||||
MG_REPORTS_DB_PORT: ${MG_REPORTS_DB_PORT}
|
||||
MG_REPORTS_DB_USER: ${MG_REPORTS_DB_USER}
|
||||
MG_REPORTS_DB_PASS: ${MG_REPORTS_DB_PASS}
|
||||
MG_REPORTS_DB_NAME: ${MG_REPORTS_DB_NAME}
|
||||
MG_REPORTS_DB_SSL_MODE: ${MG_REPORTS_DB_SSL_MODE}
|
||||
MG_REPORTS_DB_SSL_CERT: ${MG_REPORTS_DB_SSL_CERT}
|
||||
MG_REPORTS_DB_SSL_KEY: ${MG_REPORTS_DB_SSL_KEY}
|
||||
MG_REPORTS_DB_SSL_ROOT_CERT: ${MG_REPORTS_DB_SSL_ROOT_CERT}
|
||||
SMQ_MESSAGE_BROKER_URL: ${SMQ_MESSAGE_BROKER_URL}
|
||||
SMQ_JAEGER_URL: ${SMQ_JAEGER_URL}
|
||||
SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO}
|
||||
SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY}
|
||||
SMQ_AUTH_GRPC_URL: ${SMQ_AUTH_GRPC_URL}
|
||||
SMQ_AUTH_GRPC_TIMEOUT: ${SMQ_AUTH_GRPC_TIMEOUT}
|
||||
SMQ_AUTH_GRPC_CLIENT_CERT: ${SMQ_AUTH_GRPC_CLIENT_CERT:+/auth-grpc-client.crt}
|
||||
SMQ_AUTH_GRPC_CLIENT_KEY: ${SMQ_AUTH_GRPC_CLIENT_KEY:+/auth-grpc-client.key}
|
||||
SMQ_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+/auth-grpc-server-ca.crt}
|
||||
SMQ_SPICEDB_PRE_SHARED_KEY: ${SMQ_SPICEDB_PRE_SHARED_KEY}
|
||||
SMQ_SPICEDB_HOST: ${SMQ_SPICEDB_HOST}
|
||||
SMQ_SPICEDB_PORT: ${SMQ_SPICEDB_PORT}
|
||||
MG_REPORTS_INSTANCE_ID: ${MG_RE_INSTANCE_ID}
|
||||
MG_EMAIL_HOST: ${MG_EMAIL_HOST}
|
||||
MG_EMAIL_PORT: ${MG_EMAIL_PORT}
|
||||
MG_EMAIL_USERNAME: ${MG_EMAIL_USERNAME}
|
||||
MG_EMAIL_PASSWORD: ${MG_EMAIL_PASSWORD}
|
||||
MG_EMAIL_FROM_ADDRESS: ${MG_EMAIL_FROM_ADDRESS}
|
||||
MG_EMAIL_FROM_NAME: ${MG_EMAIL_FROM_NAME}
|
||||
MG_EMAIL_TEMPLATE: ${MG_EMAIL_TEMPLATE}
|
||||
MG_TIMESCALE_READER_GRPC_URL: ${MG_TIMESCALE_READER_GRPC_URL}
|
||||
MG_TIMESCALE_READER_GRPC_TIMEOUT: ${MG_TIMESCALE_READER_GRPC_TIMEOUT}
|
||||
MG_TIMESCALE_READER_GRPC_CLIENT_CERT: ${MG_TIMESCALE_READER_GRPC_CLIENT_CERT}
|
||||
MG_TIMESCALE_READER_GRPC_SERVER_CA_CERTS: ${MG_TIMESCALE_READER_GRPC_SERVER_CA_CERTS}
|
||||
MG_TIMESCALE_READER_GRPC_CLIENT_KEY: ${MG_TIMESCALE_READER_GRPC_CLIENT_KEY}
|
||||
SMQ_DOMAINS_GRPC_URL: ${SMQ_DOMAINS_GRPC_URL}
|
||||
SMQ_DOMAINS_GRPC_TIMEOUT: ${SMQ_DOMAINS_GRPC_TIMEOUT}
|
||||
SMQ_DOMAINS_GRPC_CLIENT_CERT: ${SMQ_DOMAINS_GRPC_CLIENT_CERT:+/domains-grpc-client.crt}
|
||||
SMQ_DOMAINS_GRPC_CLIENT_KEY: ${SMQ_DOMAINS_GRPC_CLIENT_KEY:+/domains-grpc-client.key}
|
||||
SMQ_DOMAINS_GRPC_SERVER_CA_CERTS: ${SMQ_DOMAINS_GRPC_SERVER_CA_CERTS:+/domains-grpc-server-ca.crt}
|
||||
ports:
|
||||
- ${MG_REPORTS_HTTP_PORT}:${MG_REPORTS_HTTP_PORT}
|
||||
networks:
|
||||
- magistrala-base-net
|
||||
volumes:
|
||||
- ./templates/${MG_REPORTS_EMAIL_TEMPLATE}:/email.tmpl
|
||||
# Auth gRPC client certificates
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_CERT:+.crt}
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key}
|
||||
target: /auth-grpc-client${SMQ_AUTH_GRPC_CLIENT_KEY:+.key}
|
||||
bind:
|
||||
create_host_path: true
|
||||
- type: bind
|
||||
source: ${SMQ_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca}
|
||||
target: /auth-grpc-server-ca${SMQ_AUTH_GRPC_SERVER_CA_CERTS:+.crt}
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
@@ -23,6 +23,7 @@ envsubst '
|
||||
${SMQ_NGINX_MQTTS_PORT}
|
||||
${MG_RE_HTTP_PORT}
|
||||
${MG_ALARMS_HTTP_PORT}
|
||||
${MG_REPORTS_HTTP_PORT}
|
||||
${SMQ_WS_ADAPTER_HTTP_PORT}' </etc/nginx/nginx.conf.template >/etc/nginx/nginx.conf
|
||||
|
||||
exec nginx -g "daemon off;"
|
||||
|
||||
@@ -100,13 +100,20 @@ http {
|
||||
proxy_pass http://re:${MG_RE_HTTP_PORT};
|
||||
}
|
||||
|
||||
# Proxy pass to rule engine service
|
||||
# Proxy pass to alarm service
|
||||
location ~ "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/(alarms)" {
|
||||
include snippets/proxy-headers.conf;
|
||||
add_header Access-Control-Expose-Headers Location;
|
||||
proxy_pass http://alarms:${MG_ALARMS_HTTP_PORT};
|
||||
}
|
||||
|
||||
# Proxy pass to reports service
|
||||
location ~ "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/(reports)" {
|
||||
include snippets/proxy-headers.conf;
|
||||
add_header Access-Control-Expose-Headers Location;
|
||||
proxy_pass http://reports:${MG_REPORTS_HTTP_PORT};
|
||||
}
|
||||
|
||||
location /health {
|
||||
include snippets/proxy-headers.conf;
|
||||
proxy_pass http://clients:${SMQ_CLIENTS_HTTP_PORT};
|
||||
|
||||
@@ -109,13 +109,20 @@ http {
|
||||
proxy_pass http://re:${MG_RE_HTTP_PORT};
|
||||
}
|
||||
|
||||
# Proxy pass to rule engine service
|
||||
# Proxy pass to alarms service
|
||||
location ~ "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/(alarms)" {
|
||||
include snippets/proxy-headers.conf;
|
||||
add_header Access-Control-Expose-Headers Location;
|
||||
proxy_pass http://alarms:${MG_ALARMS_HTTP_PORT};
|
||||
}
|
||||
|
||||
# Proxy pass to reports service
|
||||
location ~ "^/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/(reports)" {
|
||||
include snippets/proxy-headers.conf;
|
||||
add_header Access-Control-Expose-Headers Location;
|
||||
proxy_pass http://reports:${MG_REPORTS_HTTP_PORT};
|
||||
}
|
||||
|
||||
location /health {
|
||||
include snippets/proxy-headers.conf;
|
||||
proxy_pass http://clients:${SMQ_CLIENTS_HTTP_PORT};
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{{.Header}}
|
||||
{{.Content}}
|
||||
{{.Footer}}
|
||||
@@ -5,16 +5,20 @@ package emailer
|
||||
|
||||
import (
|
||||
"github.com/absmach/magistrala/internal/email"
|
||||
"github.com/absmach/magistrala/re"
|
||||
)
|
||||
|
||||
var _ re.Emailer = (*emailer)(nil)
|
||||
var _ Emailer = (*emailer)(nil)
|
||||
|
||||
type Emailer interface {
|
||||
// SendEmailNotification sends an email to the recipients based on a trigger.
|
||||
SendEmailNotification(to []string, from, subject, header, user, content, footer string, attachments map[string][]byte) error
|
||||
}
|
||||
|
||||
type emailer struct {
|
||||
agent *email.Agent
|
||||
}
|
||||
|
||||
func New(a *email.Config) (re.Emailer, error) {
|
||||
func New(a *email.Config) (Emailer, error) {
|
||||
e, err := email.New(a)
|
||||
return &emailer{agent: e}, err
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package logger
|
||||
|
||||
import "log/slog"
|
||||
|
||||
type RunInfo struct {
|
||||
Level slog.Level
|
||||
Details []slog.Attr
|
||||
Message string
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package re
|
||||
package schedule
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrInvalidRecurringType = errors.New("invalid recurring type")
|
||||
|
||||
// Type can be daily, weekly or monthly.
|
||||
type Recurring uint
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package re
|
||||
package ticker
|
||||
|
||||
import "time"
|
||||
|
||||
@@ -177,227 +177,3 @@ func disableRuleEndpoint(s re.Service) endpoint.Endpoint {
|
||||
return updateRuleStatusRes{Rule: rule}, err
|
||||
}
|
||||
}
|
||||
|
||||
func generateReportEndpoint(svc re.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(generateReportReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return generateReportResp{}, err
|
||||
}
|
||||
|
||||
res, err := svc.GenerateReport(ctx, session, re.ReportConfig{
|
||||
Name: req.Name,
|
||||
DomainID: req.DomainID,
|
||||
Config: req.Config,
|
||||
Metrics: req.Metrics,
|
||||
Email: req.Email,
|
||||
}, req.action)
|
||||
if err != nil {
|
||||
return generateReportResp{}, err
|
||||
}
|
||||
|
||||
switch req.action {
|
||||
case re.DownloadReport:
|
||||
return downloadReportResp{
|
||||
File: res.File,
|
||||
}, nil
|
||||
case re.EmailReport:
|
||||
return emailReportResp{}, nil
|
||||
default:
|
||||
return generateReportResp{
|
||||
Total: res.Total,
|
||||
From: res.From,
|
||||
To: res.To,
|
||||
Aggregation: res.Aggregation,
|
||||
Reports: res.Reports,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func listReportsConfigEndpoint(svc re.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(listReportsConfigReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return listReportsConfigRes{}, err
|
||||
}
|
||||
|
||||
page, err := svc.ListReportsConfig(ctx, session, req.PageMeta)
|
||||
if err != nil {
|
||||
return listReportsConfigRes{}, err
|
||||
}
|
||||
|
||||
return listReportsConfigRes{
|
||||
pageRes: pageRes{
|
||||
Limit: page.Limit,
|
||||
Offset: page.Offset,
|
||||
Total: page.Total,
|
||||
},
|
||||
ReportConfigs: page.ReportConfigs,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func deleteReportConfigEndpoint(svc re.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(deleteReportConfigReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return deleteReportConfigRes{}, err
|
||||
}
|
||||
|
||||
err := svc.RemoveReportConfig(ctx, session, req.ID)
|
||||
if err != nil {
|
||||
return deleteReportConfigRes{false}, err
|
||||
}
|
||||
|
||||
return deleteReportConfigRes{true}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func updateReportConfigEndpoint(svc re.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(updateReportConfigReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return updateReportConfigRes{}, err
|
||||
}
|
||||
|
||||
cfg, err := svc.UpdateReportConfig(ctx, session, req.ReportConfig)
|
||||
if err != nil {
|
||||
return updateReportConfigRes{}, err
|
||||
}
|
||||
|
||||
return updateReportConfigRes{ReportConfig: cfg}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func updateReportScheduleEndpoint(s re.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(updateReportScheduleReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return updateReportConfigRes{}, err
|
||||
}
|
||||
|
||||
rpt := re.ReportConfig{
|
||||
ID: req.id,
|
||||
Schedule: req.Schedule,
|
||||
}
|
||||
|
||||
updatedReport, err := s.UpdateReportSchedule(ctx, session, rpt)
|
||||
if err != nil {
|
||||
return updateReportConfigRes{}, err
|
||||
}
|
||||
return updateReportConfigRes{ReportConfig: updatedReport}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func viewReportConfigEndpoint(svc re.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(viewReportConfigReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return viewReportConfigRes{}, err
|
||||
}
|
||||
|
||||
cfg, err := svc.ViewReportConfig(ctx, session, req.ID)
|
||||
if err != nil {
|
||||
return viewReportConfigRes{}, err
|
||||
}
|
||||
|
||||
return viewReportConfigRes{ReportConfig: cfg}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func addReportConfigEndpoint(svc re.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(addReportConfigReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return addReportConfigRes{}, err
|
||||
}
|
||||
|
||||
cfg, err := svc.AddReportConfig(ctx, session, req.ReportConfig)
|
||||
if err != nil {
|
||||
return addReportConfigRes{}, err
|
||||
}
|
||||
|
||||
return addReportConfigRes{
|
||||
ReportConfig: cfg,
|
||||
created: true,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func enableReportConfigEndpoint(svc re.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(updateReportStatusReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return updateReportConfigRes{}, err
|
||||
}
|
||||
|
||||
cfg, err := svc.EnableReportConfig(ctx, session, req.id)
|
||||
if err != nil {
|
||||
return updateReportConfigRes{}, err
|
||||
}
|
||||
|
||||
return updateReportConfigRes{ReportConfig: cfg}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func disableReportConfigEndpoint(svc re.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(updateReportStatusReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return updateReportConfigRes{}, err
|
||||
}
|
||||
|
||||
cfg, err := svc.DisableReportConfig(ctx, session, req.id)
|
||||
if err != nil {
|
||||
return updateReportConfigRes{}, err
|
||||
}
|
||||
|
||||
return updateReportConfigRes{ReportConfig: cfg}, nil
|
||||
}
|
||||
}
|
||||
|
||||
+4
-711
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
"github.com/0x6flab/namegenerator"
|
||||
"github.com/absmach/magistrala/internal/testsutil"
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
pkgSch "github.com/absmach/magistrala/pkg/schedule"
|
||||
"github.com/absmach/magistrala/re"
|
||||
"github.com/absmach/magistrala/re/api"
|
||||
"github.com/absmach/magistrala/re/mocks"
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
smqlog "github.com/absmach/supermq/logger"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -40,9 +41,9 @@ var (
|
||||
validToken = "valid"
|
||||
invalidToken = "invalid"
|
||||
now = time.Now().UTC().Truncate(time.Minute)
|
||||
schedule = re.Schedule{
|
||||
schedule = pkgSch.Schedule{
|
||||
StartDateTime: now.Add(-1 * time.Hour),
|
||||
Recurring: re.Daily,
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
}
|
||||
@@ -55,31 +56,6 @@ var (
|
||||
"name": "test",
|
||||
},
|
||||
}
|
||||
reportConfig = re.ReportConfig{
|
||||
ID: validID,
|
||||
Name: namegen.Generate(),
|
||||
DomainID: domainID,
|
||||
Schedule: schedule,
|
||||
Status: re.EnabledStatus,
|
||||
Metrics: []re.ReqMetric{
|
||||
{
|
||||
ChannelID: "channel1",
|
||||
ClientIDs: []string{"client1"},
|
||||
Name: "metric_name",
|
||||
},
|
||||
},
|
||||
Config: &re.MetricConfig{
|
||||
From: "now()-1h",
|
||||
To: "now()",
|
||||
Title: title,
|
||||
Aggregation: re.AggConfig{AggType: re.AggregationAVG, Interval: "1h"},
|
||||
},
|
||||
Email: &re.EmailSetting{
|
||||
To: []string{"test@example.com"},
|
||||
Subject: "Test Report",
|
||||
},
|
||||
}
|
||||
title = "test_title"
|
||||
)
|
||||
|
||||
type testRequest struct {
|
||||
@@ -991,686 +967,3 @@ type respBody struct {
|
||||
ID string `json:"id"`
|
||||
Status re.Status `json:"status"`
|
||||
}
|
||||
|
||||
func TestAddReportConfigEndpoint(t *testing.T) {
|
||||
ts, svc, authn := newRuleEngineServer()
|
||||
defer ts.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
cfg re.ReportConfig
|
||||
domainID string
|
||||
token string
|
||||
contentType string
|
||||
status int
|
||||
authnRes smqauthn.Session
|
||||
authnErr error
|
||||
svcRes re.ReportConfig
|
||||
svcErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "add report config successfully",
|
||||
cfg: reportConfig,
|
||||
token: validToken,
|
||||
contentType: contentType,
|
||||
domainID: domainID,
|
||||
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
|
||||
status: http.StatusCreated,
|
||||
svcRes: reportConfig,
|
||||
},
|
||||
{
|
||||
desc: "add report config with invalid token",
|
||||
cfg: reportConfig,
|
||||
token: invalidToken,
|
||||
authnRes: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
contentType: contentType,
|
||||
authnErr: svcerr.ErrAuthentication,
|
||||
status: http.StatusUnauthorized,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "add report config with empty token",
|
||||
token: "",
|
||||
authnRes: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
cfg: reportConfig,
|
||||
contentType: contentType,
|
||||
status: http.StatusUnauthorized,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "add report config with empty domainID",
|
||||
token: validToken,
|
||||
cfg: reportConfig,
|
||||
contentType: contentType,
|
||||
status: http.StatusBadRequest,
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
{
|
||||
desc: "add report config with invalid content type",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
cfg: reportConfig,
|
||||
contentType: "application/xml",
|
||||
status: http.StatusUnsupportedMediaType,
|
||||
err: apiutil.ErrUnsupportedContentType,
|
||||
},
|
||||
{
|
||||
desc: "add report config with service error",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
|
||||
cfg: reportConfig,
|
||||
contentType: contentType,
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
status: http.StatusForbidden,
|
||||
err: svcerr.ErrAuthorization,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
data := toJSON(tc.cfg)
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodPost,
|
||||
url: fmt.Sprintf("%s/%s/reports/configs", ts.URL, tc.domainID),
|
||||
contentType: tc.contentType,
|
||||
token: tc.token,
|
||||
body: strings.NewReader(data),
|
||||
}
|
||||
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
|
||||
svcCall := svc.On("AddReportConfig", mock.Anything, tc.authnRes, mock.Anything).Return(tc.svcRes, tc.svcErr)
|
||||
res, err := req.make()
|
||||
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var errRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&errRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if errRes.Err != "" || errRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewReportConfigEndpoint(t *testing.T) {
|
||||
ts, svc, authn := newRuleEngineServer()
|
||||
defer ts.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
domainID string
|
||||
token string
|
||||
contentType string
|
||||
status int
|
||||
authnRes smqauthn.Session
|
||||
authnErr error
|
||||
svcRes re.ReportConfig
|
||||
svcErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "view report config successfully",
|
||||
id: validID,
|
||||
token: validToken,
|
||||
contentType: contentType,
|
||||
domainID: domainID,
|
||||
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
|
||||
status: http.StatusOK,
|
||||
svcRes: reportConfig,
|
||||
},
|
||||
{
|
||||
desc: "view report config with invalid token",
|
||||
id: validID,
|
||||
token: invalidToken,
|
||||
authnRes: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
contentType: contentType,
|
||||
authnErr: svcerr.ErrAuthentication,
|
||||
status: http.StatusUnauthorized,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "view report config with empty token",
|
||||
token: "",
|
||||
authnRes: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
contentType: contentType,
|
||||
status: http.StatusUnauthorized,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "view report config with empty domainID",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
contentType: contentType,
|
||||
status: http.StatusBadRequest,
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
{
|
||||
desc: "view report config with service error",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
|
||||
id: validID,
|
||||
contentType: contentType,
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
status: http.StatusForbidden,
|
||||
err: svcerr.ErrAuthorization,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("%s/%s/reports/configs/%s", ts.URL, tc.domainID, tc.id),
|
||||
contentType: tc.contentType,
|
||||
token: tc.token,
|
||||
}
|
||||
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
|
||||
svcCall := svc.On("ViewReportConfig", mock.Anything, tc.authnRes, tc.id).Return(tc.svcRes, tc.svcErr)
|
||||
res, err := req.make()
|
||||
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var errRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&errRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if errRes.Err != "" || errRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListReportsConfigEndpoint(t *testing.T) {
|
||||
ts, svc, authn := newRuleEngineServer()
|
||||
defer ts.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
query string
|
||||
domainID string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
listReportsResponse re.ReportConfigPage
|
||||
status int
|
||||
authnErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "list reports config successfully",
|
||||
domainID: domainID,
|
||||
token: validToken,
|
||||
status: http.StatusOK,
|
||||
listReportsResponse: re.ReportConfigPage{
|
||||
ReportConfigs: []re.ReportConfig{reportConfig},
|
||||
PageMeta: re.PageMeta{Total: 1},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list reports config with empty token",
|
||||
domainID: domainID,
|
||||
token: "",
|
||||
status: http.StatusUnauthorized,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "list reports config with invalid token",
|
||||
domainID: domainID,
|
||||
token: invalidToken,
|
||||
status: http.StatusUnauthorized,
|
||||
authnErr: svcerr.ErrAuthentication,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodGet,
|
||||
url: ts.URL + "/" + tc.domainID + "/reports/configs?" + tc.query,
|
||||
contentType: contentType,
|
||||
token: tc.token,
|
||||
}
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
|
||||
}
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
|
||||
svcCall := svc.On("ListReportsConfig", mock.Anything, tc.session, mock.Anything).Return(tc.listReportsResponse, tc.err)
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var bodyRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&bodyRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if bodyRes.Err != "" || bodyRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(bodyRes.Err), errors.New(bodyRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateReportConfigEndpoint(t *testing.T) {
|
||||
ts, svc, authn := newRuleEngineServer()
|
||||
defer ts.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
id string
|
||||
domainID string
|
||||
updateReq re.ReportConfig
|
||||
contentType string
|
||||
session smqauthn.Session
|
||||
svcResp re.ReportConfig
|
||||
svcErr error
|
||||
status int
|
||||
authnErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update report config successfully",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
updateReq: reportConfig,
|
||||
contentType: contentType,
|
||||
svcResp: reportConfig,
|
||||
status: http.StatusOK,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update report config with invalid token",
|
||||
token: invalidToken,
|
||||
session: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
updateReq: reportConfig,
|
||||
contentType: contentType,
|
||||
authnErr: svcerr.ErrAuthentication,
|
||||
status: http.StatusUnauthorized,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "update report config with empty token",
|
||||
token: "",
|
||||
session: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
updateReq: reportConfig,
|
||||
contentType: contentType,
|
||||
status: http.StatusUnauthorized,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "update report config with empty domainID",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
updateReq: reportConfig,
|
||||
contentType: contentType,
|
||||
status: http.StatusBadRequest,
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
{
|
||||
desc: "update report config with invalid content type",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
domainID: domainID,
|
||||
updateReq: reportConfig,
|
||||
contentType: "application/xml",
|
||||
svcResp: reportConfig,
|
||||
status: http.StatusUnsupportedMediaType,
|
||||
err: apiutil.ErrUnsupportedContentType,
|
||||
},
|
||||
{
|
||||
desc: "update report config with service error",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
domainID: domainID,
|
||||
updateReq: reportConfig,
|
||||
contentType: contentType,
|
||||
svcResp: re.ReportConfig{},
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
status: http.StatusForbidden,
|
||||
err: svcerr.ErrAuthorization,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
data := toJSON(tc.updateReq)
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodPatch,
|
||||
url: fmt.Sprintf("%s/%s/reports/configs/%s", ts.URL, tc.domainID, tc.id),
|
||||
contentType: tc.contentType,
|
||||
token: tc.token,
|
||||
body: strings.NewReader(data),
|
||||
}
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
|
||||
}
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
|
||||
svcCall := svc.On("UpdateReportConfig", mock.Anything, tc.session, mock.Anything).Return(tc.svcResp, tc.svcErr)
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var errRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&errRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if errRes.Err != "" || errRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteReportConfigEndpoint(t *testing.T) {
|
||||
ts, svc, authn := newRuleEngineServer()
|
||||
defer ts.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
id string
|
||||
domainID string
|
||||
session smqauthn.Session
|
||||
svcErr error
|
||||
status int
|
||||
authnErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "delete report config successfully",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
svcErr: nil,
|
||||
status: http.StatusNoContent,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "delete report config with invalid token",
|
||||
token: invalidToken,
|
||||
session: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
authnErr: svcerr.ErrAuthentication,
|
||||
status: http.StatusUnauthorized,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "delete report config with empty token",
|
||||
token: "",
|
||||
session: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
status: http.StatusUnauthorized,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "delete report config with empty domainID",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
status: http.StatusBadRequest,
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
{
|
||||
desc: "delete report config with service error",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
domainID: domainID,
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
status: http.StatusForbidden,
|
||||
err: svcerr.ErrAuthorization,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodDelete,
|
||||
url: fmt.Sprintf("%s/%s/reports/configs/%s", ts.URL, tc.domainID, tc.id),
|
||||
token: tc.token,
|
||||
}
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
|
||||
}
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
|
||||
svcCall := svc.On("RemoveReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcErr)
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnableReportConfigEndpoint(t *testing.T) {
|
||||
ts, svc, authn := newRuleEngineServer()
|
||||
defer ts.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
id string
|
||||
domainID string
|
||||
session smqauthn.Session
|
||||
svcResp re.ReportConfig
|
||||
svcErr error
|
||||
status int
|
||||
authnErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "enable report config successfully",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
svcResp: reportConfig,
|
||||
svcErr: nil,
|
||||
status: http.StatusOK,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "enable report config with invalid token",
|
||||
token: invalidToken,
|
||||
session: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
authnErr: svcerr.ErrAuthentication,
|
||||
status: http.StatusUnauthorized,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "enable report config with empty token",
|
||||
token: "",
|
||||
session: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
status: http.StatusUnauthorized,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "enable report config with empty domainID",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
status: http.StatusBadRequest,
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
{
|
||||
desc: "enable report config with service error",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
domainID: domainID,
|
||||
svcResp: re.ReportConfig{},
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
status: http.StatusForbidden,
|
||||
err: svcerr.ErrAuthorization,
|
||||
},
|
||||
{
|
||||
desc: "enable report config with empty id",
|
||||
token: validToken,
|
||||
id: "",
|
||||
domainID: domainID,
|
||||
status: http.StatusBadRequest,
|
||||
err: apiutil.ErrMissingID,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodPost,
|
||||
url: fmt.Sprintf("%s/%s/reports/configs/%s/enable", ts.URL, tc.domainID, tc.id),
|
||||
token: tc.token,
|
||||
}
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
|
||||
}
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
|
||||
svcCall := svc.On("EnableReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcResp, tc.svcErr)
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var errRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&errRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if errRes.Err != "" || errRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisableReportConfigEndpoint(t *testing.T) {
|
||||
ts, svc, authn := newRuleEngineServer()
|
||||
defer ts.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
id string
|
||||
domainID string
|
||||
session smqauthn.Session
|
||||
svcResp re.ReportConfig
|
||||
svcErr error
|
||||
status int
|
||||
authnErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "disable report config successfully",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
svcResp: reportConfig,
|
||||
svcErr: nil,
|
||||
status: http.StatusOK,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "disable report config with invalid token",
|
||||
token: invalidToken,
|
||||
session: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
authnErr: svcerr.ErrAuthentication,
|
||||
status: http.StatusUnauthorized,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "disable report config with empty token",
|
||||
token: "",
|
||||
session: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
status: http.StatusUnauthorized,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "disable report config with empty domainID",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
status: http.StatusBadRequest,
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
{
|
||||
desc: "disable report config with service error",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
domainID: domainID,
|
||||
svcResp: re.ReportConfig{},
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
status: http.StatusForbidden,
|
||||
err: svcerr.ErrAuthorization,
|
||||
},
|
||||
{
|
||||
desc: "disable report config with empty id",
|
||||
token: validToken,
|
||||
id: "",
|
||||
domainID: domainID,
|
||||
status: http.StatusBadRequest,
|
||||
err: apiutil.ErrMissingID,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodPost,
|
||||
url: fmt.Sprintf("%s/%s/reports/configs/%s/disable", ts.URL, tc.domainID, tc.id),
|
||||
token: tc.token,
|
||||
}
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
|
||||
}
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
|
||||
svcCall := svc.On("DisableReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcResp, tc.svcErr)
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var errRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&errRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if errRes.Err != "" || errRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+2
-156
@@ -4,30 +4,16 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/schedule"
|
||||
"github.com/absmach/magistrala/re"
|
||||
api "github.com/absmach/supermq/api/http"
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidReportAction = errors.New("invalid report action")
|
||||
errMetricsNotProvided = errors.New("metrics not provided")
|
||||
errMissingReportConfig = errors.New("missing report config")
|
||||
errMissingReportEmailConfig = errors.New("missing report email config")
|
||||
errInvalidRecurringPeriod = errors.New("invalid recurring period")
|
||||
errTitleSize = errors.New("invalid title size")
|
||||
)
|
||||
|
||||
const (
|
||||
maxLimitSize = 1000
|
||||
MaxNameSize = 1024
|
||||
MaxTitleSize = 37
|
||||
|
||||
errInvalidMetric = "invalid metric[%d]: %w"
|
||||
)
|
||||
|
||||
type addRuleReq struct {
|
||||
@@ -85,7 +71,7 @@ func (req updateRuleReq) validate() error {
|
||||
|
||||
type updateRuleScheduleReq struct {
|
||||
id string
|
||||
Schedule re.Schedule `json:"schedule,omitempty"`
|
||||
Schedule schedule.Schedule `json:"schedule,omitempty"`
|
||||
}
|
||||
|
||||
func (req updateRuleScheduleReq) validate() error {
|
||||
@@ -119,143 +105,3 @@ func (req deleteRuleReq) validate() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type updateReportConfigReq struct {
|
||||
re.ReportConfig `json:",inline"`
|
||||
}
|
||||
|
||||
func (req updateReportConfigReq) validate() error {
|
||||
if req.ID == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
return validateReportConfig(req.ReportConfig, false, false)
|
||||
}
|
||||
|
||||
type updateReportScheduleReq struct {
|
||||
id string
|
||||
Schedule re.Schedule `json:"schedule,omitempty"`
|
||||
}
|
||||
|
||||
func (req updateReportScheduleReq) validate() error {
|
||||
if req.id == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type addReportConfigReq struct {
|
||||
re.ReportConfig `json:",inline"`
|
||||
}
|
||||
|
||||
func (req addReportConfigReq) validate() error {
|
||||
if req.Name == "" {
|
||||
return apiutil.ErrMissingName
|
||||
}
|
||||
return validateReportConfig(req.ReportConfig, false, false)
|
||||
}
|
||||
|
||||
type viewReportConfigReq struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func (req viewReportConfigReq) validate() error {
|
||||
if req.ID == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type listReportsConfigReq struct {
|
||||
re.PageMeta `json:",inline"`
|
||||
}
|
||||
|
||||
func (req listReportsConfigReq) validate() error {
|
||||
if req.Limit > maxLimitSize {
|
||||
return svcerr.ErrMalformedEntity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type deleteReportConfigReq struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func (req deleteReportConfigReq) validate() error {
|
||||
if req.ID == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type generateReportReq struct {
|
||||
re.ReportConfig
|
||||
action re.ReportAction
|
||||
}
|
||||
|
||||
func (req generateReportReq) validate() error {
|
||||
if len(req.Config.Title) > MaxTitleSize {
|
||||
return errors.Wrap(apiutil.ErrValidation, errTitleSize)
|
||||
}
|
||||
|
||||
switch req.action {
|
||||
case re.ViewReport, re.DownloadReport:
|
||||
return validateReportConfig(req.ReportConfig, true, true)
|
||||
case re.EmailReport:
|
||||
return validateReportConfig(req.ReportConfig, false, true)
|
||||
default:
|
||||
return errors.Wrap(apiutil.ErrValidation, errInvalidReportAction)
|
||||
}
|
||||
}
|
||||
|
||||
type updateReportStatusReq struct {
|
||||
id string
|
||||
}
|
||||
|
||||
func (req updateReportStatusReq) validate() error {
|
||||
if req.id == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateReportConfig(req re.ReportConfig, skipEmailValidation bool, skipSchedularValidation bool) error {
|
||||
if len(req.Metrics) == 0 {
|
||||
return errors.Wrap(apiutil.ErrValidation, errMetricsNotProvided)
|
||||
}
|
||||
for i, metric := range req.Metrics {
|
||||
if err := metric.Validate(); err != nil {
|
||||
return errors.Wrap(apiutil.ErrValidation, fmt.Errorf(errInvalidMetric, i+1, err))
|
||||
}
|
||||
}
|
||||
|
||||
if req.Config == nil {
|
||||
return errMissingReportConfig
|
||||
}
|
||||
if err := req.Config.Validate(); err != nil {
|
||||
return errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
if skipEmailValidation {
|
||||
return nil
|
||||
}
|
||||
if req.Email == nil {
|
||||
return errMissingReportEmailConfig
|
||||
}
|
||||
if err := req.Email.Validate(); err != nil {
|
||||
return errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
if skipSchedularValidation {
|
||||
return nil
|
||||
}
|
||||
|
||||
return validateScheduler(req.Schedule)
|
||||
}
|
||||
|
||||
func validateScheduler(sch re.Schedule) error {
|
||||
if sch.Recurring != re.None && sch.RecurringPeriod < 1 {
|
||||
return errInvalidRecurringPeriod
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ package api
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/re"
|
||||
"github.com/absmach/supermq"
|
||||
@@ -19,11 +18,6 @@ var (
|
||||
_ supermq.Response = (*rulesPageRes)(nil)
|
||||
_ supermq.Response = (*updateRuleRes)(nil)
|
||||
_ supermq.Response = (*deleteRuleRes)(nil)
|
||||
_ supermq.Response = (*addReportConfigRes)(nil)
|
||||
_ supermq.Response = (*viewReportConfigRes)(nil)
|
||||
_ supermq.Response = (*updateReportConfigRes)(nil)
|
||||
_ supermq.Response = (*deleteReportConfigRes)(nil)
|
||||
_ supermq.Response = (*listReportsConfigRes)(nil)
|
||||
)
|
||||
|
||||
type pageRes struct {
|
||||
@@ -142,144 +136,3 @@ func (res deleteRuleRes) Headers() map[string]string {
|
||||
func (res deleteRuleRes) Empty() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type generateReportResp struct {
|
||||
Total uint64 `json:"total"`
|
||||
From time.Time `json:"from,omitempty"`
|
||||
To time.Time `json:"to,omitempty"`
|
||||
Aggregation re.AggConfig `json:"aggregation,omitempty"`
|
||||
Reports []re.Report `json:"reports,omitempty"`
|
||||
}
|
||||
|
||||
func (res generateReportResp) Code() int {
|
||||
return http.StatusCreated
|
||||
}
|
||||
|
||||
func (res generateReportResp) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res generateReportResp) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type addReportConfigRes struct {
|
||||
re.ReportConfig `json:",inline"`
|
||||
created bool
|
||||
}
|
||||
|
||||
func (res addReportConfigRes) Code() int {
|
||||
if res.created {
|
||||
return http.StatusCreated
|
||||
}
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res addReportConfigRes) Headers() map[string]string {
|
||||
if res.created {
|
||||
return map[string]string{}
|
||||
}
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res addReportConfigRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type viewReportConfigRes struct {
|
||||
re.ReportConfig `json:",inline"`
|
||||
}
|
||||
|
||||
func (res viewReportConfigRes) Code() int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res viewReportConfigRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res viewReportConfigRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type updateReportConfigRes struct {
|
||||
re.ReportConfig `json:",inline"`
|
||||
}
|
||||
|
||||
func (res updateReportConfigRes) Code() int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res updateReportConfigRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res updateReportConfigRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type deleteReportConfigRes struct {
|
||||
deleted bool
|
||||
}
|
||||
|
||||
func (res deleteReportConfigRes) Code() int {
|
||||
if res.deleted {
|
||||
return http.StatusNoContent
|
||||
}
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res deleteReportConfigRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res deleteReportConfigRes) Empty() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type listReportsConfigRes struct {
|
||||
pageRes
|
||||
ReportConfigs []re.ReportConfig `json:"report_configs"`
|
||||
}
|
||||
|
||||
func (res listReportsConfigRes) Code() int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res listReportsConfigRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res listReportsConfigRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type downloadReportResp struct {
|
||||
File re.ReportFile
|
||||
}
|
||||
|
||||
func (res downloadReportResp) Code() int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res downloadReportResp) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res downloadReportResp) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type emailReportResp struct{}
|
||||
|
||||
func (res emailReportResp) Code() int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res emailReportResp) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res emailReportResp) Empty() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ package api
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -100,72 +99,6 @@ func MakeHandler(svc re.Service, authn mgauthn.Authentication, mux *chi.Mux, log
|
||||
), "disable_rule").ServeHTTP)
|
||||
})
|
||||
})
|
||||
r.Route("/reports", func(r chi.Router) {
|
||||
r.Post("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
generateReportEndpoint(svc),
|
||||
decodeGenerateReportRequest,
|
||||
encodeFileDownloadResponse,
|
||||
opts...,
|
||||
), "generate_report").ServeHTTP)
|
||||
|
||||
r.Route("/configs", func(r chi.Router) {
|
||||
r.Post("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
addReportConfigEndpoint(svc),
|
||||
decodeAddReportConfigRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "add_report_config").ServeHTTP)
|
||||
|
||||
r.Get("/{reportID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
viewReportConfigEndpoint(svc),
|
||||
decodeViewReportConfigRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "view_report_config").ServeHTTP)
|
||||
|
||||
r.Patch("/{reportID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
updateReportConfigEndpoint(svc),
|
||||
decodeUpdateReportConfigRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "update_report_config").ServeHTTP)
|
||||
|
||||
r.Patch("/{reportID}/schedule", otelhttp.NewHandler(kithttp.NewServer(
|
||||
updateReportScheduleEndpoint(svc),
|
||||
decodeUpdateReportScheduleRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "update_report_scheduler").ServeHTTP)
|
||||
|
||||
r.Delete("/{reportID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
deleteReportConfigEndpoint(svc),
|
||||
decodeDeleteReportConfigRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "delete_report_config").ServeHTTP)
|
||||
|
||||
r.Get("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
listReportsConfigEndpoint(svc),
|
||||
decodeListReportsConfigRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "list_reports_config").ServeHTTP)
|
||||
|
||||
r.Post("/{reportID}/enable", otelhttp.NewHandler(kithttp.NewServer(
|
||||
enableReportConfigEndpoint(svc),
|
||||
decodeUpdateReportStatusRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "enable_report_config").ServeHTTP)
|
||||
|
||||
r.Post("/{reportID}/disable", otelhttp.NewHandler(kithttp.NewServer(
|
||||
disableReportConfigEndpoint(svc),
|
||||
decodeUpdateReportStatusRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "disable_report_config").ServeHTTP)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -276,136 +209,3 @@ func decodeDeleteRuleRequest(_ context.Context, r *http.Request) (interface{}, e
|
||||
|
||||
return deleteRuleReq{id: id}, nil
|
||||
}
|
||||
|
||||
func decodeGenerateReportRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
|
||||
}
|
||||
|
||||
a, err := apiutil.ReadStringQuery(r, actionKey, defAction)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
action, err := re.ToReportAction(a)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
req := generateReportReq{
|
||||
action: action,
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, errors.Wrap(err, apiutil.ErrValidation)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeAddReportConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
|
||||
}
|
||||
var config re.ReportConfig
|
||||
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
|
||||
return nil, errors.Wrap(err, apiutil.ErrValidation)
|
||||
}
|
||||
return addReportConfigReq{ReportConfig: config}, nil
|
||||
}
|
||||
|
||||
func decodeViewReportConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
id := chi.URLParam(r, reportIdKey)
|
||||
return viewReportConfigReq{ID: id}, nil
|
||||
}
|
||||
|
||||
func decodeUpdateReportConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
|
||||
}
|
||||
var config re.ReportConfig
|
||||
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
|
||||
return nil, errors.Wrap(err, apiutil.ErrValidation)
|
||||
}
|
||||
config.ID = chi.URLParam(r, reportIdKey)
|
||||
return updateReportConfigReq{ReportConfig: config}, nil
|
||||
}
|
||||
|
||||
func decodeUpdateReportScheduleRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
|
||||
}
|
||||
|
||||
req := updateReportScheduleReq{
|
||||
id: chi.URLParam(r, reportIdKey),
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err))
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeUpdateReportStatusRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
req := updateReportStatusReq{
|
||||
id: chi.URLParam(r, reportIdKey),
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeDeleteReportConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
id := chi.URLParam(r, reportIdKey)
|
||||
return deleteReportConfigReq{ID: id}, nil
|
||||
}
|
||||
|
||||
func decodeListReportsConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
offset, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
limit, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
status, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefStatus)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
st, err := re.ToStatus(status)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
name, err := apiutil.ReadStringQuery(r, api.NameKey, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
return listReportsConfigReq{
|
||||
PageMeta: re.PageMeta{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Status: st,
|
||||
Name: name,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func encodeFileDownloadResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
|
||||
switch resp := response.(type) {
|
||||
case downloadReportResp:
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", resp.File.Name))
|
||||
w.Header().Set("Content-Type", resp.File.Format.ContentType())
|
||||
_, err := w.Write(resp.File.Data)
|
||||
return err
|
||||
default:
|
||||
if ar, ok := response.(supermq.Response); ok {
|
||||
for k, v := range ar.Headers() {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
w.Header().Set("Content-Type", api.ContentType)
|
||||
w.WriteHeader(ar.Code())
|
||||
|
||||
if ar.Empty() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package re
|
||||
|
||||
type Emailer interface {
|
||||
// SendEmailNotification sends an email to the recipients based on a trigger.
|
||||
SendEmailNotification(to []string, from, subject, header, user, content, footer string, attachments map[string][]byte) error
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package emailer contains the domain concept definitions needed to support
|
||||
// Magistrala re email service functionality.
|
||||
package emailer
|
||||
+9
-41
@@ -11,6 +11,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
pkglog "github.com/absmach/magistrala/pkg/logger"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/messaging"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
@@ -77,7 +78,7 @@ func matchSubject(published, subscribed string) bool {
|
||||
return len(s) == n
|
||||
}
|
||||
|
||||
func (re *re) process(ctx context.Context, r Rule, msg *messaging.Message) RunInfo {
|
||||
func (re *re) process(ctx context.Context, r Rule, msg *messaging.Message) pkglog.RunInfo {
|
||||
l := lua.NewState()
|
||||
defer l.Close()
|
||||
preload(l)
|
||||
@@ -99,30 +100,30 @@ func (re *re) process(ctx context.Context, r Rule, msg *messaging.Message) RunIn
|
||||
slog.Time("exec_time", time.Now().UTC()),
|
||||
}
|
||||
if err := l.DoString(r.Logic.Value); err != nil {
|
||||
return RunInfo{Level: slog.LevelError, Message: fmt.Sprintf("failed to run rule logic: %s", err), Details: details}
|
||||
return pkglog.RunInfo{Level: slog.LevelError, Message: fmt.Sprintf("failed to run rule logic: %s", err), Details: details}
|
||||
}
|
||||
// Get the last result.
|
||||
result := l.Get(-1)
|
||||
if result == lua.LNil {
|
||||
return RunInfo{Level: slog.LevelWarn, Message: "rule with nil script result", Details: details}
|
||||
return pkglog.RunInfo{Level: slog.LevelWarn, Message: "rule with nil script result", Details: details}
|
||||
}
|
||||
// Converting Lua is an expensive operation, so
|
||||
// don't do it if there are no outputs.
|
||||
if len(r.Logic.Outputs) == 0 {
|
||||
return RunInfo{Level: slog.LevelWarn, Message: "rule with no output channels", Details: details}
|
||||
return pkglog.RunInfo{Level: slog.LevelWarn, Message: "rule with no output channels", Details: details}
|
||||
}
|
||||
var err error
|
||||
res := convertLua(result)
|
||||
for _, o := range r.Logic.Outputs {
|
||||
// If value is false, don't run the follow-up.
|
||||
if v, ok := res.(bool); ok && !v {
|
||||
return RunInfo{Level: slog.LevelInfo, Message: "logic returned false", Details: details}
|
||||
return pkglog.RunInfo{Level: slog.LevelInfo, Message: "logic returned false", Details: details}
|
||||
}
|
||||
if e := re.handleOutput(ctx, o, r, msg, res); e != nil {
|
||||
err = errors.Wrap(e, err)
|
||||
}
|
||||
}
|
||||
ret := RunInfo{Level: slog.LevelInfo, Message: "rule processed successfully", Details: details}
|
||||
ret := pkglog.RunInfo{Level: slog.LevelInfo, Message: "rule processed successfully", Details: details}
|
||||
if err != nil {
|
||||
ret.Level = slog.LevelError
|
||||
ret.Message = fmt.Sprintf("failed to handle rule output: %s", err)
|
||||
@@ -161,7 +162,7 @@ func (re *re) StartScheduler(ctx context.Context) error {
|
||||
|
||||
page, err := re.repo.ListRules(ctx, pm)
|
||||
if err != nil {
|
||||
re.runInfo <- RunInfo{
|
||||
re.runInfo <- pkglog.RunInfo{
|
||||
Level: slog.LevelError,
|
||||
Message: fmt.Sprintf("failed to list rules: %s", err),
|
||||
Details: []slog.Attr{slog.Time("due", due)},
|
||||
@@ -173,7 +174,7 @@ func (re *re) StartScheduler(ctx context.Context) error {
|
||||
for _, r := range page.Rules {
|
||||
go func(rule Rule) {
|
||||
if _, err := re.repo.UpdateRuleDue(ctx, rule.ID, rule.Schedule.NextDue()); err != nil {
|
||||
re.runInfo <- RunInfo{Level: slog.LevelError, Message: fmt.Sprintf("failed to update rule: %s", err), Details: []slog.Attr{slog.Time("time", time.Now().UTC())}}
|
||||
re.runInfo <- pkglog.RunInfo{Level: slog.LevelError, Message: fmt.Sprintf("failed to update rule: %s", err), Details: []slog.Attr{slog.Time("time", time.Now().UTC())}}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -188,39 +189,6 @@ func (re *re) StartScheduler(ctx context.Context) error {
|
||||
}
|
||||
// Reset due, it will reset in the page meta as well.
|
||||
due = time.Now().UTC()
|
||||
|
||||
reportConfigs, err := re.repo.ListReportsConfig(ctx, pm)
|
||||
if err != nil {
|
||||
re.runInfo <- RunInfo{
|
||||
Level: slog.LevelError,
|
||||
Message: fmt.Sprintf("failed to list reports : %s", err),
|
||||
Details: []slog.Attr{slog.Time("due", due)},
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for _, c := range reportConfigs.ReportConfigs {
|
||||
go func(cfg ReportConfig) {
|
||||
if _, err := re.repo.UpdateReportDue(ctx, cfg.ID, cfg.Schedule.NextDue()); err != nil {
|
||||
re.runInfo <- RunInfo{Level: slog.LevelError, Message: fmt.Sprintf("failed to update report: %s", err), Details: []slog.Attr{slog.Time("time", time.Now().UTC())}}
|
||||
return
|
||||
}
|
||||
_, err := re.generateReport(ctx, cfg, EmailReport)
|
||||
ret := RunInfo{
|
||||
Details: []slog.Attr{
|
||||
slog.String("domain_id", cfg.DomainID),
|
||||
slog.String("report_id", cfg.ID),
|
||||
slog.String("report_name", cfg.Name),
|
||||
slog.Time("exec_time", time.Now().UTC()),
|
||||
},
|
||||
}
|
||||
if err != nil {
|
||||
ret.Level = slog.LevelError
|
||||
ret.Message = fmt.Sprintf("failed to generate report: %s", err)
|
||||
}
|
||||
re.runInfo <- ret
|
||||
}(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,15 +15,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
errDomainCreateConfigs = errors.New("not authorized to create report configs in domain")
|
||||
errDomainViewConfigs = errors.New("not authorized to view report configs in domain")
|
||||
errDomainUpdateConfigs = errors.New("not authorized to update report configs in domain")
|
||||
errDomainDeleteConfigs = errors.New("not authorized to delete report configs in domain")
|
||||
errDomainCreateRules = errors.New("not authorized to create rules in domain")
|
||||
errDomainViewRules = errors.New("not authorized to view rules in domain")
|
||||
errDomainUpdateRules = errors.New("not authorized to update rules in domain")
|
||||
errDomainDeleteRules = errors.New("not authorized to delete rules in domain")
|
||||
errDomainGenerateReports = errors.New("not authorized to generate reports in domain")
|
||||
errDomainCreateRules = errors.New("not authorized to create rules in domain")
|
||||
errDomainViewRules = errors.New("not authorized to view rules in domain")
|
||||
errDomainUpdateRules = errors.New("not authorized to update rules in domain")
|
||||
errDomainDeleteRules = errors.New("not authorized to delete rules in domain")
|
||||
)
|
||||
|
||||
type authorizationMiddleware struct {
|
||||
@@ -167,150 +162,6 @@ func (am *authorizationMiddleware) DisableRule(ctx context.Context, session auth
|
||||
return am.svc.DisableRule(ctx, session, id)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) AddReportConfig(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return re.ReportConfig{}, errors.Wrap(errDomainCreateConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.AddReportConfig(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) ViewReportConfig(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return re.ReportConfig{}, errors.Wrap(errDomainViewConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.ViewReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) UpdateReportConfig(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return re.ReportConfig{}, errors.Wrap(errDomainUpdateConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.UpdateReportConfig(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return re.ReportConfig{}, errors.Wrap(errDomainDeleteConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.UpdateReportSchedule(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) RemoveReportConfig(ctx context.Context, session authn.Session, id string) error {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return errors.Wrap(errDomainDeleteConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.RemoveReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) ListReportsConfig(ctx context.Context, session authn.Session, pm re.PageMeta) (re.ReportConfigPage, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return re.ReportConfigPage{}, errors.Wrap(errDomainViewConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.ListReportsConfig(ctx, session, pm)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) EnableReportConfig(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return re.ReportConfig{}, errors.Wrap(errDomainUpdateConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.EnableReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) DisableReportConfig(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return re.ReportConfig{}, errors.Wrap(errDomainUpdateConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.DisableReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) GenerateReport(ctx context.Context, session authn.Session, config re.ReportConfig, action re.ReportAction) (re.ReportPage, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return re.ReportPage{}, errors.Wrap(errDomainGenerateReports, err)
|
||||
}
|
||||
|
||||
return am.svc.GenerateReport(ctx, session, config, action)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) StartScheduler(ctx context.Context) error {
|
||||
return am.svc.StartScheduler(ctx)
|
||||
}
|
||||
|
||||
@@ -220,174 +220,3 @@ func (lm *loggingMiddleware) Handle(msg *messaging.Message) (err error) {
|
||||
func (lm *loggingMiddleware) Cancel() error {
|
||||
return lm.Cancel()
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) GenerateReport(ctx context.Context, session authn.Session, config re.ReportConfig, action re.ReportAction) (page re.ReportPage, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Generate report failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Generate report completed", args...)
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.GenerateReport(ctx, session, config, action)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) AddReportConfig(ctx context.Context, session authn.Session, config re.ReportConfig) (res re.ReportConfig, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("domain_id", session.DomainID),
|
||||
slog.String("report_name", config.Name),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Add report config failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Add report config completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.AddReportConfig(ctx, session, config)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) ViewReportConfig(ctx context.Context, session authn.Session, id string) (res re.ReportConfig, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("domain_id", session.DomainID),
|
||||
slog.Group("report_config",
|
||||
slog.String("id", res.ID),
|
||||
slog.String("name", res.Name),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("View report config failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("View report config completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.ViewReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) UpdateReportConfig(ctx context.Context, session authn.Session, config re.ReportConfig) (res re.ReportConfig, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("domain_id", session.DomainID),
|
||||
slog.Group("report_config",
|
||||
slog.String("id", config.ID),
|
||||
slog.String("name", config.Name),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Update report config failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Update report config completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.UpdateReportConfig(ctx, session, config)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg re.ReportConfig) (res re.ReportConfig, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("domain_id", session.DomainID),
|
||||
slog.Group("report",
|
||||
slog.String("id", cfg.ID),
|
||||
slog.Any("schedule", cfg.Schedule),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Update report schedule failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Update report schedule completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.UpdateReportSchedule(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) ListReportsConfig(ctx context.Context, session authn.Session, pm re.PageMeta) (pg re.ReportConfigPage, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("domain_id", session.DomainID),
|
||||
slog.Group("page",
|
||||
slog.Uint64("offset", pm.Offset),
|
||||
slog.Uint64("limit", pm.Limit),
|
||||
slog.Uint64("total", pg.Total),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("List reports config failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("List reports config completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.ListReportsConfig(ctx, session, pm)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) DisableReportConfig(ctx context.Context, session authn.Session, id string) (res re.ReportConfig, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("domain_id", session.DomainID),
|
||||
slog.Group("report_config",
|
||||
slog.String("id", res.ID),
|
||||
slog.String("name", res.Name),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Disable report config failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Disable report config completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.DisableReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) EnableReportConfig(ctx context.Context, session authn.Session, id string) (res re.ReportConfig, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("domain_id", session.DomainID),
|
||||
slog.Group("report_config",
|
||||
slog.String("id", res.ID),
|
||||
slog.String("name", res.Name),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Enable report config failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Enable report config completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.EnableReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) RemoveReportConfig(ctx context.Context, session authn.Session, id string) (err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("domain_id", session.DomainID),
|
||||
slog.String("report_config_id", id),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Remove report config failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Remove report config completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.RemoveReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
@@ -42,61 +42,6 @@ func (_m *Repository) EXPECT() *Repository_Expecter {
|
||||
return &Repository_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// AddReportConfig provides a mock function for the type Repository
|
||||
func (_mock *Repository) AddReportConfig(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for AddReportConfig")
|
||||
}
|
||||
|
||||
var r0 re.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, re.ReportConfig) (re.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, cfg)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, re.ReportConfig) re.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, cfg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(re.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, re.ReportConfig) error); ok {
|
||||
r1 = returnFunc(ctx, cfg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_AddReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddReportConfig'
|
||||
type Repository_AddReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// AddReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - cfg
|
||||
func (_e *Repository_Expecter) AddReportConfig(ctx interface{}, cfg interface{}) *Repository_AddReportConfig_Call {
|
||||
return &Repository_AddReportConfig_Call{Call: _e.mock.On("AddReportConfig", ctx, cfg)}
|
||||
}
|
||||
|
||||
func (_c *Repository_AddReportConfig_Call) Run(run func(ctx context.Context, cfg re.ReportConfig)) *Repository_AddReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(re.ReportConfig))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_AddReportConfig_Call) Return(reportConfig re.ReportConfig, err error) *Repository_AddReportConfig_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_AddReportConfig_Call) RunAndReturn(run func(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error)) *Repository_AddReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// AddRule provides a mock function for the type Repository
|
||||
func (_mock *Repository) AddRule(ctx context.Context, r re.Rule) (re.Rule, error) {
|
||||
ret := _mock.Called(ctx, r)
|
||||
@@ -152,61 +97,6 @@ func (_c *Repository_AddRule_Call) RunAndReturn(run func(ctx context.Context, r
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListReportsConfig provides a mock function for the type Repository
|
||||
func (_mock *Repository) ListReportsConfig(ctx context.Context, pm re.PageMeta) (re.ReportConfigPage, error) {
|
||||
ret := _mock.Called(ctx, pm)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListReportsConfig")
|
||||
}
|
||||
|
||||
var r0 re.ReportConfigPage
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, re.PageMeta) (re.ReportConfigPage, error)); ok {
|
||||
return returnFunc(ctx, pm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, re.PageMeta) re.ReportConfigPage); ok {
|
||||
r0 = returnFunc(ctx, pm)
|
||||
} else {
|
||||
r0 = ret.Get(0).(re.ReportConfigPage)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, re.PageMeta) error); ok {
|
||||
r1 = returnFunc(ctx, pm)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_ListReportsConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListReportsConfig'
|
||||
type Repository_ListReportsConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListReportsConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - pm
|
||||
func (_e *Repository_Expecter) ListReportsConfig(ctx interface{}, pm interface{}) *Repository_ListReportsConfig_Call {
|
||||
return &Repository_ListReportsConfig_Call{Call: _e.mock.On("ListReportsConfig", ctx, pm)}
|
||||
}
|
||||
|
||||
func (_c *Repository_ListReportsConfig_Call) Run(run func(ctx context.Context, pm re.PageMeta)) *Repository_ListReportsConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(re.PageMeta))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ListReportsConfig_Call) Return(reportConfigPage re.ReportConfigPage, err error) *Repository_ListReportsConfig_Call {
|
||||
_c.Call.Return(reportConfigPage, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ListReportsConfig_Call) RunAndReturn(run func(ctx context.Context, pm re.PageMeta) (re.ReportConfigPage, error)) *Repository_ListReportsConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListRules provides a mock function for the type Repository
|
||||
func (_mock *Repository) ListRules(ctx context.Context, pm re.PageMeta) (re.Page, error) {
|
||||
ret := _mock.Called(ctx, pm)
|
||||
@@ -262,52 +152,6 @@ func (_c *Repository_ListRules_Call) RunAndReturn(run func(ctx context.Context,
|
||||
return _c
|
||||
}
|
||||
|
||||
// RemoveReportConfig provides a mock function for the type Repository
|
||||
func (_mock *Repository) RemoveReportConfig(ctx context.Context, id string) error {
|
||||
ret := _mock.Called(ctx, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RemoveReportConfig")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
||||
r0 = returnFunc(ctx, id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Repository_RemoveReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveReportConfig'
|
||||
type Repository_RemoveReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RemoveReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - id
|
||||
func (_e *Repository_Expecter) RemoveReportConfig(ctx interface{}, id interface{}) *Repository_RemoveReportConfig_Call {
|
||||
return &Repository_RemoveReportConfig_Call{Call: _e.mock.On("RemoveReportConfig", ctx, id)}
|
||||
}
|
||||
|
||||
func (_c *Repository_RemoveReportConfig_Call) Run(run func(ctx context.Context, id string)) *Repository_RemoveReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_RemoveReportConfig_Call) Return(err error) *Repository_RemoveReportConfig_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_RemoveReportConfig_Call) RunAndReturn(run func(ctx context.Context, id string) error) *Repository_RemoveReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RemoveRule provides a mock function for the type Repository
|
||||
func (_mock *Repository) RemoveRule(ctx context.Context, id string) error {
|
||||
ret := _mock.Called(ctx, id)
|
||||
@@ -354,227 +198,6 @@ func (_c *Repository_RemoveRule_Call) RunAndReturn(run func(ctx context.Context,
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateReportConfig provides a mock function for the type Repository
|
||||
func (_mock *Repository) UpdateReportConfig(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateReportConfig")
|
||||
}
|
||||
|
||||
var r0 re.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, re.ReportConfig) (re.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, cfg)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, re.ReportConfig) re.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, cfg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(re.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, re.ReportConfig) error); ok {
|
||||
r1 = returnFunc(ctx, cfg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_UpdateReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportConfig'
|
||||
type Repository_UpdateReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - cfg
|
||||
func (_e *Repository_Expecter) UpdateReportConfig(ctx interface{}, cfg interface{}) *Repository_UpdateReportConfig_Call {
|
||||
return &Repository_UpdateReportConfig_Call{Call: _e.mock.On("UpdateReportConfig", ctx, cfg)}
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportConfig_Call) Run(run func(ctx context.Context, cfg re.ReportConfig)) *Repository_UpdateReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(re.ReportConfig))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportConfig_Call) Return(reportConfig re.ReportConfig, err error) *Repository_UpdateReportConfig_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportConfig_Call) RunAndReturn(run func(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error)) *Repository_UpdateReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateReportConfigStatus provides a mock function for the type Repository
|
||||
func (_mock *Repository) UpdateReportConfigStatus(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateReportConfigStatus")
|
||||
}
|
||||
|
||||
var r0 re.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, re.ReportConfig) (re.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, cfg)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, re.ReportConfig) re.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, cfg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(re.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, re.ReportConfig) error); ok {
|
||||
r1 = returnFunc(ctx, cfg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_UpdateReportConfigStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportConfigStatus'
|
||||
type Repository_UpdateReportConfigStatus_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateReportConfigStatus is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - cfg
|
||||
func (_e *Repository_Expecter) UpdateReportConfigStatus(ctx interface{}, cfg interface{}) *Repository_UpdateReportConfigStatus_Call {
|
||||
return &Repository_UpdateReportConfigStatus_Call{Call: _e.mock.On("UpdateReportConfigStatus", ctx, cfg)}
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportConfigStatus_Call) Run(run func(ctx context.Context, cfg re.ReportConfig)) *Repository_UpdateReportConfigStatus_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(re.ReportConfig))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportConfigStatus_Call) Return(reportConfig re.ReportConfig, err error) *Repository_UpdateReportConfigStatus_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportConfigStatus_Call) RunAndReturn(run func(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error)) *Repository_UpdateReportConfigStatus_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateReportDue provides a mock function for the type Repository
|
||||
func (_mock *Repository) UpdateReportDue(ctx context.Context, id string, due time.Time) (re.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, id, due)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateReportDue")
|
||||
}
|
||||
|
||||
var r0 re.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, time.Time) (re.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, id, due)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, time.Time) re.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, id, due)
|
||||
} else {
|
||||
r0 = ret.Get(0).(re.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string, time.Time) error); ok {
|
||||
r1 = returnFunc(ctx, id, due)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_UpdateReportDue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportDue'
|
||||
type Repository_UpdateReportDue_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateReportDue is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - id
|
||||
// - due
|
||||
func (_e *Repository_Expecter) UpdateReportDue(ctx interface{}, id interface{}, due interface{}) *Repository_UpdateReportDue_Call {
|
||||
return &Repository_UpdateReportDue_Call{Call: _e.mock.On("UpdateReportDue", ctx, id, due)}
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportDue_Call) Run(run func(ctx context.Context, id string, due time.Time)) *Repository_UpdateReportDue_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(time.Time))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportDue_Call) Return(reportConfig re.ReportConfig, err error) *Repository_UpdateReportDue_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportDue_Call) RunAndReturn(run func(ctx context.Context, id string, due time.Time) (re.ReportConfig, error)) *Repository_UpdateReportDue_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateReportSchedule provides a mock function for the type Repository
|
||||
func (_mock *Repository) UpdateReportSchedule(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateReportSchedule")
|
||||
}
|
||||
|
||||
var r0 re.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, re.ReportConfig) (re.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, cfg)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, re.ReportConfig) re.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, cfg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(re.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, re.ReportConfig) error); ok {
|
||||
r1 = returnFunc(ctx, cfg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_UpdateReportSchedule_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportSchedule'
|
||||
type Repository_UpdateReportSchedule_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateReportSchedule is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - cfg
|
||||
func (_e *Repository_Expecter) UpdateReportSchedule(ctx interface{}, cfg interface{}) *Repository_UpdateReportSchedule_Call {
|
||||
return &Repository_UpdateReportSchedule_Call{Call: _e.mock.On("UpdateReportSchedule", ctx, cfg)}
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportSchedule_Call) Run(run func(ctx context.Context, cfg re.ReportConfig)) *Repository_UpdateReportSchedule_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(re.ReportConfig))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportSchedule_Call) Return(reportConfig re.ReportConfig, err error) *Repository_UpdateReportSchedule_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportSchedule_Call) RunAndReturn(run func(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error)) *Repository_UpdateReportSchedule_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateRule provides a mock function for the type Repository
|
||||
func (_mock *Repository) UpdateRule(ctx context.Context, r re.Rule) (re.Rule, error) {
|
||||
ret := _mock.Called(ctx, r)
|
||||
@@ -796,61 +419,6 @@ func (_c *Repository_UpdateRuleStatus_Call) RunAndReturn(run func(ctx context.Co
|
||||
return _c
|
||||
}
|
||||
|
||||
// ViewReportConfig provides a mock function for the type Repository
|
||||
func (_mock *Repository) ViewReportConfig(ctx context.Context, id string) (re.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ViewReportConfig")
|
||||
}
|
||||
|
||||
var r0 re.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) (re.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, id)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) re.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, id)
|
||||
} else {
|
||||
r0 = ret.Get(0).(re.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = returnFunc(ctx, id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_ViewReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ViewReportConfig'
|
||||
type Repository_ViewReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ViewReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - id
|
||||
func (_e *Repository_Expecter) ViewReportConfig(ctx interface{}, id interface{}) *Repository_ViewReportConfig_Call {
|
||||
return &Repository_ViewReportConfig_Call{Call: _e.mock.On("ViewReportConfig", ctx, id)}
|
||||
}
|
||||
|
||||
func (_c *Repository_ViewReportConfig_Call) Run(run func(ctx context.Context, id string)) *Repository_ViewReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ViewReportConfig_Call) Return(reportConfig re.ReportConfig, err error) *Repository_ViewReportConfig_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ViewReportConfig_Call) RunAndReturn(run func(ctx context.Context, id string) (re.ReportConfig, error)) *Repository_ViewReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ViewRule provides a mock function for the type Repository
|
||||
func (_mock *Repository) ViewRule(ctx context.Context, id string) (re.Rule, error) {
|
||||
ret := _mock.Called(ctx, id)
|
||||
|
||||
@@ -43,62 +43,6 @@ func (_m *Service) EXPECT() *Service_Expecter {
|
||||
return &Service_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// AddReportConfig provides a mock function for the type Service
|
||||
func (_mock *Service) AddReportConfig(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, session, cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for AddReportConfig")
|
||||
}
|
||||
|
||||
var r0 re.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.ReportConfig) (re.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, session, cfg)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.ReportConfig) re.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, session, cfg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(re.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, re.ReportConfig) error); ok {
|
||||
r1 = returnFunc(ctx, session, cfg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_AddReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddReportConfig'
|
||||
type Service_AddReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// AddReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - cfg
|
||||
func (_e *Service_Expecter) AddReportConfig(ctx interface{}, session interface{}, cfg interface{}) *Service_AddReportConfig_Call {
|
||||
return &Service_AddReportConfig_Call{Call: _e.mock.On("AddReportConfig", ctx, session, cfg)}
|
||||
}
|
||||
|
||||
func (_c *Service_AddReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, cfg re.ReportConfig)) *Service_AddReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(re.ReportConfig))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_AddReportConfig_Call) Return(reportConfig re.ReportConfig, err error) *Service_AddReportConfig_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_AddReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error)) *Service_AddReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// AddRule provides a mock function for the type Service
|
||||
func (_mock *Service) AddRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
|
||||
ret := _mock.Called(ctx, session, r)
|
||||
@@ -199,62 +143,6 @@ func (_c *Service_Cancel_Call) RunAndReturn(run func() error) *Service_Cancel_Ca
|
||||
return _c
|
||||
}
|
||||
|
||||
// DisableReportConfig provides a mock function for the type Service
|
||||
func (_mock *Service) DisableReportConfig(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, session, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DisableReportConfig")
|
||||
}
|
||||
|
||||
var r0 re.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) (re.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, session, id)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) re.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r0 = ret.Get(0).(re.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
|
||||
r1 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_DisableReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisableReportConfig'
|
||||
type Service_DisableReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DisableReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - id
|
||||
func (_e *Service_Expecter) DisableReportConfig(ctx interface{}, session interface{}, id interface{}) *Service_DisableReportConfig_Call {
|
||||
return &Service_DisableReportConfig_Call{Call: _e.mock.On("DisableReportConfig", ctx, session, id)}
|
||||
}
|
||||
|
||||
func (_c *Service_DisableReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_DisableReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_DisableReportConfig_Call) Return(reportConfig re.ReportConfig, err error) *Service_DisableReportConfig_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_DisableReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error)) *Service_DisableReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DisableRule provides a mock function for the type Service
|
||||
func (_mock *Service) DisableRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
|
||||
ret := _mock.Called(ctx, session, id)
|
||||
@@ -311,62 +199,6 @@ func (_c *Service_DisableRule_Call) RunAndReturn(run func(ctx context.Context, s
|
||||
return _c
|
||||
}
|
||||
|
||||
// EnableReportConfig provides a mock function for the type Service
|
||||
func (_mock *Service) EnableReportConfig(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, session, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for EnableReportConfig")
|
||||
}
|
||||
|
||||
var r0 re.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) (re.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, session, id)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) re.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r0 = ret.Get(0).(re.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
|
||||
r1 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_EnableReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EnableReportConfig'
|
||||
type Service_EnableReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// EnableReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - id
|
||||
func (_e *Service_Expecter) EnableReportConfig(ctx interface{}, session interface{}, id interface{}) *Service_EnableReportConfig_Call {
|
||||
return &Service_EnableReportConfig_Call{Call: _e.mock.On("EnableReportConfig", ctx, session, id)}
|
||||
}
|
||||
|
||||
func (_c *Service_EnableReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_EnableReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_EnableReportConfig_Call) Return(reportConfig re.ReportConfig, err error) *Service_EnableReportConfig_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_EnableReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error)) *Service_EnableReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// EnableRule provides a mock function for the type Service
|
||||
func (_mock *Service) EnableRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
|
||||
ret := _mock.Called(ctx, session, id)
|
||||
@@ -423,63 +255,6 @@ func (_c *Service_EnableRule_Call) RunAndReturn(run func(ctx context.Context, se
|
||||
return _c
|
||||
}
|
||||
|
||||
// GenerateReport provides a mock function for the type Service
|
||||
func (_mock *Service) GenerateReport(ctx context.Context, session authn.Session, config re.ReportConfig, action re.ReportAction) (re.ReportPage, error) {
|
||||
ret := _mock.Called(ctx, session, config, action)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GenerateReport")
|
||||
}
|
||||
|
||||
var r0 re.ReportPage
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.ReportConfig, re.ReportAction) (re.ReportPage, error)); ok {
|
||||
return returnFunc(ctx, session, config, action)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.ReportConfig, re.ReportAction) re.ReportPage); ok {
|
||||
r0 = returnFunc(ctx, session, config, action)
|
||||
} else {
|
||||
r0 = ret.Get(0).(re.ReportPage)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, re.ReportConfig, re.ReportAction) error); ok {
|
||||
r1 = returnFunc(ctx, session, config, action)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_GenerateReport_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GenerateReport'
|
||||
type Service_GenerateReport_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GenerateReport is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - config
|
||||
// - action
|
||||
func (_e *Service_Expecter) GenerateReport(ctx interface{}, session interface{}, config interface{}, action interface{}) *Service_GenerateReport_Call {
|
||||
return &Service_GenerateReport_Call{Call: _e.mock.On("GenerateReport", ctx, session, config, action)}
|
||||
}
|
||||
|
||||
func (_c *Service_GenerateReport_Call) Run(run func(ctx context.Context, session authn.Session, config re.ReportConfig, action re.ReportAction)) *Service_GenerateReport_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(re.ReportConfig), args[3].(re.ReportAction))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_GenerateReport_Call) Return(reportPage re.ReportPage, err error) *Service_GenerateReport_Call {
|
||||
_c.Call.Return(reportPage, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_GenerateReport_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, config re.ReportConfig, action re.ReportAction) (re.ReportPage, error)) *Service_GenerateReport_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Handle provides a mock function for the type Service
|
||||
func (_mock *Service) Handle(msg *messaging.Message) error {
|
||||
ret := _mock.Called(msg)
|
||||
@@ -525,62 +300,6 @@ func (_c *Service_Handle_Call) RunAndReturn(run func(msg *messaging.Message) err
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListReportsConfig provides a mock function for the type Service
|
||||
func (_mock *Service) ListReportsConfig(ctx context.Context, session authn.Session, pm re.PageMeta) (re.ReportConfigPage, error) {
|
||||
ret := _mock.Called(ctx, session, pm)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListReportsConfig")
|
||||
}
|
||||
|
||||
var r0 re.ReportConfigPage
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.PageMeta) (re.ReportConfigPage, error)); ok {
|
||||
return returnFunc(ctx, session, pm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.PageMeta) re.ReportConfigPage); ok {
|
||||
r0 = returnFunc(ctx, session, pm)
|
||||
} else {
|
||||
r0 = ret.Get(0).(re.ReportConfigPage)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, re.PageMeta) error); ok {
|
||||
r1 = returnFunc(ctx, session, pm)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_ListReportsConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListReportsConfig'
|
||||
type Service_ListReportsConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListReportsConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - pm
|
||||
func (_e *Service_Expecter) ListReportsConfig(ctx interface{}, session interface{}, pm interface{}) *Service_ListReportsConfig_Call {
|
||||
return &Service_ListReportsConfig_Call{Call: _e.mock.On("ListReportsConfig", ctx, session, pm)}
|
||||
}
|
||||
|
||||
func (_c *Service_ListReportsConfig_Call) Run(run func(ctx context.Context, session authn.Session, pm re.PageMeta)) *Service_ListReportsConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(re.PageMeta))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ListReportsConfig_Call) Return(reportConfigPage re.ReportConfigPage, err error) *Service_ListReportsConfig_Call {
|
||||
_c.Call.Return(reportConfigPage, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ListReportsConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, pm re.PageMeta) (re.ReportConfigPage, error)) *Service_ListReportsConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListRules provides a mock function for the type Service
|
||||
func (_mock *Service) ListRules(ctx context.Context, session authn.Session, pm re.PageMeta) (re.Page, error) {
|
||||
ret := _mock.Called(ctx, session, pm)
|
||||
@@ -637,53 +356,6 @@ func (_c *Service_ListRules_Call) RunAndReturn(run func(ctx context.Context, ses
|
||||
return _c
|
||||
}
|
||||
|
||||
// RemoveReportConfig provides a mock function for the type Service
|
||||
func (_mock *Service) RemoveReportConfig(ctx context.Context, session authn.Session, id string) error {
|
||||
ret := _mock.Called(ctx, session, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RemoveReportConfig")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok {
|
||||
r0 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Service_RemoveReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveReportConfig'
|
||||
type Service_RemoveReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RemoveReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - id
|
||||
func (_e *Service_Expecter) RemoveReportConfig(ctx interface{}, session interface{}, id interface{}) *Service_RemoveReportConfig_Call {
|
||||
return &Service_RemoveReportConfig_Call{Call: _e.mock.On("RemoveReportConfig", ctx, session, id)}
|
||||
}
|
||||
|
||||
func (_c *Service_RemoveReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_RemoveReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_RemoveReportConfig_Call) Return(err error) *Service_RemoveReportConfig_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_RemoveReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) error) *Service_RemoveReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RemoveRule provides a mock function for the type Service
|
||||
func (_mock *Service) RemoveRule(ctx context.Context, session authn.Session, id string) error {
|
||||
ret := _mock.Called(ctx, session, id)
|
||||
@@ -776,118 +448,6 @@ func (_c *Service_StartScheduler_Call) RunAndReturn(run func(ctx context.Context
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateReportConfig provides a mock function for the type Service
|
||||
func (_mock *Service) UpdateReportConfig(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, session, cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateReportConfig")
|
||||
}
|
||||
|
||||
var r0 re.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.ReportConfig) (re.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, session, cfg)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.ReportConfig) re.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, session, cfg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(re.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, re.ReportConfig) error); ok {
|
||||
r1 = returnFunc(ctx, session, cfg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_UpdateReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportConfig'
|
||||
type Service_UpdateReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - cfg
|
||||
func (_e *Service_Expecter) UpdateReportConfig(ctx interface{}, session interface{}, cfg interface{}) *Service_UpdateReportConfig_Call {
|
||||
return &Service_UpdateReportConfig_Call{Call: _e.mock.On("UpdateReportConfig", ctx, session, cfg)}
|
||||
}
|
||||
|
||||
func (_c *Service_UpdateReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, cfg re.ReportConfig)) *Service_UpdateReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(re.ReportConfig))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_UpdateReportConfig_Call) Return(reportConfig re.ReportConfig, err error) *Service_UpdateReportConfig_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_UpdateReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error)) *Service_UpdateReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateReportSchedule provides a mock function for the type Service
|
||||
func (_mock *Service) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, session, cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateReportSchedule")
|
||||
}
|
||||
|
||||
var r0 re.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.ReportConfig) (re.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, session, cfg)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, re.ReportConfig) re.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, session, cfg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(re.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, re.ReportConfig) error); ok {
|
||||
r1 = returnFunc(ctx, session, cfg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_UpdateReportSchedule_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportSchedule'
|
||||
type Service_UpdateReportSchedule_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateReportSchedule is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - cfg
|
||||
func (_e *Service_Expecter) UpdateReportSchedule(ctx interface{}, session interface{}, cfg interface{}) *Service_UpdateReportSchedule_Call {
|
||||
return &Service_UpdateReportSchedule_Call{Call: _e.mock.On("UpdateReportSchedule", ctx, session, cfg)}
|
||||
}
|
||||
|
||||
func (_c *Service_UpdateReportSchedule_Call) Run(run func(ctx context.Context, session authn.Session, cfg re.ReportConfig)) *Service_UpdateReportSchedule_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(re.ReportConfig))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_UpdateReportSchedule_Call) Return(reportConfig re.ReportConfig, err error) *Service_UpdateReportSchedule_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_UpdateReportSchedule_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, cfg re.ReportConfig) (re.ReportConfig, error)) *Service_UpdateReportSchedule_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateRule provides a mock function for the type Service
|
||||
func (_mock *Service) UpdateRule(ctx context.Context, session authn.Session, r re.Rule) (re.Rule, error) {
|
||||
ret := _mock.Called(ctx, session, r)
|
||||
@@ -1000,62 +560,6 @@ func (_c *Service_UpdateRuleSchedule_Call) RunAndReturn(run func(ctx context.Con
|
||||
return _c
|
||||
}
|
||||
|
||||
// ViewReportConfig provides a mock function for the type Service
|
||||
func (_mock *Service) ViewReportConfig(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, session, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ViewReportConfig")
|
||||
}
|
||||
|
||||
var r0 re.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) (re.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, session, id)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) re.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r0 = ret.Get(0).(re.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
|
||||
r1 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_ViewReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ViewReportConfig'
|
||||
type Service_ViewReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ViewReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - id
|
||||
func (_e *Service_Expecter) ViewReportConfig(ctx interface{}, session interface{}, id interface{}) *Service_ViewReportConfig_Call {
|
||||
return &Service_ViewReportConfig_Call{Call: _e.mock.On("ViewReportConfig", ctx, session, id)}
|
||||
}
|
||||
|
||||
func (_c *Service_ViewReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_ViewReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ViewReportConfig_Call) Return(reportConfig re.ReportConfig, err error) *Service_ViewReportConfig_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ViewReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) (re.ReportConfig, error)) *Service_ViewReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ViewRule provides a mock function for the type Service
|
||||
func (_mock *Service) ViewRule(ctx context.Context, session authn.Session, id string) (re.Rule, error) {
|
||||
ret := _mock.Called(ctx, session, id)
|
||||
|
||||
@@ -43,32 +43,6 @@ func Migration() *migrate.MemoryMigrationSource {
|
||||
`DROP TABLE IF EXISTS rules`,
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: "rules_02",
|
||||
Up: []string{
|
||||
`CREATE TABLE IF NOT EXISTS report_config (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
name VARCHAR(1024),
|
||||
description TEXT,
|
||||
domain_id VARCHAR(36) NOT NULL,
|
||||
status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0),
|
||||
created_at TIMESTAMP,
|
||||
created_by VARCHAR(254),
|
||||
updated_at TIMESTAMP,
|
||||
updated_by VARCHAR(254),
|
||||
time TIMESTAMP,
|
||||
recurring SMALLINT,
|
||||
recurring_period SMALLINT,
|
||||
start_datetime TIMESTAMP,
|
||||
config JSONB,
|
||||
email JSONB,
|
||||
metrics JSONB
|
||||
);`,
|
||||
},
|
||||
Down: []string{
|
||||
`DROP TABLE IF EXISTS report_config;`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,318 +353,3 @@ func pageRulesQuery(pm re.PageMeta) string {
|
||||
|
||||
return q
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) AddReportConfig(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error) {
|
||||
q := `
|
||||
INSERT INTO report_config (id, name, description, domain_id, config, metrics,
|
||||
email, start_datetime, time, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status)
|
||||
VALUES (:id, :name, :description, :domain_id, :config, :metrics,
|
||||
:email, :start_datetime, :time, :recurring, :recurring_period, :created_at, :created_by, :updated_at, :updated_by, :status)
|
||||
RETURNING id, name, description, domain_id, config, metrics,
|
||||
email, start_datetime, time, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;
|
||||
`
|
||||
dbr, err := reportToDb(cfg)
|
||||
if err != nil {
|
||||
return re.ReportConfig{}, err
|
||||
}
|
||||
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
|
||||
if err != nil {
|
||||
return re.ReportConfig{}, err
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
var dbReport dbReport
|
||||
if row.Next() {
|
||||
if err := row.StructScan(&dbReport); err != nil {
|
||||
return re.ReportConfig{}, err
|
||||
}
|
||||
}
|
||||
|
||||
report, err := dbToReport(dbReport)
|
||||
if err != nil {
|
||||
return re.ReportConfig{}, err
|
||||
}
|
||||
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) ViewReportConfig(ctx context.Context, id string) (re.ReportConfig, error) {
|
||||
q := `
|
||||
SELECT id, name, description, domain_id, config, metrics,
|
||||
email, start_datetime, time, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status
|
||||
FROM report_config
|
||||
WHERE id = $1;
|
||||
`
|
||||
row := repo.DB.QueryRowxContext(ctx, q, id)
|
||||
if err := row.Err(); err != nil {
|
||||
return re.ReportConfig{}, err
|
||||
}
|
||||
var dbr dbReport
|
||||
if err := row.StructScan(&dbr); err != nil {
|
||||
return re.ReportConfig{}, err
|
||||
}
|
||||
rpt, err := dbToReport(dbr)
|
||||
if err != nil {
|
||||
return re.ReportConfig{}, err
|
||||
}
|
||||
|
||||
return rpt, nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) UpdateReportConfigStatus(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error) {
|
||||
q := `UPDATE report_config SET status = :status, updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id
|
||||
RETURNING id, name, description, domain_id, metrics, email, config,
|
||||
start_datetime, time, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;`
|
||||
|
||||
dbRpt, err := reportToDb(cfg)
|
||||
if err != nil {
|
||||
return re.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
row, err := repo.DB.NamedQueryContext(ctx, q, dbRpt)
|
||||
if err != nil {
|
||||
return re.ReportConfig{}, postgres.HandleError(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
dbr := dbReport{}
|
||||
if row.Next() {
|
||||
if err := row.StructScan(&dbr); err != nil {
|
||||
return re.ReportConfig{}, err
|
||||
}
|
||||
|
||||
res, err := dbToReport(dbr)
|
||||
if err != nil {
|
||||
return re.ReportConfig{}, err
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
return re.ReportConfig{}, repoerr.ErrNotFound
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) UpdateReportConfig(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error) {
|
||||
var query []string
|
||||
|
||||
if cfg.Name != "" {
|
||||
query = append(query, "name = :name")
|
||||
}
|
||||
|
||||
if cfg.Description != "" {
|
||||
query = append(query, "description = :description")
|
||||
}
|
||||
|
||||
if len(cfg.Metrics) > 0 {
|
||||
query = append(query, "metrics = :metrics")
|
||||
}
|
||||
|
||||
if cfg.Email != nil {
|
||||
query = append(query, "email = :email")
|
||||
}
|
||||
|
||||
if cfg.Config != nil {
|
||||
query = append(query, "config = :config")
|
||||
}
|
||||
|
||||
var q string
|
||||
if len(query) > 0 {
|
||||
q = fmt.Sprintf("%s", strings.Join(query, ", "))
|
||||
}
|
||||
|
||||
q = fmt.Sprintf(`
|
||||
UPDATE report_config
|
||||
SET %s,
|
||||
updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id
|
||||
RETURNING id, name, description, domain_id, config, metrics,
|
||||
email, start_datetime, time, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;
|
||||
`, q)
|
||||
|
||||
dbr, err := reportToDb(cfg)
|
||||
if err != nil {
|
||||
return re.ReportConfig{}, err
|
||||
}
|
||||
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
|
||||
if err != nil {
|
||||
return re.ReportConfig{}, err
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
var dbReport dbReport
|
||||
if row.Next() {
|
||||
if err := row.StructScan(&dbReport); err != nil {
|
||||
return re.ReportConfig{}, err
|
||||
}
|
||||
}
|
||||
rpt, err := dbToReport(dbReport)
|
||||
if err != nil {
|
||||
return re.ReportConfig{}, err
|
||||
}
|
||||
|
||||
return rpt, nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) UpdateReportSchedule(ctx context.Context, cfg re.ReportConfig) (re.ReportConfig, error) {
|
||||
q := `
|
||||
UPDATE report_config
|
||||
SET start_datetime = :start_datetime, time = :time, recurring = :recurring,
|
||||
recurring_period = :recurring_period, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id
|
||||
RETURNING id, name, description, domain_id, config, metrics,
|
||||
email, start_datetime, time, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;
|
||||
`
|
||||
|
||||
dbr, err := reportToDb(cfg)
|
||||
if err != nil {
|
||||
return re.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
|
||||
if err != nil {
|
||||
return re.ReportConfig{}, postgres.HandleError(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
var dbReport dbReport
|
||||
if row.Next() {
|
||||
if err := row.StructScan(&dbReport); err != nil {
|
||||
return re.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
}
|
||||
report, err := dbToReport(dbReport)
|
||||
if err != nil {
|
||||
return re.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) RemoveReportConfig(ctx context.Context, id string) error {
|
||||
q := `
|
||||
DELETE FROM report_config
|
||||
WHERE id = $1;
|
||||
`
|
||||
|
||||
result, err := repo.DB.ExecContext(ctx, q, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := result.RowsAffected(); err != nil {
|
||||
return repoerr.ErrNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) ListReportsConfig(ctx context.Context, pm re.PageMeta) (re.ReportConfigPage, error) {
|
||||
listReportsQuery := `
|
||||
SELECT id, name, description, domain_id, metrics, email, config,
|
||||
start_datetime, time, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status
|
||||
FROM report_config rc %s %s;
|
||||
`
|
||||
|
||||
pgData := ""
|
||||
if pm.Limit != 0 {
|
||||
pgData = "LIMIT :limit"
|
||||
}
|
||||
if pm.Offset != 0 {
|
||||
pgData += " OFFSET :offset"
|
||||
}
|
||||
pq := pageReportQuery(pm)
|
||||
q := fmt.Sprintf(listReportsQuery, pq, pgData)
|
||||
rows, err := repo.DB.NamedQueryContext(ctx, q, pm)
|
||||
if err != nil {
|
||||
return re.ReportConfigPage{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
cfgs := []re.ReportConfig{}
|
||||
for rows.Next() {
|
||||
var r dbReport
|
||||
if err := rows.StructScan(&r); err != nil {
|
||||
return re.ReportConfigPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
rpt, err := dbToReport(r)
|
||||
if err != nil {
|
||||
return re.ReportConfigPage{}, err
|
||||
}
|
||||
cfgs = append(cfgs, rpt)
|
||||
}
|
||||
|
||||
cq := fmt.Sprintf(`SELECT COUNT(*) FROM report_config rc %s;`, pq)
|
||||
|
||||
total, err := postgres.Total(ctx, repo.DB, cq, pm)
|
||||
if err != nil {
|
||||
return re.ReportConfigPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
pm.Total = total
|
||||
ret := re.ReportConfigPage{
|
||||
PageMeta: pm,
|
||||
ReportConfigs: cfgs,
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) UpdateReportDue(ctx context.Context, id string, due time.Time) (re.ReportConfig, error) {
|
||||
q := `
|
||||
UPDATE report_config
|
||||
SET time = :time, updated_at = :updated_at WHERE id = :id
|
||||
RETURNING id, name, description, domain_id, config, metrics,
|
||||
email, start_datetime, time, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;
|
||||
`
|
||||
|
||||
dbr := dbReport{
|
||||
ID: id,
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
Time: sql.NullTime{Time: due},
|
||||
}
|
||||
if !due.IsZero() {
|
||||
dbr.Time.Valid = true
|
||||
}
|
||||
|
||||
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
|
||||
if err != nil {
|
||||
return re.ReportConfig{}, postgres.HandleError(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
var dbReport dbReport
|
||||
if row.Next() {
|
||||
if err := row.StructScan(&dbReport); err != nil {
|
||||
return re.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
}
|
||||
report, err := dbToReport(dbReport)
|
||||
if err != nil {
|
||||
return re.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func pageReportQuery(pm re.PageMeta) string {
|
||||
var query []string
|
||||
if pm.Status != re.AllStatus {
|
||||
query = append(query, "rc.status = :status")
|
||||
}
|
||||
if pm.Domain != "" {
|
||||
query = append(query, "rc.domain_id = :domain_id")
|
||||
}
|
||||
if pm.ScheduledBefore != nil {
|
||||
query = append(query, "rc.time < :scheduled_before")
|
||||
}
|
||||
if pm.ScheduledAfter != nil {
|
||||
query = append(query, "rc.time > :scheduled_after")
|
||||
}
|
||||
if pm.Name != "" {
|
||||
query = append(query, "rc.name ILIKE '%' || :name || '%'")
|
||||
}
|
||||
|
||||
var q string
|
||||
if len(query) > 0 {
|
||||
q = fmt.Sprintf("WHERE %s", strings.Join(query, " AND "))
|
||||
}
|
||||
|
||||
return q
|
||||
}
|
||||
|
||||
+22
-144
@@ -8,6 +8,7 @@ import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/schedule"
|
||||
"github.com/absmach/magistrala/re"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/lib/pq"
|
||||
@@ -15,46 +16,26 @@ import (
|
||||
|
||||
// dbRule represents the database structure for a Rule.
|
||||
type dbRule struct {
|
||||
ID string `db:"id"`
|
||||
Name string `db:"name"`
|
||||
DomainID string `db:"domain_id"`
|
||||
Metadata []byte `db:"metadata,omitempty"`
|
||||
InputChannel string `db:"input_channel"`
|
||||
InputTopic sql.NullString `db:"input_topic"`
|
||||
LogicType re.ScriptType `db:"logic_type"`
|
||||
LogicOutputs pq.Int32Array `db:"logic_output"`
|
||||
LogicValue string `db:"logic_value"`
|
||||
OutputChannel sql.NullString `db:"output_channel"`
|
||||
OutputTopic sql.NullString `db:"output_topic"`
|
||||
StartDateTime sql.NullTime `db:"start_datetime"`
|
||||
Time sql.NullTime `db:"time"`
|
||||
Recurring re.Recurring `db:"recurring"`
|
||||
RecurringPeriod uint `db:"recurring_period"`
|
||||
Status re.Status `db:"status"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
CreatedBy string `db:"created_by"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
UpdatedBy string `db:"updated_by"`
|
||||
}
|
||||
|
||||
// dbReport represents the database structure for a Report.
|
||||
type dbReport struct {
|
||||
ID string `db:"id"`
|
||||
Name string `db:"name"`
|
||||
Description string `db:"description"`
|
||||
DomainID string `db:"domain_id"`
|
||||
StartDateTime sql.NullTime `db:"start_datetime"`
|
||||
Time sql.NullTime `db:"time"`
|
||||
Recurring re.Recurring `db:"recurring"`
|
||||
RecurringPeriod uint `db:"recurring_period"`
|
||||
Status re.Status `db:"status"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
CreatedBy string `db:"created_by"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
UpdatedBy string `db:"updated_by"`
|
||||
Config []byte `db:"config,omitempty"`
|
||||
Metrics []byte `db:"metrics"`
|
||||
Email []byte `db:"email"`
|
||||
ID string `db:"id"`
|
||||
Name string `db:"name"`
|
||||
DomainID string `db:"domain_id"`
|
||||
Metadata []byte `db:"metadata,omitempty"`
|
||||
InputChannel string `db:"input_channel"`
|
||||
InputTopic sql.NullString `db:"input_topic"`
|
||||
LogicType re.ScriptType `db:"logic_type"`
|
||||
LogicOutputs pq.Int32Array `db:"logic_output"`
|
||||
LogicValue string `db:"logic_value"`
|
||||
OutputChannel sql.NullString `db:"output_channel"`
|
||||
OutputTopic sql.NullString `db:"output_topic"`
|
||||
StartDateTime sql.NullTime `db:"start_datetime"`
|
||||
Time sql.NullTime `db:"time"`
|
||||
Recurring schedule.Recurring `db:"recurring"`
|
||||
RecurringPeriod uint `db:"recurring_period"`
|
||||
Status re.Status `db:"status"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
CreatedBy string `db:"created_by"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
UpdatedBy string `db:"updated_by"`
|
||||
}
|
||||
|
||||
func ruleToDb(r re.Rule) (dbRule, error) {
|
||||
@@ -127,7 +108,7 @@ func dbToRule(dto dbRule) (re.Rule, error) {
|
||||
},
|
||||
OutputChannel: fromNullString(dto.OutputChannel),
|
||||
OutputTopic: fromNullString(dto.OutputTopic),
|
||||
Schedule: re.Schedule{
|
||||
Schedule: schedule.Schedule{
|
||||
StartDateTime: dto.StartDateTime.Time,
|
||||
Time: dto.Time.Time,
|
||||
Recurring: dto.Recurring,
|
||||
@@ -141,109 +122,6 @@ func dbToRule(dto dbRule) (re.Rule, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func reportToDb(r re.ReportConfig) (dbReport, error) {
|
||||
config := []byte("{}")
|
||||
if r.Config != nil {
|
||||
b, err := json.Marshal(r.Config)
|
||||
if err != nil {
|
||||
return dbReport{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
config = b
|
||||
}
|
||||
|
||||
metrics := []byte("{}")
|
||||
if r.Metrics != nil {
|
||||
m, err := json.Marshal(r.Metrics)
|
||||
if err != nil {
|
||||
return dbReport{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
metrics = m
|
||||
}
|
||||
|
||||
email := []byte("{}")
|
||||
if r.Email != nil {
|
||||
e, err := json.Marshal(r.Email)
|
||||
if err != nil {
|
||||
return dbReport{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
email = e
|
||||
}
|
||||
|
||||
start := sql.NullTime{Time: r.Schedule.StartDateTime}
|
||||
if !r.Schedule.StartDateTime.IsZero() {
|
||||
start.Valid = true
|
||||
}
|
||||
t := sql.NullTime{Time: r.Schedule.Time}
|
||||
if !r.Schedule.Time.IsZero() {
|
||||
t.Valid = true
|
||||
}
|
||||
|
||||
return dbReport{
|
||||
ID: r.ID,
|
||||
Name: r.Name,
|
||||
Description: r.Description,
|
||||
DomainID: r.DomainID,
|
||||
StartDateTime: start,
|
||||
Time: t,
|
||||
Recurring: r.Schedule.Recurring,
|
||||
RecurringPeriod: r.Schedule.RecurringPeriod,
|
||||
Status: r.Status,
|
||||
CreatedAt: r.CreatedAt,
|
||||
CreatedBy: r.CreatedBy,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
UpdatedBy: r.UpdatedBy,
|
||||
Config: config,
|
||||
Metrics: metrics,
|
||||
Email: email,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func dbToReport(dto dbReport) (re.ReportConfig, error) {
|
||||
var config re.MetricConfig
|
||||
if dto.Config != nil {
|
||||
if err := json.Unmarshal(dto.Config, &config); err != nil {
|
||||
return re.ReportConfig{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
}
|
||||
|
||||
var email re.EmailSetting
|
||||
if dto.Email != nil {
|
||||
if err := json.Unmarshal(dto.Email, &email); err != nil {
|
||||
return re.ReportConfig{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
}
|
||||
|
||||
var metrics []re.ReqMetric
|
||||
if dto.Metrics != nil {
|
||||
if err := json.Unmarshal(dto.Metrics, &metrics); err != nil {
|
||||
return re.ReportConfig{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
}
|
||||
|
||||
rpt := re.ReportConfig{
|
||||
ID: dto.ID,
|
||||
Name: dto.Name,
|
||||
Description: dto.Description,
|
||||
DomainID: dto.DomainID,
|
||||
Config: &config,
|
||||
Metrics: metrics,
|
||||
Schedule: re.Schedule{
|
||||
StartDateTime: dto.StartDateTime.Time,
|
||||
Time: dto.Time.Time,
|
||||
Recurring: dto.Recurring,
|
||||
RecurringPeriod: dto.RecurringPeriod,
|
||||
},
|
||||
Email: &email,
|
||||
Status: dto.Status,
|
||||
CreatedAt: dto.CreatedAt,
|
||||
CreatedBy: dto.CreatedBy,
|
||||
UpdatedAt: dto.UpdatedAt,
|
||||
UpdatedBy: dto.UpdatedBy,
|
||||
}
|
||||
|
||||
return rpt, nil
|
||||
}
|
||||
|
||||
func toNullString(value string) sql.NullString {
|
||||
if value == "" {
|
||||
return sql.NullString{Valid: false}
|
||||
|
||||
+16
-24
@@ -5,15 +5,13 @@ package re
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/schedule"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
)
|
||||
|
||||
var ErrInvalidRecurringType = errors.New("invalid recurring type")
|
||||
|
||||
const protocol = "nats"
|
||||
|
||||
// ScriptOutput is the indicator for type of the logic
|
||||
@@ -80,25 +78,19 @@ type (
|
||||
)
|
||||
|
||||
type Rule struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
DomainID string `json:"domain"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
InputChannel string `json:"input_channel"`
|
||||
InputTopic string `json:"input_topic"`
|
||||
Logic Script `json:"logic"`
|
||||
OutputChannel string `json:"output_channel,omitempty"`
|
||||
OutputTopic string `json:"output_topic,omitempty"`
|
||||
Schedule Schedule `json:"schedule"`
|
||||
Status Status `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
CreatedBy string `json:"created_by"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
UpdatedBy string `json:"updated_by"`
|
||||
}
|
||||
|
||||
type RunInfo struct {
|
||||
Level slog.Level
|
||||
Details []slog.Attr
|
||||
Message string
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
DomainID string `json:"domain"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
InputChannel string `json:"input_channel"`
|
||||
InputTopic string `json:"input_topic"`
|
||||
Logic Script `json:"logic"`
|
||||
OutputChannel string `json:"output_channel,omitempty"`
|
||||
OutputTopic string `json:"output_topic,omitempty"`
|
||||
Schedule schedule.Schedule `json:"schedule"`
|
||||
Status Status `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
CreatedBy string `json:"created_by"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
UpdatedBy string `json:"updated_by"`
|
||||
}
|
||||
|
||||
+22
-417
@@ -5,22 +5,20 @@ package re
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
grpcReadersV1 "github.com/absmach/magistrala/api/grpc/readers/v1"
|
||||
"github.com/absmach/magistrala/pkg/reltime"
|
||||
"github.com/absmach/magistrala/pkg/emailer"
|
||||
pkglog "github.com/absmach/magistrala/pkg/logger"
|
||||
"github.com/absmach/magistrala/pkg/schedule"
|
||||
"github.com/absmach/magistrala/pkg/ticker"
|
||||
"github.com/absmach/supermq"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/absmach/supermq/pkg/messaging"
|
||||
"github.com/absmach/supermq/pkg/transformers/senml"
|
||||
)
|
||||
|
||||
const limit = 1000
|
||||
|
||||
type Repository interface {
|
||||
AddRule(ctx context.Context, r Rule) (Rule, error)
|
||||
ViewRule(ctx context.Context, id string) (Rule, error)
|
||||
@@ -30,33 +28,24 @@ type Repository interface {
|
||||
UpdateRuleStatus(ctx context.Context, r Rule) (Rule, error)
|
||||
ListRules(ctx context.Context, pm PageMeta) (Page, error)
|
||||
UpdateRuleDue(ctx context.Context, id string, due time.Time) (Rule, error)
|
||||
|
||||
AddReportConfig(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
|
||||
ViewReportConfig(ctx context.Context, id string) (ReportConfig, error)
|
||||
UpdateReportConfig(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
|
||||
UpdateReportSchedule(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
|
||||
RemoveReportConfig(ctx context.Context, id string) error
|
||||
UpdateReportConfigStatus(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
|
||||
ListReportsConfig(ctx context.Context, pm PageMeta) (ReportConfigPage, error)
|
||||
UpdateReportDue(ctx context.Context, id string, due time.Time) (ReportConfig, error)
|
||||
}
|
||||
|
||||
// PageMeta contains page metadata that helps navigation.
|
||||
type PageMeta struct {
|
||||
Total uint64 `json:"total" db:"total"`
|
||||
Offset uint64 `json:"offset" db:"offset"`
|
||||
Limit uint64 `json:"limit" db:"limit"`
|
||||
Dir string `json:"dir" db:"dir"`
|
||||
Name string `json:"name" db:"name"`
|
||||
InputChannel string `json:"input_channel,omitempty" db:"input_channel"`
|
||||
InputTopic *string `json:"input_topic,omitempty" db:"input_topic"`
|
||||
Scheduled *bool `json:"scheduled,omitempty"`
|
||||
OutputChannel string `json:"output_channel,omitempty" db:"output_channel"`
|
||||
Status Status `json:"status,omitempty" db:"status"`
|
||||
Domain string `json:"domain_id,omitempty" db:"domain_id"`
|
||||
ScheduledBefore *time.Time `json:"scheduled_before,omitempty" db:"scheduled_before"` // Filter rules scheduled before this time
|
||||
ScheduledAfter *time.Time `json:"scheduled_after,omitempty" db:"scheduled_after"` // Filter rules scheduled after this time
|
||||
Recurring *Recurring `json:"recurring,omitempty" db:"recurring"` // Filter by recurring type
|
||||
Total uint64 `json:"total" db:"total"`
|
||||
Offset uint64 `json:"offset" db:"offset"`
|
||||
Limit uint64 `json:"limit" db:"limit"`
|
||||
Dir string `json:"dir" db:"dir"`
|
||||
Name string `json:"name" db:"name"`
|
||||
InputChannel string `json:"input_channel,omitempty" db:"input_channel"`
|
||||
InputTopic *string `json:"input_topic,omitempty" db:"input_topic"`
|
||||
Scheduled *bool `json:"scheduled,omitempty"`
|
||||
OutputChannel string `json:"output_channel,omitempty" db:"output_channel"`
|
||||
Status Status `json:"status,omitempty" db:"status"`
|
||||
Domain string `json:"domain_id,omitempty" db:"domain_id"`
|
||||
ScheduledBefore *time.Time `json:"scheduled_before,omitempty" db:"scheduled_before"` // Filter rules scheduled before this time
|
||||
ScheduledAfter *time.Time `json:"scheduled_after,omitempty" db:"scheduled_after"` // Filter rules scheduled after this time
|
||||
Recurring *schedule.Recurring `json:"recurring,omitempty" db:"recurring"` // Filter by recurring type
|
||||
}
|
||||
|
||||
type Page struct {
|
||||
@@ -77,32 +66,22 @@ type Service interface {
|
||||
EnableRule(ctx context.Context, session authn.Session, id string) (Rule, error)
|
||||
DisableRule(ctx context.Context, session authn.Session, id string) (Rule, error)
|
||||
|
||||
AddReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error)
|
||||
ViewReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error)
|
||||
UpdateReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error)
|
||||
UpdateReportSchedule(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error)
|
||||
RemoveReportConfig(ctx context.Context, session authn.Session, id string) error
|
||||
ListReportsConfig(ctx context.Context, session authn.Session, pm PageMeta) (ReportConfigPage, error)
|
||||
EnableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error)
|
||||
DisableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error)
|
||||
|
||||
GenerateReport(ctx context.Context, session authn.Session, config ReportConfig, action ReportAction) (ReportPage, error)
|
||||
StartScheduler(ctx context.Context) error
|
||||
}
|
||||
|
||||
type re struct {
|
||||
repo Repository
|
||||
runInfo chan RunInfo
|
||||
runInfo chan pkglog.RunInfo
|
||||
idp supermq.IDProvider
|
||||
rePubSub messaging.PubSub
|
||||
writersPub messaging.Publisher
|
||||
alarmsPub messaging.Publisher
|
||||
ticker Ticker
|
||||
email Emailer
|
||||
ticker ticker.Ticker
|
||||
email emailer.Emailer
|
||||
readers grpcReadersV1.ReadersServiceClient
|
||||
}
|
||||
|
||||
func NewService(repo Repository, runInfo chan RunInfo, idp supermq.IDProvider, rePubSub messaging.PubSub, writersPub, alarmsPub messaging.Publisher, tck Ticker, emailer Emailer, readers grpcReadersV1.ReadersServiceClient) Service {
|
||||
func NewService(repo Repository, runInfo chan pkglog.RunInfo, idp supermq.IDProvider, rePubSub messaging.PubSub, writersPub, alarmsPub messaging.Publisher, tck ticker.Ticker, emailer emailer.Emailer, readers grpcReadersV1.ReadersServiceClient) Service {
|
||||
return &re{
|
||||
repo: repo,
|
||||
idp: idp,
|
||||
@@ -229,377 +208,3 @@ func (re *re) DisableRule(ctx context.Context, session authn.Session, id string)
|
||||
func (re *re) Cancel() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (re *re) AddReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error) {
|
||||
id, err := re.idp.ID()
|
||||
if err != nil {
|
||||
return ReportConfig{}, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
cfg.ID = id
|
||||
cfg.CreatedAt = now
|
||||
cfg.CreatedBy = session.UserID
|
||||
cfg.DomainID = session.DomainID
|
||||
cfg.Status = EnabledStatus
|
||||
|
||||
if cfg.Schedule.StartDateTime.IsZero() {
|
||||
cfg.Schedule.StartDateTime = now
|
||||
}
|
||||
cfg.Schedule.Time = cfg.Schedule.StartDateTime
|
||||
|
||||
reportConfig, err := re.repo.AddReportConfig(ctx, cfg)
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
return reportConfig, nil
|
||||
}
|
||||
|
||||
func (re *re) ViewReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error) {
|
||||
cfg, err := re.repo.ViewReportConfig(ctx, id)
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (re *re) UpdateReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error) {
|
||||
cfg.UpdatedAt = time.Now().UTC()
|
||||
cfg.UpdatedBy = session.UserID
|
||||
reportConfig, err := re.repo.UpdateReportConfig(ctx, cfg)
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
return reportConfig, nil
|
||||
}
|
||||
|
||||
func (re *re) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error) {
|
||||
cfg.UpdatedAt = time.Now().UTC()
|
||||
cfg.UpdatedBy = session.UserID
|
||||
cfg.Schedule.Time = cfg.Schedule.StartDateTime
|
||||
c, err := re.repo.UpdateReportSchedule(ctx, cfg)
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (re *re) RemoveReportConfig(ctx context.Context, session authn.Session, id string) error {
|
||||
if err := re.repo.RemoveReportConfig(ctx, id); err != nil {
|
||||
return errors.Wrap(svcerr.ErrRemoveEntity, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (re *re) ListReportsConfig(ctx context.Context, session authn.Session, pm PageMeta) (ReportConfigPage, error) {
|
||||
pm.Domain = session.DomainID
|
||||
page, err := re.repo.ListReportsConfig(ctx, pm)
|
||||
if err != nil {
|
||||
return ReportConfigPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
||||
}
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func (re *re) EnableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error) {
|
||||
status, err := ToStatus(Enabled)
|
||||
if err != nil {
|
||||
return ReportConfig{}, err
|
||||
}
|
||||
cfg := ReportConfig{
|
||||
ID: id,
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
UpdatedBy: session.UserID,
|
||||
Status: status,
|
||||
}
|
||||
cfg, err = re.repo.UpdateReportConfigStatus(ctx, cfg)
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (re *re) DisableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error) {
|
||||
status, err := ToStatus(Disabled)
|
||||
if err != nil {
|
||||
return ReportConfig{}, err
|
||||
}
|
||||
cfg := ReportConfig{
|
||||
ID: id,
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
UpdatedBy: session.UserID,
|
||||
Status: status,
|
||||
}
|
||||
cfg, err = re.repo.UpdateReportConfigStatus(ctx, cfg)
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (re *re) GenerateReport(ctx context.Context, session authn.Session, config ReportConfig, action ReportAction) (ReportPage, error) {
|
||||
config.DomainID = session.DomainID
|
||||
|
||||
if config.Status != EnabledStatus {
|
||||
return ReportPage{}, svcerr.ErrInvalidStatus
|
||||
}
|
||||
|
||||
reportPage, err := re.generateReport(ctx, config, action)
|
||||
if err != nil {
|
||||
return ReportPage{}, err
|
||||
}
|
||||
|
||||
return reportPage, nil
|
||||
}
|
||||
|
||||
func (re *re) generateReport(ctx context.Context, cfg ReportConfig, action ReportAction) (ReportPage, error) {
|
||||
genReportFile, err := generateFileFunc(action, cfg.Config.FileFormat)
|
||||
if err != nil {
|
||||
return ReportPage{}, err
|
||||
}
|
||||
|
||||
agg := grpcReadersV1.Aggregation_AGGREGATION_UNSPECIFIED
|
||||
switch cfg.Config.Aggregation.AggType {
|
||||
case AggregationMAX:
|
||||
agg = grpcReadersV1.Aggregation_MAX
|
||||
case AggregationMIN:
|
||||
agg = grpcReadersV1.Aggregation_MIN
|
||||
case AggregationCOUNT:
|
||||
agg = grpcReadersV1.Aggregation_COUNT
|
||||
case AggregationAVG:
|
||||
agg = grpcReadersV1.Aggregation_AVG
|
||||
case AggregationSUM:
|
||||
agg = grpcReadersV1.Aggregation_SUM
|
||||
}
|
||||
|
||||
from, err := reltime.Parse(cfg.Config.From)
|
||||
if err != nil {
|
||||
return ReportPage{}, err
|
||||
}
|
||||
to, err := reltime.Parse(cfg.Config.To)
|
||||
if err != nil {
|
||||
return ReportPage{}, err
|
||||
}
|
||||
pm := &grpcReadersV1.PageMetadata{
|
||||
Aggregation: agg,
|
||||
Limit: limit,
|
||||
From: float64(from.UnixMicro()),
|
||||
To: float64(to.UnixNano()),
|
||||
Interval: cfg.Config.Aggregation.Interval,
|
||||
}
|
||||
|
||||
var mets []Metric
|
||||
var reports []Report
|
||||
for _, metric := range cfg.Metrics {
|
||||
switch {
|
||||
case len(metric.ClientIDs) != 0:
|
||||
for _, clientID := range metric.ClientIDs {
|
||||
mets = append(mets, Metric{
|
||||
ChannelID: metric.ChannelID,
|
||||
ClientID: clientID,
|
||||
Name: metric.Name,
|
||||
Subtopic: metric.Subtopic,
|
||||
Protocol: metric.Protocol,
|
||||
Format: metric.Format,
|
||||
})
|
||||
}
|
||||
default:
|
||||
mets = append(mets, Metric{
|
||||
ChannelID: metric.ChannelID,
|
||||
Name: metric.Name,
|
||||
Subtopic: metric.Subtopic,
|
||||
Protocol: metric.Protocol,
|
||||
Format: metric.Format,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, metric := range mets {
|
||||
sMsgs := []senml.Message{}
|
||||
|
||||
pm.Offset = uint64(0)
|
||||
pm.Name = metric.Name
|
||||
if metric.ClientID != "" {
|
||||
pm.Publisher = metric.ClientID
|
||||
}
|
||||
if metric.Subtopic != "" {
|
||||
pm.Subtopic = metric.Subtopic
|
||||
}
|
||||
if metric.Protocol != "" {
|
||||
pm.Protocol = metric.Protocol
|
||||
}
|
||||
if metric.Format != "" {
|
||||
pm.Format = metric.Format
|
||||
}
|
||||
|
||||
msgs, err := re.readers.ReadMessages(ctx, &grpcReadersV1.ReadMessagesReq{
|
||||
ChannelId: metric.ChannelID,
|
||||
DomainId: cfg.DomainID,
|
||||
PageMetadata: pm,
|
||||
})
|
||||
if err != nil {
|
||||
return ReportPage{}, err
|
||||
}
|
||||
for _, msg := range msgs.Messages {
|
||||
sMsgs = append(sMsgs, convertToSenml(msg.GetSenml()))
|
||||
}
|
||||
|
||||
for msgs.GetTotal() > (pm.Offset + pm.Limit) {
|
||||
pm.Offset = pm.Offset + pm.Limit
|
||||
msgs, err := re.readers.ReadMessages(ctx, &grpcReadersV1.ReadMessagesReq{
|
||||
ChannelId: metric.ChannelID,
|
||||
DomainId: cfg.DomainID,
|
||||
PageMetadata: pm,
|
||||
})
|
||||
if err != nil {
|
||||
return ReportPage{}, err
|
||||
}
|
||||
for _, msg := range msgs.Messages {
|
||||
sMsgs = append(sMsgs, convertToSenml(msg.GetSenml()))
|
||||
}
|
||||
}
|
||||
|
||||
reports = append(reports, convertToReports(metric, sMsgs)...)
|
||||
}
|
||||
|
||||
switch {
|
||||
case genReportFile != nil:
|
||||
data, err := genReportFile(cfg.Config.Title, reports)
|
||||
if err != nil {
|
||||
return ReportPage{}, err
|
||||
}
|
||||
timeStr := strings.ReplaceAll(time.Now().Format(time.RFC3339), ":", "")
|
||||
filePrefix := cfg.Name
|
||||
if filePrefix == "" {
|
||||
filePrefix = "report"
|
||||
}
|
||||
fileName := fmt.Sprintf("%s_%s.%s", filePrefix, timeStr, cfg.Config.FileFormat.Extension())
|
||||
|
||||
file := ReportFile{
|
||||
Name: fileName,
|
||||
Data: data,
|
||||
Format: cfg.Config.FileFormat,
|
||||
}
|
||||
|
||||
switch action {
|
||||
case EmailReport:
|
||||
if err := re.emailReports(*cfg.Email, file); err != nil {
|
||||
return ReportPage{}, errors.Wrap(err, svcerr.ErrCreateEntity)
|
||||
}
|
||||
|
||||
return ReportPage{}, nil
|
||||
default:
|
||||
return ReportPage{
|
||||
File: file,
|
||||
}, nil
|
||||
}
|
||||
|
||||
default:
|
||||
return ReportPage{
|
||||
From: from,
|
||||
To: to,
|
||||
Aggregation: cfg.Config.Aggregation,
|
||||
Total: uint64(len(reports)),
|
||||
Reports: reports,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func generateFileFunc(action ReportAction, format Format) (func(string, []Report) ([]byte, error), error) {
|
||||
switch action {
|
||||
case DownloadReport, EmailReport:
|
||||
switch format {
|
||||
case PDF:
|
||||
return generatePDFReport, nil
|
||||
case CSV:
|
||||
return generateCSVReport, nil
|
||||
default:
|
||||
return nil, errors.New("file format not supported")
|
||||
}
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (re *re) emailReports(es EmailSetting, file ReportFile) error {
|
||||
if err := es.Validate(); err != nil {
|
||||
return errors.Wrap(svcerr.ErrMalformedEntity, err)
|
||||
}
|
||||
|
||||
attachments := map[string][]byte{
|
||||
file.Name: file.Data,
|
||||
}
|
||||
|
||||
if err := re.email.SendEmailNotification(
|
||||
es.To,
|
||||
"",
|
||||
es.Subject,
|
||||
"",
|
||||
"",
|
||||
es.Content,
|
||||
"",
|
||||
attachments,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertToSenml(g *grpcReadersV1.SenMLMessage) senml.Message {
|
||||
if g == nil {
|
||||
return senml.Message{}
|
||||
}
|
||||
return senml.Message{
|
||||
Protocol: g.Base.GetProtocol(),
|
||||
Subtopic: g.Base.GetSubtopic(),
|
||||
Publisher: g.Base.GetPublisher(),
|
||||
Channel: g.Base.GetChannel(),
|
||||
Name: g.GetName(),
|
||||
Unit: g.GetUnit(),
|
||||
Time: g.GetTime(),
|
||||
UpdateTime: g.GetUpdateTime(),
|
||||
Value: g.Value,
|
||||
StringValue: g.StringValue,
|
||||
DataValue: g.DataValue,
|
||||
BoolValue: g.BoolValue,
|
||||
Sum: g.Sum,
|
||||
}
|
||||
}
|
||||
|
||||
func convertToReports(metric Metric, senmlMsgs []senml.Message) []Report {
|
||||
if metric.ClientID != "" {
|
||||
return []Report{
|
||||
{
|
||||
Metric: metric,
|
||||
Messages: senmlMsgs,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return groupReportsByPublisher(metric, senmlMsgs)
|
||||
}
|
||||
|
||||
func groupReportsByPublisher(metric Metric, sMsgs []senml.Message) []Report {
|
||||
publishers := map[string][]senml.Message{}
|
||||
|
||||
for _, msg := range sMsgs {
|
||||
publishers[msg.Publisher] = append(publishers[msg.Publisher], msg)
|
||||
}
|
||||
|
||||
var groupedReports []Report
|
||||
for publisher, messages := range publishers {
|
||||
gMetric := metric
|
||||
gMetric.ClientID = publisher
|
||||
groupedReports = append(groupedReports, Report{
|
||||
Metric: gMetric,
|
||||
Messages: messages,
|
||||
})
|
||||
}
|
||||
|
||||
return groupedReports
|
||||
}
|
||||
|
||||
+30
-453
@@ -11,6 +11,8 @@ import (
|
||||
|
||||
"github.com/0x6flab/namegenerator"
|
||||
"github.com/absmach/magistrala/internal/testsutil"
|
||||
pkglog "github.com/absmach/magistrala/pkg/logger"
|
||||
pkgSch "github.com/absmach/magistrala/pkg/schedule"
|
||||
"github.com/absmach/magistrala/re"
|
||||
"github.com/absmach/magistrala/re/mocks"
|
||||
readmocks "github.com/absmach/magistrala/readers/mocks"
|
||||
@@ -32,26 +34,15 @@ var (
|
||||
ruleName = namegen.Generate()
|
||||
ruleID = testsutil.GenerateUUID(&testing.T{})
|
||||
inputChannel = "test.channel"
|
||||
schedule = re.Schedule{
|
||||
schedule = pkgSch.Schedule{
|
||||
StartDateTime: time.Now().Add(-time.Hour),
|
||||
Recurring: re.Daily,
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: time.Now().Add(-time.Hour),
|
||||
}
|
||||
reportName = namegen.Generate()
|
||||
rptConfig = re.ReportConfig{
|
||||
ID: testsutil.GenerateUUID(&testing.T{}),
|
||||
Name: reportName,
|
||||
DomainID: domainID,
|
||||
Status: re.EnabledStatus,
|
||||
Schedule: schedule,
|
||||
CreatedBy: userID,
|
||||
UpdatedBy: userID,
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
)
|
||||
|
||||
func newService(t *testing.T, runInfo chan re.RunInfo) (re.Service, *mocks.Repository, *pubsubmocks.PubSub, *mocks.Ticker) {
|
||||
func newService(t *testing.T, runInfo chan pkglog.RunInfo) (re.Service, *mocks.Repository, *pubsubmocks.PubSub, *mocks.Ticker) {
|
||||
repo := new(mocks.Repository)
|
||||
mockTicker := new(mocks.Ticker)
|
||||
idProvider := uuid.NewMock()
|
||||
@@ -62,7 +53,7 @@ func newService(t *testing.T, runInfo chan re.RunInfo) (re.Service, *mocks.Repos
|
||||
}
|
||||
|
||||
func TestAddRule(t *testing.T) {
|
||||
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
|
||||
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
|
||||
ruleName := namegen.Generate()
|
||||
now := time.Now().Add(time.Hour)
|
||||
cases := []struct {
|
||||
@@ -81,8 +72,8 @@ func TestAddRule(t *testing.T) {
|
||||
rule: re.Rule{
|
||||
Name: ruleName,
|
||||
InputChannel: inputChannel,
|
||||
Schedule: re.Schedule{
|
||||
Recurring: re.Daily,
|
||||
Schedule: pkgSch.Schedule{
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
},
|
||||
@@ -91,8 +82,8 @@ func TestAddRule(t *testing.T) {
|
||||
Name: ruleName,
|
||||
ID: ruleID,
|
||||
InputChannel: inputChannel,
|
||||
Schedule: re.Schedule{
|
||||
Recurring: re.Daily,
|
||||
Schedule: pkgSch.Schedule{
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
},
|
||||
@@ -111,8 +102,8 @@ func TestAddRule(t *testing.T) {
|
||||
rule: re.Rule{
|
||||
Name: ruleName,
|
||||
InputChannel: inputChannel,
|
||||
Schedule: re.Schedule{
|
||||
Recurring: re.Daily,
|
||||
Schedule: pkgSch.Schedule{
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
},
|
||||
@@ -137,7 +128,7 @@ func TestAddRule(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestViewRule(t *testing.T) {
|
||||
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
|
||||
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
|
||||
|
||||
now := time.Now().Add(time.Hour)
|
||||
cases := []struct {
|
||||
@@ -158,8 +149,8 @@ func TestViewRule(t *testing.T) {
|
||||
Name: ruleName,
|
||||
ID: ruleID,
|
||||
InputChannel: inputChannel,
|
||||
Schedule: re.Schedule{
|
||||
Recurring: re.Daily,
|
||||
Schedule: pkgSch.Schedule{
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
},
|
||||
@@ -195,7 +186,7 @@ func TestViewRule(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUpdateRule(t *testing.T) {
|
||||
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
|
||||
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
|
||||
|
||||
newName := namegen.Generate()
|
||||
now := time.Now().Add(time.Hour)
|
||||
@@ -216,8 +207,8 @@ func TestUpdateRule(t *testing.T) {
|
||||
Name: newName,
|
||||
ID: ruleID,
|
||||
InputChannel: inputChannel,
|
||||
Schedule: re.Schedule{
|
||||
Recurring: re.Daily,
|
||||
Schedule: pkgSch.Schedule{
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
},
|
||||
@@ -229,8 +220,8 @@ func TestUpdateRule(t *testing.T) {
|
||||
Name: newName,
|
||||
ID: ruleID,
|
||||
InputChannel: inputChannel,
|
||||
Schedule: re.Schedule{
|
||||
Recurring: re.Daily,
|
||||
Schedule: pkgSch.Schedule{
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
},
|
||||
@@ -252,8 +243,8 @@ func TestUpdateRule(t *testing.T) {
|
||||
Name: ruleName,
|
||||
ID: ruleID,
|
||||
InputChannel: inputChannel,
|
||||
Schedule: re.Schedule{
|
||||
Recurring: re.Daily,
|
||||
Schedule: pkgSch.Schedule{
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
},
|
||||
@@ -280,7 +271,7 @@ func TestUpdateRule(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestListRules(t *testing.T) {
|
||||
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
|
||||
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
|
||||
numRules := 50
|
||||
now := time.Now().Add(time.Hour)
|
||||
var rules []re.Rule
|
||||
@@ -292,8 +283,8 @@ func TestListRules(t *testing.T) {
|
||||
Status: re.EnabledStatus,
|
||||
CreatedAt: now,
|
||||
CreatedBy: userID,
|
||||
Schedule: re.Schedule{
|
||||
Recurring: re.Daily,
|
||||
Schedule: pkgSch.Schedule{
|
||||
Recurring: pkgSch.Daily,
|
||||
Time: now.Add(1 * time.Hour),
|
||||
RecurringPeriod: 1,
|
||||
StartDateTime: now.Add(-1 * time.Hour),
|
||||
@@ -385,7 +376,7 @@ func TestListRules(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRemoveRule(t *testing.T) {
|
||||
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
|
||||
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
@@ -425,7 +416,7 @@ func TestRemoveRule(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnableRule(t *testing.T) {
|
||||
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
|
||||
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
|
||||
|
||||
now := time.Now()
|
||||
|
||||
@@ -484,7 +475,7 @@ func TestEnableRule(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDisableRule(t *testing.T) {
|
||||
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
|
||||
svc, repo, _, _ := newService(t, make(chan pkglog.RunInfo))
|
||||
|
||||
now := time.Now()
|
||||
|
||||
@@ -543,7 +534,7 @@ func TestDisableRule(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHandle(t *testing.T) {
|
||||
svc, repo, pubmocks, _ := newService(t, make(chan re.RunInfo))
|
||||
svc, repo, pubmocks, _ := newService(t, make(chan pkglog.RunInfo))
|
||||
now := time.Now()
|
||||
scheduled := false
|
||||
cases := []struct {
|
||||
@@ -600,7 +591,6 @@ func TestHandle(t *testing.T) {
|
||||
}
|
||||
})
|
||||
repoCall1 := pubmocks.On("Publish", mock.Anything, mock.Anything, mock.Anything).Return(tc.publishErr)
|
||||
repoCall2 := repo.On("ListReportsConfig", mock.Anything, mock.Anything).Return(re.ReportConfigPage{}, nil)
|
||||
|
||||
err = svc.Handle(tc.message)
|
||||
assert.Nil(t, err)
|
||||
@@ -609,424 +599,13 @@ func TestHandle(t *testing.T) {
|
||||
|
||||
repoCall.Unset()
|
||||
repoCall1.Unset()
|
||||
repoCall2.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddReportConfig(t *testing.T) {
|
||||
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
session authn.Session
|
||||
cfg re.ReportConfig
|
||||
res re.ReportConfig
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "Add report config successfully",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
cfg: re.ReportConfig{
|
||||
Name: reportName,
|
||||
Schedule: schedule,
|
||||
},
|
||||
res: rptConfig,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "Add report config with failed repo",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
cfg: re.ReportConfig{
|
||||
Name: reportName,
|
||||
Schedule: schedule,
|
||||
},
|
||||
err: repoerr.ErrCreateEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("AddReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
|
||||
res, err := svc.AddReportConfig(context.Background(), tc.session, tc.cfg)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.NotEmpty(t, res.ID, "expected non-empty result in ID")
|
||||
assert.Equal(t, tc.cfg.Name, res.Name)
|
||||
assert.Equal(t, tc.cfg.Schedule, res.Schedule)
|
||||
}
|
||||
defer repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewReportConfig(t *testing.T) {
|
||||
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
session authn.Session
|
||||
id string
|
||||
res re.ReportConfig
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "view report config successfully",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
res: rptConfig,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "view report config with failed repo",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
err: svcerr.ErrViewEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("ViewReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
|
||||
res, err := svc.ViewReportConfig(context.Background(), tc.session, tc.id)
|
||||
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.res, res)
|
||||
}
|
||||
defer repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateReportConfig(t *testing.T) {
|
||||
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
|
||||
|
||||
newName := namegen.Generate()
|
||||
now := time.Now().Add(time.Hour)
|
||||
cases := []struct {
|
||||
desc string
|
||||
session authn.Session
|
||||
cfg re.ReportConfig
|
||||
res re.ReportConfig
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update report config successfully",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
cfg: re.ReportConfig{
|
||||
Name: newName,
|
||||
ID: rptConfig.ID,
|
||||
Schedule: schedule,
|
||||
},
|
||||
res: re.ReportConfig{
|
||||
Name: newName,
|
||||
ID: rptConfig.ID,
|
||||
DomainID: rptConfig.DomainID,
|
||||
Status: rptConfig.Status,
|
||||
Schedule: rptConfig.Schedule,
|
||||
UpdatedAt: now,
|
||||
UpdatedBy: userID,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update report config with failed repo",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
cfg: re.ReportConfig{
|
||||
Name: rptConfig.Name,
|
||||
ID: rptConfig.ID,
|
||||
Schedule: schedule,
|
||||
},
|
||||
err: svcerr.ErrUpdateEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("UpdateReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
|
||||
res, err := svc.UpdateReportConfig(context.Background(), tc.session, tc.cfg)
|
||||
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.res, res)
|
||||
}
|
||||
defer repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListReportsConfig(t *testing.T) {
|
||||
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
|
||||
numConfigs := 50
|
||||
now := time.Now().Add(time.Hour)
|
||||
var configs []re.ReportConfig
|
||||
for i := 0; i < numConfigs; i++ {
|
||||
c := re.ReportConfig{
|
||||
ID: testsutil.GenerateUUID(t),
|
||||
Name: namegen.Generate(),
|
||||
DomainID: domainID,
|
||||
Status: re.EnabledStatus,
|
||||
CreatedAt: now,
|
||||
CreatedBy: userID,
|
||||
Schedule: schedule,
|
||||
}
|
||||
configs = append(configs, c)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
session authn.Session
|
||||
pageMeta re.PageMeta
|
||||
res re.ReportConfigPage
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "list report configs successfully",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
pageMeta: re.PageMeta{},
|
||||
res: re.ReportConfigPage{
|
||||
PageMeta: re.PageMeta{
|
||||
Total: uint64(numConfigs),
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
},
|
||||
ReportConfigs: configs[0:10],
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list report configs successfully with limit",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
pageMeta: re.PageMeta{
|
||||
Limit: 100,
|
||||
},
|
||||
res: re.ReportConfigPage{
|
||||
PageMeta: re.PageMeta{
|
||||
Total: uint64(numConfigs),
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
},
|
||||
ReportConfigs: configs[0:numConfigs],
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list report configs successfully with offset",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
pageMeta: re.PageMeta{
|
||||
Offset: 20,
|
||||
Limit: 10,
|
||||
},
|
||||
res: re.ReportConfigPage{
|
||||
PageMeta: re.PageMeta{
|
||||
Total: uint64(numConfigs),
|
||||
Offset: 20,
|
||||
Limit: 10,
|
||||
},
|
||||
ReportConfigs: configs[20:30],
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list report configs with failed repo",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
pageMeta: re.PageMeta{},
|
||||
err: svcerr.ErrViewEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("ListReportsConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
|
||||
res, err := svc.ListReportsConfig(context.Background(), tc.session, tc.pageMeta)
|
||||
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.res, res)
|
||||
}
|
||||
defer repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveReportConfig(t *testing.T) {
|
||||
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
session authn.Session
|
||||
id string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "remove report config successfully",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "remove report config with failed repo",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
err: svcerr.ErrRemoveEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("RemoveReportConfig", mock.Anything, mock.Anything).Return(tc.err)
|
||||
err := svc.RemoveReportConfig(context.Background(), tc.session, tc.id)
|
||||
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
defer repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnableReportConfig(t *testing.T) {
|
||||
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
session authn.Session
|
||||
id string
|
||||
status re.Status
|
||||
res re.ReportConfig
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "enable report config successfully",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
status: re.EnabledStatus,
|
||||
res: rptConfig,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "enable report config with failed repo",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
status: re.EnabledStatus,
|
||||
err: svcerr.ErrUpdateEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("UpdateReportConfigStatus", context.Background(), mock.Anything).Return(tc.res, tc.err)
|
||||
res, err := svc.EnableReportConfig(context.Background(), tc.session, tc.id)
|
||||
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.res, res)
|
||||
}
|
||||
defer repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisableReportConfig(t *testing.T) {
|
||||
svc, repo, _, _ := newService(t, make(chan re.RunInfo))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
session authn.Session
|
||||
id string
|
||||
status re.Status
|
||||
res re.ReportConfig
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "disable report config successfully",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
status: re.DisabledStatus,
|
||||
res: re.ReportConfig{
|
||||
ID: rptConfig.ID,
|
||||
Name: rptConfig.Name,
|
||||
DomainID: rptConfig.DomainID,
|
||||
Status: re.DisabledStatus,
|
||||
Schedule: schedule,
|
||||
UpdatedBy: userID,
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "disable report config with failed repo",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
status: re.DisabledStatus,
|
||||
err: svcerr.ErrUpdateEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("UpdateReportConfigStatus", mock.Anything, mock.Anything).Return(tc.res, tc.err)
|
||||
res, err := svc.DisableReportConfig(context.Background(), tc.session, tc.id)
|
||||
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.res, res)
|
||||
}
|
||||
defer repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartScheduler(t *testing.T) {
|
||||
now := time.Now().Truncate(time.Minute)
|
||||
ri := make(chan re.RunInfo)
|
||||
ri := make(chan pkglog.RunInfo)
|
||||
svc, repo, _, ticker := newService(t, ri)
|
||||
|
||||
ctxCases := []struct {
|
||||
@@ -1078,7 +657,6 @@ func TestStartScheduler(t *testing.T) {
|
||||
for _, tc := range ctxCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("ListRules", mock.Anything, mock.Anything).Return(tc.page, tc.listErr)
|
||||
repoCall1 := repo.On("ListReportsConfig", mock.Anything, mock.Anything).Return(re.ReportConfigPage{}, nil)
|
||||
tickChan := make(chan time.Time)
|
||||
tickCall := ticker.On("Tick").Return((<-chan time.Time)(tickChan))
|
||||
tickCall1 := ticker.On("Stop").Return()
|
||||
@@ -1093,7 +671,6 @@ func TestStartScheduler(t *testing.T) {
|
||||
err := <-errc
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("expected error %v but got %v", tc.err, err))
|
||||
repoCall.Unset()
|
||||
repoCall1.Unset()
|
||||
tickCall.Unset()
|
||||
tickCall1.Unset()
|
||||
})
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package api contains API-related concerns: endpoint definitions, middlewares
|
||||
// and all resource representations.
|
||||
package api
|
||||
@@ -0,0 +1,238 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/reports"
|
||||
api "github.com/absmach/supermq/api/http"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
)
|
||||
|
||||
func generateReportEndpoint(svc reports.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(generateReportReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return generateReportResp{}, err
|
||||
}
|
||||
|
||||
res, err := svc.GenerateReport(ctx, session, reports.ReportConfig{
|
||||
Name: req.Name,
|
||||
DomainID: req.DomainID,
|
||||
Config: req.Config,
|
||||
Metrics: req.Metrics,
|
||||
Email: req.Email,
|
||||
}, req.action)
|
||||
if err != nil {
|
||||
return generateReportResp{}, err
|
||||
}
|
||||
|
||||
switch req.action {
|
||||
case reports.DownloadReport:
|
||||
return downloadReportResp{
|
||||
File: res.File,
|
||||
}, nil
|
||||
case reports.EmailReport:
|
||||
return emailReportResp{}, nil
|
||||
default:
|
||||
return generateReportResp{
|
||||
Total: res.Total,
|
||||
From: res.From,
|
||||
To: res.To,
|
||||
Aggregation: res.Aggregation,
|
||||
Reports: res.Reports,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func listReportsConfigEndpoint(svc reports.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(listReportsConfigReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return listReportsConfigRes{}, err
|
||||
}
|
||||
|
||||
page, err := svc.ListReportsConfig(ctx, session, req.PageMeta)
|
||||
if err != nil {
|
||||
return listReportsConfigRes{}, err
|
||||
}
|
||||
|
||||
return listReportsConfigRes{
|
||||
pageRes: pageRes{
|
||||
Limit: page.Limit,
|
||||
Offset: page.Offset,
|
||||
Total: page.Total,
|
||||
},
|
||||
ReportConfigs: page.ReportConfigs,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func deleteReportConfigEndpoint(svc reports.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(deleteReportConfigReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return deleteReportConfigRes{}, err
|
||||
}
|
||||
|
||||
err := svc.RemoveReportConfig(ctx, session, req.ID)
|
||||
if err != nil {
|
||||
return deleteReportConfigRes{false}, err
|
||||
}
|
||||
|
||||
return deleteReportConfigRes{true}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func updateReportConfigEndpoint(svc reports.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(updateReportConfigReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return updateReportConfigRes{}, err
|
||||
}
|
||||
|
||||
cfg, err := svc.UpdateReportConfig(ctx, session, req.ReportConfig)
|
||||
if err != nil {
|
||||
return updateReportConfigRes{}, err
|
||||
}
|
||||
|
||||
return updateReportConfigRes{ReportConfig: cfg}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func updateReportScheduleEndpoint(s reports.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(updateReportScheduleReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return updateReportConfigRes{}, err
|
||||
}
|
||||
|
||||
rpt := reports.ReportConfig{
|
||||
ID: req.id,
|
||||
Schedule: req.Schedule,
|
||||
}
|
||||
|
||||
updatedReport, err := s.UpdateReportSchedule(ctx, session, rpt)
|
||||
if err != nil {
|
||||
return updateReportConfigRes{}, err
|
||||
}
|
||||
return updateReportConfigRes{ReportConfig: updatedReport}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func viewReportConfigEndpoint(svc reports.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(viewReportConfigReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return viewReportConfigRes{}, err
|
||||
}
|
||||
|
||||
cfg, err := svc.ViewReportConfig(ctx, session, req.ID)
|
||||
if err != nil {
|
||||
return viewReportConfigRes{}, err
|
||||
}
|
||||
|
||||
return viewReportConfigRes{ReportConfig: cfg}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func addReportConfigEndpoint(svc reports.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(addReportConfigReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return addReportConfigRes{}, err
|
||||
}
|
||||
|
||||
cfg, err := svc.AddReportConfig(ctx, session, req.ReportConfig)
|
||||
if err != nil {
|
||||
return addReportConfigRes{}, err
|
||||
}
|
||||
|
||||
return addReportConfigRes{
|
||||
ReportConfig: cfg,
|
||||
created: true,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func enableReportConfigEndpoint(svc reports.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(updateReportStatusReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return updateReportConfigRes{}, err
|
||||
}
|
||||
|
||||
cfg, err := svc.EnableReportConfig(ctx, session, req.id)
|
||||
if err != nil {
|
||||
return updateReportConfigRes{}, err
|
||||
}
|
||||
|
||||
return updateReportConfigRes{ReportConfig: cfg}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func disableReportConfigEndpoint(svc reports.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
session, ok := ctx.Value(api.SessionKey).(authn.Session)
|
||||
if !ok {
|
||||
return nil, svcerr.ErrAuthorization
|
||||
}
|
||||
|
||||
req := request.(updateReportStatusReq)
|
||||
if err := req.validate(); err != nil {
|
||||
return updateReportConfigRes{}, err
|
||||
}
|
||||
|
||||
cfg, err := svc.DisableReportConfig(ctx, session, req.id)
|
||||
if err != nil {
|
||||
return updateReportConfigRes{}, err
|
||||
}
|
||||
|
||||
return updateReportConfigRes{ReportConfig: cfg}, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,813 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/0x6flab/namegenerator"
|
||||
"github.com/absmach/magistrala/internal/testsutil"
|
||||
pkgSch "github.com/absmach/magistrala/pkg/schedule"
|
||||
"github.com/absmach/magistrala/reports"
|
||||
"github.com/absmach/magistrala/reports/api"
|
||||
"github.com/absmach/magistrala/reports/mocks"
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
"github.com/absmach/supermq/auth"
|
||||
smqlog "github.com/absmach/supermq/logger"
|
||||
smqauthn "github.com/absmach/supermq/pkg/authn"
|
||||
authnmocks "github.com/absmach/supermq/pkg/authn/mocks"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
const contentType = "application/json"
|
||||
|
||||
var (
|
||||
namegen = namegenerator.NewGenerator()
|
||||
domainID = testsutil.GenerateUUID(&testing.T{})
|
||||
userID = testsutil.GenerateUUID(&testing.T{})
|
||||
validID = testsutil.GenerateUUID(&testing.T{})
|
||||
validToken = "valid"
|
||||
invalidToken = "invalid"
|
||||
now = time.Now().UTC().Truncate(time.Minute)
|
||||
schedule = pkgSch.Schedule{
|
||||
StartDateTime: now.Add(-1 * time.Hour),
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: now,
|
||||
}
|
||||
reportConfig = reports.ReportConfig{
|
||||
ID: validID,
|
||||
Name: namegen.Generate(),
|
||||
DomainID: domainID,
|
||||
Schedule: schedule,
|
||||
Status: reports.EnabledStatus,
|
||||
Metrics: []reports.ReqMetric{
|
||||
{
|
||||
ChannelID: "channel1",
|
||||
ClientIDs: []string{"client1"},
|
||||
Name: "metric_name",
|
||||
},
|
||||
},
|
||||
Config: &reports.MetricConfig{
|
||||
From: "now()-1h",
|
||||
To: "now()",
|
||||
Title: title,
|
||||
Aggregation: reports.AggConfig{AggType: reports.AggregationAVG, Interval: "1h"},
|
||||
},
|
||||
Email: &reports.EmailSetting{
|
||||
To: []string{"test@example.com"},
|
||||
Subject: "Test Report",
|
||||
},
|
||||
}
|
||||
title = "test_title"
|
||||
)
|
||||
|
||||
type testRequest struct {
|
||||
client *http.Client
|
||||
method string
|
||||
url string
|
||||
contentType string
|
||||
token string
|
||||
body io.Reader
|
||||
}
|
||||
|
||||
func (tr testRequest) make() (*http.Response, error) {
|
||||
req, err := http.NewRequest(tr.method, tr.url, tr.body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tr.token != "" {
|
||||
req.Header.Set("Authorization", apiutil.BearerPrefix+tr.token)
|
||||
}
|
||||
|
||||
if tr.contentType != "" {
|
||||
req.Header.Set("Content-Type", tr.contentType)
|
||||
}
|
||||
|
||||
req.Header.Set("Referer", "http://localhost")
|
||||
|
||||
return tr.client.Do(req)
|
||||
}
|
||||
|
||||
func newReportsServer() (*httptest.Server, *mocks.Service, *authnmocks.Authentication) {
|
||||
svc := new(mocks.Service)
|
||||
authn := new(authnmocks.Authentication)
|
||||
|
||||
logger := smqlog.NewMock()
|
||||
mux := chi.NewRouter()
|
||||
api.MakeHandler(svc, authn, mux, logger, "")
|
||||
|
||||
return httptest.NewServer(mux), svc, authn
|
||||
}
|
||||
|
||||
func toJSON(data any) string {
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(jsonData)
|
||||
}
|
||||
|
||||
func TestAddReportConfigEndpoint(t *testing.T) {
|
||||
ts, svc, authn := newReportsServer()
|
||||
defer ts.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
cfg reports.ReportConfig
|
||||
domainID string
|
||||
token string
|
||||
contentType string
|
||||
status int
|
||||
authnRes smqauthn.Session
|
||||
authnErr error
|
||||
svcRes reports.ReportConfig
|
||||
svcErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "add report config successfully",
|
||||
cfg: reportConfig,
|
||||
token: validToken,
|
||||
contentType: contentType,
|
||||
domainID: domainID,
|
||||
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
|
||||
status: http.StatusCreated,
|
||||
svcRes: reportConfig,
|
||||
},
|
||||
{
|
||||
desc: "add report config with invalid token",
|
||||
cfg: reportConfig,
|
||||
token: invalidToken,
|
||||
authnRes: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
contentType: contentType,
|
||||
authnErr: svcerr.ErrAuthentication,
|
||||
status: http.StatusUnauthorized,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "add report config with empty token",
|
||||
token: "",
|
||||
authnRes: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
cfg: reportConfig,
|
||||
contentType: contentType,
|
||||
status: http.StatusUnauthorized,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "add report config with empty domainID",
|
||||
token: validToken,
|
||||
cfg: reportConfig,
|
||||
contentType: contentType,
|
||||
status: http.StatusBadRequest,
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
{
|
||||
desc: "add report config with invalid content type",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
cfg: reportConfig,
|
||||
contentType: "application/xml",
|
||||
status: http.StatusUnsupportedMediaType,
|
||||
err: apiutil.ErrUnsupportedContentType,
|
||||
},
|
||||
{
|
||||
desc: "add report config with service error",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
|
||||
cfg: reportConfig,
|
||||
contentType: contentType,
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
status: http.StatusForbidden,
|
||||
err: svcerr.ErrAuthorization,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
data := toJSON(tc.cfg)
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodPost,
|
||||
url: fmt.Sprintf("%s/%s/reports/configs", ts.URL, tc.domainID),
|
||||
contentType: tc.contentType,
|
||||
token: tc.token,
|
||||
body: strings.NewReader(data),
|
||||
}
|
||||
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
|
||||
svcCall := svc.On("AddReportConfig", mock.Anything, tc.authnRes, mock.Anything).Return(tc.svcRes, tc.svcErr)
|
||||
res, err := req.make()
|
||||
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var errRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&errRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if errRes.Err != "" || errRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewReportConfigEndpoint(t *testing.T) {
|
||||
ts, svc, authn := newReportsServer()
|
||||
defer ts.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
id string
|
||||
domainID string
|
||||
token string
|
||||
contentType string
|
||||
status int
|
||||
authnRes smqauthn.Session
|
||||
authnErr error
|
||||
svcRes reports.ReportConfig
|
||||
svcErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "view report config successfully",
|
||||
id: validID,
|
||||
token: validToken,
|
||||
contentType: contentType,
|
||||
domainID: domainID,
|
||||
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
|
||||
status: http.StatusOK,
|
||||
svcRes: reportConfig,
|
||||
},
|
||||
{
|
||||
desc: "view report config with invalid token",
|
||||
id: validID,
|
||||
token: invalidToken,
|
||||
authnRes: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
contentType: contentType,
|
||||
authnErr: svcerr.ErrAuthentication,
|
||||
status: http.StatusUnauthorized,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "view report config with empty token",
|
||||
token: "",
|
||||
authnRes: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
contentType: contentType,
|
||||
status: http.StatusUnauthorized,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "view report config with empty domainID",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
contentType: contentType,
|
||||
status: http.StatusBadRequest,
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
{
|
||||
desc: "view report config with service error",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
authnRes: smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID},
|
||||
id: validID,
|
||||
contentType: contentType,
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
status: http.StatusForbidden,
|
||||
err: svcerr.ErrAuthorization,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("%s/%s/reports/configs/%s", ts.URL, tc.domainID, tc.id),
|
||||
contentType: tc.contentType,
|
||||
token: tc.token,
|
||||
}
|
||||
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.authnRes, tc.authnErr)
|
||||
svcCall := svc.On("ViewReportConfig", mock.Anything, tc.authnRes, tc.id).Return(tc.svcRes, tc.svcErr)
|
||||
res, err := req.make()
|
||||
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var errRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&errRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if errRes.Err != "" || errRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListReportsConfigEndpoint(t *testing.T) {
|
||||
ts, svc, authn := newReportsServer()
|
||||
defer ts.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
query string
|
||||
domainID string
|
||||
token string
|
||||
session smqauthn.Session
|
||||
listReportsResponse reports.ReportConfigPage
|
||||
status int
|
||||
authnErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "list reports config successfully",
|
||||
domainID: domainID,
|
||||
token: validToken,
|
||||
status: http.StatusOK,
|
||||
listReportsResponse: reports.ReportConfigPage{
|
||||
ReportConfigs: []reports.ReportConfig{reportConfig},
|
||||
PageMeta: reports.PageMeta{Total: 1},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list reports config with empty token",
|
||||
domainID: domainID,
|
||||
token: "",
|
||||
status: http.StatusUnauthorized,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "list reports config with invalid token",
|
||||
domainID: domainID,
|
||||
token: invalidToken,
|
||||
status: http.StatusUnauthorized,
|
||||
authnErr: svcerr.ErrAuthentication,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodGet,
|
||||
url: ts.URL + "/" + tc.domainID + "/reports/configs?" + tc.query,
|
||||
contentType: contentType,
|
||||
token: tc.token,
|
||||
}
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
|
||||
}
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
|
||||
svcCall := svc.On("ListReportsConfig", mock.Anything, tc.session, mock.Anything).Return(tc.listReportsResponse, tc.err)
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var bodyRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&bodyRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if bodyRes.Err != "" || bodyRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(bodyRes.Err), errors.New(bodyRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateReportConfigEndpoint(t *testing.T) {
|
||||
ts, svc, authn := newReportsServer()
|
||||
defer ts.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
id string
|
||||
domainID string
|
||||
updateReq reports.ReportConfig
|
||||
contentType string
|
||||
session smqauthn.Session
|
||||
svcResp reports.ReportConfig
|
||||
svcErr error
|
||||
status int
|
||||
authnErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update report config successfully",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
updateReq: reportConfig,
|
||||
contentType: contentType,
|
||||
svcResp: reportConfig,
|
||||
status: http.StatusOK,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update report config with invalid token",
|
||||
token: invalidToken,
|
||||
session: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
updateReq: reportConfig,
|
||||
contentType: contentType,
|
||||
authnErr: svcerr.ErrAuthentication,
|
||||
status: http.StatusUnauthorized,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "update report config with empty token",
|
||||
token: "",
|
||||
session: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
updateReq: reportConfig,
|
||||
contentType: contentType,
|
||||
status: http.StatusUnauthorized,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "update report config with empty domainID",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
updateReq: reportConfig,
|
||||
contentType: contentType,
|
||||
status: http.StatusBadRequest,
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
{
|
||||
desc: "update report config with invalid content type",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
domainID: domainID,
|
||||
updateReq: reportConfig,
|
||||
contentType: "application/xml",
|
||||
svcResp: reportConfig,
|
||||
status: http.StatusUnsupportedMediaType,
|
||||
err: apiutil.ErrUnsupportedContentType,
|
||||
},
|
||||
{
|
||||
desc: "update report config with service error",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
domainID: domainID,
|
||||
updateReq: reportConfig,
|
||||
contentType: contentType,
|
||||
svcResp: reports.ReportConfig{},
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
status: http.StatusForbidden,
|
||||
err: svcerr.ErrAuthorization,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
data := toJSON(tc.updateReq)
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodPatch,
|
||||
url: fmt.Sprintf("%s/%s/reports/configs/%s", ts.URL, tc.domainID, tc.id),
|
||||
contentType: tc.contentType,
|
||||
token: tc.token,
|
||||
body: strings.NewReader(data),
|
||||
}
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
|
||||
}
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
|
||||
svcCall := svc.On("UpdateReportConfig", mock.Anything, tc.session, mock.Anything).Return(tc.svcResp, tc.svcErr)
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var errRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&errRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if errRes.Err != "" || errRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteReportConfigEndpoint(t *testing.T) {
|
||||
ts, svc, authn := newReportsServer()
|
||||
defer ts.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
id string
|
||||
domainID string
|
||||
session smqauthn.Session
|
||||
svcErr error
|
||||
status int
|
||||
authnErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "delete report config successfully",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
svcErr: nil,
|
||||
status: http.StatusNoContent,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "delete report config with invalid token",
|
||||
token: invalidToken,
|
||||
session: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
authnErr: svcerr.ErrAuthentication,
|
||||
status: http.StatusUnauthorized,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "delete report config with empty token",
|
||||
token: "",
|
||||
session: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
status: http.StatusUnauthorized,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "delete report config with empty domainID",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
status: http.StatusBadRequest,
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
{
|
||||
desc: "delete report config with service error",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
domainID: domainID,
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
status: http.StatusForbidden,
|
||||
err: svcerr.ErrAuthorization,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodDelete,
|
||||
url: fmt.Sprintf("%s/%s/reports/configs/%s", ts.URL, tc.domainID, tc.id),
|
||||
token: tc.token,
|
||||
}
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
|
||||
}
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
|
||||
svcCall := svc.On("RemoveReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcErr)
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnableReportConfigEndpoint(t *testing.T) {
|
||||
ts, svc, authn := newReportsServer()
|
||||
defer ts.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
id string
|
||||
domainID string
|
||||
session smqauthn.Session
|
||||
svcResp reports.ReportConfig
|
||||
svcErr error
|
||||
status int
|
||||
authnErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "enable report config successfully",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
svcResp: reportConfig,
|
||||
svcErr: nil,
|
||||
status: http.StatusOK,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "enable report config with invalid token",
|
||||
token: invalidToken,
|
||||
session: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
authnErr: svcerr.ErrAuthentication,
|
||||
status: http.StatusUnauthorized,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "enable report config with empty token",
|
||||
token: "",
|
||||
session: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
status: http.StatusUnauthorized,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "enable report config with empty domainID",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
status: http.StatusBadRequest,
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
{
|
||||
desc: "enable report config with service error",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
domainID: domainID,
|
||||
svcResp: reports.ReportConfig{},
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
status: http.StatusForbidden,
|
||||
err: svcerr.ErrAuthorization,
|
||||
},
|
||||
{
|
||||
desc: "enable report config with empty id",
|
||||
token: validToken,
|
||||
id: "",
|
||||
domainID: domainID,
|
||||
status: http.StatusBadRequest,
|
||||
err: apiutil.ErrMissingID,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodPost,
|
||||
url: fmt.Sprintf("%s/%s/reports/configs/%s/enable", ts.URL, tc.domainID, tc.id),
|
||||
token: tc.token,
|
||||
}
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
|
||||
}
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
|
||||
svcCall := svc.On("EnableReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcResp, tc.svcErr)
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var errRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&errRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if errRes.Err != "" || errRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisableReportConfigEndpoint(t *testing.T) {
|
||||
ts, svc, authn := newReportsServer()
|
||||
defer ts.Close()
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
token string
|
||||
id string
|
||||
domainID string
|
||||
session smqauthn.Session
|
||||
svcResp reports.ReportConfig
|
||||
svcErr error
|
||||
status int
|
||||
authnErr error
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "disable report config successfully",
|
||||
token: validToken,
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
svcResp: reportConfig,
|
||||
svcErr: nil,
|
||||
status: http.StatusOK,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "disable report config with invalid token",
|
||||
token: invalidToken,
|
||||
session: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
authnErr: svcerr.ErrAuthentication,
|
||||
status: http.StatusUnauthorized,
|
||||
err: svcerr.ErrAuthentication,
|
||||
},
|
||||
{
|
||||
desc: "disable report config with empty token",
|
||||
token: "",
|
||||
session: smqauthn.Session{},
|
||||
domainID: domainID,
|
||||
id: validID,
|
||||
status: http.StatusUnauthorized,
|
||||
err: apiutil.ErrBearerToken,
|
||||
},
|
||||
{
|
||||
desc: "disable report config with empty domainID",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
status: http.StatusBadRequest,
|
||||
err: apiutil.ErrMissingDomainID,
|
||||
},
|
||||
{
|
||||
desc: "disable report config with service error",
|
||||
token: validToken,
|
||||
id: validID,
|
||||
domainID: domainID,
|
||||
svcResp: reports.ReportConfig{},
|
||||
svcErr: svcerr.ErrAuthorization,
|
||||
status: http.StatusForbidden,
|
||||
err: svcerr.ErrAuthorization,
|
||||
},
|
||||
{
|
||||
desc: "disable report config with empty id",
|
||||
token: validToken,
|
||||
id: "",
|
||||
domainID: domainID,
|
||||
status: http.StatusBadRequest,
|
||||
err: apiutil.ErrMissingID,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
req := testRequest{
|
||||
client: ts.Client(),
|
||||
method: http.MethodPost,
|
||||
url: fmt.Sprintf("%s/%s/reports/configs/%s/disable", ts.URL, tc.domainID, tc.id),
|
||||
token: tc.token,
|
||||
}
|
||||
if tc.token == validToken {
|
||||
tc.session = smqauthn.Session{DomainUserID: auth.EncodeDomainUserID(domainID, userID), UserID: userID, DomainID: domainID}
|
||||
}
|
||||
authCall := authn.On("Authenticate", mock.Anything, tc.token).Return(tc.session, tc.authnErr)
|
||||
svcCall := svc.On("DisableReportConfig", mock.Anything, tc.session, tc.id).Return(tc.svcResp, tc.svcErr)
|
||||
res, err := req.make()
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
|
||||
var errRes respBody
|
||||
err = json.NewDecoder(res.Body).Decode(&errRes)
|
||||
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error while decoding response body: %s", tc.desc, err))
|
||||
if errRes.Err != "" || errRes.Message != "" {
|
||||
err = errors.Wrap(errors.New(errRes.Err), errors.New(errRes.Message))
|
||||
}
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
|
||||
svcCall.Unset()
|
||||
authCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type respBody struct {
|
||||
Err string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
Total uint64 `json:"total"`
|
||||
ID string `json:"id"`
|
||||
Status reports.Status `json:"status"`
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/schedule"
|
||||
"github.com/absmach/magistrala/reports"
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
)
|
||||
|
||||
const (
|
||||
maxLimitSize = 1000
|
||||
MaxNameSize = 1024
|
||||
MaxTitleSize = 37
|
||||
|
||||
errInvalidMetric = "invalid metric[%d]: %w"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidReportAction = errors.New("invalid report action")
|
||||
errMetricsNotProvided = errors.New("metrics not provided")
|
||||
errMissingReportConfig = errors.New("missing report config")
|
||||
errMissingReportEmailConfig = errors.New("missing report email config")
|
||||
errInvalidRecurringPeriod = errors.New("invalid recurring period")
|
||||
errTitleSize = errors.New("invalid title size")
|
||||
)
|
||||
|
||||
type addReportConfigReq struct {
|
||||
reports.ReportConfig `json:",inline"`
|
||||
}
|
||||
|
||||
func (req addReportConfigReq) validate() error {
|
||||
if req.Name == "" {
|
||||
return apiutil.ErrMissingName
|
||||
}
|
||||
return validateReportConfig(req.ReportConfig, false, false)
|
||||
}
|
||||
|
||||
type viewReportConfigReq struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func (req viewReportConfigReq) validate() error {
|
||||
if req.ID == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type listReportsConfigReq struct {
|
||||
reports.PageMeta `json:",inline"`
|
||||
}
|
||||
|
||||
func (req listReportsConfigReq) validate() error {
|
||||
if req.Limit > maxLimitSize {
|
||||
return svcerr.ErrMalformedEntity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type updateReportConfigReq struct {
|
||||
reports.ReportConfig `json:",inline"`
|
||||
}
|
||||
|
||||
func (req updateReportConfigReq) validate() error {
|
||||
if req.ID == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
return validateReportConfig(req.ReportConfig, false, false)
|
||||
}
|
||||
|
||||
type updateReportScheduleReq struct {
|
||||
id string
|
||||
Schedule schedule.Schedule `json:"schedule,omitempty"`
|
||||
}
|
||||
|
||||
func (req updateReportScheduleReq) validate() error {
|
||||
if req.id == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type deleteReportConfigReq struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func (req deleteReportConfigReq) validate() error {
|
||||
if req.ID == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type generateReportReq struct {
|
||||
reports.ReportConfig
|
||||
action reports.ReportAction
|
||||
}
|
||||
|
||||
func (req generateReportReq) validate() error {
|
||||
if len(req.Config.Title) > MaxTitleSize {
|
||||
return errors.Wrap(apiutil.ErrValidation, errTitleSize)
|
||||
}
|
||||
|
||||
switch req.action {
|
||||
case reports.ViewReport, reports.DownloadReport:
|
||||
return validateReportConfig(req.ReportConfig, true, true)
|
||||
case reports.EmailReport:
|
||||
return validateReportConfig(req.ReportConfig, false, true)
|
||||
default:
|
||||
return errors.Wrap(apiutil.ErrValidation, errInvalidReportAction)
|
||||
}
|
||||
}
|
||||
|
||||
type updateReportStatusReq struct {
|
||||
id string
|
||||
}
|
||||
|
||||
func (req updateReportStatusReq) validate() error {
|
||||
if req.id == "" {
|
||||
return apiutil.ErrMissingID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateReportConfig(req reports.ReportConfig, skipEmailValidation bool, skipSchedularValidation bool) error {
|
||||
if len(req.Metrics) == 0 {
|
||||
return errors.Wrap(apiutil.ErrValidation, errMetricsNotProvided)
|
||||
}
|
||||
for i, metric := range req.Metrics {
|
||||
if err := metric.Validate(); err != nil {
|
||||
return errors.Wrap(apiutil.ErrValidation, fmt.Errorf(errInvalidMetric, i+1, err))
|
||||
}
|
||||
}
|
||||
|
||||
if req.Config == nil {
|
||||
return errors.Wrap(errMissingReportConfig, apiutil.ErrValidation)
|
||||
}
|
||||
if err := req.Config.Validate(); err != nil {
|
||||
return errors.Wrap(err, apiutil.ErrValidation)
|
||||
}
|
||||
|
||||
if skipEmailValidation {
|
||||
return nil
|
||||
}
|
||||
if req.Email == nil {
|
||||
return errors.Wrap(errMissingReportEmailConfig, apiutil.ErrValidation)
|
||||
}
|
||||
if err := req.Email.Validate(); err != nil {
|
||||
return errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
if skipSchedularValidation {
|
||||
return nil
|
||||
}
|
||||
|
||||
return validateScheduler(req.Schedule)
|
||||
}
|
||||
|
||||
func validateScheduler(sch schedule.Schedule) error {
|
||||
if sch.Recurring != schedule.None && sch.RecurringPeriod < 1 {
|
||||
return errors.Wrap(apiutil.ErrValidation, errInvalidRecurringPeriod)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/reports"
|
||||
"github.com/absmach/supermq"
|
||||
)
|
||||
|
||||
var (
|
||||
_ supermq.Response = (*addReportConfigRes)(nil)
|
||||
_ supermq.Response = (*viewReportConfigRes)(nil)
|
||||
_ supermq.Response = (*updateReportConfigRes)(nil)
|
||||
_ supermq.Response = (*deleteReportConfigRes)(nil)
|
||||
_ supermq.Response = (*listReportsConfigRes)(nil)
|
||||
)
|
||||
|
||||
type pageRes struct {
|
||||
Limit uint64 `json:"limit,omitempty"`
|
||||
Offset uint64 `json:"offset"`
|
||||
Total uint64 `json:"total"`
|
||||
}
|
||||
|
||||
type generateReportResp struct {
|
||||
Total uint64 `json:"total"`
|
||||
From time.Time `json:"from,omitempty"`
|
||||
To time.Time `json:"to,omitempty"`
|
||||
Aggregation reports.AggConfig `json:"aggregation,omitempty"`
|
||||
Reports []reports.Report `json:"reports,omitempty"`
|
||||
}
|
||||
|
||||
func (res generateReportResp) Code() int {
|
||||
return http.StatusCreated
|
||||
}
|
||||
|
||||
func (res generateReportResp) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res generateReportResp) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type addReportConfigRes struct {
|
||||
reports.ReportConfig `json:",inline"`
|
||||
created bool
|
||||
}
|
||||
|
||||
func (res addReportConfigRes) Code() int {
|
||||
if res.created {
|
||||
return http.StatusCreated
|
||||
}
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res addReportConfigRes) Headers() map[string]string {
|
||||
if res.created {
|
||||
return map[string]string{}
|
||||
}
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res addReportConfigRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type viewReportConfigRes struct {
|
||||
reports.ReportConfig `json:",inline"`
|
||||
}
|
||||
|
||||
func (res viewReportConfigRes) Code() int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res viewReportConfigRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res viewReportConfigRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type updateReportConfigRes struct {
|
||||
reports.ReportConfig `json:",inline"`
|
||||
}
|
||||
|
||||
func (res updateReportConfigRes) Code() int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res updateReportConfigRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res updateReportConfigRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type deleteReportConfigRes struct {
|
||||
deleted bool
|
||||
}
|
||||
|
||||
func (res deleteReportConfigRes) Code() int {
|
||||
if res.deleted {
|
||||
return http.StatusNoContent
|
||||
}
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res deleteReportConfigRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res deleteReportConfigRes) Empty() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type listReportsConfigRes struct {
|
||||
pageRes
|
||||
ReportConfigs []reports.ReportConfig `json:"report_configs"`
|
||||
}
|
||||
|
||||
func (res listReportsConfigRes) Code() int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res listReportsConfigRes) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res listReportsConfigRes) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type downloadReportResp struct {
|
||||
File reports.ReportFile
|
||||
}
|
||||
|
||||
func (res downloadReportResp) Code() int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res downloadReportResp) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res downloadReportResp) Empty() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type emailReportResp struct{}
|
||||
|
||||
func (res emailReportResp) Code() int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
func (res emailReportResp) Headers() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func (res emailReportResp) Empty() bool {
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/absmach/magistrala/reports"
|
||||
"github.com/absmach/supermq"
|
||||
api "github.com/absmach/supermq/api/http"
|
||||
apiutil "github.com/absmach/supermq/api/http/util"
|
||||
mgauthn "github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/go-chi/chi/v5"
|
||||
kithttp "github.com/go-kit/kit/transport/http"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
)
|
||||
|
||||
const (
|
||||
reportIdKey = "reportID"
|
||||
statusKey = "status"
|
||||
actionKey = "action"
|
||||
defAction = "view"
|
||||
)
|
||||
|
||||
// MakeHandler creates an HTTP handler for the service endpoints.
|
||||
func MakeHandler(svc reports.Service, authn mgauthn.Authentication, mux *chi.Mux, logger *slog.Logger, instanceID string) http.Handler {
|
||||
opts := []kithttp.ServerOption{
|
||||
kithttp.ServerErrorEncoder(apiutil.LoggingErrorEncoder(logger, api.EncodeError)),
|
||||
}
|
||||
mux.Group(func(r chi.Router) {
|
||||
r.Use(api.AuthenticateMiddleware(authn, true))
|
||||
r.Route("/{domainID}", func(r chi.Router) {
|
||||
r.Route("/reports", func(r chi.Router) {
|
||||
r.Post("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
generateReportEndpoint(svc),
|
||||
decodeGenerateReportRequest,
|
||||
encodeFileDownloadResponse,
|
||||
opts...,
|
||||
), "generate_report").ServeHTTP)
|
||||
|
||||
r.Route("/configs", func(r chi.Router) {
|
||||
r.Post("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
addReportConfigEndpoint(svc),
|
||||
decodeAddReportConfigRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "add_report_config").ServeHTTP)
|
||||
|
||||
r.Get("/{reportID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
viewReportConfigEndpoint(svc),
|
||||
decodeViewReportConfigRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "view_report_config").ServeHTTP)
|
||||
|
||||
r.Patch("/{reportID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
updateReportConfigEndpoint(svc),
|
||||
decodeUpdateReportConfigRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "update_report_config").ServeHTTP)
|
||||
|
||||
r.Patch("/{reportID}/schedule", otelhttp.NewHandler(kithttp.NewServer(
|
||||
updateReportScheduleEndpoint(svc),
|
||||
decodeUpdateReportScheduleRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "update_report_scheduler").ServeHTTP)
|
||||
|
||||
r.Delete("/{reportID}", otelhttp.NewHandler(kithttp.NewServer(
|
||||
deleteReportConfigEndpoint(svc),
|
||||
decodeDeleteReportConfigRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "delete_report_config").ServeHTTP)
|
||||
|
||||
r.Get("/", otelhttp.NewHandler(kithttp.NewServer(
|
||||
listReportsConfigEndpoint(svc),
|
||||
decodeListReportsConfigRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "list_reports_config").ServeHTTP)
|
||||
|
||||
r.Post("/{reportID}/enable", otelhttp.NewHandler(kithttp.NewServer(
|
||||
enableReportConfigEndpoint(svc),
|
||||
decodeUpdateReportStatusRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "enable_report_config").ServeHTTP)
|
||||
|
||||
r.Post("/{reportID}/disable", otelhttp.NewHandler(kithttp.NewServer(
|
||||
disableReportConfigEndpoint(svc),
|
||||
decodeUpdateReportStatusRequest,
|
||||
api.EncodeResponse,
|
||||
opts...,
|
||||
), "disable_report_config").ServeHTTP)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
mux.Get("/health", supermq.Health("rule_engine", instanceID))
|
||||
mux.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
func decodeGenerateReportRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
|
||||
}
|
||||
|
||||
a, err := apiutil.ReadStringQuery(r, actionKey, defAction)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
action, err := reports.ToReportAction(a)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
|
||||
req := generateReportReq{
|
||||
action: action,
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, errors.Wrap(err, apiutil.ErrValidation)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeAddReportConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
|
||||
}
|
||||
var config reports.ReportConfig
|
||||
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
|
||||
return nil, errors.Wrap(err, apiutil.ErrValidation)
|
||||
}
|
||||
return addReportConfigReq{ReportConfig: config}, nil
|
||||
}
|
||||
|
||||
func decodeViewReportConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
id := chi.URLParam(r, reportIdKey)
|
||||
return viewReportConfigReq{ID: id}, nil
|
||||
}
|
||||
|
||||
func decodeUpdateReportConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
|
||||
}
|
||||
var config reports.ReportConfig
|
||||
if err := json.NewDecoder(r.Body).Decode(&config); err != nil {
|
||||
return nil, errors.Wrap(err, apiutil.ErrValidation)
|
||||
}
|
||||
config.ID = chi.URLParam(r, reportIdKey)
|
||||
return updateReportConfigReq{ReportConfig: config}, nil
|
||||
}
|
||||
|
||||
func decodeUpdateReportScheduleRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
if !strings.Contains(r.Header.Get("Content-Type"), api.ContentType) {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, apiutil.ErrUnsupportedContentType)
|
||||
}
|
||||
|
||||
req := updateReportScheduleReq{
|
||||
id: chi.URLParam(r, reportIdKey),
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, errors.Wrap(errors.ErrMalformedEntity, err))
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeUpdateReportStatusRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
req := updateReportStatusReq{
|
||||
id: chi.URLParam(r, reportIdKey),
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeDeleteReportConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
id := chi.URLParam(r, reportIdKey)
|
||||
return deleteReportConfigReq{ID: id}, nil
|
||||
}
|
||||
|
||||
func decodeListReportsConfigRequest(_ context.Context, r *http.Request) (interface{}, error) {
|
||||
offset, err := apiutil.ReadNumQuery[uint64](r, api.OffsetKey, api.DefOffset)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
limit, err := apiutil.ReadNumQuery[uint64](r, api.LimitKey, api.DefLimit)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
status, err := apiutil.ReadStringQuery(r, api.StatusKey, api.DefStatus)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
st, err := reports.ToStatus(status)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
name, err := apiutil.ReadStringQuery(r, api.NameKey, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(apiutil.ErrValidation, err)
|
||||
}
|
||||
return listReportsConfigReq{
|
||||
PageMeta: reports.PageMeta{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Status: st,
|
||||
Name: name,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func encodeFileDownloadResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
|
||||
switch resp := response.(type) {
|
||||
case downloadReportResp:
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", resp.File.Name))
|
||||
w.Header().Set("Content-Type", resp.File.Format.ContentType())
|
||||
_, err := w.Write(resp.File.Data)
|
||||
return err
|
||||
default:
|
||||
if ar, ok := response.(supermq.Response); ok {
|
||||
for k, v := range ar.Headers() {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
w.Header().Set("Content-Type", api.ContentType)
|
||||
w.WriteHeader(ar.Code())
|
||||
|
||||
if ar.Empty() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package re
|
||||
package reports
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package reports
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
pkglog "github.com/absmach/magistrala/pkg/logger"
|
||||
)
|
||||
|
||||
func (r *report) StartScheduler(ctx context.Context) error {
|
||||
defer r.ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-r.ticker.Tick():
|
||||
due := time.Now().UTC()
|
||||
|
||||
pm := PageMeta{
|
||||
Status: EnabledStatus,
|
||||
ScheduledBefore: &due,
|
||||
}
|
||||
|
||||
reportConfigs, err := r.repo.ListReportsConfig(ctx, pm)
|
||||
if err != nil {
|
||||
r.runInfo <- pkglog.RunInfo{
|
||||
Level: slog.LevelError,
|
||||
Message: fmt.Sprintf("failed to list reports : %s", err),
|
||||
Details: []slog.Attr{slog.Time("due", due)},
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for _, c := range reportConfigs.ReportConfigs {
|
||||
go func(cfg ReportConfig) {
|
||||
if _, err := r.repo.UpdateReportDue(ctx, cfg.ID, cfg.Schedule.NextDue()); err != nil {
|
||||
r.runInfo <- pkglog.RunInfo{Level: slog.LevelError, Message: fmt.Sprintf("failed to update report: %s", err), Details: []slog.Attr{slog.Time("time", time.Now().UTC())}}
|
||||
return
|
||||
}
|
||||
_, err := r.generateReport(ctx, cfg, EmailReport)
|
||||
ret := pkglog.RunInfo{
|
||||
Details: []slog.Attr{
|
||||
slog.String("domain_id", cfg.DomainID),
|
||||
slog.String("report_id", cfg.ID),
|
||||
slog.String("report_name", cfg.Name),
|
||||
slog.Time("exec_time", time.Now().UTC()),
|
||||
},
|
||||
}
|
||||
if err != nil {
|
||||
ret.Level = slog.LevelError
|
||||
ret.Message = fmt.Sprintf("failed to generate report: %s", err)
|
||||
}
|
||||
r.runInfo <- ret
|
||||
}(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/reports"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
smqauthz "github.com/absmach/supermq/pkg/authz"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/policies"
|
||||
)
|
||||
|
||||
var (
|
||||
errDomainCreateConfigs = errors.New("not authorized to create report configs in domain")
|
||||
errDomainViewConfigs = errors.New("not authorized to view report configs in domain")
|
||||
errDomainUpdateConfigs = errors.New("not authorized to update report configs in domain")
|
||||
errDomainDeleteConfigs = errors.New("not authorized to delete report configs in domain")
|
||||
errDomainGenerateReports = errors.New("not authorized to generate reports in domain")
|
||||
)
|
||||
|
||||
type authorizationMiddleware struct {
|
||||
svc reports.Service
|
||||
authz smqauthz.Authorization
|
||||
}
|
||||
|
||||
// AuthorizationMiddleware adds authorization to the reports service.
|
||||
func AuthorizationMiddleware(svc reports.Service, authz smqauthz.Authorization) (reports.Service, error) {
|
||||
return &authorizationMiddleware{
|
||||
svc: svc,
|
||||
authz: authz,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) AddReportConfig(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errDomainCreateConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.AddReportConfig(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) ViewReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errDomainViewConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.ViewReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) UpdateReportConfig(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errDomainUpdateConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.UpdateReportConfig(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errDomainDeleteConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.UpdateReportSchedule(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) RemoveReportConfig(ctx context.Context, session authn.Session, id string) error {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return errors.Wrap(errDomainDeleteConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.RemoveReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) ListReportsConfig(ctx context.Context, session authn.Session, pm reports.PageMeta) (reports.ReportConfigPage, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return reports.ReportConfigPage{}, errors.Wrap(errDomainViewConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.ListReportsConfig(ctx, session, pm)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) EnableReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errDomainUpdateConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.EnableReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) DisableReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errDomainUpdateConfigs, err)
|
||||
}
|
||||
|
||||
return am.svc.DisableReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) GenerateReport(ctx context.Context, session authn.Session, config reports.ReportConfig, action reports.ReportAction) (reports.ReportPage, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
SubjectKind: policies.UsersKind,
|
||||
Subject: session.DomainUserID,
|
||||
Object: session.DomainID,
|
||||
ObjectType: policies.DomainType,
|
||||
Permission: policies.MembershipPermission,
|
||||
}); err != nil {
|
||||
return reports.ReportPage{}, errors.Wrap(errDomainGenerateReports, err)
|
||||
}
|
||||
|
||||
return am.svc.GenerateReport(ctx, session, config, action)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) StartScheduler(ctx context.Context) error {
|
||||
return am.svc.StartScheduler(ctx)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) authorize(ctx context.Context, pr smqauthz.PolicyReq) error {
|
||||
if err := am.authz.Authorize(ctx, pr); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/reports"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
)
|
||||
|
||||
var _ reports.Service = (*loggingMiddleware)(nil)
|
||||
|
||||
type loggingMiddleware struct {
|
||||
logger *slog.Logger
|
||||
svc reports.Service
|
||||
}
|
||||
|
||||
func LoggingMiddleware(svc reports.Service, logger *slog.Logger) reports.Service {
|
||||
return &loggingMiddleware{logger, svc}
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) StartScheduler(ctx context.Context) (err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Start scheduler failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Start scheduler completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.StartScheduler(ctx)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) GenerateReport(ctx context.Context, session authn.Session, config reports.ReportConfig, action reports.ReportAction) (page reports.ReportPage, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Generate report failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Generate report completed", args...)
|
||||
}(time.Now())
|
||||
|
||||
return lm.svc.GenerateReport(ctx, session, config, action)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) AddReportConfig(ctx context.Context, session authn.Session, config reports.ReportConfig) (res reports.ReportConfig, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("domain_id", session.DomainID),
|
||||
slog.String("report_name", config.Name),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Add report config failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Add report config completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.AddReportConfig(ctx, session, config)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) ViewReportConfig(ctx context.Context, session authn.Session, id string) (res reports.ReportConfig, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("domain_id", session.DomainID),
|
||||
slog.Group("report_config",
|
||||
slog.String("id", res.ID),
|
||||
slog.String("name", res.Name),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("View report config failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("View report config completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.ViewReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) UpdateReportConfig(ctx context.Context, session authn.Session, config reports.ReportConfig) (res reports.ReportConfig, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("domain_id", session.DomainID),
|
||||
slog.Group("report_config",
|
||||
slog.String("id", config.ID),
|
||||
slog.String("name", config.Name),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Update report config failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Update report config completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.UpdateReportConfig(ctx, session, config)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (res reports.ReportConfig, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("domain_id", session.DomainID),
|
||||
slog.Group("report",
|
||||
slog.String("id", cfg.ID),
|
||||
slog.Any("schedule", cfg.Schedule),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Update report schedule failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Update report schedule completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.UpdateReportSchedule(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) ListReportsConfig(ctx context.Context, session authn.Session, pm reports.PageMeta) (pg reports.ReportConfigPage, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("domain_id", session.DomainID),
|
||||
slog.Group("page",
|
||||
slog.Uint64("offset", pm.Offset),
|
||||
slog.Uint64("limit", pm.Limit),
|
||||
slog.Uint64("total", pg.Total),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("List reports config failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("List reports config completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.ListReportsConfig(ctx, session, pm)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) DisableReportConfig(ctx context.Context, session authn.Session, id string) (res reports.ReportConfig, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("domain_id", session.DomainID),
|
||||
slog.Group("report_config",
|
||||
slog.String("id", res.ID),
|
||||
slog.String("name", res.Name),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Disable report config failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Disable report config completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.DisableReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) EnableReportConfig(ctx context.Context, session authn.Session, id string) (res reports.ReportConfig, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("domain_id", session.DomainID),
|
||||
slog.Group("report_config",
|
||||
slog.String("id", res.ID),
|
||||
slog.String("name", res.Name),
|
||||
),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Enable report config failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Enable report config completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.EnableReportConfig(ctx, session, id)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) RemoveReportConfig(ctx context.Context, session authn.Session, id string) (err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
slog.String("domain_id", session.DomainID),
|
||||
slog.String("report_config_id", id),
|
||||
}
|
||||
if err != nil {
|
||||
args = append(args, slog.String("error", err.Error()))
|
||||
lm.logger.Warn("Remove report config failed", args...)
|
||||
return
|
||||
}
|
||||
lm.logger.Info("Remove report config completed successfully", args...)
|
||||
}(time.Now())
|
||||
return lm.svc.RemoveReportConfig(ctx, session, id)
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
// Copyright (c) Abstract Machines
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/reports"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// NewRepository creates a new instance of Repository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewRepository(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Repository {
|
||||
mock := &Repository{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// Repository is an autogenerated mock type for the Repository type
|
||||
type Repository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type Repository_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *Repository) EXPECT() *Repository_Expecter {
|
||||
return &Repository_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// AddReportConfig provides a mock function for the type Repository
|
||||
func (_mock *Repository) AddReportConfig(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for AddReportConfig")
|
||||
}
|
||||
|
||||
var r0 reports.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.ReportConfig) (reports.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, cfg)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.ReportConfig) reports.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, cfg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(reports.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, reports.ReportConfig) error); ok {
|
||||
r1 = returnFunc(ctx, cfg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_AddReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddReportConfig'
|
||||
type Repository_AddReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// AddReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - cfg
|
||||
func (_e *Repository_Expecter) AddReportConfig(ctx interface{}, cfg interface{}) *Repository_AddReportConfig_Call {
|
||||
return &Repository_AddReportConfig_Call{Call: _e.mock.On("AddReportConfig", ctx, cfg)}
|
||||
}
|
||||
|
||||
func (_c *Repository_AddReportConfig_Call) Run(run func(ctx context.Context, cfg reports.ReportConfig)) *Repository_AddReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(reports.ReportConfig))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_AddReportConfig_Call) Return(reportConfig reports.ReportConfig, err error) *Repository_AddReportConfig_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_AddReportConfig_Call) RunAndReturn(run func(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error)) *Repository_AddReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListReportsConfig provides a mock function for the type Repository
|
||||
func (_mock *Repository) ListReportsConfig(ctx context.Context, pm reports.PageMeta) (reports.ReportConfigPage, error) {
|
||||
ret := _mock.Called(ctx, pm)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListReportsConfig")
|
||||
}
|
||||
|
||||
var r0 reports.ReportConfigPage
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.PageMeta) (reports.ReportConfigPage, error)); ok {
|
||||
return returnFunc(ctx, pm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.PageMeta) reports.ReportConfigPage); ok {
|
||||
r0 = returnFunc(ctx, pm)
|
||||
} else {
|
||||
r0 = ret.Get(0).(reports.ReportConfigPage)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, reports.PageMeta) error); ok {
|
||||
r1 = returnFunc(ctx, pm)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_ListReportsConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListReportsConfig'
|
||||
type Repository_ListReportsConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListReportsConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - pm
|
||||
func (_e *Repository_Expecter) ListReportsConfig(ctx interface{}, pm interface{}) *Repository_ListReportsConfig_Call {
|
||||
return &Repository_ListReportsConfig_Call{Call: _e.mock.On("ListReportsConfig", ctx, pm)}
|
||||
}
|
||||
|
||||
func (_c *Repository_ListReportsConfig_Call) Run(run func(ctx context.Context, pm reports.PageMeta)) *Repository_ListReportsConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(reports.PageMeta))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ListReportsConfig_Call) Return(reportConfigPage reports.ReportConfigPage, err error) *Repository_ListReportsConfig_Call {
|
||||
_c.Call.Return(reportConfigPage, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ListReportsConfig_Call) RunAndReturn(run func(ctx context.Context, pm reports.PageMeta) (reports.ReportConfigPage, error)) *Repository_ListReportsConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RemoveReportConfig provides a mock function for the type Repository
|
||||
func (_mock *Repository) RemoveReportConfig(ctx context.Context, id string) error {
|
||||
ret := _mock.Called(ctx, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RemoveReportConfig")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
||||
r0 = returnFunc(ctx, id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Repository_RemoveReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveReportConfig'
|
||||
type Repository_RemoveReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RemoveReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - id
|
||||
func (_e *Repository_Expecter) RemoveReportConfig(ctx interface{}, id interface{}) *Repository_RemoveReportConfig_Call {
|
||||
return &Repository_RemoveReportConfig_Call{Call: _e.mock.On("RemoveReportConfig", ctx, id)}
|
||||
}
|
||||
|
||||
func (_c *Repository_RemoveReportConfig_Call) Run(run func(ctx context.Context, id string)) *Repository_RemoveReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_RemoveReportConfig_Call) Return(err error) *Repository_RemoveReportConfig_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_RemoveReportConfig_Call) RunAndReturn(run func(ctx context.Context, id string) error) *Repository_RemoveReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateReportConfig provides a mock function for the type Repository
|
||||
func (_mock *Repository) UpdateReportConfig(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateReportConfig")
|
||||
}
|
||||
|
||||
var r0 reports.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.ReportConfig) (reports.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, cfg)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.ReportConfig) reports.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, cfg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(reports.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, reports.ReportConfig) error); ok {
|
||||
r1 = returnFunc(ctx, cfg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_UpdateReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportConfig'
|
||||
type Repository_UpdateReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - cfg
|
||||
func (_e *Repository_Expecter) UpdateReportConfig(ctx interface{}, cfg interface{}) *Repository_UpdateReportConfig_Call {
|
||||
return &Repository_UpdateReportConfig_Call{Call: _e.mock.On("UpdateReportConfig", ctx, cfg)}
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportConfig_Call) Run(run func(ctx context.Context, cfg reports.ReportConfig)) *Repository_UpdateReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(reports.ReportConfig))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportConfig_Call) Return(reportConfig reports.ReportConfig, err error) *Repository_UpdateReportConfig_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportConfig_Call) RunAndReturn(run func(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error)) *Repository_UpdateReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateReportConfigStatus provides a mock function for the type Repository
|
||||
func (_mock *Repository) UpdateReportConfigStatus(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateReportConfigStatus")
|
||||
}
|
||||
|
||||
var r0 reports.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.ReportConfig) (reports.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, cfg)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.ReportConfig) reports.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, cfg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(reports.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, reports.ReportConfig) error); ok {
|
||||
r1 = returnFunc(ctx, cfg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_UpdateReportConfigStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportConfigStatus'
|
||||
type Repository_UpdateReportConfigStatus_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateReportConfigStatus is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - cfg
|
||||
func (_e *Repository_Expecter) UpdateReportConfigStatus(ctx interface{}, cfg interface{}) *Repository_UpdateReportConfigStatus_Call {
|
||||
return &Repository_UpdateReportConfigStatus_Call{Call: _e.mock.On("UpdateReportConfigStatus", ctx, cfg)}
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportConfigStatus_Call) Run(run func(ctx context.Context, cfg reports.ReportConfig)) *Repository_UpdateReportConfigStatus_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(reports.ReportConfig))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportConfigStatus_Call) Return(reportConfig reports.ReportConfig, err error) *Repository_UpdateReportConfigStatus_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportConfigStatus_Call) RunAndReturn(run func(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error)) *Repository_UpdateReportConfigStatus_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateReportDue provides a mock function for the type Repository
|
||||
func (_mock *Repository) UpdateReportDue(ctx context.Context, id string, due time.Time) (reports.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, id, due)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateReportDue")
|
||||
}
|
||||
|
||||
var r0 reports.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, time.Time) (reports.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, id, due)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, time.Time) reports.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, id, due)
|
||||
} else {
|
||||
r0 = ret.Get(0).(reports.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string, time.Time) error); ok {
|
||||
r1 = returnFunc(ctx, id, due)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_UpdateReportDue_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportDue'
|
||||
type Repository_UpdateReportDue_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateReportDue is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - id
|
||||
// - due
|
||||
func (_e *Repository_Expecter) UpdateReportDue(ctx interface{}, id interface{}, due interface{}) *Repository_UpdateReportDue_Call {
|
||||
return &Repository_UpdateReportDue_Call{Call: _e.mock.On("UpdateReportDue", ctx, id, due)}
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportDue_Call) Run(run func(ctx context.Context, id string, due time.Time)) *Repository_UpdateReportDue_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(time.Time))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportDue_Call) Return(reportConfig reports.ReportConfig, err error) *Repository_UpdateReportDue_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportDue_Call) RunAndReturn(run func(ctx context.Context, id string, due time.Time) (reports.ReportConfig, error)) *Repository_UpdateReportDue_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateReportSchedule provides a mock function for the type Repository
|
||||
func (_mock *Repository) UpdateReportSchedule(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateReportSchedule")
|
||||
}
|
||||
|
||||
var r0 reports.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.ReportConfig) (reports.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, cfg)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, reports.ReportConfig) reports.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, cfg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(reports.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, reports.ReportConfig) error); ok {
|
||||
r1 = returnFunc(ctx, cfg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_UpdateReportSchedule_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportSchedule'
|
||||
type Repository_UpdateReportSchedule_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateReportSchedule is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - cfg
|
||||
func (_e *Repository_Expecter) UpdateReportSchedule(ctx interface{}, cfg interface{}) *Repository_UpdateReportSchedule_Call {
|
||||
return &Repository_UpdateReportSchedule_Call{Call: _e.mock.On("UpdateReportSchedule", ctx, cfg)}
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportSchedule_Call) Run(run func(ctx context.Context, cfg reports.ReportConfig)) *Repository_UpdateReportSchedule_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(reports.ReportConfig))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportSchedule_Call) Return(reportConfig reports.ReportConfig, err error) *Repository_UpdateReportSchedule_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_UpdateReportSchedule_Call) RunAndReturn(run func(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error)) *Repository_UpdateReportSchedule_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ViewReportConfig provides a mock function for the type Repository
|
||||
func (_mock *Repository) ViewReportConfig(ctx context.Context, id string) (reports.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ViewReportConfig")
|
||||
}
|
||||
|
||||
var r0 reports.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) (reports.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, id)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string) reports.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, id)
|
||||
} else {
|
||||
r0 = ret.Get(0).(reports.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = returnFunc(ctx, id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Repository_ViewReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ViewReportConfig'
|
||||
type Repository_ViewReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ViewReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - id
|
||||
func (_e *Repository_Expecter) ViewReportConfig(ctx interface{}, id interface{}) *Repository_ViewReportConfig_Call {
|
||||
return &Repository_ViewReportConfig_Call{Call: _e.mock.On("ViewReportConfig", ctx, id)}
|
||||
}
|
||||
|
||||
func (_c *Repository_ViewReportConfig_Call) Run(run func(ctx context.Context, id string)) *Repository_ViewReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ViewReportConfig_Call) Return(reportConfig reports.ReportConfig, err error) *Repository_ViewReportConfig_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ViewReportConfig_Call) RunAndReturn(run func(ctx context.Context, id string) (reports.ReportConfig, error)) *Repository_ViewReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
@@ -0,0 +1,584 @@
|
||||
// Code generated by mockery; DO NOT EDIT.
|
||||
// github.com/vektra/mockery
|
||||
// template: testify
|
||||
// Copyright (c) Abstract Machines
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/absmach/magistrala/reports"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// NewService creates a new instance of Service. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewService(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *Service {
|
||||
mock := &Service{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
// Service is an autogenerated mock type for the Service type
|
||||
type Service struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type Service_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *Service) EXPECT() *Service_Expecter {
|
||||
return &Service_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// AddReportConfig provides a mock function for the type Service
|
||||
func (_mock *Service) AddReportConfig(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, session, cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for AddReportConfig")
|
||||
}
|
||||
|
||||
var r0 reports.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.ReportConfig) (reports.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, session, cfg)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.ReportConfig) reports.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, session, cfg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(reports.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, reports.ReportConfig) error); ok {
|
||||
r1 = returnFunc(ctx, session, cfg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_AddReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddReportConfig'
|
||||
type Service_AddReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// AddReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - cfg
|
||||
func (_e *Service_Expecter) AddReportConfig(ctx interface{}, session interface{}, cfg interface{}) *Service_AddReportConfig_Call {
|
||||
return &Service_AddReportConfig_Call{Call: _e.mock.On("AddReportConfig", ctx, session, cfg)}
|
||||
}
|
||||
|
||||
func (_c *Service_AddReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, cfg reports.ReportConfig)) *Service_AddReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(reports.ReportConfig))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_AddReportConfig_Call) Return(reportConfig reports.ReportConfig, err error) *Service_AddReportConfig_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_AddReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error)) *Service_AddReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DisableReportConfig provides a mock function for the type Service
|
||||
func (_mock *Service) DisableReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, session, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DisableReportConfig")
|
||||
}
|
||||
|
||||
var r0 reports.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) (reports.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, session, id)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) reports.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r0 = ret.Get(0).(reports.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
|
||||
r1 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_DisableReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisableReportConfig'
|
||||
type Service_DisableReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DisableReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - id
|
||||
func (_e *Service_Expecter) DisableReportConfig(ctx interface{}, session interface{}, id interface{}) *Service_DisableReportConfig_Call {
|
||||
return &Service_DisableReportConfig_Call{Call: _e.mock.On("DisableReportConfig", ctx, session, id)}
|
||||
}
|
||||
|
||||
func (_c *Service_DisableReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_DisableReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_DisableReportConfig_Call) Return(reportConfig reports.ReportConfig, err error) *Service_DisableReportConfig_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_DisableReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error)) *Service_DisableReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// EnableReportConfig provides a mock function for the type Service
|
||||
func (_mock *Service) EnableReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, session, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for EnableReportConfig")
|
||||
}
|
||||
|
||||
var r0 reports.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) (reports.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, session, id)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) reports.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r0 = ret.Get(0).(reports.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
|
||||
r1 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_EnableReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EnableReportConfig'
|
||||
type Service_EnableReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// EnableReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - id
|
||||
func (_e *Service_Expecter) EnableReportConfig(ctx interface{}, session interface{}, id interface{}) *Service_EnableReportConfig_Call {
|
||||
return &Service_EnableReportConfig_Call{Call: _e.mock.On("EnableReportConfig", ctx, session, id)}
|
||||
}
|
||||
|
||||
func (_c *Service_EnableReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_EnableReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_EnableReportConfig_Call) Return(reportConfig reports.ReportConfig, err error) *Service_EnableReportConfig_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_EnableReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error)) *Service_EnableReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GenerateReport provides a mock function for the type Service
|
||||
func (_mock *Service) GenerateReport(ctx context.Context, session authn.Session, config reports.ReportConfig, action reports.ReportAction) (reports.ReportPage, error) {
|
||||
ret := _mock.Called(ctx, session, config, action)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GenerateReport")
|
||||
}
|
||||
|
||||
var r0 reports.ReportPage
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.ReportConfig, reports.ReportAction) (reports.ReportPage, error)); ok {
|
||||
return returnFunc(ctx, session, config, action)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.ReportConfig, reports.ReportAction) reports.ReportPage); ok {
|
||||
r0 = returnFunc(ctx, session, config, action)
|
||||
} else {
|
||||
r0 = ret.Get(0).(reports.ReportPage)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, reports.ReportConfig, reports.ReportAction) error); ok {
|
||||
r1 = returnFunc(ctx, session, config, action)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_GenerateReport_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GenerateReport'
|
||||
type Service_GenerateReport_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GenerateReport is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - config
|
||||
// - action
|
||||
func (_e *Service_Expecter) GenerateReport(ctx interface{}, session interface{}, config interface{}, action interface{}) *Service_GenerateReport_Call {
|
||||
return &Service_GenerateReport_Call{Call: _e.mock.On("GenerateReport", ctx, session, config, action)}
|
||||
}
|
||||
|
||||
func (_c *Service_GenerateReport_Call) Run(run func(ctx context.Context, session authn.Session, config reports.ReportConfig, action reports.ReportAction)) *Service_GenerateReport_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(reports.ReportConfig), args[3].(reports.ReportAction))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_GenerateReport_Call) Return(reportPage reports.ReportPage, err error) *Service_GenerateReport_Call {
|
||||
_c.Call.Return(reportPage, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_GenerateReport_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, config reports.ReportConfig, action reports.ReportAction) (reports.ReportPage, error)) *Service_GenerateReport_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListReportsConfig provides a mock function for the type Service
|
||||
func (_mock *Service) ListReportsConfig(ctx context.Context, session authn.Session, pm reports.PageMeta) (reports.ReportConfigPage, error) {
|
||||
ret := _mock.Called(ctx, session, pm)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListReportsConfig")
|
||||
}
|
||||
|
||||
var r0 reports.ReportConfigPage
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.PageMeta) (reports.ReportConfigPage, error)); ok {
|
||||
return returnFunc(ctx, session, pm)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.PageMeta) reports.ReportConfigPage); ok {
|
||||
r0 = returnFunc(ctx, session, pm)
|
||||
} else {
|
||||
r0 = ret.Get(0).(reports.ReportConfigPage)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, reports.PageMeta) error); ok {
|
||||
r1 = returnFunc(ctx, session, pm)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_ListReportsConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListReportsConfig'
|
||||
type Service_ListReportsConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListReportsConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - pm
|
||||
func (_e *Service_Expecter) ListReportsConfig(ctx interface{}, session interface{}, pm interface{}) *Service_ListReportsConfig_Call {
|
||||
return &Service_ListReportsConfig_Call{Call: _e.mock.On("ListReportsConfig", ctx, session, pm)}
|
||||
}
|
||||
|
||||
func (_c *Service_ListReportsConfig_Call) Run(run func(ctx context.Context, session authn.Session, pm reports.PageMeta)) *Service_ListReportsConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(reports.PageMeta))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ListReportsConfig_Call) Return(reportConfigPage reports.ReportConfigPage, err error) *Service_ListReportsConfig_Call {
|
||||
_c.Call.Return(reportConfigPage, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ListReportsConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, pm reports.PageMeta) (reports.ReportConfigPage, error)) *Service_ListReportsConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RemoveReportConfig provides a mock function for the type Service
|
||||
func (_mock *Service) RemoveReportConfig(ctx context.Context, session authn.Session, id string) error {
|
||||
ret := _mock.Called(ctx, session, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RemoveReportConfig")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) error); ok {
|
||||
r0 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Service_RemoveReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveReportConfig'
|
||||
type Service_RemoveReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// RemoveReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - id
|
||||
func (_e *Service_Expecter) RemoveReportConfig(ctx interface{}, session interface{}, id interface{}) *Service_RemoveReportConfig_Call {
|
||||
return &Service_RemoveReportConfig_Call{Call: _e.mock.On("RemoveReportConfig", ctx, session, id)}
|
||||
}
|
||||
|
||||
func (_c *Service_RemoveReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_RemoveReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_RemoveReportConfig_Call) Return(err error) *Service_RemoveReportConfig_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_RemoveReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) error) *Service_RemoveReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// StartScheduler provides a mock function for the type Service
|
||||
func (_mock *Service) StartScheduler(ctx context.Context) error {
|
||||
ret := _mock.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for StartScheduler")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context) error); ok {
|
||||
r0 = returnFunc(ctx)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// Service_StartScheduler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StartScheduler'
|
||||
type Service_StartScheduler_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// StartScheduler is a helper method to define mock.On call
|
||||
// - ctx
|
||||
func (_e *Service_Expecter) StartScheduler(ctx interface{}) *Service_StartScheduler_Call {
|
||||
return &Service_StartScheduler_Call{Call: _e.mock.On("StartScheduler", ctx)}
|
||||
}
|
||||
|
||||
func (_c *Service_StartScheduler_Call) Run(run func(ctx context.Context)) *Service_StartScheduler_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_StartScheduler_Call) Return(err error) *Service_StartScheduler_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_StartScheduler_Call) RunAndReturn(run func(ctx context.Context) error) *Service_StartScheduler_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateReportConfig provides a mock function for the type Service
|
||||
func (_mock *Service) UpdateReportConfig(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, session, cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateReportConfig")
|
||||
}
|
||||
|
||||
var r0 reports.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.ReportConfig) (reports.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, session, cfg)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.ReportConfig) reports.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, session, cfg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(reports.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, reports.ReportConfig) error); ok {
|
||||
r1 = returnFunc(ctx, session, cfg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_UpdateReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportConfig'
|
||||
type Service_UpdateReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - cfg
|
||||
func (_e *Service_Expecter) UpdateReportConfig(ctx interface{}, session interface{}, cfg interface{}) *Service_UpdateReportConfig_Call {
|
||||
return &Service_UpdateReportConfig_Call{Call: _e.mock.On("UpdateReportConfig", ctx, session, cfg)}
|
||||
}
|
||||
|
||||
func (_c *Service_UpdateReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, cfg reports.ReportConfig)) *Service_UpdateReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(reports.ReportConfig))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_UpdateReportConfig_Call) Return(reportConfig reports.ReportConfig, err error) *Service_UpdateReportConfig_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_UpdateReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error)) *Service_UpdateReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdateReportSchedule provides a mock function for the type Service
|
||||
func (_mock *Service) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, session, cfg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateReportSchedule")
|
||||
}
|
||||
|
||||
var r0 reports.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.ReportConfig) (reports.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, session, cfg)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, reports.ReportConfig) reports.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, session, cfg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(reports.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, reports.ReportConfig) error); ok {
|
||||
r1 = returnFunc(ctx, session, cfg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_UpdateReportSchedule_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateReportSchedule'
|
||||
type Service_UpdateReportSchedule_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateReportSchedule is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - cfg
|
||||
func (_e *Service_Expecter) UpdateReportSchedule(ctx interface{}, session interface{}, cfg interface{}) *Service_UpdateReportSchedule_Call {
|
||||
return &Service_UpdateReportSchedule_Call{Call: _e.mock.On("UpdateReportSchedule", ctx, session, cfg)}
|
||||
}
|
||||
|
||||
func (_c *Service_UpdateReportSchedule_Call) Run(run func(ctx context.Context, session authn.Session, cfg reports.ReportConfig)) *Service_UpdateReportSchedule_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(reports.ReportConfig))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_UpdateReportSchedule_Call) Return(reportConfig reports.ReportConfig, err error) *Service_UpdateReportSchedule_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_UpdateReportSchedule_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, cfg reports.ReportConfig) (reports.ReportConfig, error)) *Service_UpdateReportSchedule_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ViewReportConfig provides a mock function for the type Service
|
||||
func (_mock *Service) ViewReportConfig(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error) {
|
||||
ret := _mock.Called(ctx, session, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ViewReportConfig")
|
||||
}
|
||||
|
||||
var r0 reports.ReportConfig
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) (reports.ReportConfig, error)); ok {
|
||||
return returnFunc(ctx, session, id)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) reports.ReportConfig); ok {
|
||||
r0 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r0 = ret.Get(0).(reports.ReportConfig)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
|
||||
r1 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Service_ViewReportConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ViewReportConfig'
|
||||
type Service_ViewReportConfig_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ViewReportConfig is a helper method to define mock.On call
|
||||
// - ctx
|
||||
// - session
|
||||
// - id
|
||||
func (_e *Service_Expecter) ViewReportConfig(ctx interface{}, session interface{}, id interface{}) *Service_ViewReportConfig_Call {
|
||||
return &Service_ViewReportConfig_Call{Call: _e.mock.On("ViewReportConfig", ctx, session, id)}
|
||||
}
|
||||
|
||||
func (_c *Service_ViewReportConfig_Call) Run(run func(ctx context.Context, session authn.Session, id string)) *Service_ViewReportConfig_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(authn.Session), args[2].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ViewReportConfig_Call) Return(reportConfig reports.ReportConfig, err error) *Service_ViewReportConfig_Call {
|
||||
_c.Call.Return(reportConfig, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ViewReportConfig_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) (reports.ReportConfig, error)) *Service_ViewReportConfig_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
_ "github.com/jackc/pgx/v5/stdlib" // required for SQL access
|
||||
migrate "github.com/rubenv/sql-migrate"
|
||||
)
|
||||
|
||||
func Migration() *migrate.MemoryMigrationSource {
|
||||
return &migrate.MemoryMigrationSource{
|
||||
Migrations: []*migrate.Migration{
|
||||
{
|
||||
Id: "reports_01",
|
||||
Up: []string{
|
||||
`CREATE TABLE IF NOT EXISTS report_config (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
name VARCHAR(1024),
|
||||
description TEXT,
|
||||
domain_id VARCHAR(36) NOT NULL,
|
||||
status SMALLINT NOT NULL DEFAULT 0 CHECK (status >= 0),
|
||||
created_at TIMESTAMP,
|
||||
created_by VARCHAR(254),
|
||||
updated_at TIMESTAMP,
|
||||
updated_by VARCHAR(254),
|
||||
due TIMESTAMPTZ,
|
||||
recurring SMALLINT,
|
||||
recurring_period SMALLINT,
|
||||
start_datetime TIMESTAMP,
|
||||
config JSONB,
|
||||
email JSONB,
|
||||
metrics JSONB
|
||||
);`,
|
||||
},
|
||||
Down: []string{
|
||||
`DROP TABLE IF EXISTS report_config;`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/magistrala/pkg/schedule"
|
||||
"github.com/absmach/magistrala/reports"
|
||||
)
|
||||
|
||||
// dbReport represents the database structure for a Report.
|
||||
type dbReport struct {
|
||||
ID string `db:"id"`
|
||||
Name string `db:"name"`
|
||||
Description string `db:"description"`
|
||||
DomainID string `db:"domain_id"`
|
||||
StartDateTime sql.NullTime `db:"start_datetime"`
|
||||
Due sql.NullTime `db:"due"`
|
||||
Recurring schedule.Recurring `db:"recurring"`
|
||||
RecurringPeriod uint `db:"recurring_period"`
|
||||
Status reports.Status `db:"status"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
CreatedBy string `db:"created_by"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
UpdatedBy string `db:"updated_by"`
|
||||
Config []byte `db:"config,omitempty"`
|
||||
Metrics []byte `db:"metrics"`
|
||||
Email []byte `db:"email"`
|
||||
}
|
||||
|
||||
func reportToDb(r reports.ReportConfig) (dbReport, error) {
|
||||
config := []byte("{}")
|
||||
if r.Config != nil {
|
||||
b, err := json.Marshal(r.Config)
|
||||
if err != nil {
|
||||
return dbReport{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
config = b
|
||||
}
|
||||
|
||||
metrics := []byte("{}")
|
||||
if r.Metrics != nil {
|
||||
m, err := json.Marshal(r.Metrics)
|
||||
if err != nil {
|
||||
return dbReport{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
metrics = m
|
||||
}
|
||||
|
||||
email := []byte("{}")
|
||||
if r.Email != nil {
|
||||
e, err := json.Marshal(r.Email)
|
||||
if err != nil {
|
||||
return dbReport{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
email = e
|
||||
}
|
||||
|
||||
start := sql.NullTime{Time: r.Schedule.StartDateTime}
|
||||
if !r.Schedule.StartDateTime.IsZero() {
|
||||
start.Valid = true
|
||||
}
|
||||
t := sql.NullTime{Time: r.Schedule.Time}
|
||||
if !r.Schedule.Time.IsZero() {
|
||||
t.Valid = true
|
||||
}
|
||||
|
||||
return dbReport{
|
||||
ID: r.ID,
|
||||
Name: r.Name,
|
||||
Description: r.Description,
|
||||
DomainID: r.DomainID,
|
||||
StartDateTime: start,
|
||||
Due: t,
|
||||
Recurring: r.Schedule.Recurring,
|
||||
RecurringPeriod: r.Schedule.RecurringPeriod,
|
||||
Status: r.Status,
|
||||
CreatedAt: r.CreatedAt,
|
||||
CreatedBy: r.CreatedBy,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
UpdatedBy: r.UpdatedBy,
|
||||
Config: config,
|
||||
Metrics: metrics,
|
||||
Email: email,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func dbToReport(dto dbReport) (reports.ReportConfig, error) {
|
||||
var config reports.MetricConfig
|
||||
if dto.Config != nil {
|
||||
if err := json.Unmarshal(dto.Config, &config); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
}
|
||||
|
||||
var email reports.EmailSetting
|
||||
if dto.Email != nil {
|
||||
if err := json.Unmarshal(dto.Email, &email); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
}
|
||||
|
||||
var metrics []reports.ReqMetric
|
||||
if dto.Metrics != nil {
|
||||
if err := json.Unmarshal(dto.Metrics, &metrics); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(errors.ErrMalformedEntity, err)
|
||||
}
|
||||
}
|
||||
|
||||
rpt := reports.ReportConfig{
|
||||
ID: dto.ID,
|
||||
Name: dto.Name,
|
||||
Description: dto.Description,
|
||||
DomainID: dto.DomainID,
|
||||
Config: &config,
|
||||
Metrics: metrics,
|
||||
Schedule: schedule.Schedule{
|
||||
StartDateTime: dto.StartDateTime.Time,
|
||||
Time: dto.Due.Time,
|
||||
Recurring: dto.Recurring,
|
||||
RecurringPeriod: dto.RecurringPeriod,
|
||||
},
|
||||
Email: &email,
|
||||
Status: dto.Status,
|
||||
CreatedAt: dto.CreatedAt,
|
||||
CreatedBy: dto.CreatedBy,
|
||||
UpdatedAt: dto.UpdatedAt,
|
||||
UpdatedBy: dto.UpdatedBy,
|
||||
}
|
||||
|
||||
return rpt, nil
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/errors"
|
||||
"github.com/absmach/magistrala/reports"
|
||||
repoerr "github.com/absmach/supermq/pkg/errors/repository"
|
||||
"github.com/absmach/supermq/pkg/postgres"
|
||||
)
|
||||
|
||||
type PostgresRepository struct {
|
||||
DB postgres.Database
|
||||
}
|
||||
|
||||
func NewRepository(db postgres.Database) reports.Repository {
|
||||
return &PostgresRepository{DB: db}
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) AddReportConfig(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
q := `
|
||||
INSERT INTO report_config (id, name, description, domain_id, config, metrics,
|
||||
email, start_datetime, due, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status)
|
||||
VALUES (:id, :name, :description, :domain_id, :config, :metrics,
|
||||
:email, :start_datetime, :due, :recurring, :recurring_period, :created_at, :created_by, :updated_at, :updated_by, :status)
|
||||
RETURNING id, name, description, domain_id, config, metrics,
|
||||
email, start_datetime, due, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;
|
||||
`
|
||||
dbr, err := reportToDb(cfg)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
var dbReport dbReport
|
||||
if row.Next() {
|
||||
if err := row.StructScan(&dbReport); err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
}
|
||||
|
||||
report, err := dbToReport(dbReport)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) ViewReportConfig(ctx context.Context, id string) (reports.ReportConfig, error) {
|
||||
q := `
|
||||
SELECT id, name, description, domain_id, config, metrics,
|
||||
email, start_datetime, due, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status
|
||||
FROM report_config
|
||||
WHERE id = $1;
|
||||
`
|
||||
row := repo.DB.QueryRowxContext(ctx, q, id)
|
||||
if err := row.Err(); err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
var dbr dbReport
|
||||
if err := row.StructScan(&dbr); err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
rpt, err := dbToReport(dbr)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
|
||||
return rpt, nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) UpdateReportConfigStatus(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
q := `UPDATE report_config SET status = :status, updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id
|
||||
RETURNING id, name, description, domain_id, metrics, email, config,
|
||||
start_datetime, due, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;`
|
||||
|
||||
dbRpt, err := reportToDb(cfg)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
row, err := repo.DB.NamedQueryContext(ctx, q, dbRpt)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, postgres.HandleError(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
dbr := dbReport{}
|
||||
if row.Next() {
|
||||
if err := row.StructScan(&dbr); err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
|
||||
res, err := dbToReport(dbr)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
return reports.ReportConfig{}, repoerr.ErrNotFound
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) UpdateReportConfig(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
var query []string
|
||||
|
||||
if cfg.Name != "" {
|
||||
query = append(query, "name = :name")
|
||||
}
|
||||
|
||||
if cfg.Description != "" {
|
||||
query = append(query, "description = :description")
|
||||
}
|
||||
|
||||
if len(cfg.Metrics) > 0 {
|
||||
query = append(query, "metrics = :metrics")
|
||||
}
|
||||
|
||||
if cfg.Email != nil {
|
||||
query = append(query, "email = :email")
|
||||
}
|
||||
|
||||
if cfg.Config != nil {
|
||||
query = append(query, "config = :config")
|
||||
}
|
||||
|
||||
var q string
|
||||
if len(query) > 0 {
|
||||
q = fmt.Sprintf("%s", strings.Join(query, ", "))
|
||||
}
|
||||
|
||||
q = fmt.Sprintf(`
|
||||
UPDATE report_config
|
||||
SET %s,
|
||||
updated_at = :updated_at, updated_by = :updated_by
|
||||
WHERE id = :id
|
||||
RETURNING id, name, description, domain_id, config, metrics,
|
||||
email, start_datetime, due, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;
|
||||
`, q)
|
||||
|
||||
dbr, err := reportToDb(cfg)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
var dbReport dbReport
|
||||
if row.Next() {
|
||||
if err := row.StructScan(&dbReport); err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
}
|
||||
rpt, err := dbToReport(dbReport)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, err
|
||||
}
|
||||
|
||||
return rpt, nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) UpdateReportSchedule(ctx context.Context, cfg reports.ReportConfig) (reports.ReportConfig, error) {
|
||||
q := `
|
||||
UPDATE report_config
|
||||
SET start_datetime = :start_datetime, due = :due, recurring = :recurring,
|
||||
recurring_period = :recurring_period, updated_at = :updated_at, updated_by = :updated_by WHERE id = :id
|
||||
RETURNING id, name, description, domain_id, config, metrics,
|
||||
email, start_datetime, due, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;
|
||||
`
|
||||
|
||||
dbr, err := reportToDb(cfg)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, postgres.HandleError(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
var dbReport dbReport
|
||||
if row.Next() {
|
||||
if err := row.StructScan(&dbReport); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
}
|
||||
report, err := dbToReport(dbReport)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) RemoveReportConfig(ctx context.Context, id string) error {
|
||||
q := `
|
||||
DELETE FROM report_config
|
||||
WHERE id = $1;
|
||||
`
|
||||
|
||||
result, err := repo.DB.ExecContext(ctx, q, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := result.RowsAffected(); err != nil {
|
||||
return repoerr.ErrNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) ListReportsConfig(ctx context.Context, pm reports.PageMeta) (reports.ReportConfigPage, error) {
|
||||
listReportsQuery := `
|
||||
SELECT id, name, description, domain_id, metrics, email, config,
|
||||
start_datetime, due, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status
|
||||
FROM report_config rc %s %s;
|
||||
`
|
||||
|
||||
pgData := ""
|
||||
if pm.Limit != 0 {
|
||||
pgData = "LIMIT :limit"
|
||||
}
|
||||
if pm.Offset != 0 {
|
||||
pgData += " OFFSET :offset"
|
||||
}
|
||||
pq := pageReportQuery(pm)
|
||||
q := fmt.Sprintf(listReportsQuery, pq, pgData)
|
||||
rows, err := repo.DB.NamedQueryContext(ctx, q, pm)
|
||||
if err != nil {
|
||||
return reports.ReportConfigPage{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
cfgs := []reports.ReportConfig{}
|
||||
for rows.Next() {
|
||||
var r dbReport
|
||||
if err := rows.StructScan(&r); err != nil {
|
||||
return reports.ReportConfigPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
rpt, err := dbToReport(r)
|
||||
if err != nil {
|
||||
return reports.ReportConfigPage{}, err
|
||||
}
|
||||
cfgs = append(cfgs, rpt)
|
||||
}
|
||||
|
||||
cq := fmt.Sprintf(`SELECT COUNT(*) FROM report_config rc %s;`, pq)
|
||||
|
||||
total, err := postgres.Total(ctx, repo.DB, cq, pm)
|
||||
if err != nil {
|
||||
return reports.ReportConfigPage{}, errors.Wrap(repoerr.ErrViewEntity, err)
|
||||
}
|
||||
pm.Total = total
|
||||
ret := reports.ReportConfigPage{
|
||||
PageMeta: pm,
|
||||
ReportConfigs: cfgs,
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) UpdateReportDue(ctx context.Context, id string, due time.Time) (reports.ReportConfig, error) {
|
||||
q := `
|
||||
UPDATE report_config
|
||||
SET due = :due, updated_at = :updated_at WHERE id = :id
|
||||
RETURNING id, name, description, domain_id, config, metrics,
|
||||
email, start_datetime, due, recurring, recurring_period, created_at, created_by, updated_at, updated_by, status;
|
||||
`
|
||||
|
||||
dbr := dbReport{
|
||||
ID: id,
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
Due: sql.NullTime{Time: due},
|
||||
}
|
||||
if !due.IsZero() {
|
||||
dbr.Due.Valid = true
|
||||
}
|
||||
|
||||
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, postgres.HandleError(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
var dbReport dbReport
|
||||
if row.Next() {
|
||||
if err := row.StructScan(&dbReport); err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
}
|
||||
report, err := dbToReport(dbReport)
|
||||
if err != nil {
|
||||
return reports.ReportConfig{}, errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
return report, nil
|
||||
}
|
||||
|
||||
func pageReportQuery(pm reports.PageMeta) string {
|
||||
var query []string
|
||||
if pm.Status != reports.AllStatus {
|
||||
query = append(query, "rc.status = :status")
|
||||
}
|
||||
if pm.Domain != "" {
|
||||
query = append(query, "rc.domain_id = :domain_id")
|
||||
}
|
||||
if pm.ScheduledBefore != nil {
|
||||
query = append(query, "rc.due < :scheduled_before")
|
||||
}
|
||||
if pm.ScheduledAfter != nil {
|
||||
query = append(query, "rc.due > :scheduled_after")
|
||||
}
|
||||
if pm.Name != "" {
|
||||
query = append(query, "rc.name ILIKE '%' || :name || '%'")
|
||||
}
|
||||
|
||||
var q string
|
||||
if len(query) > 0 {
|
||||
q = fmt.Sprintf("WHERE %s", strings.Join(query, " AND "))
|
||||
}
|
||||
|
||||
return q
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package re
|
||||
package reports
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
@@ -11,6 +12,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/absmach/magistrala/pkg/reltime"
|
||||
"github.com/absmach/magistrala/pkg/schedule"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
"github.com/absmach/supermq/pkg/transformers/senml"
|
||||
)
|
||||
@@ -142,19 +145,19 @@ func (rm ReqMetric) Validate() error {
|
||||
}
|
||||
|
||||
type ReportConfig struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
DomainID string `json:"domain_id"`
|
||||
Schedule Schedule `json:"schedule,omitempty"`
|
||||
Config *MetricConfig `json:"config,omitempty"`
|
||||
Email *EmailSetting `json:"email,omitempty"`
|
||||
Metrics []ReqMetric `json:"metrics,omitempty"`
|
||||
Status Status `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
CreatedBy string `json:"created_by,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
UpdatedBy string `json:"updated_by,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
DomainID string `json:"domain_id"`
|
||||
Schedule schedule.Schedule `json:"schedule,omitempty"`
|
||||
Config *MetricConfig `json:"config,omitempty"`
|
||||
Email *EmailSetting `json:"email,omitempty"`
|
||||
Metrics []ReqMetric `json:"metrics,omitempty"`
|
||||
Status Status `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
CreatedBy string `json:"created_by,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
UpdatedBy string `json:"updated_by,omitempty"`
|
||||
}
|
||||
|
||||
type ReportConfigPage struct {
|
||||
@@ -370,3 +373,39 @@ func (a *Aggregation) UnmarshalJSON(data []byte) error {
|
||||
*a = val
|
||||
return err
|
||||
}
|
||||
|
||||
type PageMeta struct {
|
||||
Total uint64 `json:"total" db:"total"`
|
||||
Offset uint64 `json:"offset" db:"offset"`
|
||||
Limit uint64 `json:"limit" db:"limit"`
|
||||
Name string `json:"name" db:"name"`
|
||||
Status Status `json:"status,omitempty" db:"status"`
|
||||
Domain string `json:"domain_id,omitempty" db:"domain_id"`
|
||||
ScheduledBefore *time.Time `json:"scheduled_before,omitempty" db:"scheduled_before"` // Filter rules scheduled before this time
|
||||
ScheduledAfter *time.Time `json:"scheduled_after,omitempty" db:"scheduled_after"` // Filter rules scheduled after this time
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
AddReportConfig(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
|
||||
ViewReportConfig(ctx context.Context, id string) (ReportConfig, error)
|
||||
UpdateReportConfig(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
|
||||
UpdateReportSchedule(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
|
||||
RemoveReportConfig(ctx context.Context, id string) error
|
||||
UpdateReportConfigStatus(ctx context.Context, cfg ReportConfig) (ReportConfig, error)
|
||||
ListReportsConfig(ctx context.Context, pm PageMeta) (ReportConfigPage, error)
|
||||
UpdateReportDue(ctx context.Context, id string, due time.Time) (ReportConfig, error)
|
||||
}
|
||||
|
||||
type Service interface {
|
||||
AddReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error)
|
||||
ViewReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error)
|
||||
UpdateReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error)
|
||||
UpdateReportSchedule(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error)
|
||||
RemoveReportConfig(ctx context.Context, session authn.Session, id string) error
|
||||
ListReportsConfig(ctx context.Context, session authn.Session, pm PageMeta) (ReportConfigPage, error)
|
||||
EnableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error)
|
||||
DisableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error)
|
||||
|
||||
GenerateReport(ctx context.Context, session authn.Session, config ReportConfig, action ReportAction) (ReportPage, error)
|
||||
StartScheduler(ctx context.Context) error
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package reports
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
grpcReadersV1 "github.com/absmach/magistrala/api/grpc/readers/v1"
|
||||
"github.com/absmach/magistrala/pkg/emailer"
|
||||
pkglog "github.com/absmach/magistrala/pkg/logger"
|
||||
"github.com/absmach/magistrala/pkg/reltime"
|
||||
"github.com/absmach/magistrala/pkg/ticker"
|
||||
"github.com/absmach/supermq"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/absmach/supermq/pkg/transformers/senml"
|
||||
)
|
||||
|
||||
const limit = 1000
|
||||
|
||||
type report struct {
|
||||
repo Repository
|
||||
runInfo chan pkglog.RunInfo
|
||||
idp supermq.IDProvider
|
||||
email emailer.Emailer
|
||||
ticker ticker.Ticker
|
||||
readers grpcReadersV1.ReadersServiceClient
|
||||
}
|
||||
|
||||
func NewService(repo Repository, runInfo chan pkglog.RunInfo, idp supermq.IDProvider, tck ticker.Ticker, emailer emailer.Emailer, readers grpcReadersV1.ReadersServiceClient) Service {
|
||||
return &report{
|
||||
repo: repo,
|
||||
idp: idp,
|
||||
runInfo: runInfo,
|
||||
email: emailer,
|
||||
ticker: tck,
|
||||
readers: readers,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *report) AddReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error) {
|
||||
id, err := r.idp.ID()
|
||||
if err != nil {
|
||||
return ReportConfig{}, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
cfg.ID = id
|
||||
cfg.CreatedAt = now
|
||||
cfg.CreatedBy = session.UserID
|
||||
cfg.DomainID = session.DomainID
|
||||
cfg.Status = EnabledStatus
|
||||
|
||||
if cfg.Schedule.StartDateTime.IsZero() {
|
||||
cfg.Schedule.StartDateTime = now
|
||||
}
|
||||
cfg.Schedule.Time = cfg.Schedule.StartDateTime
|
||||
|
||||
reportConfig, err := r.repo.AddReportConfig(ctx, cfg)
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
return reportConfig, nil
|
||||
}
|
||||
|
||||
func (r *report) ViewReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error) {
|
||||
cfg, err := r.repo.ViewReportConfig(ctx, id)
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (r *report) UpdateReportConfig(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error) {
|
||||
cfg.UpdatedAt = time.Now().UTC()
|
||||
cfg.UpdatedBy = session.UserID
|
||||
reportConfig, err := r.repo.UpdateReportConfig(ctx, cfg)
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
return reportConfig, nil
|
||||
}
|
||||
|
||||
func (r *report) UpdateReportSchedule(ctx context.Context, session authn.Session, cfg ReportConfig) (ReportConfig, error) {
|
||||
cfg.UpdatedAt = time.Now().UTC()
|
||||
cfg.UpdatedBy = session.UserID
|
||||
cfg.Schedule.Time = cfg.Schedule.StartDateTime
|
||||
c, err := r.repo.UpdateReportSchedule(ctx, cfg)
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (r *report) RemoveReportConfig(ctx context.Context, session authn.Session, id string) error {
|
||||
if err := r.repo.RemoveReportConfig(ctx, id); err != nil {
|
||||
return errors.Wrap(svcerr.ErrRemoveEntity, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *report) ListReportsConfig(ctx context.Context, session authn.Session, pm PageMeta) (ReportConfigPage, error) {
|
||||
pm.Domain = session.DomainID
|
||||
page, err := r.repo.ListReportsConfig(ctx, pm)
|
||||
if err != nil {
|
||||
return ReportConfigPage{}, errors.Wrap(svcerr.ErrViewEntity, err)
|
||||
}
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func (r *report) EnableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error) {
|
||||
status, err := ToStatus(Enabled)
|
||||
if err != nil {
|
||||
return ReportConfig{}, err
|
||||
}
|
||||
cfg := ReportConfig{
|
||||
ID: id,
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
UpdatedBy: session.UserID,
|
||||
Status: status,
|
||||
}
|
||||
cfg, err = r.repo.UpdateReportConfigStatus(ctx, cfg)
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (r *report) DisableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error) {
|
||||
status, err := ToStatus(Disabled)
|
||||
if err != nil {
|
||||
return ReportConfig{}, err
|
||||
}
|
||||
cfg := ReportConfig{
|
||||
ID: id,
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
UpdatedBy: session.UserID,
|
||||
Status: status,
|
||||
}
|
||||
cfg, err = r.repo.UpdateReportConfigStatus(ctx, cfg)
|
||||
if err != nil {
|
||||
return ReportConfig{}, errors.Wrap(svcerr.ErrUpdateEntity, err)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (r *report) GenerateReport(ctx context.Context, session authn.Session, config ReportConfig, action ReportAction) (ReportPage, error) {
|
||||
config.DomainID = session.DomainID
|
||||
|
||||
if config.Status != EnabledStatus {
|
||||
return ReportPage{}, svcerr.ErrInvalidStatus
|
||||
}
|
||||
|
||||
reportPage, err := r.generateReport(ctx, config, action)
|
||||
if err != nil {
|
||||
return ReportPage{}, err
|
||||
}
|
||||
|
||||
return reportPage, nil
|
||||
}
|
||||
|
||||
func (r *report) generateReport(ctx context.Context, cfg ReportConfig, action ReportAction) (ReportPage, error) {
|
||||
genReportFile, err := generateFileFunc(action, cfg.Config.FileFormat)
|
||||
if err != nil {
|
||||
return ReportPage{}, err
|
||||
}
|
||||
|
||||
agg := grpcReadersV1.Aggregation_AGGREGATION_UNSPECIFIED
|
||||
switch cfg.Config.Aggregation.AggType {
|
||||
case AggregationMAX:
|
||||
agg = grpcReadersV1.Aggregation_MAX
|
||||
case AggregationMIN:
|
||||
agg = grpcReadersV1.Aggregation_MIN
|
||||
case AggregationCOUNT:
|
||||
agg = grpcReadersV1.Aggregation_COUNT
|
||||
case AggregationAVG:
|
||||
agg = grpcReadersV1.Aggregation_AVG
|
||||
case AggregationSUM:
|
||||
agg = grpcReadersV1.Aggregation_SUM
|
||||
}
|
||||
|
||||
from, err := reltime.Parse(cfg.Config.From)
|
||||
if err != nil {
|
||||
return ReportPage{}, err
|
||||
}
|
||||
to, err := reltime.Parse(cfg.Config.To)
|
||||
if err != nil {
|
||||
return ReportPage{}, err
|
||||
}
|
||||
pm := &grpcReadersV1.PageMetadata{
|
||||
Aggregation: agg,
|
||||
Limit: limit,
|
||||
From: float64(from.UnixMicro()),
|
||||
To: float64(to.UnixNano()),
|
||||
Interval: cfg.Config.Aggregation.Interval,
|
||||
}
|
||||
|
||||
var mets []Metric
|
||||
var reports []Report
|
||||
for _, metric := range cfg.Metrics {
|
||||
switch {
|
||||
case len(metric.ClientIDs) != 0:
|
||||
for _, clientID := range metric.ClientIDs {
|
||||
mets = append(mets, Metric{
|
||||
ChannelID: metric.ChannelID,
|
||||
ClientID: clientID,
|
||||
Name: metric.Name,
|
||||
Subtopic: metric.Subtopic,
|
||||
Protocol: metric.Protocol,
|
||||
Format: metric.Format,
|
||||
})
|
||||
}
|
||||
default:
|
||||
mets = append(mets, Metric{
|
||||
ChannelID: metric.ChannelID,
|
||||
Name: metric.Name,
|
||||
Subtopic: metric.Subtopic,
|
||||
Protocol: metric.Protocol,
|
||||
Format: metric.Format,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, metric := range mets {
|
||||
sMsgs := []senml.Message{}
|
||||
|
||||
pm.Offset = uint64(0)
|
||||
pm.Name = metric.Name
|
||||
if metric.ClientID != "" {
|
||||
pm.Publisher = metric.ClientID
|
||||
}
|
||||
if metric.Subtopic != "" {
|
||||
pm.Subtopic = metric.Subtopic
|
||||
}
|
||||
if metric.Protocol != "" {
|
||||
pm.Protocol = metric.Protocol
|
||||
}
|
||||
if metric.Format != "" {
|
||||
pm.Format = metric.Format
|
||||
}
|
||||
|
||||
msgs, err := r.readers.ReadMessages(ctx, &grpcReadersV1.ReadMessagesReq{
|
||||
ChannelId: metric.ChannelID,
|
||||
DomainId: cfg.DomainID,
|
||||
PageMetadata: pm,
|
||||
})
|
||||
if err != nil {
|
||||
return ReportPage{}, err
|
||||
}
|
||||
for _, msg := range msgs.Messages {
|
||||
sMsgs = append(sMsgs, convertToSenml(msg.GetSenml()))
|
||||
}
|
||||
|
||||
for msgs.GetTotal() > (pm.Offset + pm.Limit) {
|
||||
pm.Offset = pm.Offset + pm.Limit
|
||||
msgs, err := r.readers.ReadMessages(ctx, &grpcReadersV1.ReadMessagesReq{
|
||||
ChannelId: metric.ChannelID,
|
||||
DomainId: cfg.DomainID,
|
||||
PageMetadata: pm,
|
||||
})
|
||||
if err != nil {
|
||||
return ReportPage{}, err
|
||||
}
|
||||
for _, msg := range msgs.Messages {
|
||||
sMsgs = append(sMsgs, convertToSenml(msg.GetSenml()))
|
||||
}
|
||||
}
|
||||
|
||||
reports = append(reports, convertToReports(metric, sMsgs)...)
|
||||
}
|
||||
|
||||
switch {
|
||||
case genReportFile != nil:
|
||||
data, err := genReportFile(cfg.Config.Title, reports)
|
||||
if err != nil {
|
||||
return ReportPage{}, err
|
||||
}
|
||||
timeStr := strings.ReplaceAll(time.Now().Format(time.RFC3339), ":", "")
|
||||
filePrefix := cfg.Name
|
||||
if filePrefix == "" {
|
||||
filePrefix = "report"
|
||||
}
|
||||
fileName := fmt.Sprintf("%s_%s.%s", filePrefix, timeStr, cfg.Config.FileFormat.Extension())
|
||||
|
||||
file := ReportFile{
|
||||
Name: fileName,
|
||||
Data: data,
|
||||
Format: cfg.Config.FileFormat,
|
||||
}
|
||||
|
||||
switch action {
|
||||
case EmailReport:
|
||||
if err := r.emailReports(*cfg.Email, file); err != nil {
|
||||
return ReportPage{}, errors.Wrap(err, svcerr.ErrCreateEntity)
|
||||
}
|
||||
|
||||
return ReportPage{}, nil
|
||||
default:
|
||||
return ReportPage{
|
||||
File: file,
|
||||
}, nil
|
||||
}
|
||||
|
||||
default:
|
||||
return ReportPage{
|
||||
From: from,
|
||||
To: to,
|
||||
Aggregation: cfg.Config.Aggregation,
|
||||
Total: uint64(len(reports)),
|
||||
Reports: reports,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func generateFileFunc(action ReportAction, format Format) (func(string, []Report) ([]byte, error), error) {
|
||||
switch action {
|
||||
case DownloadReport, EmailReport:
|
||||
switch format {
|
||||
case PDF:
|
||||
return generatePDFReport, nil
|
||||
case CSV:
|
||||
return generateCSVReport, nil
|
||||
default:
|
||||
return nil, errors.New("file format not supported")
|
||||
}
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *report) emailReports(es EmailSetting, file ReportFile) error {
|
||||
if err := es.Validate(); err != nil {
|
||||
return errors.Wrap(svcerr.ErrMalformedEntity, err)
|
||||
}
|
||||
|
||||
attachments := map[string][]byte{
|
||||
file.Name: file.Data,
|
||||
}
|
||||
|
||||
if err := r.email.SendEmailNotification(
|
||||
es.To,
|
||||
"",
|
||||
es.Subject,
|
||||
"",
|
||||
"",
|
||||
es.Content,
|
||||
"",
|
||||
attachments,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertToSenml(g *grpcReadersV1.SenMLMessage) senml.Message {
|
||||
if g == nil {
|
||||
return senml.Message{}
|
||||
}
|
||||
return senml.Message{
|
||||
Protocol: g.Base.GetProtocol(),
|
||||
Subtopic: g.Base.GetSubtopic(),
|
||||
Publisher: g.Base.GetPublisher(),
|
||||
Channel: g.Base.GetChannel(),
|
||||
Name: g.GetName(),
|
||||
Unit: g.GetUnit(),
|
||||
Time: g.GetTime(),
|
||||
UpdateTime: g.GetUpdateTime(),
|
||||
Value: g.Value,
|
||||
StringValue: g.StringValue,
|
||||
DataValue: g.DataValue,
|
||||
BoolValue: g.BoolValue,
|
||||
Sum: g.Sum,
|
||||
}
|
||||
}
|
||||
|
||||
func convertToReports(metric Metric, senmlMsgs []senml.Message) []Report {
|
||||
if metric.ClientID != "" {
|
||||
return []Report{
|
||||
{
|
||||
Metric: metric,
|
||||
Messages: senmlMsgs,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return groupReportsByPublisher(metric, senmlMsgs)
|
||||
}
|
||||
|
||||
func groupReportsByPublisher(metric Metric, sMsgs []senml.Message) []Report {
|
||||
publishers := map[string][]senml.Message{}
|
||||
|
||||
for _, msg := range sMsgs {
|
||||
publishers[msg.Publisher] = append(publishers[msg.Publisher], msg)
|
||||
}
|
||||
|
||||
var groupedReports []Report
|
||||
for publisher, messages := range publishers {
|
||||
gMetric := metric
|
||||
gMetric.ClientID = publisher
|
||||
groupedReports = append(groupedReports, Report{
|
||||
Metric: gMetric,
|
||||
Messages: messages,
|
||||
})
|
||||
}
|
||||
|
||||
return groupedReports
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package reports_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/0x6flab/namegenerator"
|
||||
"github.com/absmach/magistrala/internal/testsutil"
|
||||
pkglog "github.com/absmach/magistrala/pkg/logger"
|
||||
pkgSch "github.com/absmach/magistrala/pkg/schedule"
|
||||
remocks "github.com/absmach/magistrala/re/mocks"
|
||||
readmocks "github.com/absmach/magistrala/readers/mocks"
|
||||
"github.com/absmach/magistrala/reports"
|
||||
"github.com/absmach/magistrala/reports/mocks"
|
||||
"github.com/absmach/supermq/pkg/authn"
|
||||
"github.com/absmach/supermq/pkg/errors"
|
||||
repoerr "github.com/absmach/supermq/pkg/errors/repository"
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
"github.com/absmach/supermq/pkg/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
var (
|
||||
namegen = namegenerator.NewGenerator()
|
||||
userID = testsutil.GenerateUUID(&testing.T{})
|
||||
domainID = testsutil.GenerateUUID(&testing.T{})
|
||||
schedule = pkgSch.Schedule{
|
||||
StartDateTime: time.Now().Add(-time.Hour),
|
||||
Recurring: pkgSch.Daily,
|
||||
RecurringPeriod: 1,
|
||||
Time: time.Now().Add(-time.Hour),
|
||||
}
|
||||
reportName = namegen.Generate()
|
||||
rptConfig = reports.ReportConfig{
|
||||
ID: testsutil.GenerateUUID(&testing.T{}),
|
||||
Name: reportName,
|
||||
DomainID: domainID,
|
||||
Status: reports.EnabledStatus,
|
||||
Schedule: schedule,
|
||||
CreatedBy: userID,
|
||||
UpdatedBy: userID,
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
)
|
||||
|
||||
func newService(runInfo chan pkglog.RunInfo) (reports.Service, *mocks.Repository, *remocks.Ticker) {
|
||||
repo := new(mocks.Repository)
|
||||
mockTicker := new(remocks.Ticker)
|
||||
idProvider := uuid.NewMock()
|
||||
readersSvc := new(readmocks.ReadersServiceClient)
|
||||
e := new(remocks.Emailer)
|
||||
return reports.NewService(repo, runInfo, idProvider, mockTicker, e, readersSvc), repo, mockTicker
|
||||
}
|
||||
|
||||
func TestAddReportConfig(t *testing.T) {
|
||||
svc, repo, _ := newService(make(chan pkglog.RunInfo))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
session authn.Session
|
||||
cfg reports.ReportConfig
|
||||
res reports.ReportConfig
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "Add report config successfully",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
cfg: reports.ReportConfig{
|
||||
Name: reportName,
|
||||
Schedule: schedule,
|
||||
},
|
||||
res: rptConfig,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "Add report config with failed repo",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
cfg: reports.ReportConfig{
|
||||
Name: reportName,
|
||||
Schedule: schedule,
|
||||
},
|
||||
err: repoerr.ErrCreateEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("AddReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
|
||||
res, err := svc.AddReportConfig(context.Background(), tc.session, tc.cfg)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.NotEmpty(t, res.ID, "expected non-empty result in ID")
|
||||
assert.Equal(t, tc.cfg.Name, res.Name)
|
||||
assert.Equal(t, tc.cfg.Schedule, res.Schedule)
|
||||
}
|
||||
defer repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewReportConfig(t *testing.T) {
|
||||
svc, repo, _ := newService(make(chan pkglog.RunInfo))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
session authn.Session
|
||||
id string
|
||||
res reports.ReportConfig
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "view report config successfully",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
res: rptConfig,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "view report config with failed repo",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
err: svcerr.ErrViewEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("ViewReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
|
||||
res, err := svc.ViewReportConfig(context.Background(), tc.session, tc.id)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.res, res)
|
||||
}
|
||||
defer repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateReportConfig(t *testing.T) {
|
||||
svc, repo, _ := newService(make(chan pkglog.RunInfo))
|
||||
|
||||
newName := namegen.Generate()
|
||||
now := time.Now().Add(time.Hour)
|
||||
cases := []struct {
|
||||
desc string
|
||||
session authn.Session
|
||||
cfg reports.ReportConfig
|
||||
res reports.ReportConfig
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "update report config successfully",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
cfg: reports.ReportConfig{
|
||||
Name: newName,
|
||||
ID: rptConfig.ID,
|
||||
Schedule: schedule,
|
||||
},
|
||||
res: reports.ReportConfig{
|
||||
Name: newName,
|
||||
ID: rptConfig.ID,
|
||||
DomainID: rptConfig.DomainID,
|
||||
Status: rptConfig.Status,
|
||||
Schedule: rptConfig.Schedule,
|
||||
UpdatedAt: now,
|
||||
UpdatedBy: userID,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "update report config with failed repo",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
cfg: reports.ReportConfig{
|
||||
Name: rptConfig.Name,
|
||||
ID: rptConfig.ID,
|
||||
Schedule: schedule,
|
||||
},
|
||||
err: svcerr.ErrUpdateEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("UpdateReportConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
|
||||
res, err := svc.UpdateReportConfig(context.Background(), tc.session, tc.cfg)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.res, res)
|
||||
}
|
||||
defer repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListReportsConfig(t *testing.T) {
|
||||
svc, repo, _ := newService(make(chan pkglog.RunInfo))
|
||||
numConfigs := 50
|
||||
now := time.Now().Add(time.Hour)
|
||||
var configs []reports.ReportConfig
|
||||
for i := 0; i < numConfigs; i++ {
|
||||
c := reports.ReportConfig{
|
||||
ID: testsutil.GenerateUUID(t),
|
||||
Name: namegen.Generate(),
|
||||
DomainID: domainID,
|
||||
Status: reports.EnabledStatus,
|
||||
CreatedAt: now,
|
||||
CreatedBy: userID,
|
||||
Schedule: schedule,
|
||||
}
|
||||
configs = append(configs, c)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
session authn.Session
|
||||
pageMeta reports.PageMeta
|
||||
res reports.ReportConfigPage
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "list report configs successfully",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
pageMeta: reports.PageMeta{},
|
||||
res: reports.ReportConfigPage{
|
||||
PageMeta: reports.PageMeta{
|
||||
Total: uint64(numConfigs),
|
||||
Offset: 0,
|
||||
Limit: 10,
|
||||
},
|
||||
ReportConfigs: configs[0:10],
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list report configs successfully with limit",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
pageMeta: reports.PageMeta{
|
||||
Limit: 100,
|
||||
},
|
||||
res: reports.ReportConfigPage{
|
||||
PageMeta: reports.PageMeta{
|
||||
Total: uint64(numConfigs),
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
},
|
||||
ReportConfigs: configs[0:numConfigs],
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list report configs successfully with offset",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
pageMeta: reports.PageMeta{
|
||||
Offset: 20,
|
||||
Limit: 10,
|
||||
},
|
||||
res: reports.ReportConfigPage{
|
||||
PageMeta: reports.PageMeta{
|
||||
Total: uint64(numConfigs),
|
||||
Offset: 20,
|
||||
Limit: 10,
|
||||
},
|
||||
ReportConfigs: configs[20:30],
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "list report configs with failed repo",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
pageMeta: reports.PageMeta{},
|
||||
err: svcerr.ErrViewEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("ListReportsConfig", mock.Anything, mock.Anything).Return(tc.res, tc.err)
|
||||
res, err := svc.ListReportsConfig(context.Background(), tc.session, tc.pageMeta)
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.res, res)
|
||||
}
|
||||
defer repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveReportConfig(t *testing.T) {
|
||||
svc, repo, _ := newService(make(chan pkglog.RunInfo))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
session authn.Session
|
||||
id string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "remove report config successfully",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "remove report config with failed repo",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
err: svcerr.ErrRemoveEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("RemoveReportConfig", mock.Anything, mock.Anything).Return(tc.err)
|
||||
err := svc.RemoveReportConfig(context.Background(), tc.session, tc.id)
|
||||
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
defer repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnableReportConfig(t *testing.T) {
|
||||
svc, repo, _ := newService(make(chan pkglog.RunInfo))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
session authn.Session
|
||||
id string
|
||||
status reports.Status
|
||||
res reports.ReportConfig
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "enable report config successfully",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
status: reports.EnabledStatus,
|
||||
res: rptConfig,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "enable report config with failed repo",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
status: reports.EnabledStatus,
|
||||
err: svcerr.ErrUpdateEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("UpdateReportConfigStatus", context.Background(), mock.Anything).Return(tc.res, tc.err)
|
||||
res, err := svc.EnableReportConfig(context.Background(), tc.session, tc.id)
|
||||
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.res, res)
|
||||
}
|
||||
defer repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisableReportConfig(t *testing.T) {
|
||||
svc, repo, _ := newService(make(chan pkglog.RunInfo))
|
||||
|
||||
cases := []struct {
|
||||
desc string
|
||||
session authn.Session
|
||||
id string
|
||||
status reports.Status
|
||||
res reports.ReportConfig
|
||||
err error
|
||||
}{
|
||||
{
|
||||
desc: "disable report config successfully",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
status: reports.DisabledStatus,
|
||||
res: reports.ReportConfig{
|
||||
ID: rptConfig.ID,
|
||||
Name: rptConfig.Name,
|
||||
DomainID: rptConfig.DomainID,
|
||||
Status: reports.DisabledStatus,
|
||||
Schedule: schedule,
|
||||
UpdatedBy: userID,
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
desc: "disable report config with failed repo",
|
||||
session: authn.Session{
|
||||
UserID: userID,
|
||||
DomainID: domainID,
|
||||
},
|
||||
id: rptConfig.ID,
|
||||
status: reports.DisabledStatus,
|
||||
err: svcerr.ErrUpdateEntity,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
repoCall := repo.On("UpdateReportConfigStatus", mock.Anything, mock.Anything).Return(tc.res, tc.err)
|
||||
res, err := svc.DisableReportConfig(context.Background(), tc.session, tc.id)
|
||||
|
||||
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
|
||||
if err == nil {
|
||||
assert.Equal(t, tc.res, res)
|
||||
}
|
||||
defer repoCall.Unset()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) Abstract Machines
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package reports
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
svcerr "github.com/absmach/supermq/pkg/errors/service"
|
||||
)
|
||||
|
||||
// Status represents Rule status.
|
||||
type Status uint8
|
||||
|
||||
// Possible User status values.
|
||||
const (
|
||||
// EnabledStatus represents enabled Rule.
|
||||
EnabledStatus Status = iota
|
||||
// DisabledStatus represents disabled Rule.
|
||||
DisabledStatus
|
||||
// DeletedStatus represents a rule that will be deleted.
|
||||
DeletedStatus
|
||||
|
||||
// AllStatus is used for querying purposes to list rules irrespective
|
||||
// of their status - both enabled and disabled. It is never stored in the
|
||||
// database as the actual User status and should always be the largest
|
||||
// value in this enumeration.
|
||||
AllStatus
|
||||
)
|
||||
|
||||
// String representation of the possible status values.
|
||||
const (
|
||||
Disabled = "disabled"
|
||||
Enabled = "enabled"
|
||||
Deleted = "deleted"
|
||||
All = "all"
|
||||
Unknown = "unknown"
|
||||
)
|
||||
|
||||
func (s Status) String() string {
|
||||
switch s {
|
||||
case DisabledStatus:
|
||||
return Disabled
|
||||
case EnabledStatus:
|
||||
return Enabled
|
||||
case DeletedStatus:
|
||||
return Deleted
|
||||
case AllStatus:
|
||||
return All
|
||||
default:
|
||||
return Unknown
|
||||
}
|
||||
}
|
||||
|
||||
// ToStatus converts string value to a valid status.
|
||||
func ToStatus(status string) (Status, error) {
|
||||
switch status {
|
||||
case "", Enabled:
|
||||
return EnabledStatus, nil
|
||||
case Disabled:
|
||||
return DisabledStatus, nil
|
||||
case Deleted:
|
||||
return DeletedStatus, nil
|
||||
case All:
|
||||
return AllStatus, nil
|
||||
}
|
||||
return Status(0), svcerr.ErrInvalidStatus
|
||||
}
|
||||
|
||||
func (s Status) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(s.String())
|
||||
}
|
||||
|
||||
func (s *Status) UnmarshalJSON(data []byte) error {
|
||||
str := strings.Trim(string(data), "\"")
|
||||
val, err := ToStatus(str)
|
||||
*s = val
|
||||
return err
|
||||
}
|
||||
@@ -38,6 +38,10 @@ packages:
|
||||
interfaces:
|
||||
Service:
|
||||
Repository:
|
||||
github.com/absmach/magistrala/reports:
|
||||
interfaces:
|
||||
Service:
|
||||
Repository:
|
||||
github.com/absmach/magistrala/api/grpc/readers/v1:
|
||||
interfaces:
|
||||
ReadersServiceClient:
|
||||
|
||||
Reference in New Issue
Block a user