Openstatus www.openstatus.dev

๐Ÿ”ฅ improve incidents (#580)

* ๐Ÿ”ฅ improve incidents

* ๐Ÿง‘โ€๐ŸŽจ improve incident

* ๐Ÿšจ incident

* โŒ› add queue

* remove queue

* ๐Ÿ›‚ update

authored by

Thibault Le Ouay and committed by
GitHub
7edd604c ce20c327

+2236 -81
+4 -5
apps/checker/cmd/main.go
··· 92 92 if err != nil { 93 93 return fmt.Errorf("unable to ping: %w", err) 94 94 } 95 - 96 95 statusCode := statusCode(res.StatusCode) 97 96 // let's retry at least once if the status code is not successful. 98 97 if !statusCode.IsSuccessful() && called < 2 { ··· 109 108 Message: res.Message, 110 109 CronTimestamp: req.CronTimestamp, 111 110 }) 112 - } else if req.Status == "error" && statusCode.IsSuccessful() { 111 + } 112 + 113 + if req.Status == "error" && statusCode.IsSuccessful() { 113 114 // Q: Why here we check the data before updating the status in this scenario? 114 115 checker.UpdateStatus(ctx, checker.UpdateData{ 115 116 MonitorId: req.MonitorID, ··· 120 121 121 122 }) 122 123 } 123 - 124 124 if err := tinybirdClient.SendEvent(ctx, res); err != nil { 125 125 log.Ctx(ctx).Error().Err(err).Msg("failed to send event to tinybird") 126 126 } ··· 141 141 log.Ctx(ctx).Error().Err(err).Msg("failed to send event to tinybird") 142 142 } 143 143 144 - // If the status was previously active, we update it to error. 145 - // Q: Why not always updating the status? My idea is that the checker should be dumb and only check the status and return it. 146 144 if req.Status == "active" { 147 145 checker.UpdateStatus(ctx, checker.UpdateData{ 148 146 MonitorId: req.MonitorID, ··· 151 149 Region: flyRegion, 152 150 }) 153 151 } 152 + 154 153 } 155 154 156 155 c.JSON(http.StatusOK, gin.H{"message": "ok"})
+22 -4
apps/checker/go.mod
··· 10 10 ) 11 11 12 12 require ( 13 + cloud.google.com/go/cloudtasks v1.12.4 // indirect 14 + cloud.google.com/go/compute v1.23.1 // indirect 15 + cloud.google.com/go/compute/metadata v0.2.3 // indirect 16 + cloud.google.com/go/iam v1.1.3 // indirect 13 17 github.com/WilfredAlmeida/unkey-go v0.2.0 // indirect 14 18 github.com/bytedance/sonic v1.9.1 // indirect 15 19 github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect ··· 20 24 github.com/go-playground/universal-translator v0.18.1 // indirect 21 25 github.com/go-playground/validator/v10 v10.14.0 // indirect 22 26 github.com/goccy/go-json v0.10.2 // indirect 27 + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 28 + github.com/golang/protobuf v1.5.3 // indirect 29 + github.com/google/s2a-go v0.1.7 // indirect 30 + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect 31 + github.com/googleapis/gax-go/v2 v2.12.0 // indirect 23 32 github.com/joho/godotenv v1.5.1 // indirect 24 33 github.com/json-iterator/go v1.1.12 // indirect 25 34 github.com/klauspost/cpuid/v2 v2.2.4 // indirect ··· 32 41 github.com/pmezard/go-difflib v1.0.0 // indirect 33 42 github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 34 43 github.com/ugorji/go/codec v1.2.11 // indirect 44 + go.opencensus.io v0.24.0 // indirect 35 45 golang.org/x/arch v0.3.0 // indirect 36 - golang.org/x/crypto v0.9.0 // indirect 37 - golang.org/x/net v0.10.0 // indirect 46 + golang.org/x/crypto v0.14.0 // indirect 47 + golang.org/x/net v0.17.0 // indirect 48 + golang.org/x/oauth2 v0.13.0 // indirect 49 + golang.org/x/sync v0.4.0 // indirect 38 50 golang.org/x/sys v0.16.0 // indirect 39 - golang.org/x/text v0.9.0 // indirect 40 - google.golang.org/protobuf v1.30.0 // indirect 51 + golang.org/x/text v0.13.0 // indirect 52 + google.golang.org/api v0.149.0 // indirect 53 + google.golang.org/appengine v1.6.7 // indirect 54 + google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect 55 + google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect 56 + google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect 57 + google.golang.org/grpc v1.59.0 // indirect 58 + google.golang.org/protobuf v1.31.0 // indirect 41 59 gopkg.in/yaml.v3 v3.0.1 // indirect 42 60 )
+125
apps/checker/go.sum
··· 1 + cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 + cloud.google.com/go/cloudtasks v1.12.4 h1:5xXuFfAjg0Z5Wb81j2GAbB3e0bwroCeSF+5jBn/L650= 3 + cloud.google.com/go/cloudtasks v1.12.4/go.mod h1:BEPu0Gtt2dU6FxZHNqqNdGqIG86qyWKBPGnsb7udGY0= 4 + cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0= 5 + cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78= 6 + cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= 7 + cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= 8 + cloud.google.com/go/iam v1.1.3 h1:18tKG7DzydKWUnLjonWcJO6wjSCAtzh4GcRKlH/Hrzc= 9 + cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= 10 + github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 1 11 github.com/WilfredAlmeida/unkey-go v0.2.0 h1:WnPDbfrwgiI2uZQmFhSATY5Q1FU8NCGtQ+h1VjJWfoI= 2 12 github.com/WilfredAlmeida/unkey-go v0.2.0/go.mod h1:KZZ52al64FQUnWx0Zf2fIFmZvznwUiHqIg0nak/BGMw= 3 13 github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= ··· 5 15 github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 6 16 github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= 7 17 github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 18 + github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 8 19 github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 9 20 github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 10 21 github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 22 + github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 23 + github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 11 24 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 12 25 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 26 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 27 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 28 + github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 29 + github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 30 + github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 31 + github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 15 32 github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 16 33 github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 17 34 github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= ··· 29 46 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 30 47 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 31 48 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 49 + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 50 + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 51 + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 52 + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 53 + github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 54 + github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 55 + github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 56 + github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 57 + github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 58 + github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 59 + github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 60 + github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 61 + github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 62 + github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 63 + github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 32 64 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 65 + github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 66 + github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 67 + github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 68 + github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 69 + github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 70 + github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 71 + github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 72 + github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 33 73 github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 34 74 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 35 75 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 76 + github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= 77 + github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= 78 + github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 79 + github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= 80 + github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= 81 + github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= 82 + github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= 36 83 github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 37 84 github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 38 85 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= ··· 58 105 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 59 106 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 60 107 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 108 + github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 61 109 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 62 110 github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= 63 111 github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= ··· 76 124 github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 77 125 github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 78 126 github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 127 + go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= 128 + go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 79 129 golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 80 130 golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 81 131 golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 132 + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 133 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 82 134 golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= 83 135 golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= 136 + golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= 137 + golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 138 + golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 139 + golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 140 + golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 141 + golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 142 + golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 143 + golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 144 + golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 145 + golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 146 + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 147 + golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 148 + golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 84 149 golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= 85 150 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 151 + golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 152 + golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 153 + golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 154 + golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= 155 + golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= 156 + golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 157 + golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 158 + golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 159 + golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= 160 + golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 161 + golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 162 + golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 163 + golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 164 + golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 86 165 golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 87 166 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 88 167 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 89 168 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 90 169 golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= 91 170 golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 171 + golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 172 + golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 173 + golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 92 174 golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= 93 175 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 176 + golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 177 + golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 178 + golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 179 + golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 180 + golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 181 + golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 182 + golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 94 183 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 95 184 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 185 + google.golang.org/api v0.149.0 h1:b2CqT6kG+zqJIVKRQ3ELJVLN1PwHZ6DJ3dW8yl82rgY= 186 + google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= 187 + google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 188 + google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 189 + google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 190 + google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 191 + google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 192 + google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 193 + google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 194 + google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA= 195 + google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= 196 + google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k= 197 + google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= 198 + google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= 199 + google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= 200 + google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 201 + google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 202 + google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 203 + google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 204 + google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 205 + google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= 206 + google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= 207 + google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 208 + google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 209 + google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 210 + google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 211 + google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 212 + google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 213 + google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 214 + google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 215 + google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 96 216 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 217 + google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 97 218 google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 98 219 google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 220 + google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 221 + google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 99 222 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 100 223 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 101 224 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 102 225 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 103 226 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 227 + honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 228 + honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 104 229 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+110 -64
apps/server/src/checker/index.ts
··· 1 1 import { Hono } from "hono"; 2 2 import { z } from "zod"; 3 3 4 - import { eq, schema } from "@openstatus/db"; 5 - import { db } from "@openstatus/db/src/db"; 4 + import { and, db, eq, isNotNull, schema } from "@openstatus/db"; 5 + import { incidentTable } from "@openstatus/db/src/schema"; 6 6 import { flyRegions } from "@openstatus/db/src/schema/monitors/constants"; 7 7 import { selectMonitorSchema } from "@openstatus/db/src/schema/monitors/validation"; 8 8 import { Redis } from "@openstatus/upstash"; ··· 24 24 const json = await c.req.json(); 25 25 const payloadSchema = z.object({ 26 26 monitorId: z.string(), 27 - status: z.enum(["active", "error"]), // that's the new status 28 27 message: z.string().optional(), 29 28 statusCode: z.number().optional(), 30 29 region: z.enum(flyRegions), 31 30 cronTimestamp: z.number().optional(), 31 + // status: z.enum(["active", "error"]), 32 32 }); 33 33 34 34 const result = payloadSchema.safeParse(json); 35 35 if (!result.success) { 36 - // console.error(result.error); 37 36 return c.text("Unprocessable Entity", 422); 38 37 } 39 - const { monitorId, status, message, region, statusCode, cronTimestamp } = 40 - result.data; 38 + const { monitorId, message, region, statusCode, cronTimestamp } = result.data; 41 39 42 40 console.log(`๐Ÿ“ update monitor status ${JSON.stringify(result.data)}`); 43 41 44 - switch (status) { 45 - case "active": 46 - if (!statusCode) { 47 - return; 48 - } 49 - await upsertMonitorStatus({ 50 - monitorId: monitorId, 51 - status: "active", 52 - region: region, 53 - }); 42 + // we check if it's an error 43 + // If status not in 200> and <300 44 + // if there's no incident create one and notify 45 + // publish event to TB 54 46 55 - await checkerAudit.publishAuditLog({ 56 - id: `monitor:${monitorId}`, 57 - action: "monitor.recovered", 58 - targets: [{ id: monitorId, type: "monitor" }], 59 - metadata: { 60 - region: region, 61 - statusCode: statusCode, 62 - cronTimestamp: cronTimestamp, 63 - }, 64 - }); 65 - break; 66 - case "error": { 67 - await upsertMonitorStatus({ 68 - monitorId: monitorId, 69 - status: "error", 47 + // if status is ok checked if there's an open incident 48 + // if open incident publish incident recovered 49 + const incident = await db 50 + .select() 51 + .from(incidentTable) 52 + .where( 53 + and( 54 + eq(incidentTable.monitorId, Number(monitorId)), 55 + isNotNull(incidentTable.resolvedAt), 56 + isNotNull(incidentTable.acknowledgedAt), 57 + ), 58 + ); 59 + 60 + // if we are in error 61 + if (statusCode && statusCode < 200 && statusCode > 300) { 62 + // create incident 63 + // trigger alerting 64 + await checkerAudit.publishAuditLog({ 65 + id: `monitor:${monitorId}`, 66 + action: "monitor.failed", 67 + targets: [{ id: monitorId, type: "monitor" }], 68 + metadata: { 70 69 region: region, 71 - }); 72 - // ALPHA 73 - await checkerAudit.publishAuditLog({ 74 - id: `monitor:${monitorId}`, 75 - action: "monitor.failed", 76 - targets: [{ id: monitorId, type: "monitor" }], 77 - metadata: { 78 - region: region, 79 - statusCode: statusCode, 80 - message, 81 - cronTimestamp, 82 - }, 83 - }); 70 + statusCode: statusCode, 71 + message, 72 + }, 73 + }); 74 + // We upsert the status of the monitor 75 + await upsertMonitorStatus({ 76 + monitorId: monitorId, 77 + status: "error", 78 + region: region, 79 + }); 80 + 81 + if (!incident) { 82 + const redisKey = `${monitorId}-${cronTimestamp}-error`; 83 + // We add the new region to the set 84 + await redis.sadd(redisKey, region); 85 + // let's add an expire to the set 86 + await redis.expire(redisKey, 60 * 60 * 24); 87 + // We get the number of regions affected 88 + const nbAffectedRegion = await redis.scard(redisKey); 84 89 85 90 const currentMonitor = await db 86 91 .select() ··· 94 99 console.log("cronTimestamp is undefined"); 95 100 } 96 101 97 - const redisKey = `${monitorId}-${cronTimestamp}`; 98 - // We add the new region to the set 99 - await redis.sadd(redisKey, region); 100 - // let's add an expire to the set 101 - await redis.expire(redisKey, 60 * 60 * 24); 102 - // We get the number of regions affected 103 - const nbAffectedRegion = await redis.scard(redisKey); 102 + const numberOfRegions = monitor.regions.length; 104 103 105 104 // If the number of affected regions is greater than half of the total region, we trigger the alerting 106 - if (nbAffectedRegion < monitor.regions.length / 2) { 107 - console.log( 108 - `Not enough affected regions (${nbAffectedRegion}/${flyRegions.length})`, 109 - ); 110 - break; 105 + // 4 of 6 monitor need to fail to trigger an alerting 106 + if (nbAffectedRegion > numberOfRegions / 2) { 107 + await triggerAlerting({ monitorId, statusCode, message, region }); 108 + // create the incident and trigger the alerting 109 + await db.insert(incidentTable).values({ 110 + monitorId: Number(monitorId), 111 + workspaceId: monitor.workspaceId, 112 + startedAt: new Date(), 113 + }); 111 114 } 112 - // Add a 113 - await triggerAlerting({ 114 - monitorId: monitorId, 115 - region: env.FLY_REGION, 116 - statusCode, 117 - message, 118 - }); 119 - break; 120 115 } 121 116 } 117 + 118 + if (statusCode && statusCode >= 200 && statusCode < 300) { 119 + await upsertMonitorStatus({ 120 + monitorId: monitorId, 121 + status: "active", 122 + region: region, 123 + }); 124 + 125 + await checkerAudit.publishAuditLog({ 126 + id: `monitor:${monitorId}`, 127 + action: "monitor.recovered", 128 + targets: [{ id: monitorId, type: "monitor" }], 129 + metadata: { region: region, statusCode: Number(statusCode) }, 130 + }); 131 + 132 + // FIX: TO BE IMPROVED 133 + // if (incident) { 134 + // const redisKey = `${monitorId}-${cronTimestamp}-resolved`; 135 + // // We add the new region to the set 136 + // await redis.sadd(redisKey, region); 137 + // // let's add an expire to the set 138 + // await redis.expire(redisKey, 60 * 60 * 24); 139 + // // We get the number of regions affected 140 + // const nbAffectedRegion = await redis.scard(redisKey); 141 + 142 + // const currentMonitor = await db 143 + // .select() 144 + // .from(schema.monitor) 145 + // .where(eq(schema.monitor.id, Number(monitorId))) 146 + // .get(); 147 + 148 + // const monitor = selectMonitorSchema.parse(currentMonitor); 149 + 150 + // if (!cronTimestamp) { 151 + // console.log("cronTimestamp is undefined"); 152 + // } 153 + 154 + // const numberOfRegions = monitor.regions.length; 155 + 156 + // // If the number of affected regions is greater than half of the total region, we trigger the alerting 157 + // // 4 of 6 monitor need to fail to trigger an alerting 158 + // if (nbAffectedRegion > numberOfRegions / 2) { 159 + // // Trigger recovery notification 160 + // // await triggerRecovery({ monitorId, statusCode, message, region }); 161 + // await db.update(incidentTable).set({ 162 + // resolvedAt: new Date(), 163 + // }); 164 + // } 165 + // } 166 + } 167 + 122 168 return c.text("Ok", 200); 123 169 });
+1 -2
apps/web/src/app/api/checker/cron/_cron.ts
··· 64 64 65 65 for (const region of selectedRegions) { 66 66 const status = 67 - monitorStatus.find(({ region }) => region === region)?.status || 68 - "active"; 67 + monitorStatus.find((m) => region === m.region)?.status || "active"; 69 68 const response = await createCronTask({ 70 69 row, 71 70 timestamp,
+24
apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/(overview)/layout.tsx
··· 1 + import * as React from "react"; 2 + 3 + import { Header } from "@/components/dashboard/header"; 4 + import { HelpCallout } from "@/components/dashboard/help-callout"; 5 + 6 + export default async function Layout({ 7 + children, 8 + }: { 9 + children: React.ReactNode; 10 + }) { 11 + return ( 12 + <div className="grid min-h-full grid-cols-1 grid-rows-[auto,1fr,auto] gap-6 md:grid-cols-2 md:gap-8"> 13 + <Header 14 + title="Incidents" 15 + description="Overview of all your incidents." 16 + actions={undefined} 17 + /> 18 + <div className="col-span-full">{children}</div> 19 + <div className="mt-8 md:mt-12"> 20 + <HelpCallout /> 21 + </div> 22 + </div> 23 + ); 24 + }
+5
apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/(overview)/loading.tsx
··· 1 + import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"; 2 + 3 + export default function Loading() { 4 + return <DataTableSkeleton />; 5 + }
+26
apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/(overview)/page.tsx
··· 1 + import * as React from "react"; 2 + 3 + import { EmptyState } from "@/components/dashboard/empty-state"; 4 + import { columns } from "@/components/data-table/incident/columns"; 5 + import { DataTable } from "@/components/data-table/incident/data-table"; 6 + import { api } from "@/trpc/server"; 7 + 8 + export default async function IncidentPage() { 9 + const incidents = await api.incident.getAllIncidents.query(); 10 + 11 + if (incidents?.length === 0) 12 + return ( 13 + <EmptyState 14 + icon="activity" 15 + title="No Incidents " 16 + description="Hopefully you will see this screen for a long time." 17 + action={undefined} 18 + /> 19 + ); 20 + 21 + return ( 22 + <> 23 + <DataTable columns={columns} data={incidents} /> 24 + </> 25 + ); 26 + }
+23
apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/[id]/layout.tsx
··· 1 + import { notFound } from "next/navigation"; 2 + 3 + import { api } from "@/trpc/server"; 4 + 5 + export default async function Layout({ 6 + children, 7 + params, 8 + }: { 9 + children: React.ReactNode; 10 + params: { workspaceSlug: string; id: string }; 11 + }) { 12 + const id = params.id; 13 + 14 + const monitor = await api.incident.getIncidentById.query({ 15 + id: Number(id), 16 + }); 17 + 18 + if (!monitor) { 19 + return notFound(); 20 + } 21 + 22 + return <div className="grid grid-cols-1 gap-6 md:gap-8">{children}</div>; 23 + }
+3
apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/[id]/page.tsx
··· 1 + export default async function IncidentPage() { 2 + return <></>; 3 + }
+85
apps/web/src/components/data-table/incident/columns.tsx
··· 1 + "use client"; 2 + 3 + import Link from "next/link"; 4 + import type { ColumnDef } from "@tanstack/react-table"; 5 + 6 + import type { Incident } from "@openstatus/db/src/schema"; 7 + 8 + import { DataTableRowActions } from "./data-table-row-actions"; 9 + 10 + export const columns: ColumnDef<Incident>[] = [ 11 + { 12 + accessorKey: "monitorId", 13 + header: "Monitor", 14 + cell: ({ row }) => { 15 + return ( 16 + <Link 17 + href={`./monitors/${row.original.id}/overview`} 18 + className="group flex items-center gap-2" 19 + > 20 + <span className="max-w-[125px] truncate group-hover:underline"> 21 + {row.original.monitorName} 22 + </span> 23 + </Link> 24 + ); 25 + }, 26 + }, 27 + { 28 + accessorKey: "startedAt", 29 + header: "Started At", 30 + cell: ({ row }) => { 31 + const { startedAt } = row.original; 32 + const date = startedAt ? new Date(startedAt).toLocaleString() : "-"; 33 + return ( 34 + <div className="flex"> 35 + <span className="text-muted-foreground max-w-[150px] truncate sm:max-w-[200px] lg:max-w-[250px] xl:max-w-[350px]"> 36 + {date} 37 + </span> 38 + </div> 39 + ); 40 + }, 41 + }, 42 + { 43 + accessorKey: "acknowledgetAt", 44 + header: "Acknowledged At", 45 + cell: ({ row }) => { 46 + const { acknowledgedAt } = row.original; 47 + const date = acknowledgedAt 48 + ? new Date(acknowledgedAt).toLocaleString() 49 + : "-"; 50 + return ( 51 + <div className="flex"> 52 + <span className="text-muted-foreground max-w-[150px] truncate sm:max-w-[200px] lg:max-w-[250px] xl:max-w-[350px]"> 53 + {date} 54 + </span> 55 + </div> 56 + ); 57 + }, 58 + }, 59 + { 60 + accessorKey: "resolvedAt", 61 + header: "Resolved At", 62 + cell: ({ row }) => { 63 + const { resolvedAt } = row.original; 64 + const date = resolvedAt ? new Date(resolvedAt).toLocaleString() : "-"; 65 + return ( 66 + <div className="flex"> 67 + <span className="text-muted-foreground max-w-[150px] truncate sm:max-w-[200px] lg:max-w-[250px] xl:max-w-[350px]"> 68 + {date} 69 + </span> 70 + </div> 71 + ); 72 + }, 73 + }, 74 + 75 + { 76 + id: "actions", 77 + cell: ({ row }) => { 78 + return ( 79 + <div className="text-right"> 80 + <DataTableRowActions row={row} /> 81 + </div> 82 + ); 83 + }, 84 + }, 85 + ];
+107
apps/web/src/components/data-table/incident/data-table-row-actions.tsx
··· 1 + "use client"; 2 + 3 + import * as React from "react"; 4 + import Link from "next/link"; 5 + import { useRouter } from "next/navigation"; 6 + import type { Row } from "@tanstack/react-table"; 7 + import { MoreHorizontal } from "lucide-react"; 8 + 9 + import { selectIncidentSchema } from "@openstatus/db/src/schema"; 10 + import { 11 + AlertDialog, 12 + AlertDialogAction, 13 + AlertDialogCancel, 14 + AlertDialogContent, 15 + AlertDialogDescription, 16 + AlertDialogFooter, 17 + AlertDialogHeader, 18 + AlertDialogTitle, 19 + AlertDialogTrigger, 20 + Button, 21 + DropdownMenu, 22 + DropdownMenuContent, 23 + DropdownMenuItem, 24 + DropdownMenuSeparator, 25 + DropdownMenuTrigger, 26 + } from "@openstatus/ui"; 27 + 28 + import { LoadingAnimation } from "@/components/loading-animation"; 29 + import { useToastAction } from "@/hooks/use-toast-action"; 30 + import { api } from "@/trpc/client"; 31 + 32 + interface DataTableRowActionsProps<TData> { 33 + row: Row<TData>; 34 + } 35 + 36 + export function DataTableRowActions<TData>({ 37 + row, 38 + }: DataTableRowActionsProps<TData>) { 39 + const incident = selectIncidentSchema.parse(row.original); 40 + const router = useRouter(); 41 + const { toast } = useToastAction(); 42 + const [isPending, startTransition] = React.useTransition(); 43 + 44 + async function resolved() { 45 + startTransition(async () => { 46 + try { 47 + if (!incident.id) return; 48 + await api.incident.resolvedIncident.mutate({ id: incident.id }); 49 + toast("success"); 50 + router.refresh(); 51 + } catch { 52 + toast("error"); 53 + } 54 + }); 55 + } 56 + 57 + async function acknowledge() { 58 + startTransition(async () => { 59 + try { 60 + // const { jobType, ...rest } = monitor; 61 + // if (!monitor.id) return; 62 + // await api.monitor.update.mutate({ 63 + // ...rest, 64 + // active: !monitor.active, 65 + // }); 66 + if (!incident.id) return; 67 + await api.incident.acknowledgeIncident.mutate({ id: incident.id }); 68 + toast("success"); 69 + router.refresh(); 70 + } catch { 71 + toast("error"); 72 + } 73 + }); 74 + } 75 + 76 + return ( 77 + <DropdownMenu> 78 + <DropdownMenuTrigger asChild> 79 + <Button 80 + variant="ghost" 81 + className="data-[state=open]:bg-accent h-8 w-8 p-0" 82 + > 83 + <span className="sr-only">Open menu</span> 84 + <MoreHorizontal className="h-4 w-4" /> 85 + </Button> 86 + </DropdownMenuTrigger> 87 + <DropdownMenuContent align="end"> 88 + <DropdownMenuItem 89 + disabled={incident.acknowledgedAt !== null} 90 + onClick={acknowledge} 91 + > 92 + Acknowledge 93 + </DropdownMenuItem> 94 + 95 + <DropdownMenuSeparator /> 96 + <DropdownMenuItem 97 + disabled={ 98 + incident.resolvedAt !== null || incident.acknowledgedAt === null 99 + } 100 + onClick={resolved} 101 + > 102 + Resolved 103 + </DropdownMenuItem> 104 + </DropdownMenuContent> 105 + </DropdownMenu> 106 + ); 107 + }
+84
apps/web/src/components/data-table/incident/data-table.tsx
··· 1 + "use client"; 2 + 3 + import * as React from "react"; 4 + import type { ColumnDef } from "@tanstack/react-table"; 5 + import { 6 + flexRender, 7 + getCoreRowModel, 8 + useReactTable, 9 + } from "@tanstack/react-table"; 10 + 11 + import { 12 + Table, 13 + TableBody, 14 + TableCell, 15 + TableHead, 16 + TableHeader, 17 + TableRow, 18 + } from "@openstatus/ui"; 19 + 20 + interface DataTableProps<TData, TValue> { 21 + columns: ColumnDef<TData, TValue>[]; 22 + data: TData[]; 23 + } 24 + 25 + // FIXME: right now, the mobile layout is messed up 26 + // https://github.com/TanStack/table/discussions/3192#discussioncomment-6458134 27 + 28 + export function DataTable<TData, TValue>({ 29 + columns, 30 + data, 31 + }: DataTableProps<TData, TValue>) { 32 + const table = useReactTable({ 33 + data, 34 + columns, 35 + getCoreRowModel: getCoreRowModel(), 36 + }); 37 + 38 + return ( 39 + <div className="rounded-md border"> 40 + <Table> 41 + <TableHeader className="bg-muted/50"> 42 + {table.getHeaderGroups().map((headerGroup) => ( 43 + <TableRow key={headerGroup.id} className="hover:bg-transparent"> 44 + {headerGroup.headers.map((header) => { 45 + return ( 46 + <TableHead key={header.id}> 47 + {header.isPlaceholder 48 + ? null 49 + : flexRender( 50 + header.column.columnDef.header, 51 + header.getContext(), 52 + )} 53 + </TableHead> 54 + ); 55 + })} 56 + </TableRow> 57 + ))} 58 + </TableHeader> 59 + <TableBody> 60 + {table.getRowModel().rows?.length ? ( 61 + table.getRowModel().rows.map((row) => ( 62 + <TableRow 63 + key={row.id} 64 + data-state={row.getIsSelected() && "selected"} 65 + > 66 + {row.getVisibleCells().map((cell) => ( 67 + <TableCell key={cell.id}> 68 + {flexRender(cell.column.columnDef.cell, cell.getContext())} 69 + </TableCell> 70 + ))} 71 + </TableRow> 72 + )) 73 + ) : ( 74 + <TableRow> 75 + <TableCell colSpan={columns.length} className="h-24 text-center"> 76 + No results. 77 + </TableCell> 78 + </TableRow> 79 + )} 80 + </TableBody> 81 + </Table> 82 + </div> 83 + ); 84 + }
+6
apps/web/src/config/pages.ts
··· 17 17 icon: "activity", 18 18 }, 19 19 { 20 + title: "Incidents", 21 + description: "All your incidents.", 22 + href: "/incidents", 23 + icon: "siren", 24 + }, 25 + { 20 26 title: "Status Pages", 21 27 description: "Where you can see all the pages.", 22 28 href: "/status-pages",
+2
packages/api/src/edge.ts
··· 1 1 import { domainRouter } from "./router/domain"; 2 + import { incidentRouter } from "./router/incident"; 2 3 import { integrationRouter } from "./router/integration"; 3 4 import { invitationRouter } from "./router/invitation"; 4 5 import { monitorRouter } from "./router/monitor"; ··· 20 21 user: userRouter, 21 22 notification: notificationRouter, 22 23 invitation: invitationRouter, 24 + incident: incidentRouter, 23 25 });
+114
packages/api/src/router/incident.ts
··· 1 + import { z } from "zod"; 2 + 3 + import { and, eq, schema } from "@openstatus/db"; 4 + import { selectIncidentSchema } from "@openstatus/db/src/schema"; 5 + 6 + import { createTRPCRouter, protectedProcedure } from "../trpc"; 7 + 8 + export const incidentRouter = createTRPCRouter({ 9 + getAllIncidents: protectedProcedure 10 + .output(z.array(selectIncidentSchema)) 11 + .query(async (opts) => { 12 + const result = await opts.ctx.db 13 + .select() 14 + .from(schema.incidentTable) 15 + .where(eq(schema.incidentTable.workspaceId, opts.ctx.workspace.id)) 16 + .leftJoin( 17 + schema.monitor, 18 + eq(schema.incidentTable.monitorId, schema.monitor.id), 19 + ) 20 + .all(); 21 + return z 22 + .array(selectIncidentSchema) 23 + .parse( 24 + result.map((r) => ({ ...r.incident, monitorName: r.monitor?.name })), 25 + ); 26 + }), 27 + 28 + getIncidentById: protectedProcedure 29 + .input(z.object({ id: z.number() })) 30 + .output(selectIncidentSchema) 31 + .query(async (opts) => { 32 + const currentIncident = await opts.ctx.db 33 + .select() 34 + .from(schema.incidentTable) 35 + .where( 36 + and( 37 + eq(schema.incidentTable.id, opts.input.id), 38 + eq(schema.incidentTable.workspaceId, opts.ctx.workspace.id), 39 + ), 40 + ) 41 + .get(); 42 + return selectIncidentSchema.parse(currentIncident); 43 + }), 44 + 45 + acknowledgeIncident: protectedProcedure 46 + .input(z.object({ id: z.number() })) 47 + .mutation(async (opts) => { 48 + const currentIncident = await opts.ctx.db 49 + .select() 50 + .from(schema.incidentTable) 51 + .where( 52 + and( 53 + eq(schema.incidentTable.id, opts.input.id), 54 + eq(schema.incidentTable.workspaceId, opts.ctx.workspace.id), 55 + ), 56 + ) 57 + .get(); 58 + if (!currentIncident) { 59 + throw new Error("Incident not found"); 60 + } 61 + if (currentIncident.acknowledgedAt) { 62 + throw new Error("Incident already acknowledged"); 63 + } 64 + await opts.ctx.db 65 + .update(schema.incidentTable) 66 + .set({ 67 + acknowledgedAt: new Date(), 68 + acknowledgedBy: opts.ctx.user.id, 69 + }) 70 + .where( 71 + and( 72 + eq(schema.incidentTable.id, opts.input.id), 73 + eq(schema.incidentTable.workspaceId, opts.ctx.workspace.id), 74 + ), 75 + ); 76 + return true; 77 + }), 78 + resolvedIncident: protectedProcedure 79 + .input(z.object({ id: z.number() })) 80 + .mutation(async (opts) => { 81 + const currentIncident = await opts.ctx.db 82 + .select() 83 + .from(schema.incidentTable) 84 + .where( 85 + and( 86 + eq(schema.incidentTable.id, opts.input.id), 87 + eq(schema.incidentTable.workspaceId, opts.ctx.workspace.id), 88 + ), 89 + ) 90 + .get(); 91 + if (!currentIncident) { 92 + throw new Error("Incident not found"); 93 + } 94 + if (!currentIncident.acknowledgedAt) { 95 + throw new Error("Incident not acknowledged"); 96 + } 97 + if (currentIncident.resolvedAt) { 98 + throw new Error("Incident already resolved"); 99 + } 100 + await opts.ctx.db 101 + .update(schema.incidentTable) 102 + .set({ 103 + resolvedAt: new Date(), 104 + resolvedBy: opts.ctx.user.id, 105 + }) 106 + .where( 107 + and( 108 + eq(schema.incidentTable.id, opts.input.id), 109 + eq(schema.incidentTable.workspaceId, opts.ctx.workspace.id), 110 + ), 111 + ); 112 + return true; 113 + }), 114 + });
+2 -2
packages/api/src/router/statusReport.ts
··· 37 37 .returning() 38 38 .get(); 39 39 40 - if (Boolean(monitors.length)) { 40 + if (monitors.length > 0) { 41 41 await opts.ctx.db 42 42 .insert(monitorsToStatusReport) 43 43 .values( ··· 50 50 .get(); 51 51 } 52 52 53 - if (Boolean(pages.length)) { 53 + if (pages.length > 0) { 54 54 await opts.ctx.db 55 55 .insert(pagesToStatusReports) 56 56 .values(
+19
packages/db/drizzle/0015_bent_sister_grimm.sql
··· 1 + CREATE TABLE `incident` ( 2 + `id` integer PRIMARY KEY NOT NULL, 3 + `title` text DEFAULT '' NOT NULL, 4 + `summary` text DEFAULT '' NOT NULL, 5 + `status` text DEFAULT 'triage' NOT NULL, 6 + `monitor_id` integer, 7 + `workspace_id` integer, 8 + `started_at` integer, 9 + `acknowledged_at` integer, 10 + `acknowledged_by` integer, 11 + `resolved_at` integer, 12 + `resolved_by` integer, 13 + `created_at` integer DEFAULT (strftime('%s', 'now')), 14 + `updated_at` integer DEFAULT (strftime('%s', 'now')), 15 + FOREIGN KEY (`monitor_id`) REFERENCES `monitor`(`id`) ON UPDATE no action ON DELETE no action, 16 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE no action, 17 + FOREIGN KEY (`acknowledged_by`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, 18 + FOREIGN KEY (`resolved_by`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action 19 + );
+1408
packages/db/drizzle/meta/0015_snapshot.json
··· 1 + { 2 + "version": "5", 3 + "dialect": "sqlite", 4 + "id": "f5a77857-319d-4575-b570-f0aacb9eab35", 5 + "prevId": "4cc8ce9b-ed70-47c1-8218-2cbc02743563", 6 + "tables": { 7 + "status_report_to_monitors": { 8 + "name": "status_report_to_monitors", 9 + "columns": { 10 + "monitor_id": { 11 + "name": "monitor_id", 12 + "type": "integer", 13 + "primaryKey": false, 14 + "notNull": true, 15 + "autoincrement": false 16 + }, 17 + "status_report_id": { 18 + "name": "status_report_id", 19 + "type": "integer", 20 + "primaryKey": false, 21 + "notNull": true, 22 + "autoincrement": false 23 + } 24 + }, 25 + "indexes": {}, 26 + "foreignKeys": { 27 + "status_report_to_monitors_monitor_id_monitor_id_fk": { 28 + "name": "status_report_to_monitors_monitor_id_monitor_id_fk", 29 + "tableFrom": "status_report_to_monitors", 30 + "tableTo": "monitor", 31 + "columnsFrom": [ 32 + "monitor_id" 33 + ], 34 + "columnsTo": [ 35 + "id" 36 + ], 37 + "onDelete": "cascade", 38 + "onUpdate": "no action" 39 + }, 40 + "status_report_to_monitors_status_report_id_status_report_id_fk": { 41 + "name": "status_report_to_monitors_status_report_id_status_report_id_fk", 42 + "tableFrom": "status_report_to_monitors", 43 + "tableTo": "status_report", 44 + "columnsFrom": [ 45 + "status_report_id" 46 + ], 47 + "columnsTo": [ 48 + "id" 49 + ], 50 + "onDelete": "cascade", 51 + "onUpdate": "no action" 52 + } 53 + }, 54 + "compositePrimaryKeys": { 55 + "status_report_to_monitors_monitor_id_status_report_id_pk": { 56 + "columns": [ 57 + "monitor_id", 58 + "status_report_id" 59 + ] 60 + } 61 + }, 62 + "uniqueConstraints": {} 63 + }, 64 + "status_reports_to_pages": { 65 + "name": "status_reports_to_pages", 66 + "columns": { 67 + "page_id": { 68 + "name": "page_id", 69 + "type": "integer", 70 + "primaryKey": false, 71 + "notNull": true, 72 + "autoincrement": false 73 + }, 74 + "status_report_id": { 75 + "name": "status_report_id", 76 + "type": "integer", 77 + "primaryKey": false, 78 + "notNull": true, 79 + "autoincrement": false 80 + } 81 + }, 82 + "indexes": {}, 83 + "foreignKeys": { 84 + "status_reports_to_pages_page_id_page_id_fk": { 85 + "name": "status_reports_to_pages_page_id_page_id_fk", 86 + "tableFrom": "status_reports_to_pages", 87 + "tableTo": "page", 88 + "columnsFrom": [ 89 + "page_id" 90 + ], 91 + "columnsTo": [ 92 + "id" 93 + ], 94 + "onDelete": "cascade", 95 + "onUpdate": "no action" 96 + }, 97 + "status_reports_to_pages_status_report_id_status_report_id_fk": { 98 + "name": "status_reports_to_pages_status_report_id_status_report_id_fk", 99 + "tableFrom": "status_reports_to_pages", 100 + "tableTo": "status_report", 101 + "columnsFrom": [ 102 + "status_report_id" 103 + ], 104 + "columnsTo": [ 105 + "id" 106 + ], 107 + "onDelete": "cascade", 108 + "onUpdate": "no action" 109 + } 110 + }, 111 + "compositePrimaryKeys": { 112 + "status_reports_to_pages_page_id_status_report_id_pk": { 113 + "columns": [ 114 + "page_id", 115 + "status_report_id" 116 + ] 117 + } 118 + }, 119 + "uniqueConstraints": {} 120 + }, 121 + "status_report": { 122 + "name": "status_report", 123 + "columns": { 124 + "id": { 125 + "name": "id", 126 + "type": "integer", 127 + "primaryKey": true, 128 + "notNull": true, 129 + "autoincrement": false 130 + }, 131 + "status": { 132 + "name": "status", 133 + "type": "text", 134 + "primaryKey": false, 135 + "notNull": true, 136 + "autoincrement": false 137 + }, 138 + "title": { 139 + "name": "title", 140 + "type": "text(256)", 141 + "primaryKey": false, 142 + "notNull": true, 143 + "autoincrement": false 144 + }, 145 + "workspace_id": { 146 + "name": "workspace_id", 147 + "type": "integer", 148 + "primaryKey": false, 149 + "notNull": false, 150 + "autoincrement": false 151 + }, 152 + "created_at": { 153 + "name": "created_at", 154 + "type": "integer", 155 + "primaryKey": false, 156 + "notNull": false, 157 + "autoincrement": false, 158 + "default": "(strftime('%s', 'now'))" 159 + }, 160 + "updated_at": { 161 + "name": "updated_at", 162 + "type": "integer", 163 + "primaryKey": false, 164 + "notNull": false, 165 + "autoincrement": false, 166 + "default": "(strftime('%s', 'now'))" 167 + } 168 + }, 169 + "indexes": {}, 170 + "foreignKeys": { 171 + "status_report_workspace_id_workspace_id_fk": { 172 + "name": "status_report_workspace_id_workspace_id_fk", 173 + "tableFrom": "status_report", 174 + "tableTo": "workspace", 175 + "columnsFrom": [ 176 + "workspace_id" 177 + ], 178 + "columnsTo": [ 179 + "id" 180 + ], 181 + "onDelete": "no action", 182 + "onUpdate": "no action" 183 + } 184 + }, 185 + "compositePrimaryKeys": {}, 186 + "uniqueConstraints": {} 187 + }, 188 + "status_report_update": { 189 + "name": "status_report_update", 190 + "columns": { 191 + "id": { 192 + "name": "id", 193 + "type": "integer", 194 + "primaryKey": true, 195 + "notNull": true, 196 + "autoincrement": false 197 + }, 198 + "status": { 199 + "name": "status", 200 + "type": "text(4)", 201 + "primaryKey": false, 202 + "notNull": true, 203 + "autoincrement": false 204 + }, 205 + "date": { 206 + "name": "date", 207 + "type": "integer", 208 + "primaryKey": false, 209 + "notNull": true, 210 + "autoincrement": false 211 + }, 212 + "message": { 213 + "name": "message", 214 + "type": "text", 215 + "primaryKey": false, 216 + "notNull": true, 217 + "autoincrement": false 218 + }, 219 + "status_report_id": { 220 + "name": "status_report_id", 221 + "type": "integer", 222 + "primaryKey": false, 223 + "notNull": true, 224 + "autoincrement": false 225 + }, 226 + "created_at": { 227 + "name": "created_at", 228 + "type": "integer", 229 + "primaryKey": false, 230 + "notNull": false, 231 + "autoincrement": false, 232 + "default": "(strftime('%s', 'now'))" 233 + }, 234 + "updated_at": { 235 + "name": "updated_at", 236 + "type": "integer", 237 + "primaryKey": false, 238 + "notNull": false, 239 + "autoincrement": false, 240 + "default": "(strftime('%s', 'now'))" 241 + } 242 + }, 243 + "indexes": {}, 244 + "foreignKeys": { 245 + "status_report_update_status_report_id_status_report_id_fk": { 246 + "name": "status_report_update_status_report_id_status_report_id_fk", 247 + "tableFrom": "status_report_update", 248 + "tableTo": "status_report", 249 + "columnsFrom": [ 250 + "status_report_id" 251 + ], 252 + "columnsTo": [ 253 + "id" 254 + ], 255 + "onDelete": "cascade", 256 + "onUpdate": "no action" 257 + } 258 + }, 259 + "compositePrimaryKeys": {}, 260 + "uniqueConstraints": {} 261 + }, 262 + "integration": { 263 + "name": "integration", 264 + "columns": { 265 + "id": { 266 + "name": "id", 267 + "type": "integer", 268 + "primaryKey": true, 269 + "notNull": true, 270 + "autoincrement": false 271 + }, 272 + "name": { 273 + "name": "name", 274 + "type": "text(256)", 275 + "primaryKey": false, 276 + "notNull": true, 277 + "autoincrement": false 278 + }, 279 + "workspace_id": { 280 + "name": "workspace_id", 281 + "type": "integer", 282 + "primaryKey": false, 283 + "notNull": false, 284 + "autoincrement": false 285 + }, 286 + "credential": { 287 + "name": "credential", 288 + "type": "text", 289 + "primaryKey": false, 290 + "notNull": false, 291 + "autoincrement": false 292 + }, 293 + "external_id": { 294 + "name": "external_id", 295 + "type": "text", 296 + "primaryKey": false, 297 + "notNull": true, 298 + "autoincrement": false 299 + }, 300 + "created_at": { 301 + "name": "created_at", 302 + "type": "integer", 303 + "primaryKey": false, 304 + "notNull": false, 305 + "autoincrement": false, 306 + "default": "(strftime('%s', 'now'))" 307 + }, 308 + "updated_at": { 309 + "name": "updated_at", 310 + "type": "integer", 311 + "primaryKey": false, 312 + "notNull": false, 313 + "autoincrement": false, 314 + "default": "(strftime('%s', 'now'))" 315 + }, 316 + "data": { 317 + "name": "data", 318 + "type": "text", 319 + "primaryKey": false, 320 + "notNull": true, 321 + "autoincrement": false 322 + } 323 + }, 324 + "indexes": {}, 325 + "foreignKeys": { 326 + "integration_workspace_id_workspace_id_fk": { 327 + "name": "integration_workspace_id_workspace_id_fk", 328 + "tableFrom": "integration", 329 + "tableTo": "workspace", 330 + "columnsFrom": [ 331 + "workspace_id" 332 + ], 333 + "columnsTo": [ 334 + "id" 335 + ], 336 + "onDelete": "no action", 337 + "onUpdate": "no action" 338 + } 339 + }, 340 + "compositePrimaryKeys": {}, 341 + "uniqueConstraints": {} 342 + }, 343 + "page": { 344 + "name": "page", 345 + "columns": { 346 + "id": { 347 + "name": "id", 348 + "type": "integer", 349 + "primaryKey": true, 350 + "notNull": true, 351 + "autoincrement": false 352 + }, 353 + "workspace_id": { 354 + "name": "workspace_id", 355 + "type": "integer", 356 + "primaryKey": false, 357 + "notNull": true, 358 + "autoincrement": false 359 + }, 360 + "title": { 361 + "name": "title", 362 + "type": "text", 363 + "primaryKey": false, 364 + "notNull": true, 365 + "autoincrement": false 366 + }, 367 + "description": { 368 + "name": "description", 369 + "type": "text", 370 + "primaryKey": false, 371 + "notNull": true, 372 + "autoincrement": false 373 + }, 374 + "icon": { 375 + "name": "icon", 376 + "type": "text(256)", 377 + "primaryKey": false, 378 + "notNull": false, 379 + "autoincrement": false, 380 + "default": "''" 381 + }, 382 + "slug": { 383 + "name": "slug", 384 + "type": "text(256)", 385 + "primaryKey": false, 386 + "notNull": true, 387 + "autoincrement": false 388 + }, 389 + "custom_domain": { 390 + "name": "custom_domain", 391 + "type": "text(256)", 392 + "primaryKey": false, 393 + "notNull": true, 394 + "autoincrement": false 395 + }, 396 + "published": { 397 + "name": "published", 398 + "type": "integer", 399 + "primaryKey": false, 400 + "notNull": false, 401 + "autoincrement": false, 402 + "default": false 403 + }, 404 + "created_at": { 405 + "name": "created_at", 406 + "type": "integer", 407 + "primaryKey": false, 408 + "notNull": false, 409 + "autoincrement": false, 410 + "default": "(strftime('%s', 'now'))" 411 + }, 412 + "updated_at": { 413 + "name": "updated_at", 414 + "type": "integer", 415 + "primaryKey": false, 416 + "notNull": false, 417 + "autoincrement": false, 418 + "default": "(strftime('%s', 'now'))" 419 + } 420 + }, 421 + "indexes": { 422 + "page_slug_unique": { 423 + "name": "page_slug_unique", 424 + "columns": [ 425 + "slug" 426 + ], 427 + "isUnique": true 428 + } 429 + }, 430 + "foreignKeys": { 431 + "page_workspace_id_workspace_id_fk": { 432 + "name": "page_workspace_id_workspace_id_fk", 433 + "tableFrom": "page", 434 + "tableTo": "workspace", 435 + "columnsFrom": [ 436 + "workspace_id" 437 + ], 438 + "columnsTo": [ 439 + "id" 440 + ], 441 + "onDelete": "cascade", 442 + "onUpdate": "no action" 443 + } 444 + }, 445 + "compositePrimaryKeys": {}, 446 + "uniqueConstraints": {} 447 + }, 448 + "monitor": { 449 + "name": "monitor", 450 + "columns": { 451 + "id": { 452 + "name": "id", 453 + "type": "integer", 454 + "primaryKey": true, 455 + "notNull": true, 456 + "autoincrement": false 457 + }, 458 + "job_type": { 459 + "name": "job_type", 460 + "type": "text", 461 + "primaryKey": false, 462 + "notNull": true, 463 + "autoincrement": false, 464 + "default": "'other'" 465 + }, 466 + "periodicity": { 467 + "name": "periodicity", 468 + "type": "text", 469 + "primaryKey": false, 470 + "notNull": true, 471 + "autoincrement": false, 472 + "default": "'other'" 473 + }, 474 + "status": { 475 + "name": "status", 476 + "type": "text", 477 + "primaryKey": false, 478 + "notNull": true, 479 + "autoincrement": false, 480 + "default": "'active'" 481 + }, 482 + "active": { 483 + "name": "active", 484 + "type": "integer", 485 + "primaryKey": false, 486 + "notNull": false, 487 + "autoincrement": false, 488 + "default": false 489 + }, 490 + "regions": { 491 + "name": "regions", 492 + "type": "text", 493 + "primaryKey": false, 494 + "notNull": true, 495 + "autoincrement": false, 496 + "default": "''" 497 + }, 498 + "url": { 499 + "name": "url", 500 + "type": "text(2048)", 501 + "primaryKey": false, 502 + "notNull": true, 503 + "autoincrement": false 504 + }, 505 + "name": { 506 + "name": "name", 507 + "type": "text(256)", 508 + "primaryKey": false, 509 + "notNull": true, 510 + "autoincrement": false, 511 + "default": "''" 512 + }, 513 + "description": { 514 + "name": "description", 515 + "type": "text", 516 + "primaryKey": false, 517 + "notNull": true, 518 + "autoincrement": false, 519 + "default": "''" 520 + }, 521 + "headers": { 522 + "name": "headers", 523 + "type": "text", 524 + "primaryKey": false, 525 + "notNull": false, 526 + "autoincrement": false, 527 + "default": "''" 528 + }, 529 + "body": { 530 + "name": "body", 531 + "type": "text", 532 + "primaryKey": false, 533 + "notNull": false, 534 + "autoincrement": false, 535 + "default": "''" 536 + }, 537 + "method": { 538 + "name": "method", 539 + "type": "text", 540 + "primaryKey": false, 541 + "notNull": false, 542 + "autoincrement": false, 543 + "default": "'GET'" 544 + }, 545 + "workspace_id": { 546 + "name": "workspace_id", 547 + "type": "integer", 548 + "primaryKey": false, 549 + "notNull": false, 550 + "autoincrement": false 551 + }, 552 + "created_at": { 553 + "name": "created_at", 554 + "type": "integer", 555 + "primaryKey": false, 556 + "notNull": false, 557 + "autoincrement": false, 558 + "default": "(strftime('%s', 'now'))" 559 + }, 560 + "updated_at": { 561 + "name": "updated_at", 562 + "type": "integer", 563 + "primaryKey": false, 564 + "notNull": false, 565 + "autoincrement": false, 566 + "default": "(strftime('%s', 'now'))" 567 + } 568 + }, 569 + "indexes": {}, 570 + "foreignKeys": { 571 + "monitor_workspace_id_workspace_id_fk": { 572 + "name": "monitor_workspace_id_workspace_id_fk", 573 + "tableFrom": "monitor", 574 + "tableTo": "workspace", 575 + "columnsFrom": [ 576 + "workspace_id" 577 + ], 578 + "columnsTo": [ 579 + "id" 580 + ], 581 + "onDelete": "no action", 582 + "onUpdate": "no action" 583 + } 584 + }, 585 + "compositePrimaryKeys": {}, 586 + "uniqueConstraints": {} 587 + }, 588 + "monitors_to_pages": { 589 + "name": "monitors_to_pages", 590 + "columns": { 591 + "monitor_id": { 592 + "name": "monitor_id", 593 + "type": "integer", 594 + "primaryKey": false, 595 + "notNull": true, 596 + "autoincrement": false 597 + }, 598 + "page_id": { 599 + "name": "page_id", 600 + "type": "integer", 601 + "primaryKey": false, 602 + "notNull": true, 603 + "autoincrement": false 604 + } 605 + }, 606 + "indexes": {}, 607 + "foreignKeys": { 608 + "monitors_to_pages_monitor_id_monitor_id_fk": { 609 + "name": "monitors_to_pages_monitor_id_monitor_id_fk", 610 + "tableFrom": "monitors_to_pages", 611 + "tableTo": "monitor", 612 + "columnsFrom": [ 613 + "monitor_id" 614 + ], 615 + "columnsTo": [ 616 + "id" 617 + ], 618 + "onDelete": "cascade", 619 + "onUpdate": "no action" 620 + }, 621 + "monitors_to_pages_page_id_page_id_fk": { 622 + "name": "monitors_to_pages_page_id_page_id_fk", 623 + "tableFrom": "monitors_to_pages", 624 + "tableTo": "page", 625 + "columnsFrom": [ 626 + "page_id" 627 + ], 628 + "columnsTo": [ 629 + "id" 630 + ], 631 + "onDelete": "cascade", 632 + "onUpdate": "no action" 633 + } 634 + }, 635 + "compositePrimaryKeys": { 636 + "monitors_to_pages_monitor_id_page_id_pk": { 637 + "columns": [ 638 + "monitor_id", 639 + "page_id" 640 + ] 641 + } 642 + }, 643 + "uniqueConstraints": {} 644 + }, 645 + "user": { 646 + "name": "user", 647 + "columns": { 648 + "id": { 649 + "name": "id", 650 + "type": "integer", 651 + "primaryKey": true, 652 + "notNull": true, 653 + "autoincrement": false 654 + }, 655 + "tenant_id": { 656 + "name": "tenant_id", 657 + "type": "text(256)", 658 + "primaryKey": false, 659 + "notNull": false, 660 + "autoincrement": false 661 + }, 662 + "first_name": { 663 + "name": "first_name", 664 + "type": "text", 665 + "primaryKey": false, 666 + "notNull": false, 667 + "autoincrement": false, 668 + "default": "''" 669 + }, 670 + "last_name": { 671 + "name": "last_name", 672 + "type": "text", 673 + "primaryKey": false, 674 + "notNull": false, 675 + "autoincrement": false, 676 + "default": "''" 677 + }, 678 + "email": { 679 + "name": "email", 680 + "type": "text", 681 + "primaryKey": false, 682 + "notNull": false, 683 + "autoincrement": false, 684 + "default": "''" 685 + }, 686 + "photo_url": { 687 + "name": "photo_url", 688 + "type": "text", 689 + "primaryKey": false, 690 + "notNull": false, 691 + "autoincrement": false, 692 + "default": "''" 693 + }, 694 + "created_at": { 695 + "name": "created_at", 696 + "type": "integer", 697 + "primaryKey": false, 698 + "notNull": false, 699 + "autoincrement": false, 700 + "default": "(strftime('%s', 'now'))" 701 + }, 702 + "updated_at": { 703 + "name": "updated_at", 704 + "type": "integer", 705 + "primaryKey": false, 706 + "notNull": false, 707 + "autoincrement": false, 708 + "default": "(strftime('%s', 'now'))" 709 + } 710 + }, 711 + "indexes": { 712 + "user_tenant_id_unique": { 713 + "name": "user_tenant_id_unique", 714 + "columns": [ 715 + "tenant_id" 716 + ], 717 + "isUnique": true 718 + } 719 + }, 720 + "foreignKeys": {}, 721 + "compositePrimaryKeys": {}, 722 + "uniqueConstraints": {} 723 + }, 724 + "users_to_workspaces": { 725 + "name": "users_to_workspaces", 726 + "columns": { 727 + "user_id": { 728 + "name": "user_id", 729 + "type": "integer", 730 + "primaryKey": false, 731 + "notNull": true, 732 + "autoincrement": false 733 + }, 734 + "workspace_id": { 735 + "name": "workspace_id", 736 + "type": "integer", 737 + "primaryKey": false, 738 + "notNull": true, 739 + "autoincrement": false 740 + }, 741 + "role": { 742 + "name": "role", 743 + "type": "text", 744 + "primaryKey": false, 745 + "notNull": true, 746 + "autoincrement": false, 747 + "default": "'member'" 748 + } 749 + }, 750 + "indexes": {}, 751 + "foreignKeys": { 752 + "users_to_workspaces_user_id_user_id_fk": { 753 + "name": "users_to_workspaces_user_id_user_id_fk", 754 + "tableFrom": "users_to_workspaces", 755 + "tableTo": "user", 756 + "columnsFrom": [ 757 + "user_id" 758 + ], 759 + "columnsTo": [ 760 + "id" 761 + ], 762 + "onDelete": "no action", 763 + "onUpdate": "no action" 764 + }, 765 + "users_to_workspaces_workspace_id_workspace_id_fk": { 766 + "name": "users_to_workspaces_workspace_id_workspace_id_fk", 767 + "tableFrom": "users_to_workspaces", 768 + "tableTo": "workspace", 769 + "columnsFrom": [ 770 + "workspace_id" 771 + ], 772 + "columnsTo": [ 773 + "id" 774 + ], 775 + "onDelete": "no action", 776 + "onUpdate": "no action" 777 + } 778 + }, 779 + "compositePrimaryKeys": { 780 + "users_to_workspaces_user_id_workspace_id_pk": { 781 + "columns": [ 782 + "user_id", 783 + "workspace_id" 784 + ] 785 + } 786 + }, 787 + "uniqueConstraints": {} 788 + }, 789 + "page_subscriber": { 790 + "name": "page_subscriber", 791 + "columns": { 792 + "id": { 793 + "name": "id", 794 + "type": "integer", 795 + "primaryKey": true, 796 + "notNull": true, 797 + "autoincrement": false 798 + }, 799 + "email": { 800 + "name": "email", 801 + "type": "text", 802 + "primaryKey": false, 803 + "notNull": true, 804 + "autoincrement": false 805 + }, 806 + "page_id": { 807 + "name": "page_id", 808 + "type": "integer", 809 + "primaryKey": false, 810 + "notNull": true, 811 + "autoincrement": false 812 + }, 813 + "token": { 814 + "name": "token", 815 + "type": "text", 816 + "primaryKey": false, 817 + "notNull": false, 818 + "autoincrement": false 819 + }, 820 + "accepted_at": { 821 + "name": "accepted_at", 822 + "type": "integer", 823 + "primaryKey": false, 824 + "notNull": false, 825 + "autoincrement": false 826 + }, 827 + "expires_at": { 828 + "name": "expires_at", 829 + "type": "integer", 830 + "primaryKey": false, 831 + "notNull": false, 832 + "autoincrement": false 833 + }, 834 + "created_at": { 835 + "name": "created_at", 836 + "type": "integer", 837 + "primaryKey": false, 838 + "notNull": false, 839 + "autoincrement": false, 840 + "default": "(strftime('%s', 'now'))" 841 + }, 842 + "updated_at": { 843 + "name": "updated_at", 844 + "type": "integer", 845 + "primaryKey": false, 846 + "notNull": false, 847 + "autoincrement": false, 848 + "default": "(strftime('%s', 'now'))" 849 + } 850 + }, 851 + "indexes": {}, 852 + "foreignKeys": { 853 + "page_subscriber_page_id_page_id_fk": { 854 + "name": "page_subscriber_page_id_page_id_fk", 855 + "tableFrom": "page_subscriber", 856 + "tableTo": "page", 857 + "columnsFrom": [ 858 + "page_id" 859 + ], 860 + "columnsTo": [ 861 + "id" 862 + ], 863 + "onDelete": "no action", 864 + "onUpdate": "no action" 865 + } 866 + }, 867 + "compositePrimaryKeys": {}, 868 + "uniqueConstraints": {} 869 + }, 870 + "workspace": { 871 + "name": "workspace", 872 + "columns": { 873 + "id": { 874 + "name": "id", 875 + "type": "integer", 876 + "primaryKey": true, 877 + "notNull": true, 878 + "autoincrement": false 879 + }, 880 + "slug": { 881 + "name": "slug", 882 + "type": "text", 883 + "primaryKey": false, 884 + "notNull": true, 885 + "autoincrement": false 886 + }, 887 + "name": { 888 + "name": "name", 889 + "type": "text", 890 + "primaryKey": false, 891 + "notNull": false, 892 + "autoincrement": false 893 + }, 894 + "stripe_id": { 895 + "name": "stripe_id", 896 + "type": "text(256)", 897 + "primaryKey": false, 898 + "notNull": false, 899 + "autoincrement": false 900 + }, 901 + "subscription_id": { 902 + "name": "subscription_id", 903 + "type": "text", 904 + "primaryKey": false, 905 + "notNull": false, 906 + "autoincrement": false 907 + }, 908 + "plan": { 909 + "name": "plan", 910 + "type": "text", 911 + "primaryKey": false, 912 + "notNull": false, 913 + "autoincrement": false 914 + }, 915 + "ends_at": { 916 + "name": "ends_at", 917 + "type": "integer", 918 + "primaryKey": false, 919 + "notNull": false, 920 + "autoincrement": false 921 + }, 922 + "paid_until": { 923 + "name": "paid_until", 924 + "type": "integer", 925 + "primaryKey": false, 926 + "notNull": false, 927 + "autoincrement": false 928 + }, 929 + "created_at": { 930 + "name": "created_at", 931 + "type": "integer", 932 + "primaryKey": false, 933 + "notNull": false, 934 + "autoincrement": false, 935 + "default": "(strftime('%s', 'now'))" 936 + }, 937 + "updated_at": { 938 + "name": "updated_at", 939 + "type": "integer", 940 + "primaryKey": false, 941 + "notNull": false, 942 + "autoincrement": false, 943 + "default": "(strftime('%s', 'now'))" 944 + } 945 + }, 946 + "indexes": { 947 + "workspace_slug_unique": { 948 + "name": "workspace_slug_unique", 949 + "columns": [ 950 + "slug" 951 + ], 952 + "isUnique": true 953 + }, 954 + "workspace_stripe_id_unique": { 955 + "name": "workspace_stripe_id_unique", 956 + "columns": [ 957 + "stripe_id" 958 + ], 959 + "isUnique": true 960 + } 961 + }, 962 + "foreignKeys": {}, 963 + "compositePrimaryKeys": {}, 964 + "uniqueConstraints": {} 965 + }, 966 + "notification": { 967 + "name": "notification", 968 + "columns": { 969 + "id": { 970 + "name": "id", 971 + "type": "integer", 972 + "primaryKey": true, 973 + "notNull": true, 974 + "autoincrement": false 975 + }, 976 + "name": { 977 + "name": "name", 978 + "type": "text", 979 + "primaryKey": false, 980 + "notNull": true, 981 + "autoincrement": false 982 + }, 983 + "provider": { 984 + "name": "provider", 985 + "type": "text", 986 + "primaryKey": false, 987 + "notNull": true, 988 + "autoincrement": false 989 + }, 990 + "data": { 991 + "name": "data", 992 + "type": "text", 993 + "primaryKey": false, 994 + "notNull": false, 995 + "autoincrement": false, 996 + "default": "'{}'" 997 + }, 998 + "workspace_id": { 999 + "name": "workspace_id", 1000 + "type": "integer", 1001 + "primaryKey": false, 1002 + "notNull": false, 1003 + "autoincrement": false 1004 + }, 1005 + "created_at": { 1006 + "name": "created_at", 1007 + "type": "integer", 1008 + "primaryKey": false, 1009 + "notNull": false, 1010 + "autoincrement": false, 1011 + "default": "(strftime('%s', 'now'))" 1012 + }, 1013 + "updated_at": { 1014 + "name": "updated_at", 1015 + "type": "integer", 1016 + "primaryKey": false, 1017 + "notNull": false, 1018 + "autoincrement": false, 1019 + "default": "(strftime('%s', 'now'))" 1020 + } 1021 + }, 1022 + "indexes": {}, 1023 + "foreignKeys": { 1024 + "notification_workspace_id_workspace_id_fk": { 1025 + "name": "notification_workspace_id_workspace_id_fk", 1026 + "tableFrom": "notification", 1027 + "tableTo": "workspace", 1028 + "columnsFrom": [ 1029 + "workspace_id" 1030 + ], 1031 + "columnsTo": [ 1032 + "id" 1033 + ], 1034 + "onDelete": "no action", 1035 + "onUpdate": "no action" 1036 + } 1037 + }, 1038 + "compositePrimaryKeys": {}, 1039 + "uniqueConstraints": {} 1040 + }, 1041 + "notifications_to_monitors": { 1042 + "name": "notifications_to_monitors", 1043 + "columns": { 1044 + "monitor_id": { 1045 + "name": "monitor_id", 1046 + "type": "integer", 1047 + "primaryKey": false, 1048 + "notNull": true, 1049 + "autoincrement": false 1050 + }, 1051 + "notification_id": { 1052 + "name": "notification_id", 1053 + "type": "integer", 1054 + "primaryKey": false, 1055 + "notNull": true, 1056 + "autoincrement": false 1057 + } 1058 + }, 1059 + "indexes": {}, 1060 + "foreignKeys": { 1061 + "notifications_to_monitors_monitor_id_monitor_id_fk": { 1062 + "name": "notifications_to_monitors_monitor_id_monitor_id_fk", 1063 + "tableFrom": "notifications_to_monitors", 1064 + "tableTo": "monitor", 1065 + "columnsFrom": [ 1066 + "monitor_id" 1067 + ], 1068 + "columnsTo": [ 1069 + "id" 1070 + ], 1071 + "onDelete": "cascade", 1072 + "onUpdate": "no action" 1073 + }, 1074 + "notifications_to_monitors_notification_id_notification_id_fk": { 1075 + "name": "notifications_to_monitors_notification_id_notification_id_fk", 1076 + "tableFrom": "notifications_to_monitors", 1077 + "tableTo": "notification", 1078 + "columnsFrom": [ 1079 + "notification_id" 1080 + ], 1081 + "columnsTo": [ 1082 + "id" 1083 + ], 1084 + "onDelete": "cascade", 1085 + "onUpdate": "no action" 1086 + } 1087 + }, 1088 + "compositePrimaryKeys": { 1089 + "notifications_to_monitors_monitor_id_notification_id_pk": { 1090 + "columns": [ 1091 + "monitor_id", 1092 + "notification_id" 1093 + ] 1094 + } 1095 + }, 1096 + "uniqueConstraints": {} 1097 + }, 1098 + "monitor_status": { 1099 + "name": "monitor_status", 1100 + "columns": { 1101 + "monitor_id": { 1102 + "name": "monitor_id", 1103 + "type": "integer", 1104 + "primaryKey": false, 1105 + "notNull": true, 1106 + "autoincrement": false 1107 + }, 1108 + "region": { 1109 + "name": "region", 1110 + "type": "text", 1111 + "primaryKey": false, 1112 + "notNull": true, 1113 + "autoincrement": false, 1114 + "default": "''" 1115 + }, 1116 + "status": { 1117 + "name": "status", 1118 + "type": "text", 1119 + "primaryKey": false, 1120 + "notNull": true, 1121 + "autoincrement": false, 1122 + "default": "'active'" 1123 + }, 1124 + "created_at": { 1125 + "name": "created_at", 1126 + "type": "integer", 1127 + "primaryKey": false, 1128 + "notNull": false, 1129 + "autoincrement": false, 1130 + "default": "(strftime('%s', 'now'))" 1131 + }, 1132 + "updated_at": { 1133 + "name": "updated_at", 1134 + "type": "integer", 1135 + "primaryKey": false, 1136 + "notNull": false, 1137 + "autoincrement": false, 1138 + "default": "(strftime('%s', 'now'))" 1139 + } 1140 + }, 1141 + "indexes": { 1142 + "monitor_status_idx": { 1143 + "name": "monitor_status_idx", 1144 + "columns": [ 1145 + "monitor_id", 1146 + "region" 1147 + ], 1148 + "isUnique": false 1149 + } 1150 + }, 1151 + "foreignKeys": { 1152 + "monitor_status_monitor_id_monitor_id_fk": { 1153 + "name": "monitor_status_monitor_id_monitor_id_fk", 1154 + "tableFrom": "monitor_status", 1155 + "tableTo": "monitor", 1156 + "columnsFrom": [ 1157 + "monitor_id" 1158 + ], 1159 + "columnsTo": [ 1160 + "id" 1161 + ], 1162 + "onDelete": "cascade", 1163 + "onUpdate": "no action" 1164 + } 1165 + }, 1166 + "compositePrimaryKeys": { 1167 + "monitor_status_monitor_id_region_pk": { 1168 + "columns": [ 1169 + "monitor_id", 1170 + "region" 1171 + ] 1172 + } 1173 + }, 1174 + "uniqueConstraints": {} 1175 + }, 1176 + "invitation": { 1177 + "name": "invitation", 1178 + "columns": { 1179 + "id": { 1180 + "name": "id", 1181 + "type": "integer", 1182 + "primaryKey": true, 1183 + "notNull": true, 1184 + "autoincrement": false 1185 + }, 1186 + "email": { 1187 + "name": "email", 1188 + "type": "text", 1189 + "primaryKey": false, 1190 + "notNull": true, 1191 + "autoincrement": false 1192 + }, 1193 + "role": { 1194 + "name": "role", 1195 + "type": "text", 1196 + "primaryKey": false, 1197 + "notNull": true, 1198 + "autoincrement": false, 1199 + "default": "'member'" 1200 + }, 1201 + "workspace_id": { 1202 + "name": "workspace_id", 1203 + "type": "integer", 1204 + "primaryKey": false, 1205 + "notNull": true, 1206 + "autoincrement": false 1207 + }, 1208 + "token": { 1209 + "name": "token", 1210 + "type": "text", 1211 + "primaryKey": false, 1212 + "notNull": true, 1213 + "autoincrement": false 1214 + }, 1215 + "expires_at": { 1216 + "name": "expires_at", 1217 + "type": "integer", 1218 + "primaryKey": false, 1219 + "notNull": true, 1220 + "autoincrement": false 1221 + }, 1222 + "created_at": { 1223 + "name": "created_at", 1224 + "type": "integer", 1225 + "primaryKey": false, 1226 + "notNull": false, 1227 + "autoincrement": false, 1228 + "default": "(strftime('%s', 'now'))" 1229 + }, 1230 + "accepted_at": { 1231 + "name": "accepted_at", 1232 + "type": "integer", 1233 + "primaryKey": false, 1234 + "notNull": false, 1235 + "autoincrement": false 1236 + } 1237 + }, 1238 + "indexes": {}, 1239 + "foreignKeys": {}, 1240 + "compositePrimaryKeys": {}, 1241 + "uniqueConstraints": {} 1242 + }, 1243 + "incident": { 1244 + "name": "incident", 1245 + "columns": { 1246 + "id": { 1247 + "name": "id", 1248 + "type": "integer", 1249 + "primaryKey": true, 1250 + "notNull": true, 1251 + "autoincrement": false 1252 + }, 1253 + "title": { 1254 + "name": "title", 1255 + "type": "text", 1256 + "primaryKey": false, 1257 + "notNull": true, 1258 + "autoincrement": false, 1259 + "default": "''" 1260 + }, 1261 + "summary": { 1262 + "name": "summary", 1263 + "type": "text", 1264 + "primaryKey": false, 1265 + "notNull": true, 1266 + "autoincrement": false, 1267 + "default": "''" 1268 + }, 1269 + "status": { 1270 + "name": "status", 1271 + "type": "text", 1272 + "primaryKey": false, 1273 + "notNull": true, 1274 + "autoincrement": false, 1275 + "default": "'triage'" 1276 + }, 1277 + "monitor_id": { 1278 + "name": "monitor_id", 1279 + "type": "integer", 1280 + "primaryKey": false, 1281 + "notNull": false, 1282 + "autoincrement": false 1283 + }, 1284 + "workspace_id": { 1285 + "name": "workspace_id", 1286 + "type": "integer", 1287 + "primaryKey": false, 1288 + "notNull": false, 1289 + "autoincrement": false 1290 + }, 1291 + "started_at": { 1292 + "name": "started_at", 1293 + "type": "integer", 1294 + "primaryKey": false, 1295 + "notNull": false, 1296 + "autoincrement": false 1297 + }, 1298 + "acknowledged_at": { 1299 + "name": "acknowledged_at", 1300 + "type": "integer", 1301 + "primaryKey": false, 1302 + "notNull": false, 1303 + "autoincrement": false 1304 + }, 1305 + "acknowledged_by": { 1306 + "name": "acknowledged_by", 1307 + "type": "integer", 1308 + "primaryKey": false, 1309 + "notNull": false, 1310 + "autoincrement": false 1311 + }, 1312 + "resolved_at": { 1313 + "name": "resolved_at", 1314 + "type": "integer", 1315 + "primaryKey": false, 1316 + "notNull": false, 1317 + "autoincrement": false 1318 + }, 1319 + "resolved_by": { 1320 + "name": "resolved_by", 1321 + "type": "integer", 1322 + "primaryKey": false, 1323 + "notNull": false, 1324 + "autoincrement": false 1325 + }, 1326 + "created_at": { 1327 + "name": "created_at", 1328 + "type": "integer", 1329 + "primaryKey": false, 1330 + "notNull": false, 1331 + "autoincrement": false, 1332 + "default": "(strftime('%s', 'now'))" 1333 + }, 1334 + "updated_at": { 1335 + "name": "updated_at", 1336 + "type": "integer", 1337 + "primaryKey": false, 1338 + "notNull": false, 1339 + "autoincrement": false, 1340 + "default": "(strftime('%s', 'now'))" 1341 + } 1342 + }, 1343 + "indexes": {}, 1344 + "foreignKeys": { 1345 + "incident_monitor_id_monitor_id_fk": { 1346 + "name": "incident_monitor_id_monitor_id_fk", 1347 + "tableFrom": "incident", 1348 + "tableTo": "monitor", 1349 + "columnsFrom": [ 1350 + "monitor_id" 1351 + ], 1352 + "columnsTo": [ 1353 + "id" 1354 + ], 1355 + "onDelete": "no action", 1356 + "onUpdate": "no action" 1357 + }, 1358 + "incident_workspace_id_workspace_id_fk": { 1359 + "name": "incident_workspace_id_workspace_id_fk", 1360 + "tableFrom": "incident", 1361 + "tableTo": "workspace", 1362 + "columnsFrom": [ 1363 + "workspace_id" 1364 + ], 1365 + "columnsTo": [ 1366 + "id" 1367 + ], 1368 + "onDelete": "no action", 1369 + "onUpdate": "no action" 1370 + }, 1371 + "incident_acknowledged_by_user_id_fk": { 1372 + "name": "incident_acknowledged_by_user_id_fk", 1373 + "tableFrom": "incident", 1374 + "tableTo": "user", 1375 + "columnsFrom": [ 1376 + "acknowledged_by" 1377 + ], 1378 + "columnsTo": [ 1379 + "id" 1380 + ], 1381 + "onDelete": "no action", 1382 + "onUpdate": "no action" 1383 + }, 1384 + "incident_resolved_by_user_id_fk": { 1385 + "name": "incident_resolved_by_user_id_fk", 1386 + "tableFrom": "incident", 1387 + "tableTo": "user", 1388 + "columnsFrom": [ 1389 + "resolved_by" 1390 + ], 1391 + "columnsTo": [ 1392 + "id" 1393 + ], 1394 + "onDelete": "no action", 1395 + "onUpdate": "no action" 1396 + } 1397 + }, 1398 + "compositePrimaryKeys": {}, 1399 + "uniqueConstraints": {} 1400 + } 1401 + }, 1402 + "enums": {}, 1403 + "_meta": { 1404 + "schemas": {}, 1405 + "tables": {}, 1406 + "columns": {} 1407 + } 1408 + }
+7
packages/db/drizzle/meta/_journal.json
··· 106 106 "when": 1702227904130, 107 107 "tag": "0014_adorable_skaar", 108 108 "breakpoints": true 109 + }, 110 + { 111 + "idx": 15, 112 + "version": "5", 113 + "when": 1705856545397, 114 + "tag": "0015_bent_sister_grimm", 115 + "breakpoints": true 109 116 } 110 117 ] 111 118 }
+44
packages/db/src/schema/incidents/incident.ts
··· 1 + import { sql } from "drizzle-orm"; 2 + import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; 3 + 4 + import { monitor } from "../monitors"; 5 + import { user } from "../users/user"; 6 + import { workspace } from "../workspaces"; 7 + 8 + export const statusIncident = [ 9 + "triage", 10 + "investigating", 11 + "identified", 12 + "monitoring", 13 + "resolved", 14 + "duplicated", 15 + ] as const; 16 + 17 + export const incidentTable = sqliteTable("incident", { 18 + id: integer("id").primaryKey(), 19 + title: text("title").default("").notNull(), 20 + summary: text("summary").default("").notNull(), 21 + status: text("status", { enum: statusIncident }).default("triage").notNull(), 22 + 23 + // Service affected by incident 24 + monitorId: integer("monitor_id").references(() => monitor.id), 25 + 26 + // Workspace where the incident happened 27 + workspaceId: integer("workspace_id").references(() => workspace.id), 28 + // Data related to incident timeline 29 + startedAt: integer("started_at", { mode: "timestamp" }), 30 + // Who has acknoledge the incident 31 + acknowledgedAt: integer("acknowledged_at", { mode: "timestamp" }), 32 + acknowledgedBy: integer("acknowledged_by").references(() => user.id), 33 + 34 + // Who has resolved it 35 + resolvedAt: integer("resolved_at", { mode: "timestamp" }), 36 + resolvedBy: integer("resolved_by").references(() => user.id), 37 + 38 + createdAt: integer("created_at", { mode: "timestamp" }).default( 39 + sql`(strftime('%s', 'now'))`, 40 + ), 41 + updatedAt: integer("updated_at", { mode: "timestamp" }).default( 42 + sql`(strftime('%s', 'now'))`, 43 + ), 44 + });
+2
packages/db/src/schema/incidents/index.ts
··· 1 + export * from "./incident"; 2 + export * from "./validation";
+10
packages/db/src/schema/incidents/validation.ts
··· 1 + import { createSelectSchema } from "drizzle-zod"; 2 + import { z } from "zod"; 3 + 4 + import { incidentTable } from "./incident"; 5 + 6 + export const selectIncidentSchema = createSelectSchema(incidentTable).extend({ 7 + monitorName: z.string().optional(), 8 + }); 9 + 10 + export type Incident = z.infer<typeof selectIncidentSchema>;
+1
packages/db/src/schema/index.ts
··· 9 9 export * from "./notifications"; 10 10 export * from "./monitor_status"; 11 11 export * from "./invitations"; 12 + export * from "./incidents";
+2 -4
packages/db/src/schema/monitors/validation.ts
··· 24 24 const regionsToArraySchema = z.preprocess((val) => { 25 25 if (String(val).length > 0) { 26 26 return String(val).split(","); 27 - } else { 28 - return []; 29 27 } 28 + return []; 30 29 }, z.array(monitorRegionSchema)); 31 30 32 31 const bodyToStringSchema = z.preprocess((val) => { ··· 41 40 } 42 41 if (String(val).length > 0) { 43 42 return JSON.parse(String(val)); 44 - } else { 45 - return []; 46 43 } 44 + return []; 47 45 }, 48 46 z.array(z.object({ key: z.string(), value: z.string() })).default([]), 49 47 );