Repository: ryotarai/waker Branch: master Commit: 24b15020a036 Files: 270 Total size: 156.8 KB Directory structure: gitextract_ao1b4jon/ ├── .dockerignore ├── .github/ │ └── workflows/ │ └── rails.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── Dockerfile ├── Gemfile ├── LICENSE.txt ├── Procfile ├── Procfile.docker ├── README.md ├── Rakefile ├── app/ │ ├── assets/ │ │ ├── images/ │ │ │ └── .keep │ │ ├── javascripts/ │ │ │ ├── application.js │ │ │ ├── comments.coffee │ │ │ ├── escalation_series.coffee │ │ │ ├── escalations.coffee │ │ │ ├── home.coffee │ │ │ ├── incident_events.coffee │ │ │ ├── incidents.coffee │ │ │ ├── maintenances.coffee │ │ │ ├── notifier_providers.coffee │ │ │ ├── notifiers.coffee │ │ │ ├── sessions.coffee │ │ │ ├── slack.coffee │ │ │ ├── topics.coffee │ │ │ └── users.coffee │ │ └── stylesheets/ │ │ ├── application.css │ │ ├── comments.scss │ │ ├── escalation_series.scss │ │ ├── escalations.scss │ │ ├── home.scss │ │ ├── incident_events.scss │ │ ├── incidents.scss │ │ ├── maintenances.scss │ │ ├── notifier_providers.scss │ │ ├── notifiers.scss │ │ ├── scaffolds.scss │ │ ├── sessions.scss │ │ ├── slack.scss │ │ ├── topics.scss │ │ └── users.scss │ ├── controllers/ │ │ ├── application_controller.rb │ │ ├── comments_controller.rb │ │ ├── concerns/ │ │ │ └── .keep │ │ ├── escalation_series_controller.rb │ │ ├── escalations_controller.rb │ │ ├── home_controller.rb │ │ ├── incident_events_controller.rb │ │ ├── incidents_controller.rb │ │ ├── maintenances_controller.rb │ │ ├── notifier_providers_controller.rb │ │ ├── notifiers_controller.rb │ │ ├── sessions_controller.rb │ │ ├── slack_controller.rb │ │ ├── topics_controller.rb │ │ └── users_controller.rb │ ├── helpers/ │ │ ├── application_helper.rb │ │ ├── comments_helper.rb │ │ ├── escalation_series_helper.rb │ │ ├── escalations_helper.rb │ │ ├── home_helper.rb │ │ ├── incident_events_helper.rb │ │ ├── incidents_helper.rb │ │ ├── maintenances_helper.rb │ │ ├── notifier_providers_helper.rb │ │ ├── notifiers_helper.rb │ │ ├── sessions_helper.rb │ │ ├── slack_helper.rb │ │ ├── topics_helper.rb │ │ └── users_helper.rb │ ├── mailers/ │ │ └── .keep │ ├── models/ │ │ ├── .keep │ │ ├── application_record.rb │ │ ├── comment.rb │ │ ├── concerns/ │ │ │ └── .keep │ │ ├── escalation.rb │ │ ├── escalation_series.rb │ │ ├── escalation_update_worker.rb │ │ ├── escalation_worker.rb │ │ ├── incident.rb │ │ ├── incident_event.rb │ │ ├── maintenance.rb │ │ ├── notification_worker.rb │ │ ├── notifier.rb │ │ ├── notifier_provider.rb │ │ ├── topic.rb │ │ └── user.rb │ └── views/ │ ├── comments/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ ├── escalation_series/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ ├── escalations/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ ├── home/ │ │ └── index.html.erb │ ├── incident_events/ │ │ └── twilio.html.erb │ ├── incidents/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ ├── layouts/ │ │ └── application.html.erb │ ├── maintenances/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ ├── notifier_providers/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── hipchat/ │ │ │ ├── acknowledged.text.erb │ │ │ ├── escalated.text.erb │ │ │ ├── opened.text.erb │ │ │ └── resolved.text.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── mailgun/ │ │ │ ├── default.html.erb │ │ │ └── default.text.erb │ │ ├── new.html.erb │ │ ├── rails_logger/ │ │ │ └── default.text.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ ├── notifiers/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ ├── sessions/ │ │ └── create.html.erb │ ├── slack/ │ │ └── interactive.html.erb │ ├── topics/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ └── users/ │ ├── _form.html.erb │ ├── edit.html.erb │ ├── index.html.erb │ ├── index.json.jbuilder │ ├── new.html.erb │ ├── show.html.erb │ └── show.json.jbuilder ├── bin/ │ ├── bundle │ ├── rails │ ├── rake │ ├── setup │ └── spring ├── config/ │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments/ │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers/ │ │ ├── assets.rb │ │ ├── backtrace_silencers.rb │ │ ├── cookies_serializer.rb │ │ ├── field_with_errors.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── omniauth.rb │ │ ├── session_store.rb │ │ ├── sidekiq.rb │ │ ├── url_options.rb │ │ └── wrap_parameters.rb │ ├── locales/ │ │ └── en.yml │ ├── routes.rb │ └── secrets.yml ├── config.ru ├── db/ │ ├── migrate/ │ │ ├── 20150120134616_create_topics.rb │ │ ├── 20150120134747_create_users.rb │ │ ├── 20150120134905_create_notifiers.rb │ │ ├── 20150120135017_create_shifts.rb │ │ ├── 20150120135123_create_escalations.rb │ │ ├── 20150120135244_create_escalation_series.rb │ │ ├── 20150120135351_add_escalation_series_to_escalation.rb │ │ ├── 20150120141627_rename_type_with_kind_of_topic.rb │ │ ├── 20150120142452_create_incidents.rb │ │ ├── 20150120151642_add_escalation_series_to_topic.rb │ │ ├── 20150120154438_add_name_to_shift.rb │ │ ├── 20150121150043_add_user_to_notifier.rb │ │ ├── 20150121150857_rename_type_with_kind_of_notifier.rb │ │ ├── 20150123132415_remove_shift.rb │ │ ├── 20150123150518_add_status_to_incident.rb │ │ ├── 20150123150947_create_incident_events.rb │ │ ├── 20150125050529_create_notifier_providers.rb │ │ ├── 20150125050556_add_provider_to_notifier.rb │ │ ├── 20150125101901_remove_kind_from_notifier.rb │ │ ├── 20150127142530_remove_user_by_from_incident_event.rb │ │ ├── 20150127152127_add_info_to_incident_event.rb │ │ ├── 20150128064248_add_email_to_user.rb │ │ ├── 20150131120557_add_enable_to_topic.rb │ │ ├── 20150131121143_set_default_of_enable_of_topic_true.rb │ │ ├── 20150131122151_rename_enable_with_enabled_of_topic.rb │ │ ├── 20150201033946_add_login_token_to_user.rb │ │ ├── 20150202144538_add_token_to_user.rb │ │ ├── 20150202151740_add_refresh_token_to_user.rb │ │ ├── 20150202152015_add_settings_to_escalation_series.rb │ │ ├── 20150202155726_add_token_expires_at_to_user.rb │ │ ├── 20150203010332_remove_escalation_series_from_escalation_series.rb │ │ ├── 20150203010417_add_settings_to_escalation_series_again.rb │ │ ├── 20150207164010_add_topic_to_notifier.rb │ │ ├── 20150218071007_add_enable_to_notifier.rb │ │ ├── 20151117011141_add_active_to_user.rb │ │ ├── 20151117013824_add_provider_and_uid_to_user.rb │ │ ├── 20151118061253_add_credentials_to_user.rb │ │ ├── 20151118061938_delete_token_from_user.rb │ │ ├── 20160210010310_create_maintenances.rb │ │ ├── 20160907123728_create_comments.rb │ │ ├── 20160914063913_add_comment_index.rb │ │ └── 20161207045554_add_filter_to_maintenance.rb │ ├── schema.rb │ └── seeds.rb ├── docker/ │ └── puma.rb ├── docker-compose.yml ├── lib/ │ ├── assets/ │ │ └── .keep │ └── tasks/ │ └── .keep ├── log/ │ └── .keep ├── public/ │ ├── 404.html │ ├── 422.html │ ├── 500.html │ └── robots.txt ├── script/ │ └── update-escalations-from-google-calendar ├── spec/ │ ├── controllers/ │ │ └── slack_controller_spec.rb │ ├── factories/ │ │ ├── escalation.rb │ │ ├── escalation_series.rb │ │ ├── incident.rb │ │ ├── incident_events.rb │ │ ├── maintenances.rb │ │ ├── notifier.rb │ │ ├── notifier_provider.rb │ │ ├── topic.rb │ │ └── user.rb │ ├── helpers/ │ │ └── slack_helper_spec.rb │ ├── models/ │ │ ├── comment_spec.rb │ │ ├── escalation_series_spec.rb │ │ ├── incident_spec.rb │ │ └── maintenance_spec.rb │ ├── rails_helper.rb │ ├── requests/ │ │ ├── incident_envets_spec.rb │ │ ├── maintenances_spec.rb │ │ └── topics_spec.rb │ ├── spec_helper.rb │ ├── support/ │ │ ├── factory_girl.rb │ │ └── sidekiq.rb │ └── views/ │ └── slack/ │ └── interactive.html.erb_spec.rb └── vendor/ └── assets/ ├── javascripts/ │ └── .keep └── stylesheets/ └── .keep ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ logs/* doc/* vendor/* coverage/* tmp/* ================================================ FILE: .github/workflows/rails.yml ================================================ name: Rails on: [push] jobs: build: runs-on: ubuntu-latest services: mysql: image: mysql:5.7 env: MYSQL_ALLOW_EMPTY_PASSWORD: "yes" container: image: ruby:2.6.5 env: MYSQL_HOST: mysql steps: - uses: actions/checkout@v1 - name: Setup YARN and NodeJS run: | curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list curl -sL https://deb.nodesource.com/setup_12.x | bash - apt-get install -y yarn nodejs - name: Build and setup run: | gem install bundler --no-document bundle install --jobs 4 --retry 3 --deployment bundle exec rails yarn:install db:setup assets:precompile bundle exec rake env: RAILS_ENV: "test" ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files for more about ignoring files. # # If you find yourself ignoring temporary files generated by your text editor # or operating system, you probably want to add a global ignore instead: # git config --global core.excludesfile '~/.gitignore_global' # Ignore bundler config. /.bundle # Ignore the default SQLite database. /db/*.sqlite3 /db/*.sqlite3-journal # Ignore all logfiles and tempfiles. /log/* !/log/.keep /tmp *.rdb .env coverage ================================================ FILE: .rspec ================================================ --color --require spec_helper ================================================ FILE: .rubocop.yml ================================================ AllCops: TargetRubyVersion: 2.4 DisplayCopNames: true DisabledByDefault: true Exclude: - 'db/**/*' - 'vendor/**/*' Rails: Enabled: true Rails/ActionFilter: EnforcedStyle: action Enabled: true Style/HashSyntax: Enabled: true Style/MethodDefParentheses: Enabled: true Style/Encoding: Enabled: true Style/For: EnforcedStyle: each Enabled: true Style/FrozenStringLiteralComment: EnforcedStyle: never Enabled: true Style/NumericLiteralPrefix: EnforcedOctalStyle: zero_only Enabled: true Style/StabbyLambdaParentheses: EnforcedStyle: require_parentheses Enabled: true Layout/EmptyLines: Enabled: true Layout/TrailingBlankLines: Enabled: true Layout/TrailingWhitespace: Enabled: true Layout/AccessModifierIndentation: EnforcedStyle: indent Enabled: true Layout/CaseIndentation: EnforcedStyle: end Enabled: true Layout/MultilineHashBraceLayout: EnforcedStyle: symmetrical Enabled: true ================================================ FILE: Dockerfile ================================================ FROM ruby:2.6.5 RUN apt update -qqy && apt -qqy install nodejs WORKDIR /tmp ADD Gemfile* /tmp/ RUN gem install bundler:2.1.4 RUN bundle install --deployment -j4 --without development test ADD . /app WORKDIR /app RUN cp -a /tmp/vendor/bundle /app/vendor/bundle && \ bundle exec rake assets:precompile CMD ["bundle", "exec", "foreman", "start", "-f", "Procfile.docker"] ================================================ FILE: Gemfile ================================================ source 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '5.2.4.1' # Use SCSS for stylesheets gem 'sass-rails', '~> 5.0' # Use Uglifier as compressor for JavaScript assets gem 'uglifier', '>= 1.3.0' # Use CoffeeScript for .coffee assets and views gem 'coffee-rails' # See https://github.com/sstephenson/execjs#readme for more supported runtimes # gem 'therubyracer', platforms: :ruby # Use jquery as the JavaScript library gem 'jquery-rails' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.0' # bundle exec rake doc:rails generates the API under doc/api. gem 'sdoc' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' # Use Unicorn as the app server # gem 'unicorn' # Use Capistrano for deployment # gem 'capistrano-rails', group: :development gem 'sidekiq', '~> 3.3.0' gem 'sinatra' gem 'faraday', '~> 0.15.4' gem 'holiday_jp' gem 'hipchat' gem 'kaminari' gem 'puma' gem 'rack-health' gem 'omniauth-google-oauth2', '~> 0.6' gem 'retriable', '3.0.1' gem 'twilio-ruby' gem 'mysql2' gem 'google-api-client', '~> 0.7.1' gem 'foreman' group :development do gem 'web-console' end group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug' # Access an IRB console on exception pages or by using <%= console %> in views # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' gem 'rspec-rails' gem 'database_rewinder' gem 'pry-byebug' gem 'dotenv-rails' gem 'factory_bot_rails' gem 'rubocop', require: false end group :test do gem 'simplecov', require: false end gem 'dogapi' gem 'aws-sdk' gem 'rails_autolink' ================================================ FILE: LICENSE.txt ================================================ The MIT License (MIT) Copyright (c) 2014-2015 Ryota Arai Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Procfile ================================================ web: bin/rails s worker: bundle exec sidekiq update_escalations: while true; do bundle exec rails runner 'EscalationUpdateWorker.perform_async()'; sleep 60; done ================================================ FILE: Procfile.docker ================================================ web: puma -C docker/puma.rb worker: bundle exec sidekiq update_escalations: while true; do bundle exec rails runner 'EscalationUpdateWorker.perform_async()'; sleep 60; done ================================================ FILE: README.md ================================================ # Waker [![Build Status](https://travis-ci.org/ryotarai/waker.svg?branch=master)](https://travis-ci.org/ryotarai/waker) Alert Escalation System ![](https://raw.githubusercontent.com/ryotarai/waker/master/doc/incidents.png) ## Overview ![](https://raw.githubusercontent.com/ryotarai/waker/master/doc/overview.png) ![](https://raw.githubusercontent.com/ryotarai/waker/master/doc/escalation.png) ## Tutorial ### 1. (Optional) Configure auth provider You can use external auth provider **optionally**. Currently, Google Auth is only supported (Patches are welcome :) ) ``` $ echo 'GOOGLE_CLIENT_ID=...' >> .env $ echo 'GOOGLE_CLIENT_SECRET=...' >> .env $ echo 'GOOGLE_DOMAIN=...' >> .env # If you restrict to use Google Apps domain ``` ### 2. Start the server ``` $ bundle install $ foreman start ``` It starts an application server and a Sidekiq worker. ### 3. (If you uses auth provider) Log in Visit [http://localhost:3000](http://localhost:3000) and log in with your credentials. A new user account is automatically created and suspended by default. You can activate a user from [http://localhost:3000/users](http://localhost:3000/users) but you have to activate it from `rails console` because you are the first user: ``` $ bundle exec rails c > User.first.update!(active: true) ``` ### 4. Create users Visit [http://localhost:3000/users/new](http://localhost:3000/users/new) and create new users. ### 5. Create a notifier provider Visit [http://localhost:3000/notifier_providers/new](http://localhost:3000/notifier_providers/new) and create a notifier provider. See [Notifier Providers](https://github.com/ryotarai/waker#notifier-providers) section for detailed information. ### 6. Create a notifier Visit [http://localhost:3000/notifiers/new](http://localhost:3000/notifiers/new) and create a notifier. See [Notifier](https://github.com/ryotarai/waker#notifiers) section for detailed information. ### 7. Create an escalation series Visit [http://localhost:3000/escalation_series/new](http://localhost:3000/escalation_series/new) and create a escalation series. Escalation series is a series of escalations. ### 8. Create escalations Visit [http://localhost:3000/escalations/new](http://localhost:3000/escalations/new) and create escalations. - `Escalate to`: Who gets escalated incidents - `Escalate after sec`: Seconds to escalate incidents since the incidents created ### 9. Create a topic Visit [http://localhost:3000/topics/new](http://localhost:3000/topics/new) and create topics. ### 10. Send alerts to the topic Suppoted alerts generaters are below: - Mailgun ( `http://localhost:3000/topics/1/mailgun` ) - Mackerel ( `http://localhost:3000/topics/1/mackerel` ) - Alertmanager ( `http://localhost:3000/topics/1/alertmanager` ) - Slack( `http://localhost:3000/topics/1/slack` ) If you want to use Mailgun, you can configure Mailgun route setting with Mailgun endpoint you can see in [http://localhost:3000/topics/1/mailgun](http://localhost:3000/topics/1/mailgun) ## Configuration ### Notifier Providers #### HipChat - `api_token` - `api_version`: `v1` or `v2` #### Twilio - `account_sid` - `auth_token` - `from`: Phone number #### Mailgun - `api_key` - `from`: Email address ### Notifiers #### Common fields These are supported by all notifier provider ``` or_conditions: - japanese_weekday: true not_between: 9:30+0900-18:30+0900 - not_japanese_weekday: true ``` #### HipChat - `room`: Room name or ID #### Twilio - `to`: Phone number #### Mailgun - `to`: Email address ================================================ FILE: Rakefile ================================================ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. require File.expand_path('../config/application', __FILE__) Rails.application.load_tasks ================================================ FILE: app/assets/images/.keep ================================================ ================================================ FILE: app/assets/javascripts/application.js ================================================ // This is a manifest file that'll be compiled into application.js, which will include all the files // listed below. // // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. // // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details // about supported directives. // //= require jquery //= require jquery_ujs //= require_tree . ================================================ FILE: app/assets/javascripts/comments.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/escalation_series.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/escalations.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/home.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/incident_events.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/incidents.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/maintenances.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/notifier_providers.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/notifiers.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/sessions.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/slack.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/topics.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/users.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/stylesheets/application.css ================================================ /* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. * * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the * compiled file so the styles you add here take precedence over styles defined in any styles * defined in the other CSS/SCSS files in this directory. It is generally better to create a new * file per style scope. * *= require_tree . *= require_self */ ================================================ FILE: app/assets/stylesheets/comments.scss ================================================ // Place all the styles related to the comments controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/escalation_series.scss ================================================ // Place all the styles related to the EscalationSeries controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/escalations.scss ================================================ // Place all the styles related to the escalations controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/home.scss ================================================ // Place all the styles related to the home controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/incident_events.scss ================================================ // Place all the styles related to the incident_events controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/incidents.scss ================================================ // Place all the styles related to the incidents controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/maintenances.scss ================================================ // Place all the styles related to the maintenances controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/notifier_providers.scss ================================================ // Place all the styles related to the NotifierProviders controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/notifiers.scss ================================================ // Place all the styles related to the notifiers controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/scaffolds.scss ================================================ body { background-color: #fff; color: #333; font-family: verdana, arial, helvetica, sans-serif; font-size: 13px; line-height: 18px; } p, ol, ul, td { font-family: verdana, arial, helvetica, sans-serif; font-size: 13px; line-height: 18px; } pre { background-color: #eee; padding: 10px; font-size: 11px; } a { color: #000; &:visited { color: #666; } &:hover { color: #fff; background-color: #000; } } div { &.field, &.actions { margin-bottom: 10px; } } #notice { color: green; } .field_with_errors { padding: 2px; background-color: red; display: table; } #error_explanation { width: 450px; border: 2px solid red; padding: 7px; padding-bottom: 0; margin-bottom: 20px; background-color: #f0f0f0; h2 { text-align: left; font-weight: bold; padding: 5px 5px 5px 15px; font-size: 12px; margin: -7px; margin-bottom: 0px; background-color: #c00; color: #fff; } ul li { font-size: 12px; list-style: square; } } ================================================ FILE: app/assets/stylesheets/sessions.scss ================================================ // Place all the styles related to the sessions controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/slack.scss ================================================ // Place all the styles related to the slack controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/topics.scss ================================================ // Place all the styles related to the topics controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/users.scss ================================================ // Place all the styles related to the users controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/controllers/application_controller.rb ================================================ class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :null_session helper_method :current_user if ENV["GOOGLE_CLIENT_ID"] before_action :login_required end def login_required unless current_user session[:user_id] = nil redirect_to '/auth/google_oauth2_with_calendar' return end unless current_user.active render text: "You are not activated yet. Please ask administrator to activate you" return end end private def current_user=(user) session[:user_id] = user.id end def current_user if user_id = session[:user_id] User.find(user_id) elsif login_token = request.headers['X-Login-Token'] User.find_by(login_token: login_token) end end end ================================================ FILE: app/controllers/comments_controller.rb ================================================ class CommentsController < ApplicationController before_action :set_comment, only: [:show, :edit, :update, :destroy] before_action :set_incident # GET /comments # GET /comments.json def index @comments = Comment.where(incident: @incident) end # GET /comments/1 # GET /comments/1.json def show end # GET /comments/new def new @comment = Comment.new end # GET /comments/1/edit def edit end # POST /comments # POST /comments.json def create p comment_params @comment = Comment.new(comment_params) respond_to do |format| if @comment.save format.html { redirect_to [@incident, @comment], notice: 'Comment was successfully created.' } format.json { render :show, status: :created, location: [@incident, @comment] } else format.html { render :new } format.json { render json: @comment.errors, status: :unprocessable_entity } end end end # PATCH/PUT /comments/1 # PATCH/PUT /comments/1.json def update respond_to do |format| if @comment.update(comment_params) format.html { redirect_to [@incident, @comment], notice: 'Comment was successfully updated.' } format.json { render :show, status: :ok, location: [@incident, @comment] } else format.html { render :edit } format.json { render json: @comment.errors, status: :unprocessable_entity } end end end # DELETE /comments/1 # DELETE /comments/1.json def destroy @comment.destroy respond_to do |format| format.html { redirect_to incident_comments_url, notice: 'Comment was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_comment @comment = Comment.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def comment_params params.require(:comment).permit(:incident_id, :user_id, :comment) end def set_incident @incident = Incident.find(params[:incident_id]) end end ================================================ FILE: app/controllers/concerns/.keep ================================================ ================================================ FILE: app/controllers/escalation_series_controller.rb ================================================ class EscalationSeriesController < ApplicationController before_action :set_escalation_series, only: [:show, :edit, :update, :destroy, :update_escalations] # GET /escalation_series # GET /escalation_series.json def index @escalation_series = EscalationSeries.all end # GET /escalation_series/1 # GET /escalation_series/1.json def show end # GET /escalation_series/new def new @escalation_series = EscalationSeries.new end # GET /escalation_series/1/edit def edit end # POST /escalation_series # POST /escalation_series.json def create @escalation_series = EscalationSeries.new(escalation_series_params) respond_to do |format| if @escalation_series.save format.html { redirect_to @escalation_series, notice: 'Escalation series was successfully created.' } format.json { render :show, status: :created, location: @escalation_series } else format.html { render :new } format.json { render json: @escalation_series.errors, status: :unprocessable_entity } end end end # PATCH/PUT /escalation_series/1 # PATCH/PUT /escalation_series/1.json def update respond_to do |format| if @escalation_series.update(escalation_series_params) format.html { redirect_to @escalation_series, notice: 'Escalation series was successfully updated.' } format.json { render :show, status: :ok, location: @escalation_series } else format.html { render :edit } format.json { render json: @escalation_series.errors, status: :unprocessable_entity } end end end # DELETE /escalation_series/1 # DELETE /escalation_series/1.json def destroy @escalation_series.destroy respond_to do |format| format.html { redirect_to escalation_series_index_url, notice: 'Escalation series was successfully destroyed.' } format.json { head :no_content } end end def update_escalations @escalation_series.update_escalations! end private # Use callbacks to share common setup or constraints between actions. def set_escalation_series @escalation_series = EscalationSeries.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def escalation_series_params params.require(:escalation_series).permit(:name, :settings).tap do |v| v[:settings] = YAML.load(v[:settings]) end end end ================================================ FILE: app/controllers/escalations_controller.rb ================================================ class EscalationsController < ApplicationController before_action :set_escalation, only: [:show, :edit, :update, :destroy] # GET /escalations # GET /escalations.json def index @escalations = Escalation.all.order('escalate_after_sec') end # GET /escalations/1 # GET /escalations/1.json def show end # GET /escalations/new def new @escalation = Escalation.new end # GET /escalations/1/edit def edit end # POST /escalations # POST /escalations.json def create @escalation = Escalation.new(escalation_params) respond_to do |format| if @escalation.save format.html { redirect_to @escalation, notice: 'Escalation was successfully created.' } format.json { render :show, status: :created, location: @escalation } else format.html { render :new } format.json { render json: @escalation.errors, status: :unprocessable_entity } end end end # PATCH/PUT /escalations/1 # PATCH/PUT /escalations/1.json def update respond_to do |format| if @escalation.update(escalation_params) format.html { redirect_to @escalation, notice: 'Escalation was successfully updated.' } format.json { render :show, status: :ok, location: @escalation } else format.html { render :edit } format.json { render json: @escalation.errors, status: :unprocessable_entity } end end end # DELETE /escalations/1 # DELETE /escalations/1.json def destroy @escalation.destroy respond_to do |format| format.html { redirect_to escalations_url, notice: 'Escalation was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_escalation @escalation = Escalation.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def escalation_params params.require(:escalation).permit(:escalate_to_id, :escalate_after_sec, :escalation_series_id) end end ================================================ FILE: app/controllers/home_controller.rb ================================================ class HomeController < ApplicationController def index redirect_to incidents_path end end ================================================ FILE: app/controllers/incident_events_controller.rb ================================================ require 'securerandom' class IncidentEventsController < ApplicationController skip_before_action :login_required, only: [:twilio], raise: false def twilio @event = IncidentEvent.find(params[:id]) language = ENV['TWILIO_LANGUAGE'] || 'en-US' resp = nil if params[:Digits] case params[:Digits] when '1' @event.incident.acknowledge! rescue nil when '2' @event.incident.resolve! rescue nil end resp = Twilio::TwiML::VoiceResponse.new do |r| r.say message: @event.incident.status, voice: 'alice', language: language r.hangup end else resp = Twilio::TwiML::VoiceResponse.new do |r| r.gather timeout: 10, numDigits: 1 do |g| g.say message: "This is Waker alert.", voice: 'alice', language: language g.say message: @event.incident.subject, voice: 'alice', language: language g.say message: "To acknowledge, press 1.", voice: 'alice', language: language g.say message: "To resolve, press 2.", voice: 'alice', language: language end end end render xml: resp.to_s end end ================================================ FILE: app/controllers/incidents_controller.rb ================================================ class IncidentsController < ApplicationController before_action :set_incidents, only: [:index, :bulk_acknowledge, :bulk_resolve] before_action :set_incident, only: [:show, :edit, :update, :destroy, :acknowledge, :resolve] before_action :ensure_hash, only: [:acknowledge, :resolve] skip_before_action :login_required, only: [:acknowledge, :resolve], raise: false # GET /incidents # GET /incidents.json def index @page = (params[:page] || 1).to_i @incidents = @incidents.order('id DESC').page(@page).per(25) end # GET /incidents/1 # GET /incidents/1.json def show end # GET /incidents/new def new @incident = Incident.new end # GET /incidents/1/edit def edit end # POST /incidents # POST /incidents.json def create @incident = Incident.new(incident_params) respond_to do |format| if @incident.save format.html { redirect_to @incident, notice: 'Incident was successfully created.' } format.json { render :show, status: :created, location: @incident } else format.html { render :new } format.json { render json: @incident.errors, status: :unprocessable_entity } end end end # PATCH/PUT /incidents/1 # PATCH/PUT /incidents/1.json def update respond_to do |format| if @incident.update(incident_params) format.html { redirect_to @incident, notice: 'Incident was successfully updated.' } format.json { render :show, status: :ok, location: @incident } else format.html { render :edit } format.json { render json: @incident.errors, status: :unprocessable_entity } end end end def bulk_acknowledge @incidents.opened.update_all(status: Incident.statuses[:acknowledged]) respond_to do |format| format.html { redirect_to incidents_url, notice: 'Incidents were successfully acknowledged.' } format.json { head :no_content } end end def bulk_resolve @incidents.update_all(status: Incident.statuses[:resolved]) respond_to do |format| format.html { redirect_to incidents_url, notice: 'Incidents were successfully resolved.' } format.json { head :no_content } end end # DELETE /incidents/1 # DELETE /incidents/1.json def destroy @incident.destroy respond_to do |format| format.html { redirect_to incidents_url, notice: 'Incident was successfully destroyed.' } format.json { head :no_content } end end def acknowledge @incident.acknowledge! respond_to do |format| if current_user format.html { redirect_to incidents_url, notice: 'Incident was successfully acknowledged.' } else format.html { render text: "Acknowledged" } end format.json { render json: {status: 'ok'} } end end def resolve @incident.resolve! respond_to do |format| if current_user format.html { redirect_to incidents_url, notice: 'Incident was successfully resolved.' } else format.html { render text: "Resolved" } end format.json { render json: {status: 'ok'} } end end private # Use callbacks to share common setup or constraints between actions. def set_incident @incident = Incident.find(params[:id]) end def set_incidents set_visible_statuses set_visible_topic @incidents = Incident.all if @visible_statuses @incidents = @incidents.where(status: @visible_statuses) end if @visible_topic @incidents = @incidents.where(topic: @visible_topic) end end def set_visible_statuses @visible_statuses = session[:incidents_statuses] # for transition from rev 0a2dd42 or earlier @visible_statuses = nil if @visible_statuses.try(:empty?) if params[:statuses] if params[:statuses] == '' @visible_statuses = nil # all else @visible_statuses = params[:statuses].split(',').map(&:to_i) end session[:incidents_statuses] = @visible_statuses end end def set_visible_topic @visible_topic = session[:incidents_topic] # for transition from rev 0a2dd42 or earlier @visible_topic = nil if @visible_topic == 'all' if params[:topic] if params[:topic] == 'all' @visible_topic = nil # all else @visible_topic = params[:topic].to_i end session[:incidents_topic] = @visible_topic end end # Never trust parameters from the scary internet, only allow the white list through. def incident_params params.require(:incident).permit(:subject, :description, :topic_id, :occured_at) end def ensure_hash unless params[:hash] == @incident.confirmation_hash render text: "Wrong hash", status: 403 end end end ================================================ FILE: app/controllers/maintenances_controller.rb ================================================ class MaintenancesController < ApplicationController before_action :set_maintenance, only: [:show, :edit, :update, :destroy] # GET /maintenances # GET /maintenances.json def index @maintenances = Maintenance.not_expired end # GET /maintenances/1 # GET /maintenances/1.json def show end # GET /maintenances/new def new now = Time.now @maintenance = Maintenance.new( start_time: now, end_time: now + 60*60 ) end # GET /maintenances/1/edit def edit end # POST /maintenances # POST /maintenances.json def create @maintenance = Maintenance.new(maintenance_params) respond_to do |format| if @maintenance.save format.html { redirect_to @maintenance, notice: 'Maintenance was successfully created.' } format.json { render :show, status: :created, location: @maintenance } else format.html { render :new } format.json { render json: @maintenance.errors, status: :unprocessable_entity } end end end # PATCH/PUT /maintenances/1 # PATCH/PUT /maintenances/1.json def update respond_to do |format| if @maintenance.update(maintenance_params) format.html { redirect_to @maintenance, notice: 'Maintenance was successfully updated.' } format.json { render :show, status: :ok, location: @maintenance } else format.html { render :edit } format.json { render json: @maintenance.errors, status: :unprocessable_entity } end end end # DELETE /maintenances/1 # DELETE /maintenances/1.json def destroy @maintenance.destroy respond_to do |format| format.html { redirect_to maintenances_url, notice: 'Maintenance was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_maintenance @maintenance = Maintenance.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def maintenance_params params.require(:maintenance).permit(:topic_id, :start_time, :end_time, :filter) end end ================================================ FILE: app/controllers/notifier_providers_controller.rb ================================================ class NotifierProvidersController < ApplicationController before_action :set_notifier_provider, only: [:show, :edit, :update, :destroy] # GET /notifier_providers # GET /notifier_providers.json def index @notifier_providers = NotifierProvider.all end # GET /notifier_providers/1 # GET /notifier_providers/1.json def show end # GET /notifier_providers/new def new @notifier_provider = NotifierProvider.new end # GET /notifier_providers/1/edit def edit end # POST /notifier_providers # POST /notifier_providers.json def create @notifier_provider = NotifierProvider.new(notifier_provider_params) respond_to do |format| if @notifier_provider.save format.html { redirect_to @notifier_provider, notice: 'Notifier provider was successfully created.' } format.json { render :show, status: :created, location: @notifier_provider } else format.html { render :new } format.json { render json: @notifier_provider.errors, status: :unprocessable_entity } end end end # PATCH/PUT /notifier_providers/1 # PATCH/PUT /notifier_providers/1.json def update respond_to do |format| if @notifier_provider.update(notifier_provider_params) format.html { redirect_to @notifier_provider, notice: 'Notifier provider was successfully updated.' } format.json { render :show, status: :ok, location: @notifier_provider } else format.html { render :edit } format.json { render json: @notifier_provider.errors, status: :unprocessable_entity } end end end # DELETE /notifier_providers/1 # DELETE /notifier_providers/1.json def destroy @notifier_provider.destroy respond_to do |format| format.html { redirect_to notifier_providers_url, notice: 'Notifier provider was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_notifier_provider @notifier_provider = NotifierProvider.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def notifier_provider_params params.require(:notifier_provider).permit(:name, :kind, :settings).tap do |v| v[:settings] = YAML.load(v[:settings]) end end end ================================================ FILE: app/controllers/notifiers_controller.rb ================================================ class NotifiersController < ApplicationController before_action :set_notifier, only: [:show, :edit, :update, :destroy] # GET /notifiers # GET /notifiers.json def index @notifiers = Notifier.all end # GET /notifiers/1 # GET /notifiers/1.json def show end # GET /notifiers/new def new @notifier = Notifier.new end # GET /notifiers/1/edit def edit end # POST /notifiers # POST /notifiers.json def create @notifier = Notifier.new(notifier_params) respond_to do |format| if @notifier.save format.html { redirect_to @notifier, notice: 'Notifier was successfully created.' } format.json { render :show, status: :created, location: @notifier } else format.html { render :new } format.json { render json: @notifier.errors, status: :unprocessable_entity } end end end # PATCH/PUT /notifiers/1 # PATCH/PUT /notifiers/1.json def update respond_to do |format| if @notifier.update(notifier_params) format.html { redirect_to @notifier, notice: 'Notifier was successfully updated.' } format.json { render :show, status: :ok, location: @notifier } else format.html { render :edit } format.json { render json: @notifier.errors, status: :unprocessable_entity } end end end # DELETE /notifiers/1 # DELETE /notifiers/1.json def destroy @notifier.destroy respond_to do |format| format.html { redirect_to notifiers_url, notice: 'Notifier was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_notifier @notifier = Notifier.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def notifier_params params.require(:notifier).permit(:user_id, :kind, :settings, :notify_after_sec, :provider_id, :topic_id, :enabled).tap do |v| v[:settings] = YAML.load(v[:settings]) end end end ================================================ FILE: app/controllers/sessions_controller.rb ================================================ class SessionsController < ApplicationController skip_before_action :login_required, only: [:create] def create @user = User.find_or_create_from_auth_hash(auth_hash) @user.update_credentials_from_auth_hash(auth_hash) self.current_user = @user redirect_to '/' end private def auth_hash request.env['omniauth.auth'] end end ================================================ FILE: app/controllers/slack_controller.rb ================================================ class SlackController < ApplicationController skip_before_action :login_required, only: [:interactive], raise: false def interactive verify! message = payload['original_message'] message['attachments'][0].delete('actions') user = payload['user']['name'] text = '' payload['actions'].each do |a| case a['value'] when 'acknowledge' incident.acknowledge! text = ":white_check_mark: @#{user} acknowledged" when 'resolve' incident.resolve! text = ":white_check_mark: @#{user} resolved" end end message['attachments'][0]['fields'] = [{ 'title' => text, 'value' => '', 'short' => false, }] render json: message end private def payload JSON.parse(params[:payload]) end private def verify! verified = false Notifier.preload(:provider).find_each do |n| settings = n.provider.settings.merge(n.settings) token = settings['verification_token'] if n.provider.slack? && settings['enable_buttons'] && payload['token'] == token verified = true break end end unless verified raise 'token verification failed' end end private def incident_id payload['callback_id'].match(/\Aincident\.(\d+)\z/)[1].to_i end private def incident Incident.find(incident_id) end end ================================================ FILE: app/controllers/topics_controller.rb ================================================ class TopicsController < ApplicationController before_action :set_topic, only: [:show, :edit, :update, :destroy, :mailgun, :mackerel, :alertmanager, :slack] skip_before_action :login_required, only: [:mailgun, :mackerel, :alertmanager, :slack], raise: false # GET /topics # GET /topics.json def index @topics = Topic.all end # GET /topics/1 # GET /topics/1.json def show end # GET /topics/new def new @topic = Topic.new end # GET /topics/1/edit def edit end # POST /topics # POST /topics.json def create @topic = Topic.new(topic_params) respond_to do |format| if @topic.save format.html { redirect_to @topic, notice: 'Topic was successfully created.' } format.json { render :show, status: :created, location: @topic } else format.html { render :new } format.json { render json: @topic.errors, status: :unprocessable_entity } end end end # PATCH/PUT /topics/1 # PATCH/PUT /topics/1.json def update respond_to do |format| if @topic.update(topic_params) format.html { redirect_to @topic, notice: 'Topic was successfully updated.' } format.json { render :show, status: :ok, location: @topic } else format.html { render :edit } format.json { render json: @topic.errors, status: :unprocessable_entity } end end end # DELETE /topics/1 # DELETE /topics/1.json def destroy @topic.destroy respond_to do |format| format.html { redirect_to topics_url, notice: 'Topic was successfully destroyed.' } format.json { head :no_content } end end # POST /topics/1/mailgun def mailgun unless @topic.enabled Rails.logger.info "Incident creation is skipped because the topic is disabled." render json: {}, status: 200 return end # http://documentation.mailgun.com/user_manual.html#routes subject = params[:subject] description = params['body-plain'] if @topic.in_maintenance?(subject, description) Rails.logger.info "Incident creation is skipped because the topic is in maintenance." render json: {}, status: 200 return end @topic.incidents.create!( subject: subject, description: description, ) render json: {}, status: 200 end # POST /topics/1/mackerel def mackerel data = JSON.parse(request.body.read) return render json: {}, status: 200 if data.dig('alert', 'status') == 'ok' || data.dig('alertGroup', 'status') == 'OK' unless @topic.enabled Rails.logger.info "Incident creation is skipped because the topic is disabled." render json: {}, status: 200 return end if data['event'] == 'alertGroup' then subject = "[#{data['alertGroup']['status']}] #{data['alertGroupSetting']['name']}" else name = data.key?('host') ? data['host']['name'] : data['alert']['monitorName'] subject = "[#{data['alert']['status']}] #{name}" end description = JSON.pretty_generate(data) if @topic.in_maintenance?(subject, description) Rails.logger.info "Incident creation is skipped because the topic is in maintenance" render json: {}, status: 200 return end @topic.incidents.create!( subject: subject, description: description, ) render json: {}, status: 200 end # POST /topics/1/alertmanager def alertmanager data = JSON.parse(request.body.read) unless @topic.enabled Rails.logger.info "Incident creation is skipped because the topic is disabled." render json: {}, status: 200 return end subject = "[#{data['commonLabels']['severity']}] #{data['commonLabels']['alertname']}: #{data['commonAnnotations']['summary']}" description = JSON.pretty_generate(data) if @topic.in_maintenance?(subject, description) Rails.logger.info "Incident creation is skipped because the topic is in maintenance" render json: {}, status: 200 return end @topic.incidents.create!( subject: subject, description: description, ) render json: {}, status: 200 end def slack unless @topic.enabled Rails.logger.info "Incident creation is skipped because the topic is disabled." render json: {}, status: 200 return end subject = "escalation from slack,channel name:#{params['channel_name']} #{params['text']}" description = "channel:#{params['channel_name']} user:#{params['user_name']} #{params['text']}" if @topic.in_maintenance?(subject, description) Rails.logger.info "Incident creation is skipped because the topic is in maintenance." render json: {}, status: 200 return end @topic.incidents.create!( subject: subject, description: description, ) render json: {text: 'accept your page'}, status: 200 end private # Use callbacks to share common setup or constraints between actions. def set_topic @topic = Topic.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def topic_params params.require(:topic).permit(:name, :kind, :escalation_series_id, :enabled) end end ================================================ FILE: app/controllers/users_controller.rb ================================================ class UsersController < ApplicationController before_action :set_user, only: [:show, :edit, :update, :destroy, :activation, :deactivation] # GET /users # GET /users.json def index @users = User.all end # GET /users/1 # GET /users/1.json def show end # GET /users/new def new @user = User.new end # GET /users/1/edit def edit end # POST /users # POST /users.json def create @user = User.new(user_params) respond_to do |format| if @user.save format.html { redirect_to @user, notice: 'User was successfully created.' } format.json { render :show, status: :created, location: @user } else format.html { render :new } format.json { render json: @user.errors, status: :unprocessable_entity } end end end # PATCH/PUT /users/1 # PATCH/PUT /users/1.json def update respond_to do |format| if @user.update(user_params) format.html { redirect_to @user, notice: 'User was successfully updated.' } format.json { render :show, status: :ok, location: @user } else format.html { render :edit } format.json { render json: @user.errors, status: :unprocessable_entity } end end end # DELETE /users/1 # DELETE /users/1.json def destroy @user.destroy respond_to do |format| format.html { redirect_to users_url, notice: 'User was successfully destroyed.' } format.json { head :no_content } end end # PATCH/PUT /users/1/activation def activation @user.update!(active: true) respond_to do |format| format.html { redirect_to users_url, notice: 'User was successfully activated.' } end end # PATCH/PUT /users/1/deactivation def deactivation @user.update!(active: false) respond_to do |format| format.html { redirect_to users_url, notice: 'User was successfully deactivated.' } end end private # Use callbacks to share common setup or constraints between actions. def set_user @user = User.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def user_params params.require(:user).permit(:name) end end ================================================ FILE: app/helpers/application_helper.rb ================================================ module ApplicationHelper end ================================================ FILE: app/helpers/comments_helper.rb ================================================ module CommentsHelper end ================================================ FILE: app/helpers/escalation_series_helper.rb ================================================ module EscalationSeriesHelper end ================================================ FILE: app/helpers/escalations_helper.rb ================================================ module EscalationsHelper end ================================================ FILE: app/helpers/home_helper.rb ================================================ module HomeHelper end ================================================ FILE: app/helpers/incident_events_helper.rb ================================================ module IncidentEventsHelper end ================================================ FILE: app/helpers/incidents_helper.rb ================================================ module IncidentsHelper end ================================================ FILE: app/helpers/maintenances_helper.rb ================================================ module MaintenancesHelper end ================================================ FILE: app/helpers/notifier_providers_helper.rb ================================================ module NotifierProvidersHelper end ================================================ FILE: app/helpers/notifiers_helper.rb ================================================ module NotifiersHelper end ================================================ FILE: app/helpers/sessions_helper.rb ================================================ module SessionsHelper end ================================================ FILE: app/helpers/slack_helper.rb ================================================ module SlackHelper end ================================================ FILE: app/helpers/topics_helper.rb ================================================ module TopicsHelper end ================================================ FILE: app/helpers/users_helper.rb ================================================ module UsersHelper end ================================================ FILE: app/mailers/.keep ================================================ ================================================ FILE: app/models/.keep ================================================ ================================================ FILE: app/models/application_record.rb ================================================ # Base ApplicationRecord Class class ApplicationRecord < ActiveRecord::Base self.abstract_class = true end ================================================ FILE: app/models/comment.rb ================================================ class Comment < ApplicationRecord belongs_to :incident belongs_to :user validates :incident, presence: true validates :user, presence: true validates :comment, presence: true end ================================================ FILE: app/models/concerns/.keep ================================================ ================================================ FILE: app/models/escalation.rb ================================================ class Escalation < ApplicationRecord belongs_to :escalation_series belongs_to :escalate_to, class_name: 'User' validates :escalation_series, presence: true validates :escalate_to, presence: true validates :escalate_after_sec, numericality: {greater_than_or_equal_to: 5} end ================================================ FILE: app/models/escalation_series.rb ================================================ class EscalationSeries < ApplicationRecord has_many :escalations, dependent: :destroy has_many :topics, dependent: :destroy validates :name, presence: true serialize :settings, JSON after_initialize :set_defaults def set_defaults self.settings ||= {} end def update_escalations! updater_class = case self.settings['update_by'] when 'google_calendar' GoogleCalendarEscalationUpdater else nil end if updater_class updater = updater_class.new(self) updater.update! end end class EscalationUpdater def initialize(series) @series = series end def update! raise NotImplementedError end private def settings @series.settings end end class GoogleCalendarEscalationUpdater < EscalationUpdater def initialize(*) super require 'google/api_client' end def update! Rails.logger.info "Update #{@series.inspect} by Google Calendar" if user_as.provider && user_as.provider != 'google_oauth2_with_calendar' raise "User ##{user_as.id} is not authenticated by 'google_oauth2_with_calendar' provider" end client = Google::APIClient.new( application_name: "Waker", application_version: "2.0.0", user_agent: "Waker/2.0.0 google-api-client" ) auth = client.authorization expired = Time.at(user_as.credentials.fetch('expires_at')) < Time.now if user_as.credentials.fetch('expires') && expired Rails.logger.info "Refreshing access token..." auth.client_id = ENV["GOOGLE_CLIENT_ID"] auth.client_secret = ENV["GOOGLE_CLIENT_SECRET"] auth.refresh_token = user_as.credentials.fetch('refresh_token') auth.grant_type = "refresh_token" auth.refresh! user_as.update!( credentials: user_as.credentials.merge( 'token' => auth.access_token, 'expires_at' => auth.expires_at.to_i, ) ) else auth.access_token = user_as.credentials.fetch('token') end calendar_api = client.discovered_api('calendar', 'v3') calendar = client.execute( api_method: calendar_api.calendar_list.list, parameters: {}, ).data.items.find do |cal| cal['summary'] == calendar_name end events = client.execute( api_method: calendar_api.events.list, parameters: { 'calendarId' => calendar['id'], 'timeMax' => (Time.now + 1).iso8601, 'timeMin' => (Time.now).iso8601, 'singleEvents' => true, }, ).data.items events.each do |event| unless event['end']['dateTime'] && event['start']['dateTime'] raise "dateTime field is not found (The event may be all-day event)\n#{event}" end end events.sort! do |a, b| a['end']['dateTime'] - a['start']['dateTime'] <=> b['end']['dateTime'] - b['start']['dateTime'] end # shortest event event = events.first persons = event['summary'].split(event_delimiter).map(&:strip) escalations = @series.escalations.order('escalate_after_sec') persons.each_with_index do |name, i| user = User.find_by(name: name) raise "User '#{name}' is not found." unless user escalation = escalations[i] escalation.update!(escalate_to: user) end end private def user_as User.find(settings.fetch('user_as_id')) end def calendar_name settings.fetch('calendar') end def event_delimiter settings.fetch('event_delimiter') end end end ================================================ FILE: app/models/escalation_update_worker.rb ================================================ class EscalationUpdateWorker include Sidekiq::Worker def perform EscalationSeries.all.each do |series| handle_series(series) end rescue => err Rails.logger.error "#{err.class}: #{err}\n#{err.backtrace.join("\n")}" end private def handle_series(series) series.update_escalations! end end ================================================ FILE: app/models/escalation_worker.rb ================================================ class EscalationWorker include Sidekiq::Worker def self.enqueue(incident, escalation) Rails.logger.info "Enqueue EscalaionWorker job" self.perform_in(escalation.escalate_after_sec, incident.id, escalation.id) end def perform(incident_id, escalation_id) incident = Incident.find(incident_id) escalation = Escalation.find(escalation_id) if incident.opened? incident.events.create( kind: :escalated, info: { escalation: escalation, escalated_to: escalation.escalate_to, }, ) end end end ================================================ FILE: app/models/incident.rb ================================================ require 'digest/sha1' class Incident < ApplicationRecord STATUSES = [:opened, :acknowledged, :resolved] belongs_to :topic has_many :events, class_name: 'IncidentEvent', dependent: :destroy enum status: STATUSES has_many :comments validates :topic, presence: true validates :subject, presence: true validates :description, presence: true validates :occured_at, presence: true after_initialize :set_defaults after_create :enqueue def acknowledge! return if self.acknowledged? || self.resolved? self.acknowledged! events.create(kind: :acknowledged) end def resolve! return if self.resolved? self.resolved! events.create(kind: :resolved) end def confirmation_hash Digest::SHA1.hexdigest("#{Rails.application.secrets.secret_key_base}#{self.id}") end private def set_defaults self.status ||= :opened self.occured_at ||= Time.now end def enqueue topic.escalation_series.escalations.each do |escalation| EscalationWorker.enqueue(self, escalation) end events.create(kind: :opened) end end ================================================ FILE: app/models/incident_event.rb ================================================ class IncidentEvent < ApplicationRecord belongs_to :incident enum kind: [:opened, :acknowledged, :resolved, :escalated, :commented, :notified] validates :incident, presence: true validates :kind, presence: true serialize :info, JSON after_create :notify after_initialize :set_defaults def set_defaults self.info ||= {} end def notify return if self.notified? Notifier.all.each do |notifier| next if notifier.topic && self.incident.topic != notifier.topic notifier.notify(self) end end def escalated_to self.info['escalated_to'] && User.find(self.info['escalated_to']['id']) end def escalation self.info['escalation'] && Escalation.find(self.info['escalation']['id']) end def notifier self.info['notifier'] && Notifier.find(self.info['notifier']['id']) end def event self.info['event'] && IncidentEvent.find(self.info['event']['id']) end end ================================================ FILE: app/models/maintenance.rb ================================================ class Maintenance < ApplicationRecord scope :active, -> { t = Time.now; where('start_time <= ? AND ? <= end_time', t, t) } scope :not_expired, -> { where('? <= end_time', Time.now) } scope :expired, -> { where('end_time < ?', Time.now) } belongs_to :topic def filter_regexp Regexp.new(filter) end end ================================================ FILE: app/models/notification_worker.rb ================================================ class NotificationWorker include Sidekiq::Worker def self.enqueue(event:, notifier:) Rails.logger.info "Enqueue NotificationWorker job" self.perform_in(notifier.notify_after_sec, event.id, notifier.id) end def perform(event_id, notifier_id) event = IncidentEvent.find(event_id) notifier = Notifier.find(notifier_id) if notifier.enabled notifier.notify_immediately(event) end end end ================================================ FILE: app/models/notifier.rb ================================================ class Notifier < ApplicationRecord belongs_to :provider, class_name: 'NotifierProvider' belongs_to :user belongs_to :topic validates :provider, presence: true validates :notify_after_sec, numericality: {greater_than_or_equal_to: 5} serialize :settings, JSON after_initialize :set_defaults def set_defaults self.settings ||= {} end def notify(event) NotificationWorker.enqueue(event: event, notifier: self) end def notify_immediately(event) provider.notify(event: event, notifier: self) end end ================================================ FILE: app/models/notifier_provider.rb ================================================ class NotifierProvider < ApplicationRecord serialize :settings, JSON enum kind: [:mailgun, :file, :rails_logger, :hipchat, :twilio, :slack, :datadog, :sns] validates :name, presence: true after_initialize :set_defaults def set_defaults self.settings ||= {} end def concrete_class self.class.const_get("#{kind.to_s.camelize}ConcreteProvider") end def notify(event:, notifier:) concrete_class.new(provider: self, notifier: notifier, event: event).notify end class ConcreteProvider def initialize(provider:, notifier:, event:) @provider = provider @notifier = notifier @event = event end def notify if skip? Rails.logger.info "Notification skipped." else if target_events.include?(kind_of_event) _notify @event.incident.events.create( kind: :notified, info: {notifier: @notifier, event: @event} ) else Rails.logger.info "Notification skipped due to target events (#{target_events} doesn't include #{kind_of_event})" end end end def _notify raise NotImplementedError end def settings @provider.settings.merge(@notifier.settings) end def skip? # or_conditions = [ # { # 'only_japanese_weekday' => true, # 'not_between' => '9:30-18:30', # }, # { # 'not_japanese_weekday' => true, # } # ] return skip_due_to_or_conditions? || skip_due_to_status_of_incident? end def skip_due_to_status_of_incident? if !@event.incident.opened? && !([:acknowledged, :resolved].include?(kind_of_event)) return true end false end def skip_due_to_or_conditions? or_conditions = settings['or_conditions'] return false unless or_conditions matched = or_conditions.any? do |condition| # japanese_weekday holiday = HolidayJp.holiday?(Date.today) || Time.now.saturday? || Time.now.sunday? if holiday && condition['japanese_weekday'] next false end if !holiday && condition['not_japanese_weekday'] next false end # between skip_due_to_between_condition = %w!between not_between!.any? do |k| if s = condition[k] start_time, end_time = s.split('-') start_time = Time.parse(start_time) end_time = Time.parse(end_time) between = start_time < Time.now && Time.now < end_time if (between && k == 'not_between') || (!between && k == 'between') next true end end false end next false if skip_due_to_between_condition true end return !matched end def all_events [:escalated, :escalated_to_me, :opened, :acknowledged, :resolved, :commented] end def target_events settings['events'] && settings['events'].map {|v| v.to_sym } end def body(formats: [:text]) template_names = [kind_of_event, 'default'] template_names.each_with_index do |template_name, i| begin rendered = ApplicationController.new.render_to_string( template: "notifier_providers/#{@provider.kind}/#{template_name}", formats: formats, layout: nil, locals: {event: @event}, ) return rendered.strip rescue ActionView::MissingTemplate raise if template_names.size - 1 == i end end end def kind_of_event if @event.escalated? && @event.escalated_to == @notifier.user :escalated_to_me else @event.kind.to_sym end end end class FileConcreteProvider < ConcreteProvider def _notify end end class RailsLoggerConcreteProvider < ConcreteProvider def _notify Rails.logger.info(body) end def target_events all_events end end class HipchatConcreteProvider < ConcreteProvider def _notify case kind_of_event when :opened color = 'red' when :acknowledged, :escalated color = 'yellow' when :resolved color = 'green' else return end client = HipChat::Client.new(api_token, api_version: api_version) client[room].send('Waker', body, color: color, notify: notify?) end private def api_token settings.fetch('api_token') end def room settings.fetch('room') end def api_version case settings.fetch('api_version') when '2', 'v2' 'v2' when '1', 'v1' 'v1' else 'v2' end end def notify? !!settings['notify'] end def target_events super || [:escalated, :opened, :acknowledged, :resolved] end end class MailgunConcreteProvider < ConcreteProvider def _notify conn = Faraday.new(url: 'https://api.mailgun.net') do |faraday| faraday.request :url_encoded faraday.response :logger faraday.adapter Faraday.default_adapter end conn.basic_auth('api', api_key) response = conn.post "/v2/#{domain}/messages", { from: from, to: to, subject: "[Waker] #{@event.incident.subject}", text: body, html: body(formats: [:html]), } Rails.logger.info "response status: #{response.status}" Rails.logger.info JSON.parse(response.body) end private def api_key settings.fetch('api_key') end def domain from.split('@').last end def from settings.fetch('from') end def to settings.fetch('to') end def target_events super || [:escalated_to_me] end end class TwilioConcreteProvider < ConcreteProvider def _notify options = {} options[:user] = basic_auth_user if basic_auth_user options[:password] = basic_auth_password if basic_auth_password url = Rails.application.routes.url_helpers.twilio_incident_event_url( @event, options ) Twilio::REST::Client.new(account_sid, auth_token).calls.create( from: from, to: to, url: url, ) end def account_sid settings.fetch('account_sid') end def auth_token settings.fetch('auth_token') end def from settings.fetch('from') end def to settings.fetch('to') end def target_events super || [:escalated_to_me] end def basic_auth_user ENV['BASIC_AUTH_USER'] end def basic_auth_password ENV['BASIC_AUTH_PASSWORD'] end end class SlackConcreteProvider < ConcreteProvider def _notify fields = [] acknowledge_url = Rails.application.routes.url_helpers.acknowledge_incident_url(@event.incident, hash: @event.incident.confirmation_hash) resolve_url = Rails.application.routes.url_helpers.resolve_incident_url(@event.incident, hash: @event.incident.confirmation_hash) comment_url = Rails.application.routes.url_helpers.new_incident_comment_url(@event.incident) actions = [] case kind_of_event when :opened color = 'danger' title = 'New incident opened' if buttons_enabled? action_links = "<#{comment_url}|Comment>" actions = [:acknowledge, :resolve] else action_links = "<#{acknowledge_url}|Acknowledge> or <#{resolve_url}|Resolve> | <#{comment_url}|Comment>" end when :acknowledged color = 'warning' title = 'Incident acknowledged' if buttons_enabled? action_links = "<#{comment_url}|Comment>" actions = [:resolve] else action_links = "<#{resolve_url}|Resolve> | <#{comment_url}|Comment>" end when :escalated color = 'warning' title = "Incident escalated to #{@event.escalated_to.name}" if buttons_enabled? action_links = "<#{comment_url}|Comment>" actions = [:acknowledge, :resolve] else action_links = "<#{acknowledge_url}|Acknowledge> or <#{resolve_url}|Resolve> | <#{comment_url}|Comment>" end when :resolved color = 'good' title = 'Incident resolved' action_links = "<#{comment_url}|Comment>" end text = @event.incident.subject if action_links text += " (#{action_links})" end action_types = { acknowledge: { "name" => "response", "text" => "Acknowledge", "type" => "button", "value" => "acknowledge", }, resolve: { "name" => "response", "text" => "Resolve", "type" => "button", "value" => "resolve", "style" => "primary", }, } attachments = [{ "fallback" => "[#{kind_of_event.to_s.capitalize}] #{@event.incident.subject}", "color" => color, "title" => title, "text" => text, "fields" => fields, "callback_id" => "incident.#{@event.incident.id}", "actions" => actions.map {|t| action_types[t] }, }] payload = {'attachments' => attachments} if channel payload['channel'] = channel end url = URI.parse(webhook_url) conn = Faraday.new(url: "#{url.scheme}://#{url.host}") do |faraday| faraday.adapter Faraday.default_adapter end conn.post do |req| req.url url.path req.headers['Content-Type'] = 'application/json' req.body = payload.to_json end end private def webhook_url settings.fetch('webhook_url') end def channel settings['channel'] end def buttons_enabled? settings['enable_buttons'] end def target_events [:escalated, :opened, :acknowledged, :resolved] end end class DatadogConcreteProvider < ConcreteProvider def _notify dog = Dogapi::Client.new(api_key, app_key) res = dog.emit_event(Dogapi::Event.new( @event.incident.description, { msg_title: @event.incident.subject, alert_type: alert_type, tags: tags, source_type_name: source_type_name, } )) Rails.logger.info res end private def api_key settings.fetch('api_key') end def app_key settings.fetch('app_key') end def alert_type settings['alert_type'] || 'error' end def tags settings['tags'] || 'waker' end def source_type_name settings['source_type_name'] || 'waker' end def target_events [:opened] end end class SnsConcreteProvider < ConcreteProvider def _notify aws_config = {} aws_config[:region] = region if region aws_config[:access_key_id] = access_key_id if access_key_id aws_config[:secret_access_key] = secret_access_key if secret_access_key sns = Aws::SNS::Client.new(aws_config) res = sns.publish( topic_arn: topic_arn, message: @event.incident.description, subject: @event.incident.subject, message_attributes: { 'kind_of_event' => { data_type: 'String', string_value: kind_of_event.to_s, } } ) Rails.logger.info res end private def topic_arn settings.fetch('topic_arn') end def region settings['region'] end def access_key_id settings['access_key_id'] end def secret_access_key settings['secret_access_key'] end def target_events all_events end end end ================================================ FILE: app/models/topic.rb ================================================ class Topic < ApplicationRecord enum kind: [:api] belongs_to :escalation_series has_many :incidents validates :name, presence: true validates :kind, presence: true validates :escalation_series, presence: true def in_maintenance?(*bodies) maints = Maintenance.active.where(topic: self) maints.any? do |m| if m.filter.blank? true else r = m.filter_regexp bodies.any? do |body| !!r.match(body) end end end end end ================================================ FILE: app/models/user.rb ================================================ require 'securerandom' class User < ApplicationRecord scope :active, -> { where(active: true) } has_many :notifiers serialize :credentials, JSON validates :name, presence: true before_save :set_defaults def self.find_or_create_from_auth_hash(auth_hash) user = self.find_by(provider: auth_hash[:provider], uid: auth_hash[:uid]) return user if user user = self.find_by(email: auth_hash[:info][:email]) if user # email is deprecated user.update!(provider: auth_hash[:provider], uid: auth_hash[:uid], email: nil) return user end self.create!(provider: auth_hash[:provider], uid: auth_hash[:uid], name: auth_hash[:info][:name]) end def update_credentials_from_auth_hash(auth_hash) self.update!(credentials: auth_hash.fetch(:credentials)) end private def set_defaults self.login_token ||= SecureRandom.hex end end ================================================ FILE: app/views/comments/_form.html.erb ================================================ <%= form_for([@incident, @comment]) do |f| %> <% if @comment.errors.any? %>

<%= pluralize(@comment.errors.count, "error") %> prohibited this comment from being saved:

<% end %> <%= f.hidden_field :incident_id, value: @incident.id %> <%= f.hidden_field :user_id, value: current_user.id %>

Incident:

<%= @incident.subject %>

<%= f.label :comment %> <%= f.text_area :comment, class: 'form-control', rows: 8 %>
<%= f.submit(class: 'btn btn-default') %>
<% end %> ================================================ FILE: app/views/comments/edit.html.erb ================================================

Editing Comment

<%= render 'form' %> <%= link_to 'Show', [@incident, @comment] %> | <%= link_to 'Back', incident_comments_path %> ================================================ FILE: app/views/comments/index.html.erb ================================================

Listing Comments

Incident:

<%= @incident.subject %>

<% @comments.each do |comment| %> <% end %>
Comment
<%= comment.comment %> (<%= comment.user.name %>) <%= link_to 'Show', [@incident, comment] %> <%= link_to 'Edit', edit_incident_comment_path(@incident, comment) %> <%= link_to 'Destroy', [@incident, comment], method: :delete, data: { confirm: 'Are you sure?' } %>

<%= link_to 'New Comment', new_incident_comment_path %> | <%= link_to 'Incident', @incident %> ================================================ FILE: app/views/comments/index.json.jbuilder ================================================ json.array!(@comments) do |comment| json.extract! comment, :id json.url comment_url(comment, format: :json) end ================================================ FILE: app/views/comments/new.html.erb ================================================

New Comment

<%= render 'form' %> <%= link_to 'Back', incident_comments_path %> ================================================ FILE: app/views/comments/show.html.erb ================================================

Incident:

<%= @incident.subject %>

Comment:

<%= auto_link(@comment.comment, :html => { :target => '_blank' }) %>

User:

<%= @comment.user.name %>

<%= link_to 'Edit', edit_incident_comment_path(@incident, @comment) %> | <%= link_to 'Back', incident_comments_path %> ================================================ FILE: app/views/comments/show.json.jbuilder ================================================ json.extract! @comment, @user, :id, :created_at, :updated_at ================================================ FILE: app/views/escalation_series/_form.html.erb ================================================ <%= form_for(@escalation_series) do |f| %> <% if @escalation_series.errors.any? %>

<%= pluralize(@escalation_series.errors.count, "error") %> prohibited this escalation_series from being saved:

<% end %>
<%= f.label :name %> <%= f.text_field :name, required: true, class: 'form-control' %>
<%= f.label :settings %> <%= f.text_area :settings, value: @escalation_series.settings.to_yaml, class: 'form-control', rows: 8 %>
<%= f.submit(class: 'btn btn-default') %> <% end %> ================================================ FILE: app/views/escalation_series/edit.html.erb ================================================

Editing Escalation Series

<%= render 'form' %> <%= link_to 'Show', @escalation_series %> | <%= link_to 'Back', escalation_series_index_path %> ================================================ FILE: app/views/escalation_series/index.html.erb ================================================

Listing Escalation Series

<% @escalation_series.each do |escalation_series| %> <% end %>
Name
<%= escalation_series.name %> <%= link_to 'Show', escalation_series %> <%= link_to 'Edit', edit_escalation_series_path(escalation_series) %> <%= link_to 'Destroy', escalation_series, method: :delete, data: { confirm: 'Are you sure?' } %>

<%= link_to 'New Escalation series', new_escalation_series_path %> ================================================ FILE: app/views/escalation_series/index.json.jbuilder ================================================ json.array!(@escalation_series) do |escalation_series| json.extract! escalation_series, :id, :name json.url escalation_series_url(escalation_series, format: :json) end ================================================ FILE: app/views/escalation_series/new.html.erb ================================================

New Escalation Series

<%= render 'form' %> <%= link_to 'Back', escalation_series_index_path %> ================================================ FILE: app/views/escalation_series/show.html.erb ================================================

Name: <%= @escalation_series.name %>

<% @escalation_series.escalations.order("escalate_after_sec").each do |escalation| %> <% end %>
<%= escalation.escalate_to.name %> escalate after <%= escalation.escalate_after_sec %> sec
<%= link_to 'Edit', edit_escalation_series_path(@escalation_series) %> | <%= link_to 'Back', escalation_series_index_path %> ================================================ FILE: app/views/escalation_series/show.json.jbuilder ================================================ json.extract! @escalation_series, :id, :name, :created_at, :updated_at ================================================ FILE: app/views/escalations/_form.html.erb ================================================ <%= form_for(@escalation) do |f| %> <% if @escalation.errors.any? %>

<%= pluralize(@escalation.errors.count, "error") %> prohibited this escalation from being saved:

<% end %>
<%= f.label :escalate_to_id %> <%= f.collection_select(:escalate_to_id, User.all, :id, :name, {}, class: 'form-control') %>
<%= f.label :escalate_after_sec %> <%= f.number_field :escalate_after_sec, min: 5, class: 'form-control' %>
<%= f.label :escalation_series_id %> <%= f.collection_select(:escalation_series_id, EscalationSeries.all, :id, :name, {}, class: 'form-control') %>
<%= f.submit(class: 'btn btn-default') %> <% end %> ================================================ FILE: app/views/escalations/edit.html.erb ================================================

Editing Escalation

<%= render 'form' %> <%= link_to 'Show', @escalation %> | <%= link_to 'Back', escalations_path %> ================================================ FILE: app/views/escalations/index.html.erb ================================================

Listing Escalations

<% @escalations.each do |escalation| %> <% end %>
Escalation Series Escalate to Escalate after sec
<%= escalation.escalation_series.name %> <%= escalation.escalate_to.name %> <%= escalation.escalate_after_sec %> <%= link_to 'Show', escalation %> <%= link_to 'Edit', edit_escalation_path(escalation) %> <%= link_to 'Destroy', escalation, method: :delete, data: { confirm: 'Are you sure?' } %>

<%= link_to 'New Escalation', new_escalation_path %> ================================================ FILE: app/views/escalations/index.json.jbuilder ================================================ json.array!(@escalations) do |escalation| json.extract! escalation, :id, :escalate_to_id, :escalate_after_sec, :escalation_series_id json.url escalation_url(escalation, format: :json) end ================================================ FILE: app/views/escalations/new.html.erb ================================================

New Escalation

<%= render 'form' %> <%= link_to 'Back', escalations_path %> ================================================ FILE: app/views/escalations/show.html.erb ================================================

Escalation series: <%= @escalation.escalation_series.name %>

Escalate to: <%= @escalation.escalate_to.name %>

Escalate after sec: <%= @escalation.escalate_after_sec %>

<%= link_to 'Edit', edit_escalation_path(@escalation) %> | <%= link_to 'Back', escalations_path %> ================================================ FILE: app/views/escalations/show.json.jbuilder ================================================ json.extract! @escalation, :id, :escalate_to_id, :escalate_after_sec, :created_at, :updated_at ================================================ FILE: app/views/home/index.html.erb ================================================

Home#index

Find me in app/views/home/index.html.erb

================================================ FILE: app/views/incident_events/twilio.html.erb ================================================

IncidentEvents#twilio

Find me in app/views/incident_events/twilio.html.erb

================================================ FILE: app/views/incidents/_form.html.erb ================================================ <%= form_for(@incident) do |f| %> <% if @incident.errors.any? %>

<%= pluralize(@incident.errors.count, "error") %> prohibited this incident from being saved:

<% end %>
<%= f.label :subject %> <%= f.text_field :subject, required: true, class: 'form-control' %>
<%= f.label :description %> <%= f.text_area :description, required: true, class: 'form-control' %>
<%= f.label :topic_id %> <%= f.collection_select(:topic_id, Topic.all, :id, :name, {}, class: 'form-control') %>
<%= f.label :occured_at %> <%= f.datetime_select :occured_at %>
<%= f.submit(class: 'btn btn-default') %> <% end %> ================================================ FILE: app/views/incidents/edit.html.erb ================================================

Editing Incident

<%= render 'form' %> <%= link_to 'Show', @incident %> | <%= link_to 'Back', incidents_path %> ================================================ FILE: app/views/incidents/index.html.erb ================================================

Listing Incidents

<% @incidents.each do |incident| %> <% if incident.comments.present? %> <% end %> <% end %>
Status Subject Topic Occured at
<% if incident.opened? %> Opened <% elsif incident.acknowledged? %> Acknowledged <% elsif incident.resolved? %> Resolved <% end %> <%= incident.subject %> <%= incident.topic.name %> <%= incident.occured_at %> <%= link_to 'Show', incident %> <%= link_to 'Ack', acknowledge_incident_path(incident, hash: incident.confirmation_hash) %> <%= link_to 'Resolve', resolve_incident_path(incident, hash: incident.confirmation_hash) %> <%= link_to 'Comment', new_incident_comment_path(incident) %>
<% incident.comments.each_with_index do |comment, i| %>
<%= auto_link(comment.comment, :html => { :target => '_blank' }) %>
(<%= comment.user.name %>)
<% end %>
<%= paginate @incidents %>
<%= link_to 'Ack All', acknowledge_incidents_path, class: "btn btn-default", method: :patch, data: { confirm: "#{@incidents.count} incidents will be marked as acked. Are you sure?" } %> <%= link_to 'Resolve All', resolve_incidents_path, class: "btn btn-success", method: :patch, data: { confirm: "#{@incidents.count} incidents will be marked as resolved. Are you sure?" } %> ================================================ FILE: app/views/incidents/index.json.jbuilder ================================================ json.array!(@incidents) do |incident| json.extract! incident, :id, :subject, :description, :topic_id, :occured_at json.url incident_url(incident, format: :json) end ================================================ FILE: app/views/incidents/new.html.erb ================================================

New Incident

<%= render 'form' %> <%= link_to 'Back', incidents_path %> ================================================ FILE: app/views/incidents/show.html.erb ================================================

Subject: <%= @incident.subject %>

Description:

<%= @incident.description %>

Topic: <%= @incident.topic.name %>

Occured at: <%= @incident.occured_at %>

<% @incident.events.each do |event| %> <% end %>
Time Event Description
<%= event.created_at %> <%= event.kind %> <% if event.escalated? %> to <%= link_to(event.escalated_to.name, event.escalated_to) %> <% elsif event.notified? %> notified by <%= link_to('notifier', event.notifier) %> <% end %>

Comment:

<% @incident.comments.each do |comment| %> <% end %>
Time Comment
<%= comment.created_at %> <%= comment.comment %> (<%= comment.user.name %>)
<%= link_to 'Edit', edit_incident_path(@incident) %> | <%= link_to 'Destroy', @incident, method: :delete, data: { confirm: 'Are you sure?' } %> | <%= link_to 'Back', incidents_path %> ================================================ FILE: app/views/incidents/show.json.jbuilder ================================================ json.extract! @incident, :id, :subject, :description, :topic_id, :occured_at, :created_at, :updated_at ================================================ FILE: app/views/layouts/application.html.erb ================================================ Waker <%= stylesheet_link_tag 'application', media: 'all' %> <%= javascript_include_tag 'application' %> <%= csrf_meta_tags %>
<% if notice %> <% end %> <%= yield %>
================================================ FILE: app/views/maintenances/_form.html.erb ================================================ <%= form_for(@maintenance) do |f| %> <% if @maintenance.errors.any? %>

<%= pluralize(@maintenance.errors.count, "error") %> prohibited this maintenance from being saved:

<% end %>
<%= f.label :topic_id %>
<%= f.collection_select(:topic_id, Topic.all, :id, :name, {}, class: 'form-control') %>
<%= f.label :filter %>
<%= f.text_field(:filter, class: 'form-control', placeholder: 'regexp') %>
<%= f.label :start_time %>
<%= f.datetime_select :start_time, class: 'form-control' %>
<%= f.label :end_time %>
<%= f.datetime_select :end_time, class: 'form-control' %>
<%= f.submit(class: 'btn btn-default') %> <% end %> ================================================ FILE: app/views/maintenances/edit.html.erb ================================================

Editing Maintenance

<%= render 'form' %> <%= link_to 'Show', @maintenance %> | <%= link_to 'Back', maintenances_path %> ================================================ FILE: app/views/maintenances/index.html.erb ================================================

Listing Maintenances

Expired maintanances are not shown

<% @maintenances.each do |maintenance| %> <% end %>
Topic Filter Regexp Start time End time
<%= maintenance.topic.name %> <%= maintenance.filter %> <%= maintenance.start_time %> <%= maintenance.end_time %> <%= link_to 'Show', maintenance %> <%= link_to 'Edit', edit_maintenance_path(maintenance) %> <%= link_to 'Destroy', maintenance, method: :delete, data: { confirm: 'Are you sure?' } %>

<%= link_to 'New Maintenance', new_maintenance_path %> ================================================ FILE: app/views/maintenances/index.json.jbuilder ================================================ json.array!(@maintenances) do |maintenance| json.extract! maintenance, :id, :topic_id, :start_time, :end_time json.url maintenance_url(maintenance, format: :json) end ================================================ FILE: app/views/maintenances/new.html.erb ================================================

New Maintenance

<%= render 'form' %> <%= link_to 'Back', maintenances_path %> ================================================ FILE: app/views/maintenances/show.html.erb ================================================

<%= notice %>

Topic: <%= @maintenance.topic.name %>

Filter (regexp): <%= @maintenance.filter %>

Start time: <%= @maintenance.start_time %>

End time: <%= @maintenance.end_time %>

<%= link_to 'Edit', edit_maintenance_path(@maintenance) %> | <%= link_to 'Back', maintenances_path %> ================================================ FILE: app/views/maintenances/show.json.jbuilder ================================================ json.extract! @maintenance, :id, :topic_id, :start_time, :end_time, :created_at, :updated_at ================================================ FILE: app/views/notifier_providers/_form.html.erb ================================================ <%= form_for(@notifier_provider) do |f| %> <% if @notifier_provider.errors.any? %>

<%= pluralize(@notifier_provider.errors.count, "error") %> prohibited this notifier_provider from being saved:

<% end %>
<%= f.label :name %> <%= f.text_field :name, required: true, class: 'form-control' %>
<%= f.label :kind %> <%= f.select :kind, nil, {}, class: 'form-control' do %> <%= options_for_select( NotifierProvider.kinds.map do |kind, _| [kind.camelize, kind, {}] end, @notifier_provider.kind ) %> <% end %>
<%= f.label :settings %> <%= f.text_area :settings, value: @notifier_provider.settings.to_yaml, class: 'form-control', rows: 8 %>
<%= f.submit(class: 'btn btn-default') %> <% end %> ================================================ FILE: app/views/notifier_providers/edit.html.erb ================================================

Editing Notifier Provider

<%= render 'form' %> <%= link_to 'Show', @notifier_provider %> | <%= link_to 'Back', notifier_providers_path %> ================================================ FILE: app/views/notifier_providers/hipchat/acknowledged.text.erb ================================================ Incident acknowledged: <%= event.incident.subject %> (Resolve) ================================================ FILE: app/views/notifier_providers/hipchat/escalated.text.erb ================================================ Incident escalated to <%= event.escalated_to.name %>: <%= event.incident.subject %> (Acknowledge) (Resolve) ================================================ FILE: app/views/notifier_providers/hipchat/opened.text.erb ================================================ New incident opened: <%= event.incident.subject %> (Acknowledge) (Resolve) ================================================ FILE: app/views/notifier_providers/hipchat/resolved.text.erb ================================================ Incident resolved: <%= event.incident.subject %> ================================================ FILE: app/views/notifier_providers/index.html.erb ================================================

Listing Notifier Providers

<% @notifier_providers.each do |notifier_provider| %> <% end %>
Name Kind Settings
<%= notifier_provider.name %> <%= notifier_provider.kind %> <%= notifier_provider.settings %> <%= link_to 'Show', notifier_provider %> <%= link_to 'Edit', edit_notifier_provider_path(notifier_provider) %> <%= link_to 'Destroy', notifier_provider, method: :delete, data: { confirm: 'Are you sure?' } %>

<%= link_to 'New Notifier provider', new_notifier_provider_path %> ================================================ FILE: app/views/notifier_providers/index.json.jbuilder ================================================ json.array!(@notifier_providers) do |notifier_provider| json.extract! notifier_provider, :id, :name, :kind, :settings json.url notifier_provider_url(notifier_provider, format: :json) end ================================================ FILE: app/views/notifier_providers/mailgun/default.html.erb ================================================ [Waker] <%= event.incident.subject %> <% ack_url = Rails.application.routes.url_helpers.acknowledge_incident_url(event.incident, hash: event.incident.confirmation_hash) ack_url_json = Rails.application.routes.url_helpers.acknowledge_incident_url(event.incident, hash: event.incident.confirmation_hash, format: :json) resolve_url = Rails.application.routes.url_helpers.resolve_incident_url(event.incident, hash: event.incident.confirmation_hash) email_markup = { "@context" => "http://schema.org", "@type" => "EmailMessage", "potentialAction" => { "@type" => "ConfirmAction", "name" => "Acknowledge incident", "handler" => { "@type" => "HttpActionHandler", "url" => ack_url_json, }, }, "description" => "Acknowledge incident: #{event.incident.subject}", } %>

<%= event.incident.subject %>

<% btn_style = 'border-radius: 4px;' \ 'text-decoration: none;' \ 'display: inline-block;' \ 'font-size: 1.3em;' \ 'padding: 5px 8px;' \ 'margin-right: 12px;' \ %>
Acknowledge
Resolve

<%= event.incident.description %>
      
================================================ FILE: app/views/notifier_providers/mailgun/default.text.erb ================================================ <%= event.incident.subject %> ==== Actions: - To ack: <%= Rails.application.routes.url_helpers.acknowledge_incident_url(event.incident, hash: event.incident.confirmation_hash) %> - To resolve: <%= Rails.application.routes.url_helpers.resolve_incident_url(event.incident, hash: event.incident.confirmation_hash) %> ---- <%= event.incident.description %> ================================================ FILE: app/views/notifier_providers/new.html.erb ================================================

New Notifier Provider

<%= render 'form' %> <%= link_to 'Back', notifier_providers_path %> ================================================ FILE: app/views/notifier_providers/rails_logger/default.text.erb ================================================ Notification: <%= event.kind %> ================================================ FILE: app/views/notifier_providers/show.html.erb ================================================

Name: <%= @notifier_provider.name %>

Kind: <%= @notifier_provider.kind %>

Settings: <%= @notifier_provider.settings %>

<%= link_to 'Edit', edit_notifier_provider_path(@notifier_provider) %> | <%= link_to 'Back', notifier_providers_path %> ================================================ FILE: app/views/notifier_providers/show.json.jbuilder ================================================ json.extract! @notifier_provider, :id, :name, :kind, :settings, :created_at, :updated_at ================================================ FILE: app/views/notifiers/_form.html.erb ================================================ <%= form_for(@notifier) do |f| %> <% if @notifier.errors.any? %>

<%= pluralize(@notifier.errors.count, "error") %> prohibited this notifier from being saved:

<% end %>
<%= f.label :user_id %> <%= f.collection_select(:user_id, User.all, :id, :name, { include_blank: true }, class: 'form-control') %>
<%= f.label :topic_id %> <%= f.collection_select(:topic_id, Topic.all, :id, :name, { include_blank: true }, class: 'form-control') %>
<%= f.label :provider_id %> <%= f.collection_select(:provider_id, NotifierProvider.all, :id, :name, {}, class: 'form-control') %>
<%= f.label :settings %> <%= f.text_area :settings, value: @notifier.settings.to_yaml, class: 'form-control', rows: 8 %>
<%= f.label :notify_after_sec %> <%= f.number_field :notify_after_sec, min: 5, class: 'form-control' %>
<%= f.label :enabled do %> <%= f.check_box :enabled %> Enabled <% end %>
<%= f.submit(class: 'btn btn-default') %> <% end %> ================================================ FILE: app/views/notifiers/edit.html.erb ================================================

Editing Notifier

<%= render 'form' %> <%= link_to 'Show', @notifier %> | <%= link_to 'Back', notifiers_path %> ================================================ FILE: app/views/notifiers/index.html.erb ================================================

Listing Notifiers

<% @notifiers.each do |notifier| %> <% end %>
User Topic Provider Settings Notify after sec Enabled
<%= notifier.user && notifier.user.name %> <%= notifier.topic && notifier.topic.name %> <%= notifier.provider.name %> <%= notifier.settings %> <%= notifier.notify_after_sec %> <%= notifier.enabled %> <%= link_to 'Show', notifier %> <%= link_to 'Edit', edit_notifier_path(notifier) %> <%= link_to 'Destroy', notifier, method: :delete, data: { confirm: 'Are you sure?' } %>

<%= link_to 'New Notifier', new_notifier_path %> ================================================ FILE: app/views/notifiers/index.json.jbuilder ================================================ json.array!(@notifiers) do |notifier| json.extract! notifier, :id, :settings, :provider_id, :provider, :topic_id, :user_id, :notify_after_sec, :created_at, :updated_at json.url notifier_url(notifier, format: :json) end ================================================ FILE: app/views/notifiers/new.html.erb ================================================

New Notifier

<%= render 'form' %> <%= link_to 'Back', notifiers_path %> ================================================ FILE: app/views/notifiers/show.html.erb ================================================

Provider: <%= @notifier.provider.name %>

User: <%= @notifier.user && @notifier.user.name %>

Topic: <%= @notifier.topic && @notifier.topic.name %>

Settings: <%= @notifier.settings %>

Notify after sec: <%= @notifier.notify_after_sec %>

Enabled: <%= @notifier.enabled %>

<%= link_to 'Edit', edit_notifier_path(@notifier) %> | <%= link_to 'Back', notifiers_path %> ================================================ FILE: app/views/notifiers/show.json.jbuilder ================================================ json.extract! @notifier, :id, :settings, :provider_id, :provider, :topic_id, :user_id, :notify_after_sec, :created_at, :updated_at ================================================ FILE: app/views/sessions/create.html.erb ================================================

Sessions#create

Find me in app/views/sessions/create.html.erb

================================================ FILE: app/views/slack/interactive.html.erb ================================================

Slack#interactive

Find me in app/views/slack/interactive.html.erb

================================================ FILE: app/views/topics/_form.html.erb ================================================ <%= form_for(@topic) do |f| %> <% if @topic.errors.any? %>

<%= pluralize(@topic.errors.count, "error") %> prohibited this topic from being saved:

<% end %>
<%= f.label :name %> <%= f.text_field :name, required: true, class: 'form-control' %>
<%= f.label :kind %> <%= f.select :kind, nil, {}, class: 'form-control' do %> <% Topic.kinds.each do |kind, _| %> <%= content_tag(:option, kind.to_s.split('_').map {|s| s.capitalize }.join(' '), value: kind) %> <% end %> <% end %>
<%= f.label :escalation_series_id %> <%= f.collection_select(:escalation_series_id, EscalationSeries.all, :id, :name, {}, class: 'form-control') %>
<%= f.label :enabled do %> <%= f.check_box :enabled %> Enabled <% end %>
<%= f.submit(class: 'btn btn-default') %> <% end %> ================================================ FILE: app/views/topics/edit.html.erb ================================================

Editing Topic

<%= render 'form' %> <%= link_to 'Show', @topic %> | <%= link_to 'Back', topics_path %> ================================================ FILE: app/views/topics/index.html.erb ================================================

Listing Topics

<% @topics.each do |topic| %> <% end %>
Name Kind Escalation Series Enabled
<%= topic.name %> <%= topic.kind %> <%= topic.escalation_series.name %> <%= topic.enabled ? 'Yes' : 'No' %> <%= link_to 'Show', topic %> <%= link_to 'Edit', edit_topic_path(topic) %> <%= link_to 'Destroy', topic, method: :delete, data: { confirm: 'Are you sure?' } %>

<%= link_to 'New Topic', new_topic_path %> ================================================ FILE: app/views/topics/index.json.jbuilder ================================================ json.array!(@topics) do |topic| json.extract! topic, :id, :name, :type json.url topic_url(topic, format: :json) end ================================================ FILE: app/views/topics/new.html.erb ================================================

New Topic

<%= render 'form' %> <%= link_to 'Back', topics_path %> ================================================ FILE: app/views/topics/show.html.erb ================================================

Name: <%= @topic.name %>

Kind: <%= @topic.kind %>

Escalation Series: <%= @topic.escalation_series.name %>

Enabled: <%= @topic.enabled ? 'Yes' : 'No' %>

Mailgun Endpoint:

<%= mailgun_topic_url(@topic, format: :json) %>

Mackerel Endpoint:

<%= mackerel_topic_url(@topic, format: :json) %>

AlertManagerEndpoint:

<%= alertmanager_topic_url(@topic, format: :json) %>

SlackEndpoint:

<%= slack_topic_url(@topic, format: :json) %>

<%= link_to 'Edit', edit_topic_path(@topic) %> | <%= link_to 'Back', topics_path %> ================================================ FILE: app/views/topics/show.json.jbuilder ================================================ json.extract! @topic, :id, :name, :type, :created_at, :updated_at ================================================ FILE: app/views/users/_form.html.erb ================================================ <%= form_for(@user) do |f| %> <% if @user.errors.any? %>

<%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:

<% end %>
<%= f.label :name %> <%= f.text_field :name, required: true, class: 'form-control' %>
<%= f.submit(class: 'btn btn-default') %> <% end %> ================================================ FILE: app/views/users/edit.html.erb ================================================

Editing User

<%= render 'form' %> <%= link_to 'Show', @user %> | <%= link_to 'Back', users_path %> ================================================ FILE: app/views/users/index.html.erb ================================================

Listing Users

<% @users.each do |user| %> <%- if user.active -%> <%- else -%> <%- end -%> <% end %>
Name Status
<%= user.name %> <%- unless user.active -%> Deactivated <%- end -%> <%= link_to 'Show', user %> <%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %><%= link_to 'Deactivate', deactivation_user_path(user), method: :patch, data: { confirm: 'Are you sure?' } %><%= link_to 'Activate', activation_user_path(user), method: :patch, data: { confirm: 'Are you sure?' } %>

================================================ FILE: app/views/users/index.json.jbuilder ================================================ json.array!(@users) do |user| json.extract! user, :id, :name json.url user_url(user, format: :json) end ================================================ FILE: app/views/users/new.html.erb ================================================

New User

<%= render 'form' %> <%= link_to 'Back', users_path %> ================================================ FILE: app/views/users/show.html.erb ================================================

Name: <%= @user.name %>

Provider: <%= @user.provider %>

UID: <%= @user.uid %>

<%= link_to 'Back', users_path %> ================================================ FILE: app/views/users/show.json.jbuilder ================================================ json.extract! @user, :id, :name, :created_at, :updated_at ================================================ FILE: bin/bundle ================================================ #!/usr/bin/env ruby ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) load Gem.bin_path('bundler', 'bundle') ================================================ FILE: bin/rails ================================================ #!/usr/bin/env ruby begin load File.expand_path("../spring", __FILE__) rescue LoadError end APP_PATH = File.expand_path('../../config/application', __FILE__) require_relative '../config/boot' require 'rails/commands' ================================================ FILE: bin/rake ================================================ #!/usr/bin/env ruby begin load File.expand_path("../spring", __FILE__) rescue LoadError end require_relative '../config/boot' require 'rake' Rake.application.run ================================================ FILE: bin/setup ================================================ #!/usr/bin/env ruby require 'pathname' # path to your application root. APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) Dir.chdir APP_ROOT do # This script is a starting point to setup your application. # Add necessary setup steps to this file: puts "== Installing dependencies ==" system "gem install bundler --conservative" system "bundle check || bundle install" # puts "\n== Copying sample files ==" # unless File.exist?("config/database.yml") # system "cp config/database.yml.sample config/database.yml" # end puts "\n== Preparing database ==" system "bin/rake db:setup" puts "\n== Removing old logs and tempfiles ==" system "rm -f log/*" system "rm -rf tmp/cache" puts "\n== Restarting application server ==" system "touch tmp/restart.txt" end ================================================ FILE: bin/spring ================================================ #!/usr/bin/env ruby # This file loads spring without using Bundler, in order to be fast # It gets overwritten when you run the `spring binstub` command unless defined?(Spring) require "rubygems" require "bundler" if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m) ENV["GEM_PATH"] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR) ENV["GEM_HOME"] = "" Gem.paths = ENV gem "spring", match[1] require "spring/binstub" end end ================================================ FILE: config/application.rb ================================================ require File.expand_path('../boot', __FILE__) # Pick the frameworks you want: require "active_model/railtie" require "active_job/railtie" require "active_record/railtie" require "action_controller/railtie" require "action_mailer/railtie" require "action_view/railtie" require "sprockets/railtie" # require "rails/test_unit/railtie" # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) module Waker class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. if time_zone = ENV['TIME_ZONE'] config.time_zone = time_zone end # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de # Do not swallow errors in after_commit/after_rollback callbacks. config.middleware.use Rack::Health config.middleware.use OmniAuth::Builder do config = {} config[:hd] = ENV['GOOGLE_DOMAIN'] if ENV['GOOGLE_DOMAIN'] provider :google_oauth2, ENV["GOOGLE_CLIENT_ID"], ENV["GOOGLE_CLIENT_SECRET"], config.merge(scope: 'userinfo.profile,userinfo.email,calendar', name: 'google_oauth2_with_calendar', access_type: 'offline', approval_prompt: 'force', prompt: 'consent') end end end ================================================ FILE: config/boot.rb ================================================ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' # Set up gems listed in the Gemfile. ================================================ FILE: config/database.yml ================================================ production: adapter: mysql2 database: waker encoding: utf8mb4 username : <%= ENV['MYSQL_USER'] || 'root' %> password: <%= ENV['MYSQL_PASSWORD'] %> host: <%= ENV['MYSQL_HOST'] || 'localhost' %> development: adapter: mysql2 database: waker_development encoding: utf8mb4 username : <%= ENV['MYSQL_USER'] || 'root' %> password: <%= ENV['MYSQL_PASSWORD'] %> host: <%= ENV['MYSQL_HOST'] || 'localhost' %> test: adapter: mysql2 database: waker_test encoding: utf8mb4 username : <%= ENV['MYSQL_USER'] || 'root' %> password: <%= ENV['MYSQL_PASSWORD'] %> host: <%= ENV['MYSQL_HOST'] || 'localhost' %> ================================================ FILE: config/environment.rb ================================================ # Load the Rails application. require File.expand_path('../application', __FILE__) # Initialize the Rails application. Rails.application.initialize! ================================================ FILE: config/environments/development.rb ================================================ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.cache_classes = false # Do not eager load code on boot. config.eager_load = false # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load # Debug mode disables concatenation and preprocessing of assets. # This option may cause significant delays in view rendering with a large # number of complex assets. config.assets.debug = true # Asset digests allow you to set far-future HTTP expiration dates on all assets, # yet still be able to expire them through the digest params. config.assets.digest = true # Adds additional error checking when serving assets at runtime. # Checks for improperly declared sprockets dependencies. # Raises helpful error messages. config.assets.raise_runtime_errors = true # Raises error for missing translations # config.action_view.raise_on_missing_translations = true end Rails.application.routes.default_url_options[:host] = 'localhost:3000' ================================================ FILE: config/environments/production.rb ================================================ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. config.cache_classes = true # Eager load code on boot. This eager loads most of Rails and # your application in memory, allowing both threaded web servers # and those relying on copy on write to perform better. # Rake tasks automatically ignore this option for performance. config.eager_load = true # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true # Enable Rack::Cache to put a simple HTTP cache in front of your application # Add `rack-cache` to your Gemfile before enabling this. # For large-scale production use, consider using a caching reverse proxy like # NGINX, varnish or squid. # config.action_dispatch.rack_cache = true # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? # Compress JavaScripts and CSS. config.assets.js_compressor = :uglifier # config.assets.css_compressor = :sass # Do not fallback to assets pipeline if a precompiled asset is missed. config.assets.compile = false # Asset digests allow you to set far-future HTTP expiration dates on all assets, # yet still be able to expire them through the digest params. config.assets.digest = true # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb # Specifies the header that your server uses for sending files. # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true # Use the lowest log level to ensure availability of diagnostic information # when problems arise. config.log_level = :debug # Prepend all log lines with the following tags. # config.log_tags = [ :subdomain, :uuid ] # Use a different logger for distributed setups. # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) if log_dir = ENV['LOG_DIR'] config.logger = ::Logger.new(File.expand_path('production.log', log_dir)) end # Use a different cache store in production. # config.cache_store = :mem_cache_store # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.action_controller.asset_host = 'http://assets.example.com' # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. # config.action_mailer.raise_delivery_errors = false # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new if ENV["RAILS_LOG_TO_STDOUT"].present? logger = ActiveSupport::Logger.new(STDOUT) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false end ================================================ FILE: config/environments/test.rb ================================================ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! config.cache_classes = true # Do not eager load code on boot. This avoids loading your whole application # just for the purpose of running a single test. If you are using a tool that # preloads Rails for running tests, you may have to set it to true. config.eager_load = false # Configure static file server for tests with Cache-Control for performance. config.serve_static_files = true config.static_cache_control = 'public, max-age=3600' # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false # Raise exceptions instead of rendering exception templates. config.action_dispatch.show_exceptions = false # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test # Randomize the order test cases are executed. config.active_support.test_order = :random # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr # Raises error for missing translations # config.action_view.raise_on_missing_translations = true end ================================================ FILE: config/initializers/assets.rb ================================================ # Be sure to restart your server when you modify this file. # Version of your assets, change this if you want to expire all your assets. Rails.application.config.assets.version = '1.0' # Add additional assets to the asset load path # Rails.application.config.assets.paths << Emoji.images_path # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. # Rails.application.config.assets.precompile += %w( search.js ) ================================================ FILE: config/initializers/backtrace_silencers.rb ================================================ # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. # Rails.backtrace_cleaner.remove_silencers! ================================================ FILE: config/initializers/cookies_serializer.rb ================================================ # Be sure to restart your server when you modify this file. Rails.application.config.action_dispatch.cookies_serializer = :json ================================================ FILE: config/initializers/field_with_errors.rb ================================================ Rails.application.configure do config.action_view.field_error_proc = lambda do |html_tag, instance| %Q{
#{html_tag}
}.html_safe end end ================================================ FILE: config/initializers/filter_parameter_logging.rb ================================================ # Be sure to restart your server when you modify this file. # Configure sensitive parameters which will be filtered from the log file. Rails.application.config.filter_parameters += [:password] ================================================ FILE: config/initializers/inflections.rb ================================================ # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format. Inflections # are locale specific, and you may define rules for as many different # locales as you wish. All of these examples are active by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.plural /^(ox)$/i, '\1en' # inflect.singular /^(ox)en/i, '\1' # inflect.irregular 'person', 'people' # inflect.uncountable %w( fish sheep ) # end # These inflection rules are supported but not enabled by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.acronym 'RESTful' # end ================================================ FILE: config/initializers/mime_types.rb ================================================ # Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: # Mime::Type.register "text/richtext", :rtf ================================================ FILE: config/initializers/omniauth.rb ================================================ OmniAuth.config.logger = Rails.logger ================================================ FILE: config/initializers/session_store.rb ================================================ # Be sure to restart your server when you modify this file. Rails.application.config.session_store :cookie_store, key: '_waker_session' ================================================ FILE: config/initializers/sidekiq.rb ================================================ namespace = ENV['REDIS_NAMESPACE'] || "waker-#{Rails.env}" host = ENV['REDIS_HOST'] || 'localhost' port = ENV['REDIS_PORT'] || 6379 Sidekiq.configure_server do |config| config.poll_interval = 5 config.redis = {url: "redis://#{host}:#{port}", namespace: namespace} end Sidekiq.configure_client do |config| config.redis = {url: "redis://#{host}:#{port}", namespace: namespace} end Sidekiq::Logging.logger = Rails.logger ================================================ FILE: config/initializers/url_options.rb ================================================ if default_host = ENV['DEFAULT_HOST'] Rails.application.routes.default_url_options[:host] = default_host end if default_protocol = ENV['DEFAULT_PROTOCOL'] Rails.application.routes.default_url_options[:protocol] = default_protocol end ================================================ FILE: config/initializers/wrap_parameters.rb ================================================ # Be sure to restart your server when you modify this file. # This file contains settings for ActionController::ParamsWrapper which # is enabled by default. # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. ActiveSupport.on_load(:action_controller) do wrap_parameters format: [:json] if respond_to?(:wrap_parameters) end # To enable root element in JSON for ActiveRecord objects. # ActiveSupport.on_load(:active_record) do # self.include_root_in_json = true # end ================================================ FILE: config/locales/en.yml ================================================ # Files in the config/locales directory are used for internationalization # and are automatically loaded by Rails. If you want to use locales other # than English, add the necessary files in this directory. # # To use the locales, use `I18n.t`: # # I18n.t 'hello' # # In views, this is aliased to just `t`: # # <%= t('hello') %> # # To use a different locale, set it with `I18n.locale`: # # I18n.locale = :es # # This would use the information in config/locales/es.yml. # # To learn more, please read the Rails Internationalization guide # available at http://guides.rubyonrails.org/i18n.html. en: hello: "Hello world" ================================================ FILE: config/routes.rb ================================================ Rails.application.routes.draw do post 'slack/interactive' get '/auth/:provider/callback', to: 'sessions#create' resources :incident_events, only: [] do member do post 'twilio' get 'twilio' end end resources :incidents do member do get 'acknowledge' get 'resolve' end collection do patch 'acknowledge', to: "incidents#bulk_acknowledge" patch 'resolve', to: "incidents#bulk_resolve" end resources :comments end resources :escalation_series do member do get 'update_escalations' end end resources :escalations resources :shifts resources :notifiers resources :notifier_providers resources :maintenances resources :users, only: [:index, :show, :destroy] do member do patch 'activation' patch 'deactivation' end end resources :topics do member do post 'mailgun' post 'mackerel' post 'alertmanager' post 'slack' end end require 'sidekiq/web' mount Sidekiq::Web => '/sidekiq' root 'home#index' # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". # You can have the root of your site routed with "root" # root 'welcome#index' # Example of regular route: # get 'products/:id' => 'catalog#view' # Example of named route that can be invoked with purchase_url(id: product.id) # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase # Example resource route (maps HTTP verbs to controller actions automatically): # resources :products # Example resource route with options: # resources :products do # member do # get 'short' # post 'toggle' # end # # collection do # get 'sold' # end # end # Example resource route with sub-resources: # resources :products do # resources :comments, :sales # resource :seller # end # Example resource route with more complex sub-resources: # resources :products do # resources :comments # resources :sales do # get 'recent', on: :collection # end # end # Example resource route with concerns: # concern :toggleable do # post 'toggle' # end # resources :posts, concerns: :toggleable # resources :photos, concerns: :toggleable # Example resource route within a namespace: # namespace :admin do # # Directs /admin/products/* to Admin::ProductsController # # (app/controllers/admin/products_controller.rb) # resources :products # end end ================================================ FILE: config/secrets.yml ================================================ # Be sure to restart your server when you modify this file. # Your secret key is used for verifying the integrity of signed cookies. # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. # You can use `rake secret` to generate a secure secret key. # Make sure the secrets in this file are kept private # if you're sharing your code publicly. development: secret_key_base: d50e30eb49152fcfff420c795f7d0f559934a6497808d4c8a34e83f1ec4807f7c36b4ca47af41bdb4d5a757e9a756f4425c748defb4054f89e63cea1788eb2d3 test: secret_key_base: 5206b6e3c7cd04a83e4743c3f0d631e025f196223d8d13742f0bd71c6e1c5d0b0a443398a4536f503d8399c8c7e58e20dadc3553e64bcdb1f4cb7dbc95a7fe0c # Do not keep production secrets in the repository, # instead read values from the environment. production: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> ================================================ FILE: config.ru ================================================ # This file is used by Rack-based servers to start the application. require ::File.expand_path('../config/environment', __FILE__) run Rails.application ================================================ FILE: db/migrate/20150120134616_create_topics.rb ================================================ class CreateTopics < ActiveRecord::Migration def change create_table :topics do |t| t.string :name t.integer :type t.timestamps null: false end end end ================================================ FILE: db/migrate/20150120134747_create_users.rb ================================================ class CreateUsers < ActiveRecord::Migration def change create_table :users do |t| t.string :name t.timestamps null: false end end end ================================================ FILE: db/migrate/20150120134905_create_notifiers.rb ================================================ class CreateNotifiers < ActiveRecord::Migration def change create_table :notifiers do |t| t.integer :type t.text :settings t.integer :notify_after_sec t.timestamps null: false end end end ================================================ FILE: db/migrate/20150120135017_create_shifts.rb ================================================ class CreateShifts < ActiveRecord::Migration def change create_table :shifts do |t| t.references :user, index: true t.timestamps null: false end add_foreign_key :shifts, :users end end ================================================ FILE: db/migrate/20150120135123_create_escalations.rb ================================================ class CreateEscalations < ActiveRecord::Migration def change create_table :escalations do |t| t.references :escalate_to, index: true t.integer :escalate_after_sec t.timestamps null: false end add_foreign_key :escalations, :users, column: 'escalate_to_id' end end ================================================ FILE: db/migrate/20150120135244_create_escalation_series.rb ================================================ class CreateEscalationSeries < ActiveRecord::Migration def change create_table :escalation_series do |t| t.string :name t.timestamps null: false end end end ================================================ FILE: db/migrate/20150120135351_add_escalation_series_to_escalation.rb ================================================ class AddEscalationSeriesToEscalation < ActiveRecord::Migration def change add_reference :escalations, :escalation_series, index: true add_foreign_key :escalations, :escalation_series end end ================================================ FILE: db/migrate/20150120141627_rename_type_with_kind_of_topic.rb ================================================ class RenameTypeWithKindOfTopic < ActiveRecord::Migration def change rename_column :topics, :type, :kind end end ================================================ FILE: db/migrate/20150120142452_create_incidents.rb ================================================ class CreateIncidents < ActiveRecord::Migration def change create_table :incidents do |t| t.string :subject t.text :description t.references :topic, index: true t.datetime :occured_at t.timestamps null: false end add_foreign_key :incidents, :topics end end ================================================ FILE: db/migrate/20150120151642_add_escalation_series_to_topic.rb ================================================ class AddEscalationSeriesToTopic < ActiveRecord::Migration def change add_reference :topics, :escalation_series, index: true add_foreign_key :topics, :escalation_series end end ================================================ FILE: db/migrate/20150120154438_add_name_to_shift.rb ================================================ class AddNameToShift < ActiveRecord::Migration def change add_column :shifts, :name, :string end end ================================================ FILE: db/migrate/20150121150043_add_user_to_notifier.rb ================================================ class AddUserToNotifier < ActiveRecord::Migration def change add_reference :notifiers, :user, index: true add_foreign_key :notifiers, :users end end ================================================ FILE: db/migrate/20150121150857_rename_type_with_kind_of_notifier.rb ================================================ class RenameTypeWithKindOfNotifier < ActiveRecord::Migration def change rename_column :notifiers, :type, :kind end end ================================================ FILE: db/migrate/20150123132415_remove_shift.rb ================================================ class RemoveShift < ActiveRecord::Migration def change drop_table :shifts end end ================================================ FILE: db/migrate/20150123150518_add_status_to_incident.rb ================================================ class AddStatusToIncident < ActiveRecord::Migration def change add_column :incidents, :status, :integer end end ================================================ FILE: db/migrate/20150123150947_create_incident_events.rb ================================================ class CreateIncidentEvents < ActiveRecord::Migration def change create_table :incident_events do |t| t.references :incident, index: true t.integer :kind t.text :text t.references :user_by, index: true t.timestamps null: false end add_foreign_key :incident_events, :incidents add_foreign_key :incident_events, :users, column: 'user_by_id' end end ================================================ FILE: db/migrate/20150125050529_create_notifier_providers.rb ================================================ class CreateNotifierProviders < ActiveRecord::Migration def change create_table :notifier_providers do |t| t.string :name t.integer :kind t.text :settings t.timestamps null: false end end end ================================================ FILE: db/migrate/20150125050556_add_provider_to_notifier.rb ================================================ class AddProviderToNotifier < ActiveRecord::Migration def change add_reference :notifiers, :provider, index: true add_foreign_key :notifiers, :notifier_providers, column: 'provider_id' end end ================================================ FILE: db/migrate/20150125101901_remove_kind_from_notifier.rb ================================================ class RemoveKindFromNotifier < ActiveRecord::Migration def change remove_column :notifiers, :kind end end ================================================ FILE: db/migrate/20150127142530_remove_user_by_from_incident_event.rb ================================================ class RemoveUserByFromIncidentEvent < ActiveRecord::Migration def change remove_foreign_key :incident_events, column: "user_by_id" remove_index :incident_events, :user_by_id remove_column :incident_events, :user_by_id end end ================================================ FILE: db/migrate/20150127152127_add_info_to_incident_event.rb ================================================ class AddInfoToIncidentEvent < ActiveRecord::Migration def change add_column :incident_events, :info, :text end end ================================================ FILE: db/migrate/20150128064248_add_email_to_user.rb ================================================ class AddEmailToUser < ActiveRecord::Migration def change add_column :users, :email, :string end end ================================================ FILE: db/migrate/20150131120557_add_enable_to_topic.rb ================================================ class AddEnableToTopic < ActiveRecord::Migration def change add_column :topics, :enable, :boolean end end ================================================ FILE: db/migrate/20150131121143_set_default_of_enable_of_topic_true.rb ================================================ class SetDefaultOfEnableOfTopicTrue < ActiveRecord::Migration def change change_column :topics, :enable, :boolean, default: true end end ================================================ FILE: db/migrate/20150131122151_rename_enable_with_enabled_of_topic.rb ================================================ class RenameEnableWithEnabledOfTopic < ActiveRecord::Migration def change rename_column :topics, :enable, :enabled end end ================================================ FILE: db/migrate/20150201033946_add_login_token_to_user.rb ================================================ class AddLoginTokenToUser < ActiveRecord::Migration def change add_column :users, :login_token, :string end end ================================================ FILE: db/migrate/20150202144538_add_token_to_user.rb ================================================ class AddTokenToUser < ActiveRecord::Migration def change add_column :users, :token, :string end end ================================================ FILE: db/migrate/20150202151740_add_refresh_token_to_user.rb ================================================ class AddRefreshTokenToUser < ActiveRecord::Migration def change add_column :users, :refresh_token, :string end end ================================================ FILE: db/migrate/20150202152015_add_settings_to_escalation_series.rb ================================================ class AddSettingsToEscalationSeries < ActiveRecord::Migration def change add_column :escalation_series, :escalation_series, :text end end ================================================ FILE: db/migrate/20150202155726_add_token_expires_at_to_user.rb ================================================ class AddTokenExpiresAtToUser < ActiveRecord::Migration def change add_column :users, :token_expires_at, :datetime end end ================================================ FILE: db/migrate/20150203010332_remove_escalation_series_from_escalation_series.rb ================================================ class RemoveEscalationSeriesFromEscalationSeries < ActiveRecord::Migration def change remove_column :escalation_series, :escalation_series end end ================================================ FILE: db/migrate/20150203010417_add_settings_to_escalation_series_again.rb ================================================ class AddSettingsToEscalationSeriesAgain < ActiveRecord::Migration def change add_column :escalation_series, :settings, :text end end ================================================ FILE: db/migrate/20150207164010_add_topic_to_notifier.rb ================================================ class AddTopicToNotifier < ActiveRecord::Migration def change add_reference :notifiers, :topic, index: true add_foreign_key :notifiers, :topics end end ================================================ FILE: db/migrate/20150218071007_add_enable_to_notifier.rb ================================================ class AddEnableToNotifier < ActiveRecord::Migration def change add_column :notifiers, :enabled, :boolean, default: true end end ================================================ FILE: db/migrate/20151117011141_add_active_to_user.rb ================================================ class AddActiveToUser < ActiveRecord::Migration def change add_column :users, :active, :boolean, default: false end end ================================================ FILE: db/migrate/20151117013824_add_provider_and_uid_to_user.rb ================================================ class AddProviderAndUidToUser < ActiveRecord::Migration def change add_column :users, :provider, :string add_column :users, :uid, :string end end ================================================ FILE: db/migrate/20151118061253_add_credentials_to_user.rb ================================================ class AddCredentialsToUser < ActiveRecord::Migration def change add_column :users, :credentials, :text end end ================================================ FILE: db/migrate/20151118061938_delete_token_from_user.rb ================================================ class DeleteTokenFromUser < ActiveRecord::Migration def change User.all.each do |u| if u.token u.update!(credentials: { token: u.token, refresh_token: u.refresh_token, expires_at: u.token_expires_at.to_i, expires: true, }) end end remove_column :users, :token remove_column :users, :token_expires_at remove_column :users, :refresh_token end end ================================================ FILE: db/migrate/20160210010310_create_maintenances.rb ================================================ class CreateMaintenances < ActiveRecord::Migration def change create_table :maintenances do |t| t.references :topic, index: true t.datetime :start_time t.datetime :end_time t.timestamps null: false end add_foreign_key :maintenances, :topics end end ================================================ FILE: db/migrate/20160907123728_create_comments.rb ================================================ class CreateComments < ActiveRecord::Migration def change create_table "comments", force: :cascade do |t| t.integer "incident_id", limit: 4, null: false t.integer "user_id", limit: 4, null: false t.text "comment", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end end end ================================================ FILE: db/migrate/20160914063913_add_comment_index.rb ================================================ class AddCommentIndex < ActiveRecord::Migration def change add_index :comments, :incident_id end end ================================================ FILE: db/migrate/20161207045554_add_filter_to_maintenance.rb ================================================ class AddFilterToMaintenance < ActiveRecord::Migration def change add_column :maintenances, :filter, :string end end ================================================ FILE: db/schema.rb ================================================ # encoding: UTF-8 # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # # Note that this schema.rb definition is the authoritative source for your # database schema. If you need to create the application database on another # system, you should be using db:schema:load, not running all the migrations # from scratch. The latter is a flawed and unsustainable approach (the more migrations # you'll amass, the slower it'll run and the greater likelihood for issues). # # It's strongly recommended that you check this file into your version control system. ActiveRecord::Schema.define(version: 20161207045554) do create_table "comments", force: :cascade do |t| t.integer "incident_id", limit: 4, null: false t.integer "user_id", limit: 4, null: false t.text "comment", limit: 65535, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end add_index "comments", ["incident_id"], name: "index_comments_on_incident_id", using: :btree create_table "escalation_series", force: :cascade do |t| t.string "name", limit: 255 t.datetime "created_at", null: false t.datetime "updated_at", null: false t.text "settings", limit: 65535 end create_table "escalations", force: :cascade do |t| t.bigint "escalate_to_id", limit: 4 t.integer "escalate_after_sec", limit: 4 t.datetime "created_at", null: false t.datetime "updated_at", null: false t.bigint "escalation_series_id", limit: 4 end add_index "escalations", ["escalate_to_id"], name: "index_escalations_on_escalate_to_id", using: :btree add_index "escalations", ["escalation_series_id"], name: "index_escalations_on_escalation_series_id", using: :btree create_table "incident_events", force: :cascade do |t| t.bigint "incident_id", limit: 4 t.integer "kind", limit: 4 t.text "text", limit: 65535 t.datetime "created_at", null: false t.datetime "updated_at", null: false t.text "info", limit: 65535 end add_index "incident_events", ["incident_id"], name: "index_incident_events_on_incident_id", using: :btree create_table "incidents", force: :cascade do |t| t.string "subject", limit: 255 t.text "description", limit: 65535 t.bigint "topic_id", limit: 4 t.datetime "occured_at" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "status", limit: 4 end add_index "incidents", ["topic_id"], name: "index_incidents_on_topic_id", using: :btree create_table "maintenances", force: :cascade do |t| t.bigint "topic_id", limit: 4 t.datetime "start_time" t.datetime "end_time" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "filter", limit: 255 end add_index "maintenances", ["topic_id"], name: "index_maintenances_on_topic_id", using: :btree create_table "notifier_providers", force: :cascade do |t| t.string "name", limit: 255 t.integer "kind", limit: 4 t.text "settings", limit: 65535 t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "notifiers", force: :cascade do |t| t.text "settings", limit: 65535 t.integer "notify_after_sec", limit: 4 t.datetime "created_at", null: false t.datetime "updated_at", null: false t.bigint "user_id", limit: 4 t.bigint "provider_id", limit: 4 t.bigint "topic_id", limit: 4 t.boolean "enabled", default: true end add_index "notifiers", ["provider_id"], name: "index_notifiers_on_provider_id", using: :btree add_index "notifiers", ["topic_id"], name: "index_notifiers_on_topic_id", using: :btree add_index "notifiers", ["user_id"], name: "index_notifiers_on_user_id", using: :btree create_table "topics", force: :cascade do |t| t.string "name", limit: 255 t.integer "kind", limit: 4 t.datetime "created_at", null: false t.datetime "updated_at", null: false t.bigint "escalation_series_id", limit: 4 t.boolean "enabled", default: true end add_index "topics", ["escalation_series_id"], name: "index_topics_on_escalation_series_id", using: :btree create_table "users", force: :cascade do |t| t.string "name", limit: 255 t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "email", limit: 255 t.string "login_token", limit: 255 t.boolean "active", default: false t.string "provider", limit: 255 t.string "uid", limit: 255 t.text "credentials", limit: 65535 end add_foreign_key "escalations", "escalation_series" add_foreign_key "escalations", "users", column: "escalate_to_id" add_foreign_key "incident_events", "incidents" add_foreign_key "incidents", "topics" add_foreign_key "maintenances", "topics" add_foreign_key "notifiers", "notifier_providers", column: "provider_id" add_foreign_key "notifiers", "topics" add_foreign_key "notifiers", "users" add_foreign_key "topics", "escalation_series" end ================================================ FILE: db/seeds.rb ================================================ # This file should contain all the record creation needed to seed the database with its default values. # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). # # Examples: # # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) # Mayor.create(name: 'Emanuel', city: cities.first) ================================================ FILE: docker/puma.rb ================================================ require 'fileutils' listen_unix = ENV['LISTEN_UNIX'] if listen_unix bind "unix://#{listen_unix}" end environment ENV['RAILS_ENV'] port = ENV['PORT'] || 8080 bind "tcp://0.0.0.0:#{port}" ================================================ FILE: docker-compose.yml ================================================ version: '3' services: app: build: . ports: - 5000:5000 environment: DATABASE_URL: 'mysql2://database/waker_development?encoding=utf8mb4&collation=utf8mb4_unicode_ci' REDIS_HOST: cache depends_on: - database - cache database: image: mysql:5 environment: MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' MYSQL_DATABASE: waker_development cache: image: redis ================================================ FILE: lib/assets/.keep ================================================ ================================================ FILE: lib/tasks/.keep ================================================ ================================================ FILE: log/.keep ================================================ ================================================ FILE: public/404.html ================================================ The page you were looking for doesn't exist (404)

The page you were looking for doesn't exist.

You may have mistyped the address or the page may have moved.

If you are the application owner check the logs for more information.

================================================ FILE: public/422.html ================================================ The change you wanted was rejected (422)

The change you wanted was rejected.

Maybe you tried to change something you didn't have access to.

If you are the application owner check the logs for more information.

================================================ FILE: public/500.html ================================================ We're sorry, but something went wrong (500)

We're sorry, but something went wrong.

If you are the application owner check the logs for more information.

================================================ FILE: public/robots.txt ================================================ # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file # # To ban all spiders from the entire site uncomment the next two lines: User-agent: * Disallow: / ================================================ FILE: script/update-escalations-from-google-calendar ================================================ #!/usr/bin/env ruby require 'optparse' require 'json' require 'faraday' require 'google/api_client' require 'google/api_client/client_secrets' require 'google/api_client/auth/file_storage' require 'google/api_client/auth/installed_app' class Configuration < Struct.new(:client_secret_file, :credential_store_file, :calendar_name, :delimiter, :url, :escalation_series, :login_token) def self.from_command_line_options self.new.tap do |config| opt = OptionParser.new opt.on('--client-secret-file VAL') {|v| config.client_secret_file = v } opt.on('--credential-store-file VAL') {|v| config.credential_store_file = v } opt.on('--calendar-name VAL') {|v| config.calendar_name = v } opt.on('--delimiter VAL') {|v| config.delimiter = v } opt.on('--url VAL') {|v| config.url = v } opt.on('--escalation-series VAL') {|v| config.escalation_series = v } opt.on('--login-token VAL') {|v| config.login_token = v } opt.parse!(ARGV) config.validate! end end def validate! members.each do |member| unless self[member] raise "#{member} is required." end end end end class WakerClient InvalidResponseError = Class.new(StandardError) def initialize(url:, token:) @url = url @token = token end def users get('/users.json') end def escalation_series get('/escalation_series.json') end def escalations get('/escalations.json') end def update_escalation(escalation, escalate_to: nil) params = {} params['escalation[escalate_to_id]'] = escalate_to.id if escalate_to update("/escalations/#{escalation.id}.json", params) end private def conn @conn ||= Faraday.new(url: @url) do |faraday| faraday.request :url_encoded faraday.response :logger if ENV['DEBUG'] faraday.adapter Faraday.default_adapter end end def get(path) res = conn.get do |req| req.url path req.headers['X-Login-Token'] = @token end parse_response(res) end def update(path, params = {}) res = conn.put do |req| req.url path req.params = params req.headers['X-Login-Token'] = @token end parse_response(res) end def parse_response(res) unless 200 <= res.status && res.status < 300 raise InvalidResponseError, "status #{res.status}\nbody: #{res.body}" end body = JSON.parse(res.body) if body.is_a?(Array) body.map {|v| OpenStruct.new(v) } elsif body.is_a?(Hash) OpenStruct.new(body) end end end def setup_google_client client = Google::APIClient.new(application_name: 'Waker', application_version: '1.0.0') file_storage = Google::APIClient::FileStorage.new($config.credential_store_file) if file_storage.authorization.nil? client_secrets = Google::APIClient::ClientSecrets.load($config.client_secret_file) flow = Google::APIClient::InstalledAppFlow.new( client_id: client_secrets.client_id, client_secret: client_secrets.client_secret, scope: ['https://www.googleapis.com/auth/calendar.readonly'] ) client.authorization = flow.authorize(file_storage) else client.authorization = file_storage.authorization end return client end $config = Configuration.from_command_line_options client = setup_google_client calendar_api = client.discovered_api('calendar', 'v3') calendar = client.execute( api_method: calendar_api.calendar_list.list, parameters: {}, ).data.items.find do |cal| cal['summary'] == $config.calendar_name end events = client.execute( api_method: calendar_api.events.list, parameters: { 'calendarId' => calendar['id'], 'timeMax' => (Time.now + 1).strftime('%FT%T%:z'), 'timeMin' => (Time.now).strftime('%FT%T%:z'), 'singleEvents' => true, }, ).data.items events.each do |event| unless event['end']['dateTime'] && event['start']['dateTime'] raise "dateTime field is not found (The event may be all-day event)\n#{event}" end end events.sort! do |a, b| a['end']['dateTime'] - a['start']['dateTime'] <=> b['end']['dateTime'] - b['start']['dateTime'] end # shortest event event = events.first persons = event['summary'].split($config.delimiter).map(&:strip) client = WakerClient.new(url: $config.url, token: $config.login_token) escalation_series = client.escalation_series.find do |series| [series.id, series.name].include?($config.escalation_series) end escalations = client.escalations.select do |escalation| escalation.escalation_series_id == escalation_series.id end.sort_by do |escalation| escalation.escalate_after_sec end users = client.users persons.each_with_index do |name, i| user = users.find {|u| u.name == name } raise "#{name} user is not found." unless user escalation = escalations[i] client.update_escalation(escalation, escalate_to: user) end ================================================ FILE: spec/controllers/slack_controller_spec.rb ================================================ require 'rails_helper' RSpec.describe SlackController, type: :controller do describe "GET interactive" do it "raises token verification failed error" do expect(get: 'slack/interactive').not_to be_routable expect{get :interactive}.to raise_error(RuntimeError, /token verification failed/) end end end ================================================ FILE: spec/factories/escalation.rb ================================================ FactoryBot.define do factory :escalation do association :escalate_to, factory: :user escalate_after_sec { 60 } escalation_series end end ================================================ FILE: spec/factories/escalation_series.rb ================================================ FactoryBot.define do factory :escalation_series do name { "Infra" } end end ================================================ FILE: spec/factories/incident.rb ================================================ FactoryBot.define do factory :incident do id { 1 } topic subject { "mysql-01 is down" } description { "alert" } end end ================================================ FILE: spec/factories/incident_events.rb ================================================ FactoryBot.define do factory :incident_event do incident_id { 1 } kind { :opened } end end ================================================ FILE: spec/factories/maintenances.rb ================================================ FactoryBot.define do factory :maintenance do topic nil start_time { "2016-02-10 10:03:10" } end_time { "2016-02-10 10:03:10" } end end ================================================ FILE: spec/factories/notifier.rb ================================================ FactoryBot.define do factory :notifier do user association :provider, factory: :notifier_provider notify_after_sec { 60 } end end ================================================ FILE: spec/factories/notifier_provider.rb ================================================ FactoryBot.define do factory :notifier_provider do name { "Logger" } kind { :rails_logger } end end ================================================ FILE: spec/factories/topic.rb ================================================ FactoryBot.define do factory :topic do name { "Infra" } kind { "api" } escalation_series end end ================================================ FILE: spec/factories/user.rb ================================================ FactoryBot.define do factory :user do name { "Ryota Arai" } end end ================================================ FILE: spec/helpers/slack_helper_spec.rb ================================================ require 'rails_helper' # Specs in this file have access to a helper object that includes # the SlackHelper. For example: # # describe SlackHelper do # describe "string concat" do # it "concats two strings with spaces" do # expect(helper.concat_strings("this","that")).to eq("this that") # end # end # end RSpec.describe SlackHelper, type: :helper do pending "add some examples to (or delete) #{__FILE__}" end ================================================ FILE: spec/models/comment_spec.rb ================================================ require 'rails_helper' RSpec.describe Comment, type: :model do let(:incident) { create(:incident) } let(:user) { create(:user) } it 'should be valid' do comment = Comment.new( incident: incident, user: user, comment: 'Help Me', ) expect(comment).to be_valid end it 'should be invalid without incident' do comment = Comment.new( incident: nil, user: user, comment: 'Help Me', ) expect(comment).not_to be_valid end it 'should be invalid without user' do comment = Comment.new( incident: incident, user: nil, comment: 'Help Me', ) expect(comment).not_to be_valid end it 'should be invalid without comment' do comment = Comment.new( incident: incident, user: user, comment: '', ) expect(comment).not_to be_valid end end ================================================ FILE: spec/models/escalation_series_spec.rb ================================================ require 'rails_helper' require 'ostruct' require 'google/api_client' require 'json' RSpec.describe EscalationSeries, type: :model do describe "after_create enqueue" do before do # I understand that this is not the recommended method, but it is enough. allow_any_instance_of(Google::APIClient).to receive(:execute).and_return( OpenStruct.new( { data: OpenStruct.new({ items: [{ 'id' => 1, 'summary' => 'test_calendar' }] }) } ), OpenStruct.new( { data: OpenStruct.new({ items: [{ 'summary' => 'c,b,a', 'start' => { 'dateTime' => Time.now }, 'end' => { 'dateTime' => Time.now + 300 }, }] }) } ), ) end it "#update_escalations" do users = %w(a b c).map { |n| create(:user, name: n, credentials: { expires_at: Time.now.to_i + 300, token: "dummy", expires: nil, }) } series = create(:escalation_series, settings: { update_by: 'google_calendar', user_as_id: users.first.id, calendar: 'test_calendar', event_delimiter: ',', }) escalations = users.map { |u| create(:escalation, escalation_series: series, escalate_to: u ) } expect(series.update_escalations!).to be_truthy users.reverse.each_with_index do |u,i| expect(Escalation.find(escalations[i].id).escalate_to.id).to eq(u.id) end end end end ================================================ FILE: spec/models/incident_spec.rb ================================================ require 'rails_helper' RSpec.describe Incident, type: :model do describe "after_create enqueue" do it "creates escalation jobs" do series = create(:escalation_series) escalation = create(:escalation, escalation_series: series) notifier = create(:notifier, user: escalation.escalate_to) topic = create(:topic, escalation_series: series) incident = nil expect { incident = create(:incident, topic: topic) }.to change(EscalationWorker.jobs, :size).by(1).and change(NotificationWorker.jobs, :size).by(1) expect { EscalationWorker.drain }.to change(incident.events, :count).by(1) event = incident.events.last expect(event.kind).to eq('escalated') expect(event.escalation).to eq(escalation) expect(NotificationWorker.jobs.size).to eq(2) end end end ================================================ FILE: spec/models/maintenance_spec.rb ================================================ require 'rails_helper' RSpec.describe Maintenance, type: :model do pending "add some examples to (or delete) #{__FILE__}" end ================================================ FILE: spec/rails_helper.rb ================================================ # This file is copied to spec/ when you run 'rails generate rspec:install' ENV["RAILS_ENV"] ||= 'test' require 'spec_helper' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' # Add additional requires below this line. Rails is not loaded until this point! # Requires supporting ruby files with custom matchers and macros, etc, in # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are # run as spec files by default. This means that files in spec/support that end # in _spec.rb will both be required and run as specs, causing the specs to be # run twice. It is recommended that you do not name files matching this glob to # end with _spec.rb. You can configure this pattern with the --pattern # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. # # The following line is provided for convenience purposes. It has the downside # of increasing the boot-up time by auto-requiring all files in the support # directory. Alternatively, in the individual `*_spec.rb` files, manually # require only the support files necessary. # Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } # Checks for pending migrations before tests are run. # If you are not using ActiveRecord, you can remove this line. ActiveRecord::Migration.maintain_test_schema! RSpec.configure do |config| # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures config.fixture_path = "#{::Rails.root}/spec/fixtures" # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false # instead of true. config.use_transactional_fixtures = true # RSpec Rails can automatically mix in different behaviours to your tests # based on their file location, for example enabling you to call `get` and # `post` in specs under `spec/controllers`. # # You can disable this behaviour by removing the line below, and instead # explicitly tag your specs with their type, e.g.: # # RSpec.describe UsersController, :type => :controller do # # ... # end # # The different available types are documented in the features, such as in # https://relishapp.com/rspec/rspec-rails/docs config.infer_spec_type_from_file_location! end ================================================ FILE: spec/requests/incident_envets_spec.rb ================================================ require 'rails_helper' RSpec.describe "IncidentEvent", type: :request do describe "POST /incident_events/:id/twilio" do let(:incident) { create(:incident) } let(:event) { create(:incident_event, incident_id: incident.id) } describe 'without params[:Digits]' do it "works!" do post "/incident_events/#{event.id}/twilio" expect(response).to have_http_status(200) end end describe 'with params[:Digits]' do before do post "/incident_events/#{event.id}/twilio", params: { Digits: digit} incident.reload end context '1' do let(:digit) { 1 } it "acknowledged" do expect(response).to have_http_status(200) expect(incident.status).to eq 'acknowledged' end end context '2' do let(:digit) { 2 } it "resolved" do expect(response).to have_http_status(200) expect(incident.status).to eq 'resolved' end end end end end ================================================ FILE: spec/requests/maintenances_spec.rb ================================================ require 'rails_helper' RSpec.describe "Maintenances", type: :request do describe "GET /maintenances" do it "works! (now write some real specs)" do get maintenances_path expect(response).to have_http_status(200) end end end ================================================ FILE: spec/requests/topics_spec.rb ================================================ require "rails_helper" RSpec.describe "Topics", type: :request do describe "POST /topics/1/mailgun" do let(:subject) { 'New Alert' } let(:body) { "Your server is on fire" } it "creates a new incident" do topic = create(:topic) post mailgun_topic_path(topic, format: :json), params: { 'subject' => subject, 'body-plain' => body, } expect(response).to have_http_status(200) incident = Incident.last expect(incident.subject).to eq(subject) expect(incident.description).to eq(body) end end describe "POST /topics/1/slack" do it "creates a new incident" do topic = create(:topic) post slack_topic_path(topic, format: :json), params: { 'channel_name' => 'test_channel', 'user_name' => 'test_user', 'text' => 'help me', } expect(response).to have_http_status(200) incident = Incident.last expect(incident.subject).to eq("escalation from slack,channel name:test_channel help me") expect(incident.description).to eq("channel:test_channel user:test_user help me") end end end ================================================ FILE: spec/spec_helper.rb ================================================ require 'simplecov' SimpleCov.start 'rails' do add_filter '/vendor/' add_filter '/.bundle/' end # This file was generated by the `rails generate rspec:install` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # The generated `.rspec` file contains `--require spec_helper` which will cause this # file to always be loaded, without a need to explicitly require it in any files. # # Given that it is always loaded, you are encouraged to keep this file as # light-weight as possible. Requiring heavyweight dependencies from this file # will add to the boot time of your test suite on EVERY test run, even for an # individual file that may not need all of that loaded. Instead, consider making # a separate helper file that requires the additional dependencies and performs # the additional setup, and require it from the spec files that actually need it. # # The `.rspec` file also contains a few flags that are not defaults but that # users commonly want. # # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| config.before :suite do DatabaseRewinder.clean_all end config.after :each do DatabaseRewinder.clean end # rspec-expectations config goes here. You can use an alternate # assertion/expectation library such as wrong or the stdlib/minitest # assertions if you prefer. config.expect_with :rspec do |expectations| # This option will default to `true` in RSpec 4. It makes the `description` # and `failure_message` of custom matchers include text for helper methods # defined using `chain`, e.g.: # be_bigger_than(2).and_smaller_than(4).description # # => "be bigger than 2 and smaller than 4" # ...rather than: # # => "be bigger than 2" expectations.include_chain_clauses_in_custom_matcher_descriptions = true end # rspec-mocks config goes here. You can use an alternate test double # library (such as bogus or mocha) by changing the `mock_with` option here. config.mock_with :rspec do |mocks| # Prevents you from mocking or stubbing a method that does not exist on # a real object. This is generally recommended, and will default to # `true` in RSpec 4. mocks.verify_partial_doubles = true end # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 config.order = :random # Seed global randomization in this process using the `--seed` CLI option. # Setting this allows you to use `--seed` to deterministically reproduce # test failures related to randomization by passing the same `--seed` value # as the one that triggered the failure. Kernel.srand config.seed # The settings below are suggested to provide a good initial experience # with RSpec, but feel free to customize to your heart's content. =begin # These two settings work together to allow you to limit a spec run # to individual examples or groups you care about by tagging them with # `:focus` metadata. When nothing is tagged with `:focus`, all examples # get run. config.filter_run :focus config.run_all_when_everything_filtered = true # Limits the available syntax to the non-monkey patched syntax that is recommended. # For more details, see: # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching config.disable_monkey_patching! # Many RSpec users commonly either run the entire suite or an individual # file, and it's useful to allow more verbose output when running an # individual spec file. if config.files_to_run.one? # Use the documentation formatter for detailed output, # unless a formatter has already been configured # (e.g. via a command-line flag). config.default_formatter = 'doc' end # Print the 10 slowest examples and example groups at the # end of the spec run, to help surface which specs are running # particularly slow. config.profile_examples = 10 =end end ================================================ FILE: spec/support/factory_girl.rb ================================================ RSpec.configure do |config| config.include FactoryBot::Syntax::Methods config.before(:suite) do begin FactoryBot.lint ensure DatabaseRewinder.clean_all end end end ================================================ FILE: spec/support/sidekiq.rb ================================================ require 'sidekiq/testing' RSpec.configure do |config| config.before(:each) do Sidekiq::Worker.clear_all end end ================================================ FILE: spec/views/slack/interactive.html.erb_spec.rb ================================================ require 'rails_helper' RSpec.describe "slack/interactive.html.erb", type: :view do pending "add some examples to (or delete) #{__FILE__}" end ================================================ FILE: vendor/assets/javascripts/.keep ================================================ ================================================ FILE: vendor/assets/stylesheets/.keep ================================================