notifier on success after failure - service downtime readable

pull/78/head
Hunter Long 2018-09-20 02:46:51 -07:00
parent 8dbf573667
commit e3c572da75
22 changed files with 308 additions and 37 deletions

View File

@ -1,4 +1,4 @@
VERSION=0.64
VERSION=0.65
BINARY_NAME=statup
GOPATH:=$(GOPATH)
GOCMD=go

View File

@ -58,6 +58,7 @@ type Notification struct {
Queue []interface{} `gorm:"-" json:"-"`
Running chan bool `gorm:"-" json:"-"`
CanTest bool `gorm:"-" json:"-"`
Online bool `gorm:"-" json:"-"`
}
type NotificationForm struct {

View File

@ -232,9 +232,7 @@ func TestRunAllQueueAndStop(t *testing.T) {
assert.Equal(t, 16, len(example.Queue))
go Queue(example)
assert.Equal(t, 16, len(example.Queue))
time.Sleep(13 * time.Second)
assert.Equal(t, 6, len(example.Queue))
time.Sleep(1 * time.Second)
time.Sleep(15 * time.Second)
assert.Equal(t, 6, len(example.Queue))
example.close()
assert.False(t, example.IsRunning())

View File

@ -1,3 +1,20 @@
/*
* Statup
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statup
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
context('Setup Process', () => {
// it('should go to setup Statup with Postgres', () => {
@ -45,4 +62,4 @@ context('Setup Process', () => {
cy.get('.card').should('have.length', 5)
})
})
})

View File

@ -1,3 +1,20 @@
/*
* Statup
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statup
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
context('Asset Tests', () => {
beforeEach(function () {
@ -36,4 +53,4 @@ context('Asset Tests', () => {
cy.request('http://localhost:8080/css/base.css').its('body').should('contain', 'BODY')
})
});
});

View File

@ -1,3 +1,20 @@
/*
* Statup
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statup
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
context('Dashboard Tests', () => {
beforeEach(function() {
@ -21,4 +38,4 @@ context('Dashboard Tests', () => {
cy.get('.col-12 > :nth-child(1)').should('contain', 'Statup')
})
});
});

View File

@ -1,3 +1,20 @@
/*
* Statup
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statup
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
context('Service Tests', () => {
beforeEach(function () {

View File

@ -1,3 +1,20 @@
/*
* Statup
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statup
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
context('Settings Forms', () => {
beforeEach(function() {
@ -28,4 +45,4 @@ context('Settings Forms', () => {
// cy.get('.header-desc').should('contain', 'This is an awesome page')
// })
});
});

View File

@ -1,3 +1,20 @@
/*
* Statup
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statup
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
context('User Testing', () => {
beforeEach(function () {
@ -49,4 +66,4 @@ context('User Testing', () => {
cy.get('tr').should('have.length', 2)
})
});
});

View File

@ -1,3 +1,20 @@
/*
* Statup
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statup
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//

View File

@ -1,3 +1,20 @@
/*
* Statup
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statup
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite

View File

@ -1,3 +1,20 @@
/*
* Statup
* Copyright (C) 2018. Hunter Long and the project contributors
* Written by Hunter Long <info@socialeck.com> and the project contributors
*
* https://github.com/hunterlong/statup
*
* The licenses for most software and other practical works are designed
* to take away your freedom to share and change the works. By contrast,
* the GNU General Public License is intended to guarantee your freedom to
* share and change all versions of a program--to make sure it remains free
* software for all its users.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.

View File

@ -32,32 +32,27 @@ type Service struct {
}
func renderServiceChartHandler(w http.ResponseWriter, r *http.Request) {
if !IsAuthenticated(r) {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
vars := mux.Vars(r)
fields := parseGet(r)
w.Header().Set("Content-Type", "text/javascript")
w.Header().Set("Cache-Control", "max-age=30")
startField := fields.Get("start")
endField := fields.Get("end")
var start time.Time
var end time.Time
if startField == "" {
start = time.Now().Add(-24 * time.Hour)
start = time.Now().Add(-24 * time.Hour).UTC()
} else {
start = time.Unix(utils.StringInt(startField), 0)
start = time.Unix(utils.StringInt(startField), 0).UTC()
}
if endField == "" {
end = time.Now()
end = time.Now().UTC()
} else {
end = time.Unix(utils.StringInt(endField), 0)
end = time.Unix(utils.StringInt(endField), 0).UTC()
}
service := core.SelectService(utils.StringInt(vars["id"]))
w.Header().Set("Content-Type", "text/javascript")
w.Header().Set("Cache-Control", "max-age=60")
data := core.GraphDataRaw(service, start, end).ToString()
out := struct {

View File

@ -54,8 +54,8 @@ func init() {
// Send will send a HTTP Post to the Discord API. It accepts type: []byte
func (u *Discord) Send(msg interface{}) error {
message := msg.([]byte)
req, _ := http.NewRequest("POST", discorder.GetValue("host"), bytes.NewBuffer(message))
message := msg.(string)
req, _ := http.NewRequest("POST", discorder.GetValue("host"), bytes.NewBuffer([]byte(message)))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
@ -73,11 +73,16 @@ func (u *Discord) Select() *notifier.Notification {
func (u *Discord) OnFailure(s *types.Service, f *types.Failure) {
msg := fmt.Sprintf(`{"content": "Your service '%v' is currently failing! Reason: %v"}`, s.Name, f.Issue)
u.AddQueue(msg)
u.Online = false
}
// OnSuccess will trigger successful service
func (u *Discord) OnSuccess(s *types.Service) {
if !u.Online {
msg := fmt.Sprintf(`{"content": "Your service '%v' is back online!"}`, s.Name)
u.AddQueue(msg)
}
u.Online = true
}
// OnSave triggers when this notifier has been saved

View File

@ -60,8 +60,31 @@ func TestDiscordNotifier(t *testing.T) {
assert.True(t, ok)
})
t.Run("Discord OnFailure", func(t *testing.T) {
discorder.OnFailure(TestService, TestFailure)
assert.Len(t, discorder.Queue, 1)
})
t.Run("Discord Check Offline", func(t *testing.T) {
assert.False(t, discorder.Online)
})
t.Run("Discord OnSuccess", func(t *testing.T) {
discorder.OnSuccess(TestService)
assert.Len(t, discorder.Queue, 2)
})
t.Run("Discord Check Back Online", func(t *testing.T) {
assert.True(t, discorder.Online)
})
t.Run("Discord OnSuccess Again", func(t *testing.T) {
discorder.OnSuccess(TestService)
assert.Len(t, discorder.Queue, 2)
})
t.Run("Discord Send", func(t *testing.T) {
err := discorder.Send([]byte(discordMessage))
err := discorder.Send(discordMessage)
assert.Nil(t, err)
})

File diff suppressed because one or more lines are too long

View File

@ -97,11 +97,34 @@ func TestEmailNotifier(t *testing.T) {
assert.True(t, ok)
})
t.Run("Emailer Test Source", func(t *testing.T) {
t.Run("Email Test Source", func(t *testing.T) {
emailSource(testEmail)
assert.NotEmpty(t, testEmail.Source)
})
t.Run("Email OnFailure", func(t *testing.T) {
emailer.OnFailure(TestService, TestFailure)
assert.Len(t, emailer.Queue, 1)
})
t.Run("Email Check Offline", func(t *testing.T) {
assert.False(t, emailer.Online)
})
t.Run("Email OnSuccess", func(t *testing.T) {
emailer.OnSuccess(TestService)
assert.Len(t, emailer.Queue, 2)
})
t.Run("Email Check Back Online", func(t *testing.T) {
assert.True(t, emailer.Online)
})
t.Run("Email OnSuccess Again", func(t *testing.T) {
emailer.OnSuccess(TestService)
assert.Len(t, emailer.Queue, 2)
})
t.Run("Email Send", func(t *testing.T) {
err := emailer.Send(testEmail)
assert.Nil(t, err)

View File

@ -80,11 +80,16 @@ func (u *LineNotify) Select() *notifier.Notification {
func (u *LineNotify) OnFailure(s *types.Service, f *types.Failure) {
msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name)
u.AddQueue(msg)
u.Online = false
}
// OnSuccess will trigger successful service
func (u *LineNotify) OnSuccess(s *types.Service) {
if !u.Online {
msg := fmt.Sprintf("Your service '%v' is back online!", s.Name)
u.AddQueue(msg)
}
u.Online = true
}
// OnSave triggers when this notifier has been saved

View File

@ -27,11 +27,10 @@ import (
)
const (
SLACK_ID = 2
SLACK_METHOD = "slack"
FAILING_TEMPLATE = `{ "attachments": [ { "fallback": "Service {{.Service.Name}} - is currently failing", "text": "<{{.Service.Domain}}|{{.Service.Name}}> - Your Statup service '{{.Service.Name}}' has just received a Failure notification with a HTTP Status code of {{.Service.LastStatusCode}}.", "fields": [ { "title": "Expected", "value": "{{.Service.Expected}}", "short": true }, { "title": "Status Code", "value": "{{.Service.LastStatusCode}}", "short": true } ], "color": "#FF0000", "thumb_url": "https://statup.io", "footer": "Statup", "footer_icon": "https://img.cjx.io/statuplogo32.png" } ] }`
SUCCESS_TEMPLATE = `{ "attachments": [ { "fallback": "Service {{.Service.Name}} - is now back online", "text": "<{{.Service.Domain}}|{{.Service.Name}}> - Your Statup service '{{.Service.Name}}' has just received a Failure notification.", "fields": [ { "title": "Issue", "value": "Awesome Project", "short": true }, { "title": "Status Code", "value": "{{.Service.LastStatusCode}}", "short": true } ], "color": "#00FF00", "thumb_url": "https://statup.io", "footer": "Statup", "footer_icon": "https://img.cjx.io/statuplogo32.png" } ] }`
TEST_TEMPLATE = `{"text":"{{.}}"}`
SLACK_TEXT = `{"text":"{{.}}"}`
)
type Slack struct {
@ -91,7 +90,6 @@ func (u *Slack) Send(msg interface{}) error {
}
defer res.Body.Close()
//contents, _ := ioutil.ReadAll(res.Body)
//fmt.Println(string(contents))
return nil
}
@ -102,7 +100,7 @@ func (u *Slack) Select() *notifier.Notification {
func (u *Slack) OnTest(n notifier.Notification) (bool, error) {
utils.Log(1, "Slack notifier loaded")
msg := fmt.Sprintf("You're Statup Slack Notifier is working correctly!")
err := parseSlackMessage(TEST_TEMPLATE, msg)
err := parseSlackMessage(SLACK_TEXT, msg)
return true, err
}
@ -110,15 +108,24 @@ func (u *Slack) OnTest(n notifier.Notification) (bool, error) {
func (u *Slack) OnFailure(s *types.Service, f *types.Failure) {
message := SlackMessage{
Service: s,
Template: FAILURE,
Template: FAILING_TEMPLATE,
Time: time.Now().Unix(),
}
parseSlackMessage(FAILING_TEMPLATE, message)
u.Online = false
}
// OnSuccess will trigger successful service
func (u *Slack) OnSuccess(s *types.Service) {
if !u.Online {
message := SlackMessage{
Service: s,
Template: SUCCESS_TEMPLATE,
Time: time.Now().Unix(),
}
parseSlackMessage(SUCCESS_TEMPLATE, message)
}
u.Online = true
}
// OnSave triggers when this notifier has been saved

View File

@ -40,6 +40,8 @@ func init() {
func TestSlackNotifier(t *testing.T) {
t.Parallel()
SLACK_URL = os.Getenv("SLACK_URL")
slacker.Host = SLACK_URL
if SLACK_URL == "" {
t.Log("Slack notifier testing skipped, missing SLACK_URL environment variable")
t.SkipNow()
@ -60,7 +62,7 @@ func TestSlackNotifier(t *testing.T) {
})
t.Run("Slack parse message", func(t *testing.T) {
err := parseSlackMessage("this is a test message!", slackTestMessage)
err := parseSlackMessage(SLACK_TEXT, "this is a test!")
assert.Nil(t, err)
assert.Equal(t, 1, len(slacker.Queue))
})
@ -71,14 +73,38 @@ func TestSlackNotifier(t *testing.T) {
assert.True(t, ok)
})
t.Run("Slack OnFailure", func(t *testing.T) {
slacker.OnFailure(TestService, TestFailure)
assert.Len(t, slacker.Queue, 2)
})
t.Run("Slack Check Offline", func(t *testing.T) {
assert.False(t, slacker.Online)
})
t.Run("Slack OnSuccess", func(t *testing.T) {
slacker.OnSuccess(TestService)
assert.Len(t, slacker.Queue, 3)
})
t.Run("Slack Check Back Online", func(t *testing.T) {
assert.True(t, slacker.Online)
})
t.Run("Slack OnSuccess Again", func(t *testing.T) {
slacker.OnSuccess(TestService)
assert.Len(t, slacker.Queue, 3)
})
t.Run("Slack Send", func(t *testing.T) {
err := slacker.Send(slackMessage)
assert.Nil(t, err)
assert.Len(t, slacker.Queue, 3)
})
t.Run("Slack Queue", func(t *testing.T) {
go notifier.Queue(slacker)
time.Sleep(1 * time.Second)
time.Sleep(2 * time.Second)
assert.Equal(t, SLACK_URL, slacker.Host)
assert.Equal(t, 0, len(slacker.Queue))
})

View File

@ -28,7 +28,7 @@ var (
TWILIO_SECRET = os.Getenv("TWILIO_SECRET")
TWILIO_FROM = os.Getenv("TWILIO_FROM")
TWILIO_TO = os.Getenv("TWILIO_TO")
twilioMessage = "The twilioNotifier notifier on Statup has been tested!"
twilioMessage = "The Twilio notifier on Statup has been tested!"
)
func init() {
@ -70,6 +70,29 @@ func TestTwilioNotifier(t *testing.T) {
assert.True(t, ok)
})
t.Run("Twilio OnFailure", func(t *testing.T) {
twilioNotifier.OnFailure(TestService, TestFailure)
assert.Len(t, twilioNotifier.Queue, 2)
})
t.Run("Twilio Check Offline", func(t *testing.T) {
assert.False(t, twilioNotifier.Online)
})
t.Run("Twilio OnSuccess", func(t *testing.T) {
twilioNotifier.OnSuccess(TestService)
assert.Len(t, twilioNotifier.Queue, 3)
})
t.Run("Twilio Check Back Online", func(t *testing.T) {
assert.True(t, twilioNotifier.Online)
})
t.Run("Twilio OnSuccess Again", func(t *testing.T) {
twilioNotifier.OnSuccess(TestService)
assert.Len(t, twilioNotifier.Queue, 3)
})
t.Run("Twilio Send", func(t *testing.T) {
err := twilioNotifier.Send(twilioMessage)
assert.Nil(t, err)

View File

@ -193,7 +193,7 @@ func copyAndCapture(w io.Writer, r io.Reader) ([]byte, error) {
func DurationReadable(d time.Duration) string {
if d.Hours() >= 1 {
return fmt.Sprintf("%0.0f hours and %0.0f minutes", d.Hours(), d.Minutes())
return fmt.Sprintf("%0.0f hours", d.Hours())
} else if d.Minutes() >= 1 {
return fmt.Sprintf("%0.0f minutes", d.Minutes())
} else if d.Seconds() >= 1 {