* TP-40559 : Incident logs (#227) * TP-40559 : Adding BeforeUpdate and AfterUpdate hook in Incident Entity * TP-40936 - Added deep compare util function * TP-40559 | Added entity and repo for logs * TP-40559 : Added log entry support for incident level updates * Fix zero diff issue * Added lowercase json parameter names * TP-40559 : Added logs for team level updates * Initialize log service * TP-41640 : Added api to fetch logs for particular incident/team * Before create and after create * Convert 2 logs to one on incident creation * Log id populate: * Add populate for team id * Branch update changes * Typo changes * PR REVIEW CHANGES * Nil fix * Fix order issue * TP-43841 | Updating fetch users from conversation to differentiate memeber and others (#245) * Build fix * Added migration script for logs --------- Co-authored-by: Sriram Bhargav <sriram.bhargav@navi.com>
This commit is contained in:
@@ -2,24 +2,24 @@ package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"houston/internal/cron"
|
||||
"houston/internal/diagnostic"
|
||||
"houston/internal/processor"
|
||||
"houston/internal/resolver"
|
||||
"houston/model/incident"
|
||||
"houston/model/severity"
|
||||
"houston/model/shedlock"
|
||||
"houston/model/tag"
|
||||
"houston/model/team"
|
||||
"houston/model/user"
|
||||
"houston/pkg/slackbot"
|
||||
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/slack-go/slack/slackevents"
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
"houston/internal/cron"
|
||||
"houston/internal/diagnostic"
|
||||
"houston/internal/processor"
|
||||
"houston/internal/resolver"
|
||||
"houston/model/incident"
|
||||
"houston/model/log"
|
||||
"houston/model/severity"
|
||||
"houston/model/shedlock"
|
||||
"houston/model/tag"
|
||||
"houston/model/team"
|
||||
"houston/model/user"
|
||||
"houston/pkg/slackbot"
|
||||
)
|
||||
|
||||
type slackHandler struct {
|
||||
@@ -35,9 +35,10 @@ type slackHandler struct {
|
||||
|
||||
func NewSlackHandler(logger *zap.Logger, gormClient *gorm.DB, socketModeClient *socketmode.Client) *slackHandler {
|
||||
severityService := severity.NewSeverityRepository(logger, gormClient)
|
||||
incidentService := incident.NewIncidentRepository(logger, gormClient, severityService)
|
||||
logRepository := log.NewLogRepository(logger, gormClient)
|
||||
tagService := tag.NewTagRepository(logger, gormClient)
|
||||
teamService := team.NewTeamRepository(logger, gormClient)
|
||||
teamService := team.NewTeamRepository(logger, gormClient, logRepository)
|
||||
incidentService := incident.NewIncidentRepository(logger, gormClient, severityService, logRepository, teamService, socketModeClient)
|
||||
userService := user.NewUserRepository(logger, gormClient)
|
||||
shedlockService := shedlock.NewShedlockRepository(logger, gormClient)
|
||||
slackbotClient := slackbot.NewSlackClient(logger, socketModeClient)
|
||||
|
||||
@@ -43,6 +43,7 @@ func (s *Server) Handler(houstonGroup *gin.RouterGroup) {
|
||||
s.teamHandler(houstonGroup)
|
||||
s.severityHandler(houstonGroup)
|
||||
s.incidentHandler(houstonGroup)
|
||||
s.logHandler(houstonGroup)
|
||||
s.usersHandler(houstonGroup)
|
||||
s.filtersHandler(houstonGroup)
|
||||
|
||||
@@ -141,6 +142,11 @@ func (s *Server) incidentHandler(houstonGroup *gin.RouterGroup) {
|
||||
houstonGroup.GET("/teamIncidents/:teamId", incidentHandler.GetTeamIncidents)
|
||||
}
|
||||
|
||||
func (s *Server) logHandler(houstonGroup *gin.RouterGroup) {
|
||||
logHandler := service.NewLogService(s.gin, s.logger, s.db)
|
||||
houstonGroup.GET("/logs/:log_type/:id", logHandler.GetLogs)
|
||||
}
|
||||
|
||||
func (s *Server) usersHandler(houstonGroup *gin.RouterGroup) {
|
||||
houstonClient := NewHoustonClient(s.logger)
|
||||
slackClient := slackbot.NewSlackClient(s.logger, houstonClient.socketModeClient)
|
||||
|
||||
@@ -141,3 +141,11 @@ func PostMessageToIncidentChannel(message string, channelId string, client *sock
|
||||
_, _, errMessage := client.PostMessage(channelId, msgOption)
|
||||
return errMessage
|
||||
}
|
||||
|
||||
func ConvertSliceToMapOfString(input []string) map[string]string {
|
||||
output := make(map[string]string)
|
||||
for _, item := range input {
|
||||
output[item] = item
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
9
db/migration/000002_add_log_schema.up.sql
Normal file
9
db/migration/000002_add_log_schema.up.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE if not exists log
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
relation_name character varying(255),
|
||||
record_id integer,
|
||||
created_at timestamp without time zone,
|
||||
changes jsonb,
|
||||
user_info jsonb
|
||||
);
|
||||
@@ -1,8 +1,13 @@
|
||||
package incident
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
"houston/model/log"
|
||||
"houston/model/severity"
|
||||
"houston/model/team"
|
||||
utils "houston/service/utils"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -15,13 +20,25 @@ type Repository struct {
|
||||
logger *zap.Logger
|
||||
gormClient *gorm.DB
|
||||
severityRepository *severity.Repository
|
||||
logRepository *log.Repository
|
||||
teamRepository *team.Repository
|
||||
socketModeClient *socketmode.Client
|
||||
}
|
||||
|
||||
func NewIncidentRepository(logger *zap.Logger, gormClient *gorm.DB, severityService *severity.Repository) *Repository {
|
||||
var valueBeforeUpdate IncidentEntity
|
||||
var valueAfterUpdate IncidentEntity
|
||||
var valueBeforeCreate IncidentEntity
|
||||
var valueAfterCreate IncidentEntity
|
||||
var differences []utils.Difference
|
||||
|
||||
func NewIncidentRepository(logger *zap.Logger, gormClient *gorm.DB, severityService *severity.Repository, logRepository *log.Repository, teamRepository *team.Repository, socketModeClient *socketmode.Client) *Repository {
|
||||
return &Repository{
|
||||
logger: logger,
|
||||
gormClient: gormClient,
|
||||
severityRepository: severityService,
|
||||
logRepository: logRepository,
|
||||
teamRepository: teamRepository,
|
||||
socketModeClient: socketModeClient,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,12 +78,141 @@ func (r *Repository) CreateIncidentEntity(request *CreateIncidentDTO) (*Incident
|
||||
return incidentEntity, nil
|
||||
}
|
||||
|
||||
func (i *IncidentEntity) AfterCreate(tx *gorm.DB) (err error) {
|
||||
println(fmt.Sprintf("AfterUpdate executed at: %v", time.Now()))
|
||||
valueBeforeCreate = IncidentEntity{}
|
||||
valueAfterCreate = IncidentEntity{}
|
||||
if err := tx.First(&valueAfterCreate, i.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
println(fmt.Sprintf("incident entity after created is: %s", valueAfterCreate))
|
||||
differences = utils.DeepCompare(valueBeforeCreate, valueAfterCreate)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *IncidentEntity) BeforeUpdate(tx *gorm.DB) (err error) {
|
||||
println(fmt.Sprintf("BeforeUpdate executed at: %v", time.Now()))
|
||||
valueBeforeUpdate = IncidentEntity{}
|
||||
if err := tx.First(&valueBeforeUpdate, i.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
println(fmt.Sprintf("incident entity before update is: %s", valueBeforeUpdate))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *IncidentEntity) AfterUpdate(tx *gorm.DB) (err error) {
|
||||
println(fmt.Sprintf("AfterUpdate executed at: %v", time.Now()))
|
||||
valueAfterUpdate = IncidentEntity{}
|
||||
if err := tx.First(&valueAfterUpdate, i.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
println(fmt.Sprintf("incident entity after updated is: %s", valueAfterUpdate))
|
||||
differences = append(differences, utils.DeepCompare(valueBeforeUpdate, valueAfterUpdate)...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) processDiffIds() []byte {
|
||||
var jsonDiff []byte
|
||||
|
||||
for index := range differences {
|
||||
switch differences[index].Attribute {
|
||||
case "Status":
|
||||
if differences[index].From != "" {
|
||||
statusIdString, _ := strconv.Atoi(differences[index].From)
|
||||
statusEntity, _ := r.GetIncidentStatusNameByStatus(uint(statusIdString))
|
||||
if statusEntity != nil {
|
||||
differences[index].From = statusEntity.Name
|
||||
}
|
||||
}
|
||||
|
||||
statusIdString, _ := strconv.Atoi(differences[index].To)
|
||||
statusEntity, _ := r.GetIncidentStatusNameByStatus(uint(statusIdString))
|
||||
differences[index].To = statusEntity.Name
|
||||
|
||||
case "SeverityId":
|
||||
if differences[index].From != "" {
|
||||
severityIdString, _ := strconv.Atoi(differences[index].From)
|
||||
severityEntity, _ := r.severityRepository.FindSeverityById(uint(severityIdString))
|
||||
if severityEntity != nil {
|
||||
differences[index].From = severityEntity.Name
|
||||
}
|
||||
}
|
||||
|
||||
severityIdString, _ := strconv.Atoi(differences[index].To)
|
||||
severityEntity, _ := r.severityRepository.FindSeverityById(uint(severityIdString))
|
||||
differences[index].To = severityEntity.Name
|
||||
|
||||
case "TeamId":
|
||||
if differences[index].From != "" {
|
||||
teamIdString, _ := strconv.Atoi(differences[index].From)
|
||||
teamEntity, _ := r.teamRepository.FindTeamById(uint(teamIdString))
|
||||
if teamEntity != nil {
|
||||
differences[index].From = teamEntity.Name
|
||||
}
|
||||
}
|
||||
teamIdString, _ := strconv.Atoi(differences[index].To)
|
||||
teamEntity, _ := r.teamRepository.FindTeamById(uint(teamIdString))
|
||||
if teamEntity != nil {
|
||||
differences[index].To = teamEntity.Name
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
jsonDiff, _ = json.Marshal(differences)
|
||||
|
||||
return jsonDiff
|
||||
}
|
||||
|
||||
func (r *Repository) processUserInfo() ([]byte, error) {
|
||||
user, err := r.socketModeClient.GetUsersInfo(valueAfterUpdate.UpdatedBy)
|
||||
|
||||
if err != nil {
|
||||
errorMessage := fmt.Sprintf("failed to get user info from slack for userID: %s", valueAfterUpdate.UpdatedBy)
|
||||
r.logger.Error(errorMessage)
|
||||
return nil, fmt.Errorf("%s. Error: %v", errorMessage, err)
|
||||
}
|
||||
|
||||
if len(*user) != 0 {
|
||||
userData := (*user)[0]
|
||||
userInfo := log.UserInfo{
|
||||
Id: userData.ID,
|
||||
Email: userData.Profile.Email,
|
||||
Name: userData.Profile.RealName,
|
||||
}
|
||||
return json.Marshal(userInfo)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%s is not a valid user", valueAfterUpdate.UpdatedBy)
|
||||
}
|
||||
|
||||
func (r *Repository) captureLogs() {
|
||||
if differences != nil && len(differences) > 0 {
|
||||
jsonUser, _ := r.processUserInfo()
|
||||
|
||||
jsonDiff := r.processDiffIds()
|
||||
|
||||
r.logRepository.CreateLog(
|
||||
&log.LogEntity{
|
||||
CreatedAt: time.Now(),
|
||||
RelationName: "incident",
|
||||
RecordId: valueAfterUpdate.ID,
|
||||
UserInfo: jsonUser,
|
||||
Changes: jsonDiff,
|
||||
})
|
||||
|
||||
differences = []utils.Difference{}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Repository) UpdateIncident(incidentEntity *IncidentEntity) error {
|
||||
result := r.gormClient.Updates(incidentEntity)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
r.captureLogs()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
24
model/log/entity.go
Normal file
24
model/log/entity.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"gorm.io/datatypes"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LogEntity struct {
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
RelationName string `gorm:"column:relation_name"`
|
||||
RecordId uint `gorm:"column:record_id"`
|
||||
UserInfo datatypes.JSON `gorm:"column:user_info"`
|
||||
Changes datatypes.JSON `gorm:"column:changes"`
|
||||
}
|
||||
|
||||
func (LogEntity) TableName() string {
|
||||
return "log"
|
||||
}
|
||||
|
||||
type UserInfo struct {
|
||||
Id string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
48
model/log/log.go
Normal file
48
model/log/log.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
logger *zap.Logger
|
||||
gormClient *gorm.DB
|
||||
}
|
||||
|
||||
func NewLogRepository(logger *zap.Logger, gormClient *gorm.DB) *Repository {
|
||||
return &Repository{
|
||||
logger: logger,
|
||||
gormClient: gormClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Repository) CreateLog(logEntity *LogEntity) (*LogEntity, error) {
|
||||
result := r.gormClient.Create(logEntity)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return logEntity, nil
|
||||
}
|
||||
|
||||
func (r *Repository) FetchLogsByRelationNameAndRecordId(relationName string, recordId uint) ([]LogEntity, error) {
|
||||
var query = r.gormClient.Model([]LogEntity{})
|
||||
var logEntity []LogEntity
|
||||
|
||||
if len(relationName) != 0 {
|
||||
query = query.Where("relation_name = ?", relationName)
|
||||
}
|
||||
|
||||
query = query.Where("record_id = ?", recordId)
|
||||
|
||||
query = query.Order("created_at ASC")
|
||||
|
||||
result := query.Find(&logEntity)
|
||||
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
return logEntity, nil
|
||||
}
|
||||
@@ -1,19 +1,30 @@
|
||||
package team
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
"houston/model/log"
|
||||
utils "houston/service/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
logger *zap.Logger
|
||||
gormClient *gorm.DB
|
||||
logger *zap.Logger
|
||||
gormClient *gorm.DB
|
||||
logRepository *log.Repository
|
||||
}
|
||||
|
||||
func NewTeamRepository(logger *zap.Logger, gormClient *gorm.DB) *Repository {
|
||||
var valueBeforeUpdate TeamEntity
|
||||
var valueAfterUpdate TeamEntity
|
||||
var differences []utils.Difference
|
||||
|
||||
func NewTeamRepository(logger *zap.Logger, gormClient *gorm.DB, logRepository *log.Repository) *Repository {
|
||||
return &Repository{
|
||||
logger: logger,
|
||||
gormClient: gormClient,
|
||||
logger: logger,
|
||||
gormClient: gormClient,
|
||||
logRepository: logRepository,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,11 +74,57 @@ func (r *Repository) FindTeamByTeamName(teamName string) (*TeamEntity, error) {
|
||||
return &teamEntity, nil
|
||||
}
|
||||
|
||||
func (t *TeamEntity) BeforeUpdate(tx *gorm.DB) (err error) {
|
||||
println(fmt.Sprintf("BeforeUpdate executed at: %v", time.Now()))
|
||||
valueBeforeUpdate = TeamEntity{}
|
||||
if err := tx.First(&valueBeforeUpdate, t.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
println(fmt.Sprintf("team entity before update is: %s", valueBeforeUpdate))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TeamEntity) AfterUpdate(tx *gorm.DB) (err error) {
|
||||
println(fmt.Sprintf("AfterUpdate executed at: %v", time.Now()))
|
||||
valueAfterUpdate = TeamEntity{}
|
||||
if err := tx.First(&valueAfterUpdate, t.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
println(fmt.Sprintf("team entity after updated is: %s", valueAfterUpdate))
|
||||
differences = utils.DeepCompare(valueBeforeUpdate, valueAfterUpdate)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) CaptureLogs() {
|
||||
if differences != nil && len(differences) > 0 {
|
||||
jsonDiff, _ := json.Marshal(differences)
|
||||
|
||||
jsonUser, _ := json.Marshal(log.UserInfo{
|
||||
Id: "",
|
||||
Email: valueAfterUpdate.UpdatedBy,
|
||||
Name: "",
|
||||
})
|
||||
|
||||
r.logRepository.CreateLog(
|
||||
&log.LogEntity{
|
||||
CreatedAt: time.Now(),
|
||||
RelationName: "team",
|
||||
RecordId: valueAfterUpdate.ID,
|
||||
Changes: jsonDiff,
|
||||
UserInfo: jsonUser,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Repository) UpdateTeam(teamEntity *TeamEntity) error {
|
||||
result := r.gormClient.Updates(teamEntity)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
r.CaptureLogs()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -76,6 +133,9 @@ func (r *Repository) UpdateTeamStatus(teamEntity *TeamEntity) error {
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
r.CaptureLogs()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -55,3 +55,12 @@ func (r *Repository) IsAHoustonUser(nameOrSlackUserId string) (bool, *UserEntity
|
||||
}
|
||||
return true, &existingUser
|
||||
}
|
||||
|
||||
func (r *Repository) GetHoustonUsersBySlackId(slackUserId []string) (*[]UserEntity, error) {
|
||||
var users []UserEntity
|
||||
result := r.gormClient.Where("slack_user_id IN ?", slackUserId).Find(&users)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
return &users, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"houston/model/incident"
|
||||
"houston/model/log"
|
||||
"houston/model/severity"
|
||||
"houston/model/team"
|
||||
response "houston/service/response"
|
||||
@@ -60,8 +61,9 @@ func (f *filterService) GetFilters(c *gin.Context) {
|
||||
func (f *filterService) GetEntityRepositories() (
|
||||
*incident.Repository, *severity.Repository, *team.Repository) {
|
||||
severityRepository := severity.NewSeverityRepository(f.logger, f.db)
|
||||
incidentRepository := incident.NewIncidentRepository(f.logger, f.db, severityRepository)
|
||||
teamRespository := team.NewTeamRepository(f.logger, f.db)
|
||||
logRepository := log.NewLogRepository(f.logger, f.db)
|
||||
teamRespository := team.NewTeamRepository(f.logger, f.db, logRepository)
|
||||
incidentRepository := incident.NewIncidentRepository(f.logger, f.db, severityRepository, logRepository, teamRespository, nil)
|
||||
return incidentRepository, severityRepository, teamRespository
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"houston/common/util"
|
||||
"houston/internal/processor/action/view"
|
||||
"houston/model/incident"
|
||||
"houston/model/log"
|
||||
"houston/model/severity"
|
||||
"houston/model/team"
|
||||
"houston/model/user"
|
||||
@@ -36,11 +37,14 @@ type IncidentServiceV2 struct {
|
||||
}
|
||||
|
||||
func NewIncidentServiceV2(logger *zap.Logger, db *gorm.DB) *IncidentServiceV2 {
|
||||
teamRepository := team.NewTeamRepository(logger, db)
|
||||
logRepository := log.NewLogRepository(logger, db)
|
||||
teamRepository := team.NewTeamRepository(logger, db, logRepository)
|
||||
severityRepository := severity.NewSeverityRepository(logger, db)
|
||||
incidentRepository := incident.NewIncidentRepository(logger, db, severityRepository)
|
||||
userRepository := user.NewUserRepository(logger, db)
|
||||
slackService := slack.NewSlackService(logger)
|
||||
incidentRepository := incident.NewIncidentRepository(
|
||||
logger, db, severityRepository, logRepository, teamRepository, slackService.SocketModeClient,
|
||||
)
|
||||
return &IncidentServiceV2{
|
||||
logger: logger,
|
||||
db: db,
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"houston/internal/processor/action"
|
||||
"houston/internal/processor/action/view"
|
||||
"houston/model/incident"
|
||||
"houston/model/log"
|
||||
"houston/model/severity"
|
||||
"houston/model/team"
|
||||
"houston/model/user"
|
||||
@@ -45,9 +46,10 @@ type incidentService struct {
|
||||
}
|
||||
|
||||
func NewIncidentService(gin *gin.Engine, logger *zap.Logger, db *gorm.DB, socketModeClient *socketmode.Client) *incidentService {
|
||||
teamRepository := team.NewTeamRepository(logger, db)
|
||||
severityRepository := severity.NewSeverityRepository(logger, db)
|
||||
incidentRepository := incident.NewIncidentRepository(logger, db, severityRepository)
|
||||
logRepository := log.NewLogRepository(logger, db)
|
||||
teamRepository := team.NewTeamRepository(logger, db, logRepository)
|
||||
incidentRepository := incident.NewIncidentRepository(logger, db, severityRepository, logRepository, teamRepository, socketModeClient)
|
||||
userRepository := user.NewUserRepository(logger, db)
|
||||
messageUpdateAction := action.NewIncidentChannelMessageUpdateAction(
|
||||
socketModeClient, logger, incidentRepository, teamRepository, severityRepository)
|
||||
|
||||
60
service/log_service.go
Normal file
60
service/log_service.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
"houston/model/log"
|
||||
service "houston/service/response"
|
||||
common "houston/service/response/common"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type logService struct {
|
||||
gin *gin.Engine
|
||||
logger *zap.Logger
|
||||
db *gorm.DB
|
||||
logRepository *log.Repository
|
||||
}
|
||||
|
||||
func NewLogService(gin *gin.Engine, logger *zap.Logger, db *gorm.DB) *logService {
|
||||
logRepository := log.NewLogRepository(logger, db)
|
||||
return &logService{
|
||||
gin: gin,
|
||||
logger: logger,
|
||||
db: db,
|
||||
logRepository: logRepository,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *logService) GetLogs(c *gin.Context) {
|
||||
logType := c.Param("log_type")
|
||||
id := c.Param("id")
|
||||
|
||||
if len(logType) == 0 {
|
||||
l.logger.Error("Log Type not provided")
|
||||
c.JSON(http.StatusBadRequest, common.ErrorResponse(errors.New("Log type not provided"), http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
|
||||
recordId, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
l.logger.Error("error in converting string to int", zap.String("id", id), zap.Error(err))
|
||||
c.JSON(http.StatusBadGateway, common.ErrorResponse(errors.New("Invalid record id"), http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
|
||||
logs, err := l.logRepository.FetchLogsByRelationNameAndRecordId(logType, uint(recordId))
|
||||
|
||||
if err != nil {
|
||||
l.logger.Error("error in fetching logs by relation name and record", zap.String("id", id), zap.String("relation_name", logType), zap.Error(err))
|
||||
c.JSON(http.StatusBadGateway, common.ErrorResponse(errors.New("Error in fetching logs"), http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
|
||||
logResponse := service.ConvertToLogResponse(logs, logType, uint(recordId))
|
||||
|
||||
c.JSON(http.StatusOK, common.SuccessResponse(logResponse, http.StatusOK))
|
||||
}
|
||||
38
service/response/log_response.go
Normal file
38
service/response/log_response.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"gorm.io/datatypes"
|
||||
"houston/model/log"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LogResponse struct {
|
||||
RelationName string `json:"relation_name"`
|
||||
RecordId uint `json:"record_id"`
|
||||
Logs []LogEntry `json:"logs"`
|
||||
}
|
||||
|
||||
type LogEntry struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UserInfo datatypes.JSON `json:"user_info"`
|
||||
Changes datatypes.JSON `json:"changes"`
|
||||
}
|
||||
|
||||
func ConvertToLogResponse(logEnties []log.LogEntity, relationName string, recordId uint) LogResponse {
|
||||
var logs []LogEntry
|
||||
|
||||
for index := range logEnties {
|
||||
logEntry := LogEntry{
|
||||
CreatedAt: logEnties[index].CreatedAt,
|
||||
UserInfo: logEnties[index].UserInfo,
|
||||
Changes: logEnties[index].Changes,
|
||||
}
|
||||
logs = append(logs, logEntry)
|
||||
}
|
||||
|
||||
return LogResponse{
|
||||
RelationName: relationName,
|
||||
RecordId: recordId,
|
||||
Logs: logs,
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
package service
|
||||
|
||||
type ChannelMembersResponse struct {
|
||||
Participants []UserResponse `json:"participants"`
|
||||
Others []UserResponse `json:"others"`
|
||||
}
|
||||
type UserResponse struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Image string `json:"image"`
|
||||
}
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Image string `json:"image"`
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
commonutil "houston/common/util"
|
||||
"houston/model/log"
|
||||
"houston/model/team"
|
||||
"houston/pkg/slackbot"
|
||||
request "houston/service/request"
|
||||
@@ -40,7 +41,8 @@ func NewTeamService(gin *gin.Engine, logger *zap.Logger, db *gorm.DB, client *sl
|
||||
}
|
||||
|
||||
func (t *TeamService) AddTeam(c *gin.Context) {
|
||||
teamRepository := team.NewTeamRepository(t.logger, t.db)
|
||||
logRepository := log.NewLogRepository(t.logger, t.db)
|
||||
teamRepository := team.NewTeamRepository(t.logger, t.db, logRepository)
|
||||
minLength := viper.GetInt("TEAM_NAME_MIN_LENGTH")
|
||||
maxLength := viper.GetInt("TEAM_NAME_MAX_LENGTH")
|
||||
authResult, _ := t.authService.checkIfManagerOrAdmin(c, "", Admin)
|
||||
@@ -165,7 +167,8 @@ func isUserInvalid(userInfo *slack.User, err error) bool {
|
||||
func (t *TeamService) GetTeams(c *gin.Context) {
|
||||
teamId := c.Param("id")
|
||||
|
||||
teamRepository := team.NewTeamRepository(t.logger, t.db)
|
||||
logRepository := log.NewLogRepository(t.logger, t.db)
|
||||
teamRepository := team.NewTeamRepository(t.logger, t.db, logRepository)
|
||||
|
||||
if teamId != "" {
|
||||
TeamId, err := strconv.Atoi(teamId)
|
||||
@@ -226,7 +229,8 @@ func (t *TeamService) GetTeams(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (t *TeamService) UpdateTeam(c *gin.Context) {
|
||||
teamRepository := team.NewTeamRepository(t.logger, t.db)
|
||||
logRepository := log.NewLogRepository(t.logger, t.db)
|
||||
teamRepository := team.NewTeamRepository(t.logger, t.db, logRepository)
|
||||
userEmail := c.GetHeader("X-User-Email")
|
||||
|
||||
var updateTeamRequest request.UpdateTeamRequest
|
||||
@@ -333,6 +337,7 @@ func (t *TeamService) UpdateTeam(c *gin.Context) {
|
||||
teamEntity.OncallHandle = (*slackUser)[0].ID
|
||||
}
|
||||
}
|
||||
teamEntity.UpdatedBy = userEmail
|
||||
|
||||
teamRepository.UpdateTeam(teamEntity)
|
||||
c.JSON(http.StatusMultiStatus, common.SuccessResponse("Team updated successfully", http.StatusOK))
|
||||
@@ -343,7 +348,8 @@ func (t *TeamService) RemoveTeamMember(c *gin.Context) {
|
||||
teamId := c.Param("id")
|
||||
slackUserId := c.Param("userId")
|
||||
|
||||
teamRepository := team.NewTeamRepository(t.logger, t.db)
|
||||
logRepository := log.NewLogRepository(t.logger, t.db)
|
||||
teamRepository := team.NewTeamRepository(t.logger, t.db, logRepository)
|
||||
TeamId, err := utils.ValidateIdParameter(teamId)
|
||||
if err != nil {
|
||||
t.logger.Error(err.Error(), zap.String("teamId", teamId), zap.Error(err))
|
||||
@@ -397,7 +403,8 @@ func (t *TeamService) MakeManager(c *gin.Context) {
|
||||
teamId := c.Param("id")
|
||||
teamMemberToMakeManager := c.Param("userId")
|
||||
|
||||
teamRepository := team.NewTeamRepository(t.logger, t.db)
|
||||
logRepository := log.NewLogRepository(t.logger, t.db)
|
||||
teamRepository := team.NewTeamRepository(t.logger, t.db, logRepository)
|
||||
|
||||
TeamId, err := utils.ValidateIdParameter(teamId)
|
||||
if err != nil {
|
||||
@@ -450,7 +457,8 @@ func (t *TeamService) RemoveTeam(c *gin.Context) {
|
||||
c.JSON(http.StatusUnauthorized, common.ErrorResponse(errors.New(fmt.Sprintf("%v is not an admin", userEmail)), http.StatusUnauthorized, nil))
|
||||
return
|
||||
}
|
||||
teamRepository := team.NewTeamRepository(t.logger, t.db)
|
||||
logRepository := log.NewLogRepository(t.logger, t.db)
|
||||
teamRepository := team.NewTeamRepository(t.logger, t.db, logRepository)
|
||||
TeamId, err := utils.ValidateIdParameter(teamId)
|
||||
if err != nil {
|
||||
t.logger.Error("error reading team id", zap.Error(err))
|
||||
|
||||
@@ -7,7 +7,12 @@ import (
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
util "houston/common/util"
|
||||
"houston/internal/cron"
|
||||
"houston/model/incident"
|
||||
"houston/model/log"
|
||||
"houston/model/severity"
|
||||
"houston/model/team"
|
||||
"houston/model/user"
|
||||
"houston/pkg/slackbot"
|
||||
service "houston/service/response"
|
||||
@@ -15,27 +20,36 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type userService struct {
|
||||
gin *gin.Engine
|
||||
logger *zap.Logger
|
||||
client *slackbot.Client
|
||||
db *gorm.DB
|
||||
socketModeClient *socketmode.Client
|
||||
authService *AuthService
|
||||
type UserService struct {
|
||||
gin *gin.Engine
|
||||
logger *zap.Logger
|
||||
client *slackbot.Client
|
||||
db *gorm.DB
|
||||
socketModeClient *socketmode.Client
|
||||
authService *AuthService
|
||||
userRepository *user.Repository
|
||||
incidentRepository *incident.Repository
|
||||
teamRepository *team.Repository
|
||||
}
|
||||
|
||||
func NewUserService(gin *gin.Engine, logger *zap.Logger, client *slackbot.Client, db *gorm.DB, socketModeClient *socketmode.Client, authService *AuthService) *userService {
|
||||
return &userService{
|
||||
gin: gin,
|
||||
logger: logger,
|
||||
client: client,
|
||||
db: db,
|
||||
socketModeClient: socketModeClient,
|
||||
authService: authService,
|
||||
func NewUserService(gin *gin.Engine, logger *zap.Logger, client *slackbot.Client, db *gorm.DB,
|
||||
socketModeClient *socketmode.Client, authService *AuthService) *UserService {
|
||||
logRepository := log.NewLogRepository(logger, db)
|
||||
teamRepository := team.NewTeamRepository(logger, db, logRepository)
|
||||
return &UserService{
|
||||
gin: gin,
|
||||
logger: logger,
|
||||
client: client,
|
||||
db: db,
|
||||
socketModeClient: socketModeClient,
|
||||
authService: authService,
|
||||
userRepository: user.NewUserRepository(logger, db),
|
||||
incidentRepository: incident.NewIncidentRepository(logger, db, severity.NewSeverityRepository(logger, db), logRepository, teamRepository, socketModeClient),
|
||||
teamRepository: teamRepository,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *userService) GetUserInfo(c *gin.Context) {
|
||||
func (u *UserService) GetUserInfo(c *gin.Context) {
|
||||
userId := c.Param("id")
|
||||
|
||||
users, err := u.client.GetUsersInfo(userId)
|
||||
@@ -53,37 +67,61 @@ func (u *userService) GetUserInfo(c *gin.Context) {
|
||||
}, http.StatusOK))
|
||||
}
|
||||
|
||||
func (u *userService) GetUsersInConversation(c *gin.Context) {
|
||||
func (u *UserService) GetUsersInConversation(c *gin.Context) {
|
||||
channelId := c.Query("channel_id")
|
||||
|
||||
incidentEntity, err := u.incidentRepository.FindIncidentByChannelId(channelId)
|
||||
if err != nil {
|
||||
u.logger.Error("error in getting incident by channel id", zap.String("channelId", channelId), zap.Error(err))
|
||||
c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
if incidentEntity == nil {
|
||||
err := errors.New(fmt.Sprintf("incident with channel id %v not found", channelId))
|
||||
c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
teamEntity, err := u.teamRepository.FindTeamById(incidentEntity.TeamId)
|
||||
if err != nil {
|
||||
u.logger.Error(fmt.Sprintf("error getting team info for team with id %v", incidentEntity.TeamId), zap.Error(err))
|
||||
c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
users, err := u.client.GetUsersInConversation(channelId)
|
||||
if err != nil {
|
||||
u.logger.Error("error in getting users from conversation", zap.String("channelId", channelId), zap.Error(err))
|
||||
u.logger.Error(fmt.Sprintf("error in getting users from channel %v", channelId), zap.Error(err))
|
||||
c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
|
||||
usersInfo, err := u.client.GetUsersInfo(users...)
|
||||
if err != nil {
|
||||
u.logger.Error("error in getting users info", zap.Any("userIds", users), zap.Error(err))
|
||||
u.logger.Error("error in getting users info", zap.Error(err))
|
||||
c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil))
|
||||
return
|
||||
}
|
||||
usersData := *usersInfo
|
||||
var usersResponses []service.UserResponse = []service.UserResponse{}
|
||||
|
||||
for userInfo := range usersData {
|
||||
usersResponses = append(usersResponses, service.UserResponse{
|
||||
Id: usersData[userInfo].ID,
|
||||
Name: usersData[userInfo].Profile.RealName,
|
||||
Email: usersData[userInfo].Profile.Email,
|
||||
Image: usersData[userInfo].Profile.Image32,
|
||||
})
|
||||
teamMembers := util.ConvertSliceToMapOfString(teamEntity.SlackUserIds)
|
||||
var participants []service.UserResponse
|
||||
var others []service.UserResponse
|
||||
for userIndex := range usersData {
|
||||
userInfo := service.UserResponse{
|
||||
Id: usersData[userIndex].ID,
|
||||
Name: usersData[userIndex].Profile.RealName,
|
||||
Email: usersData[userIndex].Profile.Email,
|
||||
Image: usersData[userIndex].Profile.Image32,
|
||||
}
|
||||
if teamMembers[usersData[userIndex].ID] != "" {
|
||||
participants = append(participants, userInfo)
|
||||
} else {
|
||||
others = append(others, userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, common.SuccessResponse(usersResponses, http.StatusOK))
|
||||
c.JSON(http.StatusOK, common.SuccessResponse(service.ChannelMembersResponse{
|
||||
Participants: participants,
|
||||
Others: others,
|
||||
}, http.StatusOK))
|
||||
}
|
||||
func (u *userService) UpdateHoustonUsers(c *gin.Context) {
|
||||
func (u *UserService) UpdateHoustonUsers(c *gin.Context) {
|
||||
|
||||
userEmail := c.GetHeader("X-User-Email")
|
||||
authResult, _ := u.authService.checkIfManagerOrAdmin(c, "", Admin, Manager)
|
||||
|
||||
40
service/utils/deep_compare.go
Normal file
40
service/utils/deep_compare.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type Difference struct {
|
||||
Attribute string `json:"attribute"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
}
|
||||
|
||||
func DeepCompare(before, after interface{}) []Difference {
|
||||
var differences []Difference
|
||||
|
||||
valBefore := reflect.ValueOf(before)
|
||||
valAfter := reflect.ValueOf(after)
|
||||
|
||||
if valBefore.Type() != valAfter.Type() {
|
||||
return append(differences, Difference{"TypeMismatch", valBefore.Type().String(), valAfter.Type().String()})
|
||||
}
|
||||
|
||||
for index := 0; index < valBefore.NumField(); index++ {
|
||||
fieldType := valBefore.Type().Field(index)
|
||||
fieldName := fieldType.Name
|
||||
fieldBefore := valBefore.Field(index)
|
||||
fieldAfter := valAfter.Field(index)
|
||||
|
||||
if fieldName != "Model" && !reflect.DeepEqual(fieldBefore.Interface(), fieldAfter.Interface()) {
|
||||
differences = append(differences, Difference{
|
||||
Attribute: fieldName,
|
||||
From: fmt.Sprintf("%v", fieldBefore.Interface()),
|
||||
To: fmt.Sprintf("%v", fieldAfter.Interface()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return differences
|
||||
}
|
||||
Reference in New Issue
Block a user