TP-44162 | Google Meet integration to create calendar invite with meeting link creation on incident creation (#277)
* TP-44158 | Adding service to get transcript files from Google Drive (#234) Adding service to get transcript files from Google Drive * TP-45120 (#275) TP-45120 | merging Google auth implementation and calendar event fix * TP-44162 | added service implementation for calendar actions * TP-44162 | Updated label in slack message * TP-44162 | Fixed build failures * TP-44162 | Updated sql migration file name * TP-44162 | added unit tests for google calendar service * TP-48200 | updated response messages in link and unlink jira apis (#278) * TP-44162 | resolved review comments * TP-44158 | Adding service to get transcript files from Google Drive (#234) Adding service to get transcript files from Google Drive * TP-45120 (#275) TP-45120 | merging Google auth implementation and calendar event fix * TP-44162 | added service implementation for calendar actions * TP-44162 | Updated label in slack message * TP-44162 | Fixed build failures * TP-44162 | Updated sql migration file name * TP-44162 | added unit tests for google calendar service * TP-44162 | resolved review comments * TP-44162 | updated few naming conventions * TP-44162 | Adding timeouts to google drive api calls and related UTs * TP-44162 | Adding drive api timeout to viper for unit test --------- Co-authored-by: Sriram Bhargav <sriram.bhargav@navi.com> Co-authored-by: Shashank Shekhar <shashank.shekhar@navi.com>
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -17,6 +17,8 @@
|
|||||||
/.idea
|
/.idea
|
||||||
.idea
|
.idea
|
||||||
/out
|
/out
|
||||||
|
/mocks
|
||||||
|
*_mock.go
|
||||||
|
|
||||||
go.sum
|
go.sum
|
||||||
|
|
||||||
|
|||||||
30
Makefile
30
Makefile
@@ -1,15 +1,11 @@
|
|||||||
.PHONY: build
|
.PHONY: docker-run
|
||||||
build:
|
docker-run: docker-build
|
||||||
go mod tidy && CGO_ENABLED=0 go build -ldflags="-s -w" -o houston cmd/main.go
|
docker run houston
|
||||||
|
|
||||||
.PHONY: docker-build
|
.PHONY: docker-build
|
||||||
docker-build:
|
docker-build:
|
||||||
docker build -t houston -f Dockerfile.houston .
|
docker build -t houston -f Dockerfile.houston .
|
||||||
|
|
||||||
.PHONY: docker-run
|
|
||||||
docker-run: docker-build
|
|
||||||
docker run houston
|
|
||||||
|
|
||||||
.PHONY: migration-up
|
.PHONY: migration-up
|
||||||
migration-up:
|
migration-up:
|
||||||
migrate -path db/migration/ -database "${POSTGRES_DSN}?sslmode=disable" -verbose up
|
migrate -path db/migration/ -database "${POSTGRES_DSN}?sslmode=disable" -verbose up
|
||||||
@@ -17,3 +13,23 @@ migration-up:
|
|||||||
.PHONY: migration-down
|
.PHONY: migration-down
|
||||||
migration-down:
|
migration-down:
|
||||||
migrate -path db/migration/ -database "${POSTGRES_DSN}?sslmode=disable" -verbose down
|
migrate -path db/migration/ -database "${POSTGRES_DSN}?sslmode=disable" -verbose down
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build: test
|
||||||
|
go mod tidy && CGO_ENABLED=0 go build -ldflags="-s -w" -o houston cmd/main.go
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test: generatemocks
|
||||||
|
go test -v -count=1 $(CURDIR)/service/...
|
||||||
|
@rm -rf $(CURDIR)/mocks
|
||||||
|
|
||||||
|
# Keep all mock file generation below this line
|
||||||
|
.PHONY: generatemocks
|
||||||
|
generatemocks:
|
||||||
|
@go install github.com/gojuno/minimock/v3/cmd/minimock@v3.1.3
|
||||||
|
@go mod tidy
|
||||||
|
@rm -rf $(CURDIR)/mocks
|
||||||
|
@echo "Generating mocks..."
|
||||||
|
@mkdir "mocks"
|
||||||
|
cd $(CURDIR)/pkg/google/googleDrive && minimock -i GoogleDriveActions -s _mock.go -o $(CURDIR)/mocks
|
||||||
|
cd $(CURDIR)/pkg/conference && minimock -i ICalendarActions -s _mock.go -o $(CURDIR)/mocks
|
||||||
|
|||||||
@@ -45,3 +45,11 @@ const (
|
|||||||
AlreadyArchivedError = "already_archived"
|
AlreadyArchivedError = "already_archived"
|
||||||
NotInChannelError = "not_in_channel"
|
NotInChannelError = "not_in_channel"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
GoogleDriveFileMimeType = "application/vnd.google-apps.folder"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConferenceMessage = "To discuss, use this *<%s|Meet link>*"
|
||||||
|
)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"houston/model/incident"
|
"houston/model/incident"
|
||||||
"houston/model/severity"
|
"houston/model/severity"
|
||||||
"houston/model/team"
|
"houston/model/team"
|
||||||
|
"houston/pkg/conference"
|
||||||
"houston/pkg/slackbot"
|
"houston/pkg/slackbot"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -90,3 +91,12 @@ func getOncallOrResponderHandle(teamEntity *team.TeamEntity, severityEntity *sev
|
|||||||
func isPseIncident(teamEntity *team.TeamEntity, severityEntity *severity.SeverityEntity) bool {
|
func isPseIncident(teamEntity *team.TeamEntity, severityEntity *severity.SeverityEntity) bool {
|
||||||
return len(strings.TrimSpace(teamEntity.PseOncallHandle)) > 0 && (severityEntity.Name == "Sev-2" || severityEntity.Name == "Sev-3")
|
return len(strings.TrimSpace(teamEntity.PseOncallHandle)) > 0 && (severityEntity.Name == "Sev-2" || severityEntity.Name == "Sev-3")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateIncidentWithConferenceDetails(incidentEntity *incident.IncidentEntity, event conference.EventData, repo *incident.Repository) {
|
||||||
|
incidentEntity.ConferenceLink = event.ConferenceLink
|
||||||
|
incidentEntity.ConferenceId = event.Id
|
||||||
|
err := repo.UpdateIncident(incidentEntity)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("Unable to update incident %s with conference details due to error: %s", incidentEntity.IncidentName, err.Error()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,9 +58,12 @@ s3.bucket.name=S3_BUCKET_NAME
|
|||||||
s3.access.key=S3_ACCESS_KEY
|
s3.access.key=S3_ACCESS_KEY
|
||||||
s3.secret.key=S3_SECRET_KEY
|
s3.secret.key=S3_SECRET_KEY
|
||||||
|
|
||||||
#gmeet
|
#conference
|
||||||
gmeet.enable=ENABLE_GMEET
|
conference.enable=ENABLE_CONFERENCE
|
||||||
gmeet.config.file.path=GMEET_CONFIG_FILE_PATH
|
conference.type=CONFERENCE_TYPE
|
||||||
|
google.auth.key.content=GOOGLE_AUTH_KEY_CONTENT
|
||||||
|
google.auth.email=GOOGLE_AUTH_EMAIL
|
||||||
|
google.api.timeout=GOOGLE_API_TIMEOUT
|
||||||
|
|
||||||
#gocd
|
#gocd
|
||||||
gocd.sa.baseurl=GOCD_SA_BASEURL
|
gocd.sa.baseurl=GOCD_SA_BASEURL
|
||||||
|
|||||||
2
db/migration/000005_add_incident_columns.up.sql
Normal file
2
db/migration/000005_add_incident_columns.up.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
alter table incident add column if not exists conference_id varchar(64);
|
||||||
|
alter table incident add column if not exists conference_link varchar(64);
|
||||||
9
go.mod
9
go.mod
@@ -10,6 +10,7 @@ require (
|
|||||||
github.com/chromedp/chromedp v0.9.1
|
github.com/chromedp/chromedp v0.9.1
|
||||||
github.com/gin-contrib/zap v0.1.0
|
github.com/gin-contrib/zap v0.1.0
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
|
github.com/gojuno/minimock/v3 v3.1.3
|
||||||
github.com/google/uuid v1.4.0
|
github.com/google/uuid v1.4.0
|
||||||
github.com/jackc/pgx/v5 v5.3.1
|
github.com/jackc/pgx/v5 v5.3.1
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
@@ -17,9 +18,11 @@ require (
|
|||||||
github.com/slack-go/slack v0.12.1
|
github.com/slack-go/slack v0.12.1
|
||||||
github.com/spf13/cobra v1.6.1
|
github.com/spf13/cobra v1.6.1
|
||||||
github.com/spf13/viper v1.16.0
|
github.com/spf13/viper v1.16.0
|
||||||
|
github.com/stretchr/testify v1.8.3
|
||||||
github.com/thoas/go-funk v0.9.3
|
github.com/thoas/go-funk v0.9.3
|
||||||
go.uber.org/zap v1.24.0
|
go.uber.org/zap v1.24.0
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||||
|
golang.org/x/oauth2 v0.13.0
|
||||||
gorm.io/datatypes v1.2.0
|
gorm.io/datatypes v1.2.0
|
||||||
gorm.io/driver/postgres v1.5.2
|
gorm.io/driver/postgres v1.5.2
|
||||||
gorm.io/gorm v1.25.2
|
gorm.io/gorm v1.25.2
|
||||||
@@ -47,6 +50,7 @@ require (
|
|||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/chromedp/sysutil v1.0.0 // indirect
|
github.com/chromedp/sysutil v1.0.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
@@ -60,13 +64,12 @@ require (
|
|||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.3.0 // indirect
|
github.com/prometheus/client_model v0.3.0 // indirect
|
||||||
github.com/prometheus/common v0.42.0 // indirect
|
github.com/prometheus/common v0.42.0 // indirect
|
||||||
github.com/prometheus/procfs v0.9.0 // indirect
|
github.com/prometheus/procfs v0.9.0 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
golang.org/x/oauth2 v0.13.0 // indirect
|
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||||
google.golang.org/grpc v1.59.0 // indirect
|
google.golang.org/grpc v1.59.0 // indirect
|
||||||
gorm.io/driver/mysql v1.4.7 // indirect
|
gorm.io/driver/mysql v1.4.7 // indirect
|
||||||
@@ -91,7 +94,7 @@ require (
|
|||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7
|
||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
|||||||
28
internal/clients/google.go
Normal file
28
internal/clients/google.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package clients
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/google"
|
||||||
|
"golang.org/x/oauth2/jwt"
|
||||||
|
"google.golang.org/api/calendar/v3"
|
||||||
|
"google.golang.org/api/drive/v2"
|
||||||
|
"houston/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getGoogleAuthJWTConfig() *jwt.Config {
|
||||||
|
content := viper.GetString("GOOGLE_AUTH_KEY_CONTENT")
|
||||||
|
//If modifying these scopes, update the scopes in service account and regenerate the keys and update the deployment portal.
|
||||||
|
config, err := google.JWTConfigFromJSON([]byte(content), calendar.CalendarScope, drive.DriveScope)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("Unable to create google JWT config due to error: %s", err.Error()))
|
||||||
|
}
|
||||||
|
config.Subject = viper.GetString("GOOGLE_AUTH_EMAIL")
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGoogleTokenSource() oauth2.TokenSource {
|
||||||
|
return getGoogleAuthJWTConfig().TokenSource(context.Background())
|
||||||
|
}
|
||||||
@@ -130,7 +130,7 @@ func (dip *DuplicateIncidentAction) DuplicateIncidentProcess(callback slack.Inte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
//ToDo() Delete Conference event if exists and if incident is duplicated
|
||||||
} else {
|
} else {
|
||||||
msgOption := slack.MsgOptionText(fmt.Sprintf("`Submitted incident id: %s is not a valid open incident. Check and resubmit`", incidentRca), false)
|
msgOption := slack.MsgOptionText(fmt.Sprintf("`Submitted incident id: %s is not a valid open incident. Check and resubmit`", incidentRca), false)
|
||||||
_, errMessage := dip.client.PostEphemeral(channelId, user.ID, msgOption)
|
_, errMessage := dip.client.PostEphemeral(channelId, user.ID, msgOption)
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ func (irp *ResolveIncidentAction) IncidentResolveProcess(callback slack.Interact
|
|||||||
}
|
}
|
||||||
msgUpdate := NewIncidentChannelMessageUpdateAction(irp.client, irp.incidentService, irp.teamRepository, irp.severityRepository)
|
msgUpdate := NewIncidentChannelMessageUpdateAction(irp.client, irp.incidentService, irp.teamRepository, irp.severityRepository)
|
||||||
msgUpdate.ProcessAction(incidentEntity.SlackChannel)
|
msgUpdate.ProcessAction(incidentEntity.SlackChannel)
|
||||||
|
//ToDo() Delete Conference event if exists and if incident is resolved
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if incidentEntity.SeverityId != incident.Sev0Id && incidentEntity.SeverityId != incident.Sev1Id {
|
if incidentEntity.SeverityId != incident.Sev0Id && incidentEntity.SeverityId != incident.Sev1Id {
|
||||||
postErr := util.PostArchivingTimeToIncidentChannel(channelId, incident.Resolved, irp.client)
|
postErr := util.PostArchivingTimeToIncidentChannel(channelId, incident.Resolved, irp.client)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package action
|
package action
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -11,19 +10,18 @@ import (
|
|||||||
"houston/model/incident"
|
"houston/model/incident"
|
||||||
"houston/model/severity"
|
"houston/model/severity"
|
||||||
"houston/model/team"
|
"houston/model/team"
|
||||||
|
conference2 "houston/pkg/conference"
|
||||||
"houston/pkg/slackbot"
|
"houston/pkg/slackbot"
|
||||||
|
"houston/service/conference"
|
||||||
incidentService "houston/service/incident"
|
incidentService "houston/service/incident"
|
||||||
request "houston/service/request"
|
request "houston/service/request"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/slack-go/slack"
|
"github.com/slack-go/slack"
|
||||||
"github.com/slack-go/slack/socketmode"
|
"github.com/slack-go/slack/socketmode"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"google.golang.org/api/calendar/v3"
|
|
||||||
"google.golang.org/api/option"
|
|
||||||
incidentHelper "houston/common/util"
|
incidentHelper "houston/common/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,14 +34,7 @@ type CreateIncidentAction struct {
|
|||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCreateIncidentProcessor(
|
func NewCreateIncidentProcessor(client *socketmode.Client, incidentService *incident.Repository, teamService *team.Repository, severityService *severity.Repository, slackbotClient *slackbot.Client, db *gorm.DB) *CreateIncidentAction {
|
||||||
client *socketmode.Client,
|
|
||||||
incidentService *incident.Repository,
|
|
||||||
teamService *team.Repository,
|
|
||||||
severityService *severity.Repository,
|
|
||||||
slackbotClient *slackbot.Client,
|
|
||||||
db *gorm.DB,
|
|
||||||
) *CreateIncidentAction {
|
|
||||||
return &CreateIncidentAction{
|
return &CreateIncidentAction{
|
||||||
client: client,
|
client: client,
|
||||||
incidentRepository: incidentService,
|
incidentRepository: incidentService,
|
||||||
@@ -123,16 +114,29 @@ func (isp *CreateIncidentAction) CreateIncidentModalCommandProcessing(callback s
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if viper.GetBool("ENABLE_GMEET") {
|
if viper.GetBool("ENABLE_CONFERENCE") {
|
||||||
gmeet, err := createGmeetLink(*channelID)
|
calendarActions := conference2.GetCalendarActions()
|
||||||
|
calendarService := conference.NewCalendarService(calendarActions)
|
||||||
|
calendarEvent, err := calendarService.CreateEvent(incidentEntity.IncidentName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("[CIP] error while creating gmeet", zap.Error(err))
|
logger.Error(fmt.Sprintf("Unable to create conference event due to error : %s", err.Error()))
|
||||||
} else {
|
} else {
|
||||||
msgOption := slack.MsgOptionText(fmt.Sprintf("gmeet: ", gmeet), false)
|
util.UpdateIncidentWithConferenceDetails(incidentEntity, calendarEvent, isp.incidentRepository)
|
||||||
isp.client.PostMessage(*channelID, msgOption)
|
msgUpdate := NewIncidentChannelMessageUpdateAction(isp.client, isp.incidentRepository, isp.teamRepository, isp.severityRepository)
|
||||||
|
msgUpdate.ProcessAction(incidentEntity.SlackChannel)
|
||||||
|
bookmarkParam := slack.AddBookmarkParameters{Link: calendarEvent.ConferenceLink, Title: calendarService.GetConferenceTitle()}
|
||||||
|
_, err := isp.client.AddBookmark(*channelID, bookmarkParam)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("Unable to add conference link as bookmark for channel %s due to error: %s", incidentEntity.SlackChannel, err.Error()))
|
||||||
|
}
|
||||||
|
msgOption := slack.MsgOptionText(fmt.Sprintf(util.ConferenceMessage, calendarEvent.ConferenceLink), false)
|
||||||
|
_, _, err = isp.client.PostMessage(*channelID, msgOption)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("Unable to post message to channel %s due to error: %s", incidentEntity.SlackChannel, err.Error()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Acknowledge the interaction callback
|
// Acknowledge the interaction callback
|
||||||
@@ -179,47 +183,6 @@ func (isp *CreateIncidentAction) CreateIncidentModalCommandProcessingV2(
|
|||||||
isp.client.Ack(*request, payload)
|
isp.client.Ack(*request, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createGmeetLink(channelName string) (string, error) {
|
|
||||||
calclient, err := calendar.NewService(context.Background(), option.WithCredentialsFile(viper.GetString("GMEET_CONFIG_FILE_PATH")))
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Unable to read client secret file: ", zap.Error(err))
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
t0 := time.Now().Format(time.RFC3339)
|
|
||||||
t1 := time.Now().Add(1 * time.Hour).Format(time.RFC3339)
|
|
||||||
event := &calendar.Event{
|
|
||||||
Summary: channelName,
|
|
||||||
Description: "Incident",
|
|
||||||
Start: &calendar.EventDateTime{
|
|
||||||
DateTime: t0,
|
|
||||||
},
|
|
||||||
End: &calendar.EventDateTime{
|
|
||||||
DateTime: t1,
|
|
||||||
},
|
|
||||||
ConferenceData: &calendar.ConferenceData{
|
|
||||||
CreateRequest: &calendar.CreateConferenceRequest{
|
|
||||||
RequestId: uuid.NewString(),
|
|
||||||
ConferenceSolutionKey: &calendar.ConferenceSolutionKey{
|
|
||||||
Type: "hangoutsMeet",
|
|
||||||
},
|
|
||||||
Status: &calendar.ConferenceRequestStatus{
|
|
||||||
StatusCode: "success",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
calendarID := "primary" //use "primary"
|
|
||||||
event, err = calclient.Events.Insert(calendarID, event).ConferenceDataVersion(1).Do()
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Unable to create event. %v\n", zap.Error(err))
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
calclient.Events.Delete(calendarID, event.Id).Do()
|
|
||||||
return event.HangoutLink, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (isp *CreateIncidentAction) createSlackChannel(incidentEntity *incident.IncidentEntity) (*string, error) {
|
func (isp *CreateIncidentAction) createSlackChannel(incidentEntity *incident.IncidentEntity) (*string, error) {
|
||||||
var channelName string
|
var channelName string
|
||||||
if viper.GetString("env") != "prod" {
|
if viper.GetString("env") != "prod" {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func IncidentSummarySection(incident *incident.IncidentEntity, team *team.TeamEn
|
|||||||
buildDescriptionBlock(incident.Description),
|
buildDescriptionBlock(incident.Description),
|
||||||
buildTypeAndChannelSectionBlock(incident, team.Name),
|
buildTypeAndChannelSectionBlock(incident, team.Name),
|
||||||
buildSeverityAndTicketSectionBlock(severity.Name),
|
buildSeverityAndTicketSectionBlock(severity.Name),
|
||||||
buildStatusAndMeetLinkSectionBlock(incidentStatus.Name),
|
buildStatusAndConferenceLinkSectionBlock(incidentStatus.Name, incident.ConferenceLink),
|
||||||
buildCreatedByAndCreatedAtSectionBlock(incident),
|
buildCreatedByAndCreatedAtSectionBlock(incident),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -62,10 +62,14 @@ func buildSeverityAndTicketSectionBlock(severityName string) *slack.SectionBlock
|
|||||||
return block
|
return block
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildStatusAndMeetLinkSectionBlock(incidentStatus string) *slack.SectionBlock {
|
func buildStatusAndConferenceLinkSectionBlock(incidentStatus string, conferenceLink string) *slack.SectionBlock {
|
||||||
|
var conferenceLinkLabel = "Integration Disabled"
|
||||||
|
if conferenceLink != "" {
|
||||||
|
conferenceLinkLabel = conferenceLink
|
||||||
|
}
|
||||||
fields := []*slack.TextBlockObject{
|
fields := []*slack.TextBlockObject{
|
||||||
slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Status*\n%s", incidentStatus), false, false),
|
slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Status*\n%s", incidentStatus), false, false),
|
||||||
slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Meeting*\n%s", "Integration Disabled"), false, false),
|
slack.NewTextBlockObject("mrkdwn", fmt.Sprintf("*Meeting*\n%s", conferenceLinkLabel), false, false),
|
||||||
}
|
}
|
||||||
block := slack.NewSectionBlock(nil, fields, nil)
|
block := slack.NewSectionBlock(nil, fields, nil)
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ type IncidentEntity struct {
|
|||||||
UpdatedBy string `gorm:"column:updated_by"`
|
UpdatedBy string `gorm:"column:updated_by"`
|
||||||
MetaData JSON `gorm:"column:meta_data"`
|
MetaData JSON `gorm:"column:meta_data"`
|
||||||
RCA string `gorm:"column:rca_text"`
|
RCA string `gorm:"column:rca_text"`
|
||||||
|
ConferenceId string `gorm:"column:conference_id"`
|
||||||
|
ConferenceLink string `gorm:"column:conference_link"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (IncidentEntity) TableName() string {
|
func (IncidentEntity) TableName() string {
|
||||||
|
|||||||
42
pkg/conference/calendar_interface.go
Normal file
42
pkg/conference/calendar_interface.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package conference
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"google.golang.org/api/calendar/v3"
|
||||||
|
"google.golang.org/api/option"
|
||||||
|
"houston/internal/clients"
|
||||||
|
"houston/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EventData struct {
|
||||||
|
EventName string
|
||||||
|
Id string
|
||||||
|
ConferenceLink string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ICalendarActions interface {
|
||||||
|
GetConferenceTitle() string
|
||||||
|
CreateEvent(eventData EventData) (EventData, error)
|
||||||
|
DeleteEvent(eventId string) error
|
||||||
|
GetEvent(eventId string) (EventData, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCalendarActions() ICalendarActions {
|
||||||
|
switch viper.GetString("CONFERENCE_TYPE") {
|
||||||
|
case "GMEET":
|
||||||
|
actions, _ := newGoogleCalendarActions()
|
||||||
|
return actions
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGoogleCalendarActions() (ICalendarActions, error) {
|
||||||
|
service, err := calendar.NewService(context.Background(), option.WithTokenSource(clients.GetGoogleTokenSource()))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("Unable to retrieve googleCalendar client due to error : %s", err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &GoogleCalendarActionsImpl{CalendarService: service}, nil
|
||||||
|
}
|
||||||
60
pkg/conference/google_calendar_actions.go
Normal file
60
pkg/conference/google_calendar_actions.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package conference
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"google.golang.org/api/calendar/v3"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const GoogleMeetTitle = "Google Meet"
|
||||||
|
const calendarId = "primary"
|
||||||
|
|
||||||
|
type GoogleCalendarActionsImpl struct {
|
||||||
|
CalendarService *calendar.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (calendarActions *GoogleCalendarActionsImpl) GetConferenceTitle() string {
|
||||||
|
return GoogleMeetTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
func (calendarActions *GoogleCalendarActionsImpl) CreateEvent(eventData EventData) (EventData, error) {
|
||||||
|
startTime := calendar.EventDateTime{Date: time.Now().Format("2006-01-02"), TimeZone: "IST"}
|
||||||
|
event := &calendar.Event{
|
||||||
|
Summary: eventData.EventName,
|
||||||
|
Description: "Google Calendar Meeting Invite for Incident " + eventData.EventName,
|
||||||
|
Start: &startTime,
|
||||||
|
//We are using end date as start date because event is a daily recurring event
|
||||||
|
End: &startTime,
|
||||||
|
ConferenceData: &calendar.ConferenceData{
|
||||||
|
CreateRequest: &calendar.CreateConferenceRequest{
|
||||||
|
RequestId: uuid.NewString(),
|
||||||
|
ConferenceSolutionKey: &calendar.ConferenceSolutionKey{
|
||||||
|
Type: "hangoutsMeet",
|
||||||
|
},
|
||||||
|
Status: &calendar.ConferenceRequestStatus{
|
||||||
|
StatusCode: "success",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
event.Recurrence = []string{"RRULE:FREQ=DAILY"}
|
||||||
|
event, err := calendarActions.CalendarService.Events.Insert(calendarId, event).ConferenceDataVersion(1).Do()
|
||||||
|
if err == nil {
|
||||||
|
eventData.Id = event.Id
|
||||||
|
eventData.ConferenceLink = event.HangoutLink
|
||||||
|
}
|
||||||
|
return eventData, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (calendarActions *GoogleCalendarActionsImpl) DeleteEvent(eventId string) error {
|
||||||
|
return calendarActions.CalendarService.Events.Delete(calendarId, eventId).Do()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (calendarActions *GoogleCalendarActionsImpl) GetEvent(eventId string) (EventData, error) {
|
||||||
|
event, err := calendarActions.CalendarService.Events.Get(calendarId, eventId).Do()
|
||||||
|
eventData := EventData{Id: eventId}
|
||||||
|
if err == nil {
|
||||||
|
eventData.ConferenceLink = event.HangoutLink
|
||||||
|
}
|
||||||
|
return eventData, err
|
||||||
|
}
|
||||||
54
pkg/google/googleDrive/drive_actions.go
Normal file
54
pkg/google/googleDrive/drive_actions.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package googleDrive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"google.golang.org/api/drive/v3"
|
||||||
|
"houston/common/util"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GoogleDriveActionsImpl struct {
|
||||||
|
filesService *drive.FilesService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driveActions *GoogleDriveActionsImpl) SearchInDrive(timeout time.Duration, query string) (*drive.FileList, error) {
|
||||||
|
driveContext, cancelFunc := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancelFunc()
|
||||||
|
fileList, err := driveActions.filesService.List().Q(query).Context(driveContext).Do()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fileList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driveActions *GoogleDriveActionsImpl) CreateDirectory(timeout time.Duration, directoryName string) (*drive.File, error) {
|
||||||
|
driveContext, cancelFunc := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancelFunc()
|
||||||
|
directory := &drive.File{MimeType: util.GoogleDriveFileMimeType, Name: directoryName}
|
||||||
|
file, err := driveActions.filesService.Create(directory).Context(driveContext).Fields("*").Do()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driveActions *GoogleDriveActionsImpl) DeleteFile(timeout time.Duration, fileId string) error {
|
||||||
|
driveContext, cancelFunc := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancelFunc()
|
||||||
|
err := driveActions.filesService.Delete(fileId).Context(driveContext).Do()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driveActions *GoogleDriveActionsImpl) CopyFile(timeout time.Duration, fileId string, directoryId string) (*drive.File, error) {
|
||||||
|
driveContext, cancelFunc := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancelFunc()
|
||||||
|
copyFile := &drive.File{Parents: []string{directoryId}}
|
||||||
|
file, err := driveActions.filesService.Copy(fileId, copyFile).Fields("*").Context(driveContext).Do()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
27
pkg/google/googleDrive/drive_interface.go
Normal file
27
pkg/google/googleDrive/drive_interface.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package googleDrive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"google.golang.org/api/drive/v3"
|
||||||
|
"google.golang.org/api/option"
|
||||||
|
"houston/internal/clients"
|
||||||
|
"houston/logger"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GoogleDriveActions interface {
|
||||||
|
SearchInDrive(timeout time.Duration, query string) (*drive.FileList, error)
|
||||||
|
CreateDirectory(timeout time.Duration, directoryName string) (*drive.File, error)
|
||||||
|
DeleteFile(timeout time.Duration, fileId string) error
|
||||||
|
CopyFile(timeout time.Duration, fileId string, directoryId string) (*drive.File, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGoogleDriveActions() (*GoogleDriveActionsImpl, error) {
|
||||||
|
driveService, err := drive.NewService(context.Background(), option.WithTokenSource(clients.GetGoogleTokenSource()))
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Unable to retrieve Drive client", zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &GoogleDriveActionsImpl{filesService: driveService.Files}, nil
|
||||||
|
}
|
||||||
50
service/conference/calendar_service.go
Normal file
50
service/conference/calendar_service.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package conference
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"houston/logger"
|
||||||
|
"houston/pkg/conference"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CalendarService struct {
|
||||||
|
calendarActions conference.ICalendarActions
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCalendarService(calendarActions conference.ICalendarActions) *CalendarService {
|
||||||
|
return &CalendarService{calendarActions: calendarActions}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (calendarService *CalendarService) CreateEvent(eventName string) (conference.EventData, error) {
|
||||||
|
eventData := conference.EventData{
|
||||||
|
EventName: eventName,
|
||||||
|
}
|
||||||
|
eventData, err := calendarService.calendarActions.CreateEvent(eventData)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("Unable to create conference event due to error: %s", err))
|
||||||
|
}
|
||||||
|
return eventData, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (calendarService *CalendarService) DeleteEvent(eventId string) error {
|
||||||
|
err := calendarService.calendarActions.DeleteEvent(eventId)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("Unable to delete conference event due to error: %s", err))
|
||||||
|
} else {
|
||||||
|
logger.Info("Successfully deleted conference event")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (calendarService *CalendarService) GetEvent(eventId string) (conference.EventData, error) {
|
||||||
|
event, err := calendarService.calendarActions.GetEvent(eventId)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("Unable to get conference event due to error: %s", err))
|
||||||
|
} else {
|
||||||
|
logger.Info("Successfully retrieved conference event details")
|
||||||
|
}
|
||||||
|
return event, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (calendarService *CalendarService) GetConferenceTitle() string {
|
||||||
|
return calendarService.calendarActions.GetConferenceTitle()
|
||||||
|
}
|
||||||
106
service/conference/calendar_service_test.go
Normal file
106
service/conference/calendar_service_test.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package conference
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gojuno/minimock/v3"
|
||||||
|
"github.com/magiconair/properties/assert"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"houston/logger"
|
||||||
|
"houston/mocks"
|
||||||
|
"houston/pkg/conference"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CalendarServiceSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CalendarServiceSuite) SetupSuite() {
|
||||||
|
logger.InitLogger()
|
||||||
|
viper.Set("CONFERENCE_TYPE", "GMEET")
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
testLabel = "testLabel"
|
||||||
|
createEventFailedError = "create event failed"
|
||||||
|
deleteEventFailedError = "delete event failed"
|
||||||
|
getEventFailedError = "get event failed"
|
||||||
|
updateEventFailedError = "update event failed"
|
||||||
|
eventIdLabel = "eventId"
|
||||||
|
)
|
||||||
|
|
||||||
|
var eventData = conference.EventData{
|
||||||
|
EventName: testLabel,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CalendarServiceSuite) Test_CreateEventFailed() {
|
||||||
|
controller := minimock.NewController(suite.T())
|
||||||
|
suite.T().Cleanup(controller.Finish)
|
||||||
|
calendarActions := mocks.NewICalendarActionsMock(controller)
|
||||||
|
calendarActions.CreateEventMock.When(
|
||||||
|
eventData).Then(conference.EventData{}, errors.New(createEventFailedError))
|
||||||
|
calendarService := NewCalendarService(calendarActions)
|
||||||
|
_, err := calendarService.CreateEvent(testLabel)
|
||||||
|
assert.Equal(suite.T(), err.Error(), createEventFailedError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CalendarServiceSuite) Test_CreateEventSuccess() {
|
||||||
|
controller := minimock.NewController(suite.T())
|
||||||
|
suite.T().Cleanup(controller.Finish)
|
||||||
|
calendarActions := mocks.NewICalendarActionsMock(controller)
|
||||||
|
calendarActions.CreateEventMock.When(
|
||||||
|
eventData).Then(conference.EventData{Id: testLabel, ConferenceLink: testLabel}, nil)
|
||||||
|
calendarService := NewCalendarService(calendarActions)
|
||||||
|
event, _ := calendarService.CreateEvent(testLabel)
|
||||||
|
assert.Equal(suite.T(), event.Id, testLabel)
|
||||||
|
assert.Equal(suite.T(), event.ConferenceLink, testLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CalendarServiceSuite) Test_Delete_EventFailed() {
|
||||||
|
controller := minimock.NewController(suite.T())
|
||||||
|
suite.T().Cleanup(controller.Finish)
|
||||||
|
calendarActions := mocks.NewICalendarActionsMock(controller)
|
||||||
|
calendarActions.DeleteEventMock.When(
|
||||||
|
eventIdLabel).Then(errors.New(deleteEventFailedError))
|
||||||
|
calendarService := NewCalendarService(calendarActions)
|
||||||
|
err := calendarService.DeleteEvent(eventIdLabel)
|
||||||
|
assert.Equal(suite.T(), err.Error(), deleteEventFailedError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CalendarServiceSuite) Test_Delete_Event_Success() {
|
||||||
|
controller := minimock.NewController(suite.T())
|
||||||
|
suite.T().Cleanup(controller.Finish)
|
||||||
|
calendarActions := mocks.NewICalendarActionsMock(controller)
|
||||||
|
calendarActions.DeleteEventMock.When(
|
||||||
|
eventIdLabel).Then(nil)
|
||||||
|
calendarService := NewCalendarService(calendarActions)
|
||||||
|
err := calendarService.DeleteEvent(eventIdLabel)
|
||||||
|
assert.Equal(suite.T(), err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CalendarServiceSuite) Test_Get_Event_Success() {
|
||||||
|
controller := minimock.NewController(suite.T())
|
||||||
|
suite.T().Cleanup(controller.Finish)
|
||||||
|
calendarActions := mocks.NewICalendarActionsMock(controller)
|
||||||
|
calendarActions.GetEventMock.When(
|
||||||
|
eventIdLabel).Then(conference.EventData{Id: eventIdLabel}, nil)
|
||||||
|
calendarService := NewCalendarService(calendarActions)
|
||||||
|
event, _ := calendarService.GetEvent(eventIdLabel)
|
||||||
|
assert.Equal(suite.T(), event.Id, eventIdLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CalendarServiceSuite) Test_Get_Event_Failed() {
|
||||||
|
controller := minimock.NewController(suite.T())
|
||||||
|
suite.T().Cleanup(controller.Finish)
|
||||||
|
calendarActions := mocks.NewICalendarActionsMock(controller)
|
||||||
|
calendarActions.GetEventMock.When(
|
||||||
|
eventIdLabel).Then(conference.EventData{}, errors.New(getEventFailedError))
|
||||||
|
calendarService := NewCalendarService(calendarActions)
|
||||||
|
_, err := calendarService.GetEvent(eventIdLabel)
|
||||||
|
assert.Equal(suite.T(), err.Error(), getEventFailedError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalendarService(t *testing.T) {
|
||||||
|
suite.Run(t, new(CalendarServiceSuite))
|
||||||
|
}
|
||||||
112
service/google/drive_service.go
Normal file
112
service/google/drive_service.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"google.golang.org/api/drive/v3"
|
||||||
|
"houston/common/util"
|
||||||
|
houstonLogger "houston/logger"
|
||||||
|
"houston/pkg/google/googleDrive"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
driveActions googleDrive.GoogleDriveActions
|
||||||
|
driveActionTimeOut time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDriveService(driveActions googleDrive.GoogleDriveActions) *Service {
|
||||||
|
return &Service{driveActions: driveActions, driveActionTimeOut: viper.GetDuration("google.api.timeout")}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driveService *Service) createDirectory(directoryName string) (*drive.File, error) {
|
||||||
|
query := fmt.Sprintf("name = '%s' and mimeType = '%v'", directoryName, util.GoogleDriveFileMimeType)
|
||||||
|
directoryList, err := driveService.driveActions.SearchInDrive(driveService.driveActionTimeOut, query)
|
||||||
|
if err != nil {
|
||||||
|
houstonLogger.Error(fmt.Sprintf("Error searching for directory %v: %v", directoryName, err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(directoryList.Files) > 0 {
|
||||||
|
if directoryList.Files[0].Trashed == false {
|
||||||
|
return directoryList.Files[0], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return driveService.driveActions.CreateDirectory(driveService.driveActionTimeOut, directoryName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driveService *Service) moveFilesToDirectory(fileList *drive.FileList, directory *drive.File) ([]string,
|
||||||
|
error) {
|
||||||
|
responseMessages := make([]string, 0)
|
||||||
|
|
||||||
|
for _, file := range fileList.Files {
|
||||||
|
// Create a copy of the file in the new directory.
|
||||||
|
_, err := driveService.driveActions.CopyFile(driveService.driveActionTimeOut, file.Id, directory.Id)
|
||||||
|
if err != nil {
|
||||||
|
houstonLogger.Error(fmt.Sprintf("Error copying file %v to directory %v: %v", file.Name, directory.Name, err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Delete the original file.
|
||||||
|
err = driveService.driveActions.DeleteFile(driveService.driveActionTimeOut, file.Id)
|
||||||
|
if err != nil {
|
||||||
|
houstonLogger.Error(fmt.Sprintf("Error deleting file %v: %v", file.Name, err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return responseMessages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driveService *Service) CollectTranscripts(fileName string) error {
|
||||||
|
query := fmt.Sprintf("name contains '%s' and mimeType != '%s'", fileName, util.GoogleDriveFileMimeType)
|
||||||
|
fileList, err := driveService.driveActions.SearchInDrive(driveService.driveActionTimeOut, query)
|
||||||
|
if err != nil {
|
||||||
|
houstonLogger.Error(fmt.Sprintf("Error searching for files named %v: %v", fileName, err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fileList.Files) == 0 {
|
||||||
|
houstonLogger.Info(fmt.Sprintf("No files found named: %v", fileName))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Check and create a directory with the specified fileName.
|
||||||
|
directory, err := driveService.createDirectory(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var filesToMove []drive.File
|
||||||
|
for _, resultFile := range fileList.Files {
|
||||||
|
for _, parent := range resultFile.Parents {
|
||||||
|
if parent != directory.Id {
|
||||||
|
filesToMove = append(filesToMove, *resultFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(filesToMove, func(index1, index2 int) bool {
|
||||||
|
timeFormat := "2023-10-17T04:30:28.465Z"
|
||||||
|
time1, err1 := time.Parse(timeFormat, fileList.Files[index1].ModifiedTime)
|
||||||
|
time2, err2 := time.Parse(timeFormat, fileList.Files[index2].ModifiedTime)
|
||||||
|
|
||||||
|
// Handle any parsing errors here if necessary.
|
||||||
|
if err1 != nil {
|
||||||
|
houstonLogger.Error(fmt.Sprintf("Error parsing time for object %s: %v", fileList.Files[index1].Name, err1))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err2 != nil {
|
||||||
|
houstonLogger.Error(fmt.Sprintf("Error parsing time for object %s: %v", fileList.Files[index2].Name, err2))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return time1.Before(time2)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Move files to the new directory.
|
||||||
|
responseMessages, err := driveService.moveFilesToDirectory(fileList, directory)
|
||||||
|
if err != nil {
|
||||||
|
houstonLogger.Error(fmt.Sprintf("Error moving files to directory %v: %v", directory.Name, err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
houstonLogger.Info(fmt.Sprintf("Response messages: %v", responseMessages))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
232
service/google/drive_service_test.go
Normal file
232
service/google/drive_service_test.go
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gojuno/minimock/v3"
|
||||||
|
"github.com/magiconair/properties/assert"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"google.golang.org/api/drive/v3"
|
||||||
|
"houston/logger"
|
||||||
|
"houston/mocks"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DriveServiceSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
driveActionTimeOut time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DriveServiceSuite) SetupSuite() {
|
||||||
|
logger.InitLogger()
|
||||||
|
viper.Set("google.api.timeout", 10*time.Second)
|
||||||
|
suite.driveActionTimeOut = viper.GetDuration("google.api.timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DriveServiceSuite) Test_CollectTranscripts_FileNotFound() {
|
||||||
|
controller := minimock.NewController(suite.T())
|
||||||
|
suite.T().Cleanup(controller.Finish)
|
||||||
|
driveActions := mocks.NewGoogleDriveActionsMock(controller)
|
||||||
|
driveActions.SearchInDriveMock.When(suite.driveActionTimeOut,
|
||||||
|
"name contains 'directoryName' and mimeType != 'application/vnd."+
|
||||||
|
"google-apps.folder'").Then(nil, errors.New("file not found"))
|
||||||
|
driveService := NewDriveService(driveActions)
|
||||||
|
err := driveService.CollectTranscripts("directoryName")
|
||||||
|
assert.Equal(suite.T(), err.Error(), "file not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DriveServiceSuite) Test_CollectTranscripts_SuccessCase() {
|
||||||
|
controller := minimock.NewController(suite.T())
|
||||||
|
suite.T().Cleanup(controller.Finish)
|
||||||
|
|
||||||
|
driveActions := mocks.NewGoogleDriveActionsMock(controller)
|
||||||
|
|
||||||
|
driveActions.SearchInDriveMock.When(suite.driveActionTimeOut, "name contains 'fileName' and mimeType != 'application/vnd.google-apps."+
|
||||||
|
"folder'").Then(&drive.FileList{
|
||||||
|
Files: []*drive.File{
|
||||||
|
{
|
||||||
|
Id: "fileId",
|
||||||
|
Name: "fileName",
|
||||||
|
MimeType: "application/vnd.google-apps.document",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
driveActions.SearchInDriveMock.When(suite.driveActionTimeOut, "name = 'fileName' and mimeType = 'application/vnd.google-apps.folder'").
|
||||||
|
Then(&drive.FileList{Files: []*drive.File{}}, nil)
|
||||||
|
driveActions.CreateDirectoryMock.When(suite.driveActionTimeOut, "fileName").Then(&drive.File{
|
||||||
|
Id: "directoryId",
|
||||||
|
Name: "fileName",
|
||||||
|
MimeType: "application/vnd.google-apps.folder",
|
||||||
|
Trashed: false,
|
||||||
|
}, nil)
|
||||||
|
driveActions.CopyFileMock.When(suite.driveActionTimeOut, "fileId", "directoryId").Then(&drive.File{
|
||||||
|
Id: "copiedFileId",
|
||||||
|
Name: "fileName",
|
||||||
|
MimeType: "application/vnd.google-apps.document",
|
||||||
|
}, nil)
|
||||||
|
driveActions.DeleteFileMock.When(suite.driveActionTimeOut, "fileId").Then(nil)
|
||||||
|
|
||||||
|
driveService := NewDriveService(driveActions)
|
||||||
|
|
||||||
|
err := driveService.CollectTranscripts("fileName")
|
||||||
|
assert.Equal(suite.T(), err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DriveServiceSuite) Test_CollectTranscripts_CreateDirectoryError() {
|
||||||
|
controller := minimock.NewController(suite.T())
|
||||||
|
suite.T().Cleanup(controller.Finish)
|
||||||
|
driveActions := mocks.NewGoogleDriveActionsMock(controller)
|
||||||
|
driveActions.SearchInDriveMock.When(suite.driveActionTimeOut, "name contains 'fileName' and mimeType != 'application/vnd.google-apps."+
|
||||||
|
"folder'").Then(&drive.FileList{
|
||||||
|
Files: []*drive.File{
|
||||||
|
{
|
||||||
|
Id: "fileId",
|
||||||
|
Name: "fileName",
|
||||||
|
MimeType: "application/vnd.google-apps.document",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
driveActions.SearchInDriveMock.When(suite.driveActionTimeOut, "name = 'fileName' and mimeType = 'application/vnd.google-apps.folder'").
|
||||||
|
Then(&drive.FileList{Files: []*drive.File{}}, nil)
|
||||||
|
driveActions.CreateDirectoryMock.When(suite.driveActionTimeOut, "fileName").Then(nil, errors.New("error creating directory"))
|
||||||
|
driveService := NewDriveService(driveActions)
|
||||||
|
err := driveService.CollectTranscripts("fileName")
|
||||||
|
assert.Equal(suite.T(), err.Error(), "error creating directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DriveServiceSuite) Test_CollectTranscripts_CopyFileError() {
|
||||||
|
controller := minimock.NewController(suite.T())
|
||||||
|
suite.T().Cleanup(controller.Finish)
|
||||||
|
driveActions := mocks.NewGoogleDriveActionsMock(controller)
|
||||||
|
driveActions.SearchInDriveMock.When(suite.driveActionTimeOut, "name contains 'fileName' and mimeType != 'application/vnd.google-apps."+
|
||||||
|
"folder'").Then(&drive.FileList{
|
||||||
|
Files: []*drive.File{
|
||||||
|
{
|
||||||
|
Id: "fileId",
|
||||||
|
Name: "fileName",
|
||||||
|
MimeType: "application/vnd.google-apps.document",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
driveActions.SearchInDriveMock.When(suite.driveActionTimeOut, "name = 'fileName' and mimeType = 'application/vnd.google-apps.folder'").
|
||||||
|
Then(&drive.FileList{Files: []*drive.File{}}, nil)
|
||||||
|
driveActions.CreateDirectoryMock.When(suite.driveActionTimeOut, "fileName").Then(&drive.File{
|
||||||
|
Id: "directoryId",
|
||||||
|
Name: "fileName",
|
||||||
|
MimeType: "application/vnd.google-apps.folder",
|
||||||
|
Trashed: false,
|
||||||
|
}, nil)
|
||||||
|
driveActions.CopyFileMock.When(suite.driveActionTimeOut, "fileId", "directoryId").Then(nil, errors.New("error copying file"))
|
||||||
|
driveService := NewDriveService(driveActions)
|
||||||
|
err := driveService.CollectTranscripts("fileName")
|
||||||
|
assert.Equal(suite.T(), err.Error(), "error copying file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DriveServiceSuite) Test_CollectTranscripts_SearchFileError() {
|
||||||
|
controller := minimock.NewController(suite.T())
|
||||||
|
suite.T().Cleanup(controller.Finish)
|
||||||
|
driveActions := mocks.NewGoogleDriveActionsMock(controller)
|
||||||
|
driveActions.SearchInDriveMock.When(suite.driveActionTimeOut, "name contains 'fileName' and mimeType != 'application/vnd.google-apps."+
|
||||||
|
"folder'").Then(nil, errors.New("error searching for files"))
|
||||||
|
driveService := NewDriveService(driveActions)
|
||||||
|
err := driveService.CollectTranscripts("fileName")
|
||||||
|
assert.Equal(suite.T(), err.Error(), "error searching for files")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DriveServiceSuite) Test_SearchInDrive_TimeOut() {
|
||||||
|
controller := minimock.NewController(suite.T())
|
||||||
|
suite.T().Cleanup(controller.Finish)
|
||||||
|
driveActions := mocks.NewGoogleDriveActionsMock(controller)
|
||||||
|
driveActions.SearchInDriveMock.When(suite.driveActionTimeOut, "name contains 'fileName' and mimeType != 'application/vnd.google-apps."+"folder'").Then(nil, errors.New("google drive api timedout"))
|
||||||
|
driveService := NewDriveService(driveActions)
|
||||||
|
err := driveService.CollectTranscripts("fileName")
|
||||||
|
assert.Equal(suite.T(), err.Error(), "google drive api timedout")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DriveServiceSuite) Test_CreateDirectory_TimeOut() {
|
||||||
|
controller := minimock.NewController(suite.T())
|
||||||
|
suite.T().Cleanup(controller.Finish)
|
||||||
|
driveActions := mocks.NewGoogleDriveActionsMock(controller)
|
||||||
|
driveActions.SearchInDriveMock.When(suite.driveActionTimeOut, "name contains 'fileName' and mimeType != 'application/vnd.google-apps."+
|
||||||
|
"folder'").Then(&drive.FileList{
|
||||||
|
Files: []*drive.File{
|
||||||
|
{
|
||||||
|
Id: "fileId",
|
||||||
|
Name: "fileName",
|
||||||
|
MimeType: "application/vnd.google-apps.document",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
driveActions.SearchInDriveMock.When(suite.driveActionTimeOut, "name = 'fileName' and mimeType = 'application/vnd.google-apps.folder'").
|
||||||
|
Then(&drive.FileList{Files: []*drive.File{}}, nil)
|
||||||
|
driveActions.CreateDirectoryMock.When(suite.driveActionTimeOut, "fileName").Then(nil, errors.New("google drive api timedout"))
|
||||||
|
driveService := NewDriveService(driveActions)
|
||||||
|
err := driveService.CollectTranscripts("fileName")
|
||||||
|
assert.Equal(suite.T(), err.Error(), "google drive api timedout")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DriveServiceSuite) Test_CopyFile_TimeOut() {
|
||||||
|
controller := minimock.NewController(suite.T())
|
||||||
|
suite.T().Cleanup(controller.Finish)
|
||||||
|
driveActions := mocks.NewGoogleDriveActionsMock(controller)
|
||||||
|
driveActions.SearchInDriveMock.When(suite.driveActionTimeOut, "name contains 'fileName' and mimeType != 'application/vnd.google-apps."+
|
||||||
|
"folder'").Then(&drive.FileList{
|
||||||
|
Files: []*drive.File{
|
||||||
|
{
|
||||||
|
Id: "fileId",
|
||||||
|
Name: "fileName",
|
||||||
|
MimeType: "application/vnd.google-apps.document",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
driveActions.SearchInDriveMock.When(suite.driveActionTimeOut, "name = 'fileName' and mimeType = 'application/vnd.google-apps.folder'").
|
||||||
|
Then(&drive.FileList{Files: []*drive.File{}}, nil)
|
||||||
|
driveActions.CreateDirectoryMock.When(suite.driveActionTimeOut, "fileName").Then(&drive.File{
|
||||||
|
Id: "directoryId",
|
||||||
|
Name: "fileName",
|
||||||
|
MimeType: "application/vnd.google-apps.folder",
|
||||||
|
Trashed: false,
|
||||||
|
}, nil)
|
||||||
|
driveActions.CopyFileMock.When(suite.driveActionTimeOut, "fileId", "directoryId").Then(nil, errors.New("google drive api timedout"))
|
||||||
|
driveService := NewDriveService(driveActions)
|
||||||
|
err := driveService.CollectTranscripts("fileName")
|
||||||
|
assert.Equal(suite.T(), err.Error(), "google drive api timedout")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *DriveServiceSuite) Test_DeleteFile_TimeOut() {
|
||||||
|
controller := minimock.NewController(suite.T())
|
||||||
|
suite.T().Cleanup(controller.Finish)
|
||||||
|
driveActions := mocks.NewGoogleDriveActionsMock(controller)
|
||||||
|
driveActions.SearchInDriveMock.When(suite.driveActionTimeOut, "name contains 'fileName' and mimeType != 'application/vnd.google-apps."+
|
||||||
|
"folder'").Then(&drive.FileList{
|
||||||
|
Files: []*drive.File{
|
||||||
|
{
|
||||||
|
Id: "fileId",
|
||||||
|
Name: "fileName",
|
||||||
|
MimeType: "application/vnd.google-apps.document",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
driveActions.SearchInDriveMock.When(suite.driveActionTimeOut, "name = 'fileName' and mimeType = 'application/vnd.google-apps.folder'").
|
||||||
|
Then(&drive.FileList{Files: []*drive.File{}}, nil)
|
||||||
|
driveActions.CreateDirectoryMock.When(suite.driveActionTimeOut, "fileName").Then(&drive.File{
|
||||||
|
Id: "directoryId",
|
||||||
|
Name: "fileName",
|
||||||
|
MimeType: "application/vnd.google-apps.folder",
|
||||||
|
Trashed: false,
|
||||||
|
}, nil)
|
||||||
|
driveActions.CopyFileMock.When(suite.driveActionTimeOut, "fileId", "directoryId").Then(&drive.File{
|
||||||
|
Id: "copiedFileId",
|
||||||
|
Name: "fileName",
|
||||||
|
MimeType: "application/vnd.google-apps.document",
|
||||||
|
}, nil)
|
||||||
|
driveActions.DeleteFileMock.When(suite.driveActionTimeOut, "fileId").Then(errors.New("google drive api timedout"))
|
||||||
|
driveService := NewDriveService(driveActions)
|
||||||
|
err := driveService.CollectTranscripts("fileName")
|
||||||
|
assert.Equal(suite.T(), err.Error(), "google drive api timedout")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDriveService(t *testing.T) {
|
||||||
|
suite.Run(t, new(DriveServiceSuite))
|
||||||
|
}
|
||||||
@@ -1,16 +1,12 @@
|
|||||||
package incident
|
package incident
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/google/uuid"
|
|
||||||
slackClient "github.com/slack-go/slack"
|
slackClient "github.com/slack-go/slack"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"google.golang.org/api/calendar/v3"
|
|
||||||
"google.golang.org/api/option"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"houston/common/util"
|
"houston/common/util"
|
||||||
"houston/internal/processor/action/view"
|
"houston/internal/processor/action/view"
|
||||||
@@ -20,6 +16,8 @@ import (
|
|||||||
"houston/model/severity"
|
"houston/model/severity"
|
||||||
"houston/model/team"
|
"houston/model/team"
|
||||||
"houston/model/user"
|
"houston/model/user"
|
||||||
|
conference2 "houston/pkg/conference"
|
||||||
|
service2 "houston/service/conference"
|
||||||
request "houston/service/request"
|
request "houston/service/request"
|
||||||
service "houston/service/response"
|
service "houston/service/response"
|
||||||
"houston/service/slack"
|
"houston/service/slack"
|
||||||
@@ -361,17 +359,23 @@ func createIncidentWorkflow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if viper.GetBool("ENABLE_GMEET") {
|
if viper.GetBool("ENABLE_CONFERENCE") {
|
||||||
gmeet, err := createGmeetLink(channel.Name)
|
calendarActions := conference2.GetCalendarActions()
|
||||||
|
calendarService := service2.NewCalendarService(calendarActions)
|
||||||
|
calendarEvent, err := calendarService.CreateEvent(channel.Name)
|
||||||
|
conferenceLink := calendarEvent.ConferenceLink
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(fmt.Sprintf("%s [%s] Error while creating gmeet", logTag, incidentName), zap.Error(err))
|
logger.Error(fmt.Sprintf("%s [%s] Error while creating conference", logTag, incidentName), zap.Error(err))
|
||||||
} else {
|
} else {
|
||||||
_, err := i.slackService.PostMessage(fmt.Sprint("gmeet: ", gmeet), false, channel)
|
util.UpdateIncidentWithConferenceDetails(incidentEntity, calendarEvent, i.incidentRepository)
|
||||||
|
//ToDo Update the summary in slack channel with Conference link. To be done post update incident refactoring
|
||||||
|
i.slackService.AddBookmark(conferenceLink, channel, calendarService.GetConferenceTitle())
|
||||||
|
_, err := i.slackService.PostMessage(fmt.Sprintf(util.ConferenceMessage, conferenceLink), false, channel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(fmt.Sprintf("%s [%s] Failed to post Google Meet link: %s", logTag, incidentName, gmeet))
|
logger.Error(fmt.Sprintf("%s [%s] Failed to post Conference link: %s", logTag, incidentName, conferenceLink))
|
||||||
}
|
}
|
||||||
|
logger.Info(fmt.Sprintf("%s [%s] Conference link posted to the channel %s", logTag, incidentName, conferenceLink))
|
||||||
}
|
}
|
||||||
logger.Info(fmt.Sprintf("%s [%s] Google Meeting link posted to the channel %s", logTag, incidentName, gmeet))
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -727,48 +731,3 @@ func postInWebhookSlackChannel(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createGmeetLink(channelName string) (string, error) {
|
|
||||||
incidentName := channelName
|
|
||||||
calclient, err := calendar.NewService(context.Background(), option.WithCredentialsFile(viper.GetString("GMEET_CONFIG_FILE_PATH")))
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(
|
|
||||||
fmt.Sprintf("%s [%s]Calander service creation failed, unable to read client secret file: ", logTag, incidentName),
|
|
||||||
zap.Error(err),
|
|
||||||
)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
t0 := time.Now().Format(time.RFC3339)
|
|
||||||
t1 := time.Now().Add(1 * time.Hour).Format(time.RFC3339)
|
|
||||||
event := &calendar.Event{
|
|
||||||
Summary: channelName,
|
|
||||||
Description: "Incident",
|
|
||||||
Start: &calendar.EventDateTime{
|
|
||||||
DateTime: t0,
|
|
||||||
},
|
|
||||||
End: &calendar.EventDateTime{
|
|
||||||
DateTime: t1,
|
|
||||||
},
|
|
||||||
ConferenceData: &calendar.ConferenceData{
|
|
||||||
CreateRequest: &calendar.CreateConferenceRequest{
|
|
||||||
RequestId: uuid.NewString(),
|
|
||||||
ConferenceSolutionKey: &calendar.ConferenceSolutionKey{
|
|
||||||
Type: "hangoutsMeet",
|
|
||||||
},
|
|
||||||
Status: &calendar.ConferenceRequestStatus{
|
|
||||||
StatusCode: "success",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
calendarID := "primary" //use "primary"
|
|
||||||
event, err = calclient.Events.Insert(calendarID, event).ConferenceDataVersion(1).Do()
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Unable to create event. %v\n", zap.Error(err))
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
calclient.Events.Delete(calendarID, event.Id).Do()
|
|
||||||
return event.HangoutLink, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -37,6 +37,15 @@ func (s *SlackService) PostMessage(message string, escape bool, channel *slack.C
|
|||||||
return timeStamp, nil
|
return timeStamp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SlackService) AddBookmark(bookmark string, channel *slack.Channel, title string) {
|
||||||
|
bookmarkParam := slack.AddBookmarkParameters{Link: bookmark, Title: title}
|
||||||
|
_, err := s.SocketModeClient.AddBookmark(channel.ID, bookmarkParam)
|
||||||
|
if err != nil {
|
||||||
|
e := fmt.Sprintf("%s Failed to add book mark into channel: %s", logTag, channel.Name)
|
||||||
|
logger.Error(e, zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SlackService) PostMessageOption(channelID string, messageOption slack.MsgOption) (string, error) {
|
func (s *SlackService) PostMessageOption(channelID string, messageOption slack.MsgOption) (string, error) {
|
||||||
_, timeStamp, err := s.SocketModeClient.PostMessage(channelID, messageOption)
|
_, timeStamp, err := s.SocketModeClient.PostMessage(channelID, messageOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user