mirror of
https://github.com/absmach/supermq.git
synced 2026-06-23 07:00:25 +00:00
NOISSUE - Update reports to use chromedp (#249)
* initial implementation Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * switch to gotenberg Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * fix top bar Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * update env variable Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * address comments Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * update changes Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * update query Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * update method Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> * address commants Signed-off-by: nyagamunene <stevenyaga2014@gmail.com> --------- Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
This commit is contained in:
+1
-1
@@ -69,7 +69,7 @@ type config struct {
|
||||
TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"`
|
||||
BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"`
|
||||
DefaultTemplatePath string `env:"MG_REPORTS_DEFAULT_TEMPLATE" envDefault:""`
|
||||
ConverterURL string `env:"MG_REPORT_BROWSERLESS_URL" envDefault:"http://localhost:4000/pdf"`
|
||||
ConverterURL string `env:"MG_PDF_CONVERTER_URL" envDefault:"http://localhost:4000/pdf"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
.header-top-bar {
|
||||
height: 8px;
|
||||
background-color: var(--primary-color);
|
||||
margin: 0 -10mm 15px -10mm;
|
||||
margin: 5px 0 10px 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -154,7 +154,7 @@ MG_REPORTS_DB_SSL_ROOT_CERT=
|
||||
MG_REPORTS_INSTANCE_ID=
|
||||
MG_REPORTS_EMAIL_TEMPLATE=reports.tmpl
|
||||
MG_REPORTS_DEFAULT_TEMPLATE=
|
||||
MG_REPORT_BROWSERLESS_URL=http://browserless:3000/pdf
|
||||
MG_PDF_CONVERTER_URL=http://pdf-generator:3000/forms/chromium/convert/html
|
||||
|
||||
### Certs
|
||||
SMQ_ADDONS_CERTS_PATH_PREFIX=./
|
||||
|
||||
@@ -395,8 +395,8 @@ services:
|
||||
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}
|
||||
MG_REPORT_TEMPLATE_PATH: ${MG_REPORT_TEMPLATE_PATH}
|
||||
MG_REPORT_BROWSERLESS_URL: ${MG_REPORT_BROWSERLESS_URL}
|
||||
MG_REPORTS_DEFAULT_TEMPLATE: ${MG_REPORTS_DEFAULT_TEMPLATE}
|
||||
MG_PDF_CONVERTER_URL: ${MG_PDF_CONVERTER_URL}
|
||||
SMQ_MESSAGE_BROKER_URL: ${SMQ_MESSAGE_BROKER_URL}
|
||||
SMQ_JAEGER_URL: ${SMQ_JAEGER_URL}
|
||||
SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO}
|
||||
@@ -450,9 +450,9 @@ services:
|
||||
bind:
|
||||
create_host_path: true
|
||||
|
||||
browserless:
|
||||
image: browserless/chrome
|
||||
container_name: magistrala-browserless
|
||||
pdf-generator:
|
||||
image: gotenberg/gotenberg:${MG_RELEASE_TAG}
|
||||
container_name: magistrala-pdf
|
||||
ports:
|
||||
- "4000:3000"
|
||||
networks:
|
||||
|
||||
@@ -167,7 +167,7 @@ func (res emailReportResp) Empty() bool {
|
||||
}
|
||||
|
||||
type viewReportTemplateRes struct {
|
||||
Template string `json:"html_template"`
|
||||
Template reports.ReportTemplate `json:"html_template"`
|
||||
}
|
||||
|
||||
func (res viewReportTemplateRes) Code() int {
|
||||
|
||||
+38
-19
@@ -7,10 +7,10 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"sort"
|
||||
"time"
|
||||
@@ -77,29 +77,48 @@ func (r *report) generate(ctx context.Context, templateContent string, data Repo
|
||||
}
|
||||
|
||||
func (r *report) htmlToPDF(ctx context.Context, htmlContent string) ([]byte, error) {
|
||||
payload := map[string]interface{}{
|
||||
"html": htmlContent,
|
||||
"options": map[string]interface{}{
|
||||
"printBackground": true,
|
||||
"margin": map[string]string{
|
||||
"top": "0",
|
||||
"bottom": "0",
|
||||
"left": "0",
|
||||
"right": "0",
|
||||
},
|
||||
"preferCSSPageSize": true,
|
||||
},
|
||||
var requestBody bytes.Buffer
|
||||
writer := multipart.NewWriter(&requestBody)
|
||||
|
||||
htmlPart, err := writer.CreateFormFile("files", "index.html")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
jsonPayload, err := json.Marshal(payload)
|
||||
if _, err := htmlPart.Write([]byte(htmlContent)); err != nil {
|
||||
return nil, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
if err := writer.WriteField("marginTop", "0"); err != nil {
|
||||
return nil, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
if err := writer.WriteField("marginBottom", "0"); err != nil {
|
||||
return nil, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
if err := writer.WriteField("marginLeft", "0."); err != nil {
|
||||
return nil, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
if err := writer.WriteField("marginRight", "0"); err != nil {
|
||||
return nil, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
if err := writer.WriteField("printBackground", "true"); err != nil {
|
||||
return nil, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
if err := writer.WriteField("waitForSelector", "body"); err != nil {
|
||||
return nil, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
if err := writer.Close(); err != nil {
|
||||
return nil, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, r.converterURL, &requestBody)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, r.converterURL, bytes.NewReader(jsonPayload))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
|
||||
@@ -198,7 +198,7 @@ func (am *authorizationMiddleware) UpdateReportTemplate(ctx context.Context, ses
|
||||
return am.svc.UpdateReportTemplate(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (am *authorizationMiddleware) ViewReportTemplate(ctx context.Context, session authn.Session, id string) (string, error) {
|
||||
func (am *authorizationMiddleware) ViewReportTemplate(ctx context.Context, session authn.Session, id string) (reports.ReportTemplate, error) {
|
||||
if err := am.authorize(ctx, smqauthz.PolicyReq{
|
||||
Domain: session.DomainID,
|
||||
SubjectType: policies.UserType,
|
||||
|
||||
@@ -227,7 +227,7 @@ func (lm *loggingMiddleware) UpdateReportTemplate(ctx context.Context, session a
|
||||
return lm.svc.UpdateReportTemplate(ctx, session, cfg)
|
||||
}
|
||||
|
||||
func (lm *loggingMiddleware) ViewReportTemplate(ctx context.Context, session authn.Session, id string) (t string, err error) {
|
||||
func (lm *loggingMiddleware) ViewReportTemplate(ctx context.Context, session authn.Session, id string) (t reports.ReportTemplate, err error) {
|
||||
defer func(begin time.Time) {
|
||||
args := []any{
|
||||
slog.String("duration", time.Since(begin).String()),
|
||||
|
||||
@@ -570,22 +570,22 @@ func (_c *Repository_ViewReportConfig_Call) RunAndReturn(run func(ctx context.Co
|
||||
}
|
||||
|
||||
// ViewReportTemplate provides a mock function for the type Repository
|
||||
func (_mock *Repository) ViewReportTemplate(ctx context.Context, domainID string, reportID string) (string, error) {
|
||||
func (_mock *Repository) ViewReportTemplate(ctx context.Context, domainID string, reportID string) (reports.ReportTemplate, error) {
|
||||
ret := _mock.Called(ctx, domainID, reportID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ViewReportTemplate")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r0 reports.ReportTemplate
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) (string, error)); ok {
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) (reports.ReportTemplate, error)); ok {
|
||||
return returnFunc(ctx, domainID, reportID)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) string); ok {
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) reports.ReportTemplate); ok {
|
||||
r0 = returnFunc(ctx, domainID, reportID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
r0 = ret.Get(0).(reports.ReportTemplate)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
|
||||
r1 = returnFunc(ctx, domainID, reportID)
|
||||
@@ -615,12 +615,12 @@ func (_c *Repository_ViewReportTemplate_Call) Run(run func(ctx context.Context,
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ViewReportTemplate_Call) Return(s string, err error) *Repository_ViewReportTemplate_Call {
|
||||
_c.Call.Return(s, err)
|
||||
func (_c *Repository_ViewReportTemplate_Call) Return(reportTemplate reports.ReportTemplate, err error) *Repository_ViewReportTemplate_Call {
|
||||
_c.Call.Return(reportTemplate, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Repository_ViewReportTemplate_Call) RunAndReturn(run func(ctx context.Context, domainID string, reportID string) (string, error)) *Repository_ViewReportTemplate_Call {
|
||||
func (_c *Repository_ViewReportTemplate_Call) RunAndReturn(run func(ctx context.Context, domainID string, reportID string) (reports.ReportTemplate, error)) *Repository_ViewReportTemplate_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -678,22 +678,22 @@ func (_c *Service_ViewReportConfig_Call) RunAndReturn(run func(ctx context.Conte
|
||||
}
|
||||
|
||||
// ViewReportTemplate provides a mock function for the type Service
|
||||
func (_mock *Service) ViewReportTemplate(ctx context.Context, session authn.Session, id string) (string, error) {
|
||||
func (_mock *Service) ViewReportTemplate(ctx context.Context, session authn.Session, id string) (reports.ReportTemplate, error) {
|
||||
ret := _mock.Called(ctx, session, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ViewReportTemplate")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r0 reports.ReportTemplate
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) (string, error)); ok {
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) (reports.ReportTemplate, error)); ok {
|
||||
return returnFunc(ctx, session, id)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) string); ok {
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, authn.Session, string) reports.ReportTemplate); ok {
|
||||
r0 = returnFunc(ctx, session, id)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
r0 = ret.Get(0).(reports.ReportTemplate)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, authn.Session, string) error); ok {
|
||||
r1 = returnFunc(ctx, session, id)
|
||||
@@ -723,12 +723,12 @@ func (_c *Service_ViewReportTemplate_Call) Run(run func(ctx context.Context, ses
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ViewReportTemplate_Call) Return(s string, err error) *Service_ViewReportTemplate_Call {
|
||||
_c.Call.Return(s, err)
|
||||
func (_c *Service_ViewReportTemplate_Call) Return(reportTemplate reports.ReportTemplate, err error) *Service_ViewReportTemplate_Call {
|
||||
_c.Call.Return(reportTemplate, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Service_ViewReportTemplate_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) (string, error)) *Service_ViewReportTemplate_Call {
|
||||
func (_c *Service_ViewReportTemplate_Call) RunAndReturn(run func(ctx context.Context, session authn.Session, id string) (reports.ReportTemplate, error)) *Service_ViewReportTemplate_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ func (repo *PostgresRepository) UpdateReportDue(ctx context.Context, id string,
|
||||
|
||||
func (repo *PostgresRepository) UpdateReportTemplate(ctx context.Context, domainID, reportID string, template reports.ReportTemplate) error {
|
||||
q := `
|
||||
UPDATE report_configs
|
||||
UPDATE report_config
|
||||
SET report_template = :report_template, updated_at = :updated_at
|
||||
WHERE id = :id AND domain_id = :domain_id`
|
||||
|
||||
@@ -335,13 +335,13 @@ func (repo *PostgresRepository) UpdateReportTemplate(ctx context.Context, domain
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *PostgresRepository) ViewReportTemplate(ctx context.Context, domainID, reportID string) (string, error) {
|
||||
func (repo *PostgresRepository) ViewReportTemplate(ctx context.Context, domainID, reportID string) (reports.ReportTemplate, error) {
|
||||
q := `
|
||||
SELECT COALESCE(report_template, '') as report_template
|
||||
FROM report_configs
|
||||
FROM report_config
|
||||
WHERE id = $1 AND domain_id = $2`
|
||||
|
||||
var template string
|
||||
var template reports.ReportTemplate
|
||||
err := repo.DB.QueryRowxContext(ctx, q, reportID, domainID).Scan(&template)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
@@ -355,28 +355,21 @@ func (repo *PostgresRepository) ViewReportTemplate(ctx context.Context, domainID
|
||||
|
||||
func (repo *PostgresRepository) DeleteReportTemplate(ctx context.Context, domainID, reportID string) error {
|
||||
q := `
|
||||
UPDATE report_configs
|
||||
SET custom_template = NULL, updated_at = :updated_at
|
||||
WHERE id = :id AND domain_id = :domain_id`
|
||||
UPDATE report_config
|
||||
SET report_template = '', updated_at = :updated_at
|
||||
WHERE id = :id AND domain_id = :domain_id`
|
||||
|
||||
dbr := dbReport{
|
||||
ID: reportID,
|
||||
DomainID: domainID,
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}
|
||||
result, err := repo.DB.ExecContext(ctx, q, dbr)
|
||||
row, err := repo.DB.NamedQueryContext(ctx, q, dbr)
|
||||
if err != nil {
|
||||
return errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
return errors.Wrap(repoerr.ErrRemoveEntity, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.Wrap(repoerr.ErrUpdateEntity, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return repoerr.ErrNotFound
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+2
-2
@@ -397,7 +397,7 @@ type Repository interface {
|
||||
UpdateReportDue(ctx context.Context, id string, due time.Time) (ReportConfig, error)
|
||||
|
||||
UpdateReportTemplate(ctx context.Context, domainID, reportID string, template ReportTemplate) error
|
||||
ViewReportTemplate(ctx context.Context, domainID, reportID string) (string, error)
|
||||
ViewReportTemplate(ctx context.Context, domainID, reportID string) (ReportTemplate, error)
|
||||
DeleteReportTemplate(ctx context.Context, domainID, reportID string) error
|
||||
}
|
||||
|
||||
@@ -412,7 +412,7 @@ type Service interface {
|
||||
DisableReportConfig(ctx context.Context, session authn.Session, id string) (ReportConfig, error)
|
||||
|
||||
UpdateReportTemplate(ctx context.Context, session authn.Session, cfg ReportConfig) error
|
||||
ViewReportTemplate(ctx context.Context, session authn.Session, id string) (string, error)
|
||||
ViewReportTemplate(ctx context.Context, session authn.Session, id string) (ReportTemplate, error)
|
||||
DeleteReportTemplate(ctx context.Context, session authn.Session, id string) error
|
||||
|
||||
GenerateReport(ctx context.Context, session authn.Session, config ReportConfig, action ReportAction) (ReportPage, error)
|
||||
|
||||
+1
-1
@@ -434,7 +434,7 @@ func (r *report) UpdateReportTemplate(ctx context.Context, session authn.Session
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *report) ViewReportTemplate(ctx context.Context, session authn.Session, id string) (string, error) {
|
||||
func (r *report) ViewReportTemplate(ctx context.Context, session authn.Session, id string) (ReportTemplate, error) {
|
||||
template, err := r.repo.ViewReportTemplate(ctx, session.DomainID, id)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(svcerr.ErrCreateEntity, err)
|
||||
|
||||
Reference in New Issue
Block a user