TP-43103 : Synch users data from slack to database and exposed api to return a list of bot users (#228)
* TP-42310| created api service to get list of slackbots (#215) * TP-42310| created api service to get list of slackbots * minor bug fix in GetAllHoustonUserBots function * Synch users to db (#213) * TP-43103| added event listeners to user detail changes and saving to db * TP-43103| refactored the upsert users scheduler * TP-43103| fixed updating users when deactivated in scheduler * TP-43103| Made the requested changes * added info loggers to user change events * made changes to process only navi workspace user changes * made addressed changes in pr and returning Real name in GetAllHoustonUserBots api * resolved merge conflicts
This commit is contained in:
committed by
GitHub
parent
aef97d4c6e
commit
6b44ec9ec3
@@ -2,24 +2,26 @@ package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"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/common/util"
|
||||
"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"
|
||||
|
||||
"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/model/log"
|
||||
)
|
||||
|
||||
type slackHandler struct {
|
||||
@@ -31,6 +33,7 @@ type slackHandler struct {
|
||||
viewSubmissionProcessor *processor.ViewSubmissionProcessor
|
||||
slashCommandResolver *resolver.SlashCommandResolver
|
||||
diagnosticCommandProcessor *processor.DiagnosticCommandProcessor
|
||||
userChangeEventProcessor *processor.UserChangeEventProcessor
|
||||
}
|
||||
|
||||
func NewSlackHandler(logger *zap.Logger, gormClient *gorm.DB, socketModeClient *socketmode.Client) *slackHandler {
|
||||
@@ -65,6 +68,9 @@ func NewSlackHandler(logger *zap.Logger, gormClient *gorm.DB, socketModeClient *
|
||||
slashCommandResolver: resolver.NewSlashCommandResolver(
|
||||
logger, diagnosticCommandProcessor, slashCommandProcessor,
|
||||
),
|
||||
userChangeEventProcessor: processor.NewUserChangeEventProcessor(
|
||||
logger, socketModeClient, userService,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,14 +100,22 @@ func (sh *slackHandler) HoustonConnect() {
|
||||
if eventErr != nil {
|
||||
sh.logger.Error("error occurred while serializing the event object", zap.Any("error", eventErr))
|
||||
} else {
|
||||
sh.logger.Info("event api", zap.String("event", string(serializedEventJson)))
|
||||
if ev.InnerEvent.Type != util.UserChangeEvent {
|
||||
sh.logger.Info("event api", zap.String("event", string(serializedEventJson)))
|
||||
}
|
||||
}
|
||||
switch ev.Type {
|
||||
case slackevents.CallbackEvent:
|
||||
iev := ev.InnerEvent
|
||||
switch ev := iev.Data.(type) {
|
||||
innerEvent := ev.InnerEvent
|
||||
|
||||
switch innerEvent.Type {
|
||||
case util.UserChangeEvent:
|
||||
sh.userChangeEventProcessor.ProcessCommand(ev, evt.Request)
|
||||
}
|
||||
|
||||
switch innerEventData := innerEvent.Data.(type) {
|
||||
case *slackevents.MemberJoinedChannelEvent:
|
||||
sh.memberJoinCallbackProcessor.ProcessCommand(ev, evt.Request)
|
||||
sh.memberJoinCallbackProcessor.ProcessCommand(innerEventData, evt.Request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,8 +160,8 @@ func (s *Server) usersHandler(houstonGroup *gin.RouterGroup) {
|
||||
houstonGroup.GET("/users/:id", usersHandler.GetUserInfo)
|
||||
houstonGroup.GET("/users", usersHandler.GetUsersInConversation)
|
||||
houstonGroup.GET("/users/update", usersHandler.UpdateHoustonUsers)
|
||||
houstonGroup.GET("/bots", usersHandler.GetAllHoustonUserBots)
|
||||
}
|
||||
|
||||
func (s *Server) filtersHandler(houstonGroup *gin.RouterGroup) {
|
||||
filtersHandler := service.NewFilterService(s.gin, s.logger, s.db)
|
||||
|
||||
|
||||
@@ -36,3 +36,8 @@ const (
|
||||
ShowIncidentSubmit = "show_incident_submit"
|
||||
MarkIncidentDuplicateSubmit = "mark_incident_duplicate_submit"
|
||||
)
|
||||
|
||||
const (
|
||||
UserChangeEvent = "user_change"
|
||||
DeactivatedUserName = "Deactivated User"
|
||||
)
|
||||
|
||||
33
common/util/user_helper.go
Normal file
33
common/util/user_helper.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package util
|
||||
|
||||
import "houston/model/user"
|
||||
|
||||
func UpdateUserFieldsIfChanged(existingUser *user.UserEntity, newUser *user.UserEntity) (*user.UserEntity, bool) {
|
||||
isChanged := false
|
||||
|
||||
if existingUser.Name != newUser.Name {
|
||||
existingUser.Name = newUser.Name
|
||||
isChanged = true
|
||||
}
|
||||
if existingUser.Email != newUser.Email {
|
||||
existingUser.Email = newUser.Email
|
||||
isChanged = true
|
||||
}
|
||||
if existingUser.Image != newUser.Image {
|
||||
existingUser.Image = newUser.Image
|
||||
isChanged = true
|
||||
}
|
||||
if existingUser.RealName != newUser.RealName {
|
||||
existingUser.RealName = newUser.RealName
|
||||
isChanged = true
|
||||
}
|
||||
if existingUser.Active != newUser.Active {
|
||||
existingUser.Active = newUser.Active
|
||||
isChanged = true
|
||||
}
|
||||
if existingUser.IsBot != newUser.IsBot {
|
||||
existingUser.IsBot = newUser.IsBot
|
||||
isChanged = true
|
||||
}
|
||||
return existingUser, isChanged
|
||||
}
|
||||
@@ -73,4 +73,6 @@ config.sa.keys=CONFIG_SA_KEYS
|
||||
create-incident.description.max-length=500
|
||||
create-incident.title.max-length=100
|
||||
|
||||
create-incident-v2-enabled=CREATE_INCIDENT_V2_ENABLED
|
||||
create-incident-v2-enabled=CREATE_INCIDENT_V2_ENABLED
|
||||
#slack details
|
||||
slack.workspace.id=SLACK_WORKSPACE_ID
|
||||
@@ -345,8 +345,9 @@ func buildOnCallAndManagerBlock(onCallHandle string, managerHandle string) *slac
|
||||
func UpsertUsers(socketModeClient *socketmode.Client, logger *zap.Logger, userService *user.Repository) {
|
||||
|
||||
fmt.Println("Running upsertUsers job at", time.Now().Format(time.RFC3339))
|
||||
userOptions := slack.GetUsersOptionLimit(600)
|
||||
|
||||
slackUsers, err := socketModeClient.GetUsers()
|
||||
slackUsers, err := socketModeClient.GetUsers(userOptions)
|
||||
if err != nil {
|
||||
logger.Error("socketMode client GetUsers error", zap.Error(err))
|
||||
return
|
||||
@@ -360,6 +361,10 @@ func UpsertUsers(socketModeClient *socketmode.Client, logger *zap.Logger, userSe
|
||||
Name: source.Name,
|
||||
SlackUserId: source.ID,
|
||||
Active: !source.Deleted,
|
||||
IsBot: source.IsBot,
|
||||
Email: source.Profile.Email,
|
||||
Image: source.Profile.Image32,
|
||||
RealName: source.Profile.RealName,
|
||||
}
|
||||
houstonSlackUserMap[source.ID] = target
|
||||
}
|
||||
@@ -382,14 +387,22 @@ func UpsertUsers(socketModeClient *socketmode.Client, logger *zap.Logger, userSe
|
||||
} else {
|
||||
// Entry is already present in houstonSlackUserMap
|
||||
var dbUser = houstonDbUserMap[id]
|
||||
if slackUser.Active != dbUser.Active || slackUser.Name != dbUser.Name {
|
||||
dbUser.Active = slackUser.Active
|
||||
dbUser.Name = slackUser.Name
|
||||
logger.Info("updating user ", zap.String("id", dbUser.SlackUserId), zap.String("name", dbUser.Name), zap.Bool("active", dbUser.Active), zap.Bool("active", dbUser.Active))
|
||||
err = userService.UpdateHoustonUsers(dbUser)
|
||||
// If User is Totally Deactivated, then only we need to update the Active Field
|
||||
if slackUser.RealName == util.DeactivatedUserName {
|
||||
dbUser.Active = false
|
||||
err = userService.UpdateHoustonUser(dbUser)
|
||||
if err != nil {
|
||||
logger.Error("UpdateHoustonUsers error", zap.Error(err))
|
||||
return
|
||||
logger.Error("Error while updating Houston user", zap.String("slackId", dbUser.SlackUserId), zap.Error(err))
|
||||
}
|
||||
} else {
|
||||
updateDbUser, isChanged := util.UpdateUserFieldsIfChanged(&dbUser, &slackUser)
|
||||
|
||||
if isChanged {
|
||||
logger.Info("updating user ", zap.String("slack id", updateDbUser.SlackUserId), zap.String("name", updateDbUser.RealName))
|
||||
err = userService.UpdateHoustonUser(*updateDbUser)
|
||||
if err != nil {
|
||||
logger.Error("Error while updating Houston user", zap.String("slackId", dbUser.SlackUserId), zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
84
internal/processor/action/user_change_event_action.go
Normal file
84
internal/processor/action/user_change_event_action.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/slack-go/slack/slackevents"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
"houston/common/util"
|
||||
"houston/model/user"
|
||||
service "houston/service/request"
|
||||
)
|
||||
|
||||
type UserChangeEventAction struct {
|
||||
logger *zap.Logger
|
||||
userRepository *user.Repository
|
||||
}
|
||||
|
||||
func NewUserChangeEventAction(logger *zap.Logger, userRepository *user.Repository) *UserChangeEventAction {
|
||||
return &UserChangeEventAction{
|
||||
logger: logger,
|
||||
userRepository: userRepository,
|
||||
}
|
||||
}
|
||||
|
||||
func (userChangeEventAction *UserChangeEventAction) PerformAction(event slackevents.EventsAPIEvent) {
|
||||
var userChangeEventRequest service.UserChangeEventRequest
|
||||
serializedEventJson, marshalError := json.Marshal(&event.InnerEvent.Data)
|
||||
if marshalError != nil {
|
||||
userChangeEventAction.logger.Error("error occurred while serializing the event object", zap.Any("error", marshalError))
|
||||
}
|
||||
unmarshalError := json.Unmarshal(serializedEventJson, &userChangeEventRequest)
|
||||
if unmarshalError != nil {
|
||||
userChangeEventAction.logger.Error("error occurred while deserializing the event object", zap.Any("error", unmarshalError), zap.String("event", string(serializedEventJson)))
|
||||
return
|
||||
}
|
||||
// If User is not part of Navi Workspace Team, then we don't need to process the event
|
||||
if userChangeEventRequest.User.Profile.TeamName != viper.GetString("slack.workspace.id") {
|
||||
return
|
||||
}
|
||||
userEntity := user.UserEntity{
|
||||
Name: userChangeEventRequest.User.Name,
|
||||
SlackUserId: userChangeEventRequest.User.ID,
|
||||
Email: userChangeEventRequest.User.Profile.Email,
|
||||
Image: userChangeEventRequest.User.Profile.Image,
|
||||
RealName: userChangeEventRequest.User.Profile.RealName,
|
||||
Active: !userChangeEventRequest.User.Deleted,
|
||||
IsBot: userChangeEventRequest.User.IsBot,
|
||||
}
|
||||
existingUser, err := userChangeEventAction.userRepository.FindHoustonUserBySlackUserId(userEntity.SlackUserId)
|
||||
if err != nil {
|
||||
userChangeEventAction.logger.Error("error in finding user", zap.String("user_slack_id", userChangeEventRequest.User.ID), zap.Error(err))
|
||||
return
|
||||
}
|
||||
if existingUser == nil {
|
||||
userChangeEventAction.logger.Info(fmt.Sprintf("inserting user %s", userEntity.Name))
|
||||
resultError := userChangeEventAction.userRepository.InsertHoustonUser(&userEntity)
|
||||
if resultError != nil {
|
||||
userChangeEventAction.logger.Error("error in inserting user", zap.String("user_slack_id", userChangeEventRequest.User.ID), zap.Error(resultError))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
// If User is Totally Deactivated then only we need to update the Active Field
|
||||
if userEntity.RealName == util.DeactivatedUserName {
|
||||
existingUser.Active = false
|
||||
resultError := userChangeEventAction.userRepository.UpdateHoustonUser(*existingUser)
|
||||
if resultError != nil {
|
||||
userChangeEventAction.logger.Error("error in updating user", zap.String("user_slack_id", userChangeEventRequest.User.ID), zap.Error(resultError))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
updatedExistingUser, isChanged := util.UpdateUserFieldsIfChanged(existingUser, &userEntity)
|
||||
|
||||
if isChanged {
|
||||
userChangeEventAction.logger.Info(fmt.Sprintf("upadting user %s", updatedExistingUser.Name))
|
||||
resultError := userChangeEventAction.userRepository.UpdateHoustonUser(*updatedExistingUser)
|
||||
if resultError != nil {
|
||||
userChangeEventAction.logger.Error("error in updating user", zap.String("user_slack_id", userChangeEventRequest.User.ID), zap.Error(resultError))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,14 @@ package processor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/slack-go/slack/slackevents"
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
"go.uber.org/zap"
|
||||
"houston/internal/processor/action"
|
||||
"houston/model/incident"
|
||||
"houston/model/severity"
|
||||
"houston/model/team"
|
||||
|
||||
"github.com/slack-go/slack/slackevents"
|
||||
"github.com/slack-go/slack/socketmode"
|
||||
"go.uber.org/zap"
|
||||
"houston/model/user"
|
||||
)
|
||||
|
||||
type eventsApiEventProcessor interface {
|
||||
@@ -42,3 +42,29 @@ func (mjc *MemberJoinedCallbackEventProcessor) ProcessCommand(event *slackevents
|
||||
var payload interface{}
|
||||
mjc.socketModeClient.Ack(*request, payload)
|
||||
}
|
||||
|
||||
type UserChangeEventProcessor struct {
|
||||
logger *zap.Logger
|
||||
socketModeClient *socketmode.Client
|
||||
userChangeAction *action.UserChangeEventAction
|
||||
}
|
||||
|
||||
func NewUserChangeEventProcessor(logger *zap.Logger, socketModeClient *socketmode.Client, userRepository *user.Repository) *UserChangeEventProcessor {
|
||||
return &UserChangeEventProcessor{
|
||||
logger: logger,
|
||||
socketModeClient: socketModeClient,
|
||||
userChangeAction: action.NewUserChangeEventAction(logger, userRepository),
|
||||
}
|
||||
}
|
||||
|
||||
func (ucep *UserChangeEventProcessor) ProcessCommand(event slackevents.EventsAPIEvent, request *socketmode.Request) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ucep.logger.Error(fmt.Sprintf("[UserChangeEventProcessor] Exception occurred: %v", r.(error)))
|
||||
}
|
||||
}()
|
||||
|
||||
ucep.userChangeAction.PerformAction(event)
|
||||
var payload interface{}
|
||||
ucep.socketModeClient.Ack(*request, payload)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ type UserEntity struct {
|
||||
Name string
|
||||
SlackUserId string
|
||||
Active bool
|
||||
IsBot bool
|
||||
Email string
|
||||
RealName string
|
||||
Image string
|
||||
}
|
||||
|
||||
func (UserEntity) TableName() string {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -26,8 +27,8 @@ func (r *Repository) InsertHoustonUsers(users []UserEntity) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) UpdateHoustonUsers(user UserEntity) error {
|
||||
result := r.gormClient.Updates(&user)
|
||||
func (r *Repository) UpdateHoustonUser(user UserEntity) error {
|
||||
result := r.gormClient.Select("*").Updates(&user)
|
||||
if result.Error != nil || result.RowsAffected != 1 {
|
||||
return result.Error
|
||||
}
|
||||
@@ -56,6 +57,27 @@ func (r *Repository) IsAHoustonUser(nameOrSlackUserId string) (bool, *UserEntity
|
||||
return true, &existingUser
|
||||
}
|
||||
|
||||
func (r *Repository) FindHoustonUserBySlackUserId(slackUserId string) (*UserEntity, error) {
|
||||
var user UserEntity
|
||||
result := r.gormClient.Where("slack_user_id = ?", slackUserId).First(&user)
|
||||
if result.Error != nil {
|
||||
// If the user is not found, return both entity and error as nil
|
||||
if errors.Is(gorm.ErrRecordNotFound, result.Error) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, result.Error
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *Repository) InsertHoustonUser(user *UserEntity) error {
|
||||
result := r.gormClient.Create(&user)
|
||||
if result.Error != nil || result.RowsAffected != 1 {
|
||||
return result.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetHoustonUsersBySlackId(slackUserId []string) (*[]UserEntity, error) {
|
||||
var users []UserEntity
|
||||
result := r.gormClient.Where("slack_user_id IN ?", slackUserId).Find(&users)
|
||||
@@ -64,3 +86,13 @@ func (r *Repository) GetHoustonUsersBySlackId(slackUserId []string) (*[]UserEnti
|
||||
}
|
||||
return &users, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetAllActiveHoustonUserBots() ([]UserEntity, error) {
|
||||
var users []UserEntity
|
||||
result := r.gormClient.Where("is_bot = ? AND active = ?", true, true).Find(&users)
|
||||
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
17
service/request/user_change_event_request.go
Normal file
17
service/request/user_change_event_request.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package service
|
||||
|
||||
type UserChangeEventRequest struct {
|
||||
User struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Deleted bool `json:"deleted"`
|
||||
IsBot bool `json:"is_bot"`
|
||||
|
||||
Profile struct {
|
||||
Email string `json:"email"`
|
||||
Image string `json:"image_32"`
|
||||
RealName string `json:"real_name"`
|
||||
TeamName string `json:"team"`
|
||||
} `json:"profile"`
|
||||
} `json:"user"`
|
||||
}
|
||||
@@ -7,6 +7,6 @@ type ChannelMembersResponse struct {
|
||||
type UserResponse struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Image string `json:"image"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Image string `json:"image,omitempty"`
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ func (u *UserService) GetUsersInConversation(c *gin.Context) {
|
||||
func (u *UserService) UpdateHoustonUsers(c *gin.Context) {
|
||||
|
||||
userEmail := c.GetHeader("X-User-Email")
|
||||
authResult, _ := u.authService.checkIfManagerOrAdmin(c, "", Admin, Manager)
|
||||
authResult, _ := u.authService.checkIfManagerOrAdmin(c, "", Admin)
|
||||
if !authResult {
|
||||
err := errors.New(fmt.Sprintf("%v is not an admin", userEmail))
|
||||
c.JSON(http.StatusUnauthorized, common.ErrorResponse(err, http.StatusUnauthorized, nil))
|
||||
@@ -140,3 +140,24 @@ func (u *UserService) UpdateHoustonUsers(c *gin.Context) {
|
||||
|
||||
c.JSON(http.StatusOK, common.SuccessResponse("done", http.StatusOK))
|
||||
}
|
||||
|
||||
func (u *UserService) GetAllHoustonUserBots(c *gin.Context) {
|
||||
userRepository := user.NewUserRepository(u.logger, u.db)
|
||||
botUsers, err := userRepository.GetAllActiveHoustonUserBots()
|
||||
if err != nil {
|
||||
u.logger.Error("error in getting all houston_user bots", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, common.ErrorResponse(err, http.StatusInternalServerError, nil))
|
||||
return
|
||||
}
|
||||
userResponses := make([]service.UserResponse, 0, len(botUsers))
|
||||
|
||||
for _, botUser := range botUsers {
|
||||
userResponse := service.UserResponse{
|
||||
Id: botUser.SlackUserId,
|
||||
Name: botUser.RealName,
|
||||
}
|
||||
userResponses = append(userResponses, userResponse)
|
||||
}
|
||||
c.JSON(http.StatusOK, common.SuccessResponse(userResponses, http.StatusOK))
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user