diff --git a/resources/db/migration/V1.4.0.1__add_center_statistics.sql b/resources/db/migration/V1.4.0.1__add_center_statistics.sql new file mode 100644 index 0000000..b90d68f --- /dev/null +++ b/resources/db/migration/V1.4.0.1__add_center_statistics.sql @@ -0,0 +1,11 @@ +create table report_center_statistics +( + operator_uuid varchar(36) not null + references operators on delete cascade on update cascade, + center_uuid varchar(36) not null + references centers on delete cascade on update cascade, + subject varchar(128) not null, + count integer not null, + constraint report_center_statistics_pk + primary key (operator_uuid, center_uuid, subject) +); \ No newline at end of file diff --git a/src/api/operators.go b/src/api/operators.go index 8a0f42a..8b1471b 100644 --- a/src/api/operators.go +++ b/src/api/operators.go @@ -25,9 +25,11 @@ import ( "bytes" "com.t-systems-mms.cwa/api/model" "com.t-systems-mms.cwa/core/api" + "com.t-systems-mms.cwa/core/security" "com.t-systems-mms.cwa/core/util" "com.t-systems-mms.cwa/repositories" "com.t-systems-mms.cwa/services" + "encoding/csv" "github.com/go-chi/chi" "github.com/go-chi/jwtauth" "github.com/go-playground/validator" @@ -63,6 +65,13 @@ func NewOperatorsAPI(operatorsRepository repositories.Operators, operatorsServic r.Use(jwtauth.Authenticator) r.Get("/current", api.Handle(operators.GetCurrentOperator)) r.Put("/current", api.Handle(operators.SaveCurrentOperator)) + + r.Group(func(r chi.Router) { + r.Use(api.RequireRole(security.RoleAdmin)) + r.Get("/", api.Handle(operators.GetAllOperators)) + r.Get("/csv", operators.GetAllOperatorsAsCSV) + r.Delete("/{operator}", api.Handle(operators.DeleteOperator)) + }) }) return operators @@ -159,3 +168,62 @@ func (c *Operators) GetOperatorLogo(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusNotFound) } + +func (c *Operators) GetAllOperators(w http.ResponseWriter, r *http.Request) (interface{}, error) { + operators, err := c.operatorsRepository.FindAll(r.Context()) + if err != nil { + return nil, err + } + + response := make([]model.OperatorDTO, len(operators)) + for i, operator := range operators { + response[i] = *model.MapToOperatorDTO(&operator) + } + + return response, nil +} + +func (c *Operators) GetAllOperatorsAsCSV(w http.ResponseWriter, r *http.Request) { + operators, err := c.operatorsRepository.FindAll(r.Context()) + if err != nil { + logrus.WithError(err).Error("Error getting operators") + api.WriteError(w, r, err) + return + } + + w.Header().Set("Content-Type", "text/csv") + if _, err := w.Write([]byte{0xEF, 0xBB, 0xBF}); err != nil { + logrus.WithError(err).Error("Error writing BOM") + return + } + + csvWriter := csv.NewWriter(w) + csvWriter.Comma = ';' + + if err := csvWriter.Write([]string{"uuid", "subject", "number", "name", "email", "receiver"}); err != nil { + logrus.WithError(err).Error("Error writing response") + return + } + + for _, operator := range operators { + if err := csvWriter.Write([]string{ + operator.UUID, + util.PtrToString(operator.Subject, ""), + util.PtrToString(operator.OperatorNumber, ""), + operator.Name, + util.PtrToString(operator.Email, ""), + util.PtrToString(operator.BugReportsReceiver, ""), + }); err != nil { + logrus.WithError(err).Error("Error writing response") + return + } + } + csvWriter.Flush() +} + +func (c *Operators) DeleteOperator(w http.ResponseWriter, r *http.Request) (interface{}, error) { + id := chi.URLParam(r, "operator") + logrus.WithField("operator", id).Info("Deleting operator") + + return nil, c.operatorsRepository.Delete(r.Context(), id) +} diff --git a/src/api/statistics.go b/src/api/statistics.go index 829bc8f..2f9c06e 100644 --- a/src/api/statistics.go +++ b/src/api/statistics.go @@ -52,10 +52,88 @@ func NewStatisticsAPI(reportsRepository repositories.BugReports, auth *jwtauth.J r.Use(api.RequireRole(security.RoleAdmin)) r.Get("/reports", statistics.getReportStatistics) + r.Get("/reports/centers", statistics.getCenterReportsStatistics) }) return statistics } +func (c *Statistics) getCenterReportsStatistics(w http.ResponseWriter, r *http.Request) { + stats, err := c.reportsRepository.GetCenterStatistics(r.Context()) + if err != nil { + logrus.WithError(err).Error("Error getting report statistics") + w.WriteHeader(http.StatusInternalServerError) + return + } + + subjects := make([]string, 0) + operators := make(map[string]domain.Operator) + centers := make(map[string]domain.Center) + data := make(map[string]map[string]map[string]uint) + for _, value := range stats { + knownSubject := false + for _, s := range subjects { + if s == value.Subject { + knownSubject = true + break + } + } + + if !knownSubject { + subjects = append(subjects, value.Subject) + } + + if _, ok := data[value.OperatorUUID]; !ok { + data[value.OperatorUUID] = make(map[string]map[string]uint) + operators[value.OperatorUUID] = *value.Operator + } + + if _, ok := data[value.OperatorUUID][value.CenterUUID]; !ok { + data[value.OperatorUUID][value.CenterUUID] = make(map[string]uint) + centers[value.CenterUUID] = *value.Center + } + + data[value.OperatorUUID][value.CenterUUID][value.Subject] = value.Count + } + + w.Header().Set("Content-Type", "text/csv") + if _, err := w.Write([]byte{0xEF, 0xBB, 0xBF}); err != nil { + logrus.WithError(err).Error("Error writing BOM") + return + } + + csvWriter := csv.NewWriter(w) + csvWriter.Comma = ';' + + headers := []string{"partner_uuid", "partner_number", "partner_name", "center_uuid", "center_name"} + headers = append(headers, subjects...) + if err := csvWriter.Write(headers); err != nil { + logrus.WithError(err).Error("Error writing response") + return + } + + for operator, operatorCenters := range data { + for center, entry := range operatorCenters { + columns := []string{ + operator, + util.PtrToString(operators[operator].OperatorNumber, ""), + operators[operator].Name, + center, + centers[center].Name, + } + for _, subject := range subjects { + columns = append(columns, strconv.Itoa(int(entry[subject]))) + } + + if err := csvWriter.Write(columns); err != nil { + logrus.WithError(err).Error("Error writing response") + return + } + } + } + + csvWriter.Flush() +} + func (c *Statistics) getReportStatistics(w http.ResponseWriter, r *http.Request) { stats, err := c.reportsRepository.GetStatistics(r.Context()) if err != nil { @@ -97,7 +175,7 @@ func (c *Statistics) getReportStatistics(w http.ResponseWriter, r *http.Request) csvWriter := csv.NewWriter(w) csvWriter.Comma = ';' - headers := []string{"partner_uuid", "partner_name", "partner_number"} + headers := []string{"partner_uuid", "partner_number", "partner_name"} headers = append(headers, subjects...) if err := csvWriter.Write(headers); err != nil { logrus.WithError(err).Error("Error writing response") diff --git a/src/repositories/bugreports.go b/src/repositories/bugreports.go index d69cbf0..85513f4 100644 --- a/src/repositories/bugreports.go +++ b/src/repositories/bugreports.go @@ -37,6 +37,15 @@ type ReportStatistics struct { Count uint } +type ReportCenterStatistics struct { + Subject string + OperatorUUID string + Operator *domain.Operator `gorm:"foreignKey:OperatorUUID"` + CenterUUID string + Center *domain.Center `gorm:"foreignKey:CenterUUID"` + Count uint +} + type BugReports interface { Repository Save(ctx context.Context, center *domain.BugReport) error @@ -47,8 +56,9 @@ type BugReports interface { FindAllByLeader(ctx context.Context, leader string) ([]domain.BugReport, error) ResetLeader(ctx context.Context, leader string) error - IncrementReportCount(ctx context.Context, operatorUUID, subject string) error + IncrementReportCount(ctx context.Context, operatorUUID, centerUUID, subject string) error GetStatistics(ctx context.Context) ([]ReportStatistics, error) + GetCenterStatistics(ctx context.Context) ([]ReportCenterStatistics, error) } type bugReportsRepository struct { @@ -105,11 +115,20 @@ func (b *bugReportsRepository) FindAll(ctx context.Context) ([]domain.BugReport, return reports, err } -func (b *bugReportsRepository) IncrementReportCount(ctx context.Context, operatorUUID, subject string) error { - return b.GetTX(ctx).Exec("insert into report_statistics (operator_uuid, subject, count)"+ +func (b *bugReportsRepository) IncrementReportCount(ctx context.Context, operatorUUID, centerUUID, subject string) error { + err := b.GetTX(ctx).Exec("insert into report_statistics (operator_uuid, subject, count) "+ "VALUES (?, ?, 1)"+ "on conflict on constraint report_statistics_pk "+ "do update set count = report_statistics.count + 1", operatorUUID, subject).Error + + if err != nil { + return err + } + + return b.GetTX(ctx).Exec("insert into report_center_statistics (operator_uuid, center_uuid, subject, count) "+ + "VALUES (?, ?, ?, 1)"+ + "on conflict on constraint report_center_statistics_pk "+ + "do update set count = report_center_statistics.count + 1", operatorUUID, centerUUID, subject).Error } func (b *bugReportsRepository) GetStatistics(ctx context.Context) ([]ReportStatistics, error) { @@ -122,3 +141,15 @@ func (b *bugReportsRepository) GetStatistics(ctx context.Context) ([]ReportStati return statistics, err } + +func (b *bugReportsRepository) GetCenterStatistics(ctx context.Context) ([]ReportCenterStatistics, error) { + var statistics []ReportCenterStatistics + err := b.GetTX(ctx). + Preload("Operator"). + Preload("Center"). + Order("operator_uuid"). + Find(&statistics). + Error + + return statistics, err +} diff --git a/src/repositories/operators.go b/src/repositories/operators.go index 6054a47..a28ea72 100644 --- a/src/repositories/operators.go +++ b/src/repositories/operators.go @@ -36,19 +36,22 @@ type OperatorsStatistics struct { } type Operators interface { + Repository FindById(ctx context.Context, id string) (domain.Operator, error) GetOrCreateByToken(ctx context.Context, subject jwt.Token) (domain.Operator, error) Save(ctx context.Context, operator domain.Operator) (domain.Operator, error) FindStatistics(ctx context.Context) (OperatorsStatistics, error) + FindAll(ctx context.Context) ([]domain.Operator, error) + Delete(ctx context.Context, id string) error } type operatorsRepository struct { - db *gorm.DB + postgresqlRepository } func NewOperatorsRepository(db *gorm.DB) Operators { return &operatorsRepository{ - db: db, + postgresqlRepository{db: db}, } } @@ -129,3 +132,17 @@ func (r *operatorsRepository) FindStatistics(ctx context.Context) (OperatorsStat Error return statistics, err } + +func (r *operatorsRepository) FindAll(ctx context.Context) ([]domain.Operator, error) { + var result []domain.Operator + err := r.db. + Model(&domain.Operator{}). + Order("uuid"). + Find(&result).Error + + return result, err +} + +func (r *operatorsRepository) Delete(ctx context.Context, id string) error { + return r.GetTX(ctx).Exec("DELETE FROM operators WHERE uuid = ?", id).Error +} diff --git a/src/services/bugreports.go b/src/services/bugreports.go index 6e5c10b..2194ae8 100644 --- a/src/services/bugreports.go +++ b/src/services/bugreports.go @@ -132,7 +132,7 @@ func (s *bugReportsService) CreateBugReport(ctx context.Context, centerUUID, sub return report, err } - if err := s.bugReportsRepository.IncrementReportCount(ctx, center.OperatorUUID, report.Subject); err != nil { + if err := s.bugReportsRepository.IncrementReportCount(ctx, center.OperatorUUID, center.UUID, report.Subject); err != nil { logrus.WithError(err).Error("Error updating report statistics") }