21
1
Fork 0
mirror of https://github.com/Evolix/chexpire.git synced 2024-05-07 02:58:39 +02:00

Merge pull request #91 from Evolix/notifications-templates

Notifications templates
This commit is contained in:
Colin Darie 2018-08-31 10:17:10 +02:00 committed by GitHub
commit 3aa1cc376e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 950 additions and 316 deletions

View file

@ -15,6 +15,18 @@ class ApplicationController < ActionController::Base
devise_parameter_sanitizer.permit(:account_update, keys: [:notifications_enabled, :locale]) devise_parameter_sanitizer.permit(:account_update, keys: [:notifications_enabled, :locale])
end end
def after_sign_in_path_for(_resource)
checks_path
end
def after_sign_up_path_for(_resource)
checks_path
end
def after_sign_out_path_for(_resource)
root_path
end
def user_not_authorized def user_not_authorized
flash[:alert] = I18n.t("user_not_authorized", scope: :flashes) flash[:alert] = I18n.t("user_not_authorized", scope: :flashes)
redirect_to(request.referrer || root_path) redirect_to(request.referrer || root_path)

View file

@ -1,7 +1,7 @@
# Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Jeremy Lecour <jlecour@evolix.fr>, 2018 Evolix <info@evolix.fr> # Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Jeremy Lecour <jlecour@evolix.fr>, 2018 Evolix <info@evolix.fr>
# License: GNU AGPL-3+ (see full text in LICENSE file) # License: GNU AGPL-3+ (see full text in LICENSE file)
class ChecksController < ApplicationController class ChecksController < ApplicationController # rubocop:disable Metrics/ClassLength
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_check, except: [:index, :new, :create, :supports] before_action :set_check, except: [:index, :new, :create, :supports]
after_action :verify_authorized, except: :index after_action :verify_authorized, except: :index
@ -29,16 +29,17 @@ class ChecksController < ApplicationController
build_empty_notification build_empty_notification
end end
def create def create # rubocop:disable Metrics/AbcSize
@check = Check.new(new_check_params) @check = Check.new(new_check_params.merge(user: current_user))
@check.user = current_user
authorize @check authorize @check
if @check.save if @check.save
flash[:notice] = t(".saved") flash[:notice] = t("checks.created", scope: :flashes)
redirect_to checks_path redirect_to checks_path
else else
flash.now[:alert] = t(".invalid") flash.now[:alert] = t("checks.invalid", scope: :flashes)
fill_or_build_new_notification
render :new render :new
end end
end end
@ -49,11 +50,12 @@ class ChecksController < ApplicationController
def update def update
if @check.update(update_check_params) if @check.update(update_check_params)
flash[:notice] = "Your check has been updated." flash[:notice] = t("checks.updated", scope: :flashes)
redirect_to checks_path redirect_to checks_path
else else
flash.now[:alert] = "An error occured." flash.now[:alert] = t("checks.invalid", scope: :flashes)
build_empty_notification
fill_or_build_new_notification
render :edit render :edit
end end
end end
@ -61,7 +63,7 @@ class ChecksController < ApplicationController
def destroy def destroy
@check.destroy! @check.destroy!
flash[:notice] = "Your check has been destroyed." flash[:notice] = t("checks.destroyed", scope: :flashes)
redirect_to checks_path redirect_to checks_path
end end
@ -86,13 +88,39 @@ class ChecksController < ApplicationController
end end
def check_params(*others) def check_params(*others)
params.require(:check) permitted = params.require(:check)
.permit(:domain, :domain_expires_at, :comment, :vendor, :round_robin, *others, .permit(:domain, :domain_expires_at, :comment, :vendor,
notifications_attributes: [:id, :channel, :recipient, :interval]) :round_robin, *others,
notification_ids: [],
notifications_attributes: [:channel, :label, :recipient, :interval])
merge_current_user!(permitted)
permitted
end
def merge_current_user!(permitted)
return unless permitted[:notifications_attributes].present?
permitted[:notifications_attributes].each_pair do |_key, attributes|
attributes.merge!(user: current_user)
end
end end
def build_empty_notification def build_empty_notification
@check.notifications.build @new_notification = @check.notifications.build
@new_notification.recipient = current_user.email
end
def fill_or_build_new_notification
last_notification = @check.notifications.last
# user has filled a new notification: we use it for the form
if last_notification.new_record?
@new_notification = last_notification
else # otherwise, set a new empty notification
build_empty_notification
end
end end
def current_sort def current_sort

View file

@ -3,45 +3,61 @@
class NotificationsController < ApplicationController class NotificationsController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_notification, except: [:create] before_action :set_notification, except: [:index, :new, :create]
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
def index
@notifications = policy_scope(Notification).order(checks_count: :desc)
end
def new
@notification = Notification.new
authorize @notification
@notification.recipient = current_user.email
end
def create def create
check = Check.find(params[:check_id]) @notification = Notification.new(notification_params)
@notification = check.notifications.build(notification_params) @notification.user = current_user
authorize @notification authorize @notification
if @notification.save if @notification.save
flash[:notice] = "Your notification has been saved." flash[:notice] = t("notifications.created", scope: :flashes)
redirect_to check_path redirect_to notifications_path
else else
flash.now[:alert] = "An error occured." flash.now[:alert] = t("notifications.invalid", scope: :flashes)
render "checks/edit" render :new
end
end
def edit; end
def update
if @notification.update(notification_params)
flash[:notice] = t("notifications.updated", scope: :flashes)
redirect_to notifications_path
else
flash.now[:alert] = t("notifications.error", scope: :flashes)
render :edit
end end
end end
def destroy def destroy
@notification.destroy! @notification.destroy!
respond_to do |format| flash[:notice] = t("notifications.destroyed", scope: :flashes)
format.js redirect_to notifications_path
end
end end
private private
def set_notification def set_notification
# joins the check because policy use the check relation @notification = Notification.find(params[:id])
@notification = Notification
.joins(:check)
.find_by!(id: params[:id], check_id: params[:check_id])
authorize @notification authorize @notification
end end
def notification_params def notification_params
params.require(:notification).permit(:channel, :recipient, :interval) params.require(:notification).permit(:label, :recipient, :interval)
end
def check_path
edit_check_path(check_id: params[:check_id])
end end
end end

View file

@ -2,5 +2,7 @@
# License: GNU AGPL-3+ (see full text in LICENSE file) # License: GNU AGPL-3+ (see full text in LICENSE file)
class PagesController < ApplicationController class PagesController < ApplicationController
def home; end def home
redirect_to checks_path if user_signed_in?
end
end end

View file

@ -0,0 +1,51 @@
// Taken from Bootstrap 4 documentation
.bd-callout {
padding: 1.25rem;
margin-top: 1.25rem;
margin-bottom: 1.25rem;
border: 1px solid #eee;
border-left-width: .25rem;
border-radius: .25rem
}
.bd-callout h4 {
margin-top: 0;
margin-bottom: .25rem
}
.bd-callout p:last-child {
margin-bottom: 0
}
.bd-callout code {
border-radius: .25rem
}
.bd-callout+.bd-callout {
margin-top: -.25rem
}
.bd-callout-info {
border-left-color: #5bc0de
}
.bd-callout-info h4 {
color: #5bc0de
}
.bd-callout-warning {
border-left-color: #f0ad4e
}
.bd-callout-warning h4 {
color: #f0ad4e
}
.bd-callout-danger {
border-left-color: #d9534f
}
.bd-callout-danger h4 {
color: #d9534f
}

View file

@ -0,0 +1,8 @@
// Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr>
// License: GNU AGPL-3+ (see full text in LICENSE file)
.notifications-table {
.action a {
color: black;
}
}

View file

@ -5,5 +5,7 @@
@import '~bootstrap/scss/bootstrap'; @import '~bootstrap/scss/bootstrap';
@import 'layout'; @import 'layout';
@import 'icons'; @import 'icons';
@import 'components/callouts';
@import 'components/users'; @import 'components/users';
@import 'components/checks'; @import 'components/checks';
@import 'components/notifications';

View file

@ -0,0 +1,5 @@
# Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr>
# License: GNU AGPL-3+ (see full text in LICENSE file)
module CheckNotificationsHelper
end

View file

@ -1,12 +1,9 @@
# Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr>
# License: GNU AGPL-3+ (see full text in LICENSE file)
module NotificationsHelper module NotificationsHelper
def many_channels_available? def many_channels_available?
Notification.channels.many? Notification.channels.many?
end end
def recipient_col_class def notification_variable_col_class
many_channels_available? ? "col-md-7" : "col-md-9" many_channels_available? ? "col-md-4" : "col-md-5"
end end
end end

View file

@ -5,8 +5,9 @@ class NotificationsMailer < ApplicationMailer
helper :application helper :application
before_action except: :recurrent_failures do before_action except: :recurrent_failures do
@notification = params.fetch(:notification) @check_notification = params.fetch(:check_notification)
@check = @notification.check @check = @check_notification.check
@notification = @check_notification.notification
end end
def domain_expires_soon def domain_expires_soon

View file

@ -34,10 +34,12 @@
class Check < ApplicationRecord class Check < ApplicationRecord
belongs_to :user belongs_to :user
has_many :logs, class_name: "CheckLog", dependent: :destroy has_many :logs, class_name: "CheckLog", dependent: :destroy
has_many :notifications, validate: true, dependent: :destroy has_many :check_notifications, dependent: :destroy
has_many :notifications, -> { order(checks_count: :desc) },
through: :check_notifications, validate: true
accepts_nested_attributes_for :notifications, accepts_nested_attributes_for :notifications,
allow_destroy: true, reject_if: lambda { |att| att["interval"].blank? }
reject_if: lambda { |at| at["recipient"].blank? && at["interval"].blank? }
enum kind: [:domain, :ssl] enum kind: [:domain, :ssl]
enum mode: [:auto, :manual] enum mode: [:auto, :manual]
@ -58,7 +60,7 @@ class Check < ApplicationRecord
before_save :reset_consecutive_failures before_save :reset_consecutive_failures
before_save :set_mode before_save :set_mode
after_update :reset_notifications after_update :reset_check_notifications
after_save :enqueue_sync after_save :enqueue_sync
scope :active, -> { where(active: true) } scope :active, -> { where(active: true) }
@ -119,10 +121,10 @@ class Check < ApplicationRecord
ResyncJob.perform_later(id) ResyncJob.perform_later(id)
end end
def reset_notifications def reset_check_notifications
return unless (saved_changes.keys & %w[domain domain_expires_at]).present? return unless (saved_changes.keys & %w[domain domain_expires_at]).present?
notifications.each(&:reset!) check_notifications.each(&:reset!)
end end
def reset_consecutive_failures def reset_consecutive_failures

View file

@ -0,0 +1,46 @@
# == Schema Information
#
# Table name: check_notifications
#
# id :bigint(8) not null, primary key
# sent_at :datetime
# status :integer default("pending"), not null
# created_at :datetime not null
# updated_at :datetime not null
# check_id :bigint(8)
# notification_id :bigint(8)
#
# Indexes
#
# index_check_notifications_on_check_id (check_id)
# index_check_notifications_on_notification_id (notification_id)
#
# Foreign Keys
#
# fk_rails_... (check_id => checks.id)
# fk_rails_... (notification_id => notifications.id)
#
# Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr>
# License: GNU AGPL-3+ (see full text in LICENSE file)
class CheckNotification < ApplicationRecord
belongs_to :check
belongs_to :notification, counter_cache: :checks_count
enum status: [:pending, :ongoing, :succeed, :failed]
scope :active_check, -> { Check.active }
scope :check_last_run_failed, -> { Check.last_run_failed }
def pending!
self.sent_at = nil
super
end
alias reset! pending!
def ongoing!
self.sent_at = Time.now
super
end
end

View file

@ -1,50 +1,45 @@
# Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Jeremy Lecour <jlecour@evolix.fr>, 2018 Evolix <info@evolix.fr> # Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Jeremy Lecour <jlecour@evolix.fr>, 2018 Evolix <info@evolix.fr>
# License: GNU AGPL-3+ (see full text in LICENSE file) # License: GNU AGPL-3+ (see full text in LICENSE file)
# == Schema Information # == Schema Information
# #
# Table name: notifications # Table name: notifications
# #
# id :bigint(8) not null, primary key # id :bigint(8) not null, primary key
# channel :integer default("email"), not null # channel :integer default("email"), not null
# interval :integer not null # checks_count :integer default(0), not null
# recipient :string(255) not null # interval :integer not null
# sent_at :datetime # label :string(255)
# status :integer default("pending"), not null # recipient :string(255) not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# check_id :bigint(8) # check_id :bigint(8)
# user_id :bigint(8)
# #
# Indexes # Indexes
# #
# index_notifications_on_check_id (check_id) # index_notifications_on_check_id (check_id)
# index_notifications_on_user_id (user_id)
# #
# Foreign Keys # Foreign Keys
# #
# fk_rails_... (check_id => checks.id) # fk_rails_... (check_id => checks.id)
# fk_rails_... (user_id => users.id)
# #
class Notification < ApplicationRecord class Notification < ApplicationRecord
belongs_to :check belongs_to :user
has_many :check_notifications, dependent: :destroy
has_many :checks, -> { order(domain_expires_at: :asc) }, through: :check_notifications
enum channel: [:email] enum channel: [:email]
enum status: [:pending, :ongoing, :succeed, :failed]
validates :channel, presence: true validates :channel, presence: true
validates :interval, numericality: { only_integer: true, greater_than_or_equal_to: 1 } validates :interval, numericality: { only_integer: true, greater_than_or_equal_to: 1 }
validates :recipient, presence: true validates :recipient, presence: true
scope :active_check, -> { Check.active } def notifical_label
scope :check_last_run_failed, -> { Check.last_run_failed } return label if label.present?
def pending! "#{recipient} (#{interval})"
self.sent_at = nil
super
end
alias reset! pending!
def ongoing!
self.sent_at = Time.now
super
end end
end end

View file

@ -39,7 +39,8 @@ class User < ApplicationRecord
devise :database_authenticatable, :registerable, devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :confirmable :recoverable, :rememberable, :validatable, :confirmable
has_many :checks has_many :checks, dependent: :destroy
has_many :notifications, dependent: :destroy
validates :tos_accepted, acceptance: true validates :tos_accepted, acceptance: true
validates :locale, inclusion: { in: I18n.available_locales.map(&:to_s) } validates :locale, inclusion: { in: I18n.available_locales.map(&:to_s) }

View file

@ -0,0 +1,24 @@
# Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr>
# License: GNU AGPL-3+ (see full text in LICENSE file)
class CheckNotificationPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.joins(:check).where(checks: { user: user })
end
end
def destroy?
check_owner?
end
def show?
false
end
private
def check_owner?
record.check.user == user
end
end

View file

@ -4,21 +4,25 @@
class NotificationPolicy < ApplicationPolicy class NotificationPolicy < ApplicationPolicy
class Scope < Scope class Scope < Scope
def resolve def resolve
scope.joins(:check).where(checks: { user: user }) scope.where(user: user)
end end
end end
def destroy? def create?
check_owner? true
end end
def show? def update?
false owner?
end
def destroy?
owner?
end end
private private
def check_owner? def owner?
record.check.user == user record.user == user
end end
end end

View file

@ -4,26 +4,26 @@
module Notifier module Notifier
module Channels module Channels
class Base class Base
def notify(notification) # rubocop:disable Metrics/MethodLength def notify(check_notification) # rubocop:disable Metrics/MethodLength
return unless supports?(notification) return unless supports?(check_notification)
notification.ongoing! check_notification.ongoing!
case notification.check.kind.to_sym case check_notification.check.kind.to_sym
when :domain when :domain
domain_notify_expires_soon(notification) domain_notify_expires_soon(check_notification)
when :ssl when :ssl
ssl_notify_expires_soon(notification) ssl_notify_expires_soon(check_notification)
else else
fail ArgumentError, fail ArgumentError,
"Invalid notification for check kind `#{notification.check.kind}`." "Invalid notification for check kind `#{check_notification.check.kind}`."
end end
end end
private private
# :nocov: # :nocov:
def supports?(_notification) def supports?(_check_notification)
fail NotImplementedError, fail NotImplementedError,
"#{self.class.name} channel did not implemented method #{__callee__}" "#{self.class.name} channel did not implemented method #{__callee__}"
end end

View file

@ -11,17 +11,21 @@ module Notifier
protected protected
def supports?(_notification) def supports?(_check_notification)
true true
end end
# Expiration notifications # Expiration notifications
def domain_notify_expires_soon(notification) def domain_notify_expires_soon(check_notification)
NotificationsMailer.with(notification: notification).domain_expires_soon.deliver_now NotificationsMailer.with(check_notification: check_notification)
.domain_expires_soon
.deliver_now
end end
def ssl_notify_expires_soon(notification) def ssl_notify_expires_soon(_notification)
NotificationsMailer.with(notification: notification).ssl_expires_soon.deliver_now NotificationsMailer.with(check_notification: check_notification)
.ssl_expires_soon
.deliver_now
end end
end end
end end

View file

@ -19,8 +19,8 @@ module Notifier
end end
def process_expires_soon def process_expires_soon
resolver.notifications_expiring_soon.find_each do |notification| resolver.notifications_expiring_soon.find_each do |check_notification|
notifier_channel_for(notification).notify(notification) notifier_channel_for(check_notification.notification).notify(check_notification)
sleep configuration.interval sleep configuration.interval
end end

View file

@ -21,8 +21,8 @@ module Notifier
private private
def scope def scope
Notification CheckNotification
.includes(:check) .includes(:check, :notification)
.where(status: [:pending, :failed]) .where(status: [:pending, :failed])
.merge(Check.active) .merge(Check.active)
.where.not(checks: { user: ignore_users }) .where.not(checks: { user: ignore_users })

View file

@ -41,18 +41,17 @@
<%= f.input :active %> <%= f.input :active %>
<% end %> <% end %>
<h2 class="mt-5"><%= t(".notifications") %></h2> <p class="mt-5 bd-callout bd-callout-info"><%= t(".notifications_hint") %></p>
<p class="alert alert-light"><%= t(".notifications_hint") %></p>
<%- check.notifications.each_with_index do |notification, index| %> <%= f.association :notifications, as: :check_boxes,
<div data-notification-id="<%= notification.id %>"> collection: policy_scope(Notification),
<%= f.fields_for :notifications, notification do |nf| %> label_method: :notifical_label,
<%= render "notifications/nested_form_headers", f: nf if index.zero? %> label_text: false %>
<%= render "notifications/nested_form", f: nf, check: check %>
<% end %> <%= f.fields_for :notifications, new_notification do |nf| %>
</div> <%= render "notifications/nested_form_headers", f: nf %>
<%= render "notifications/nested_form", f: nf, notification: new_notification %>
<% end %> <% end %>
<%= f.button :submit, class: "btn-primary mt-5" %> <%= f.button :submit, class: "btn-primary mt-5" %>
<% end %> <% end %>

View file

@ -7,16 +7,20 @@
<th scope="col"></th> <th scope="col"></th>
<th scope="col"> <th scope="col">
<%= t(".th.domain") %> <%= t(".th.domain") %>
<span class="sort-links mx-sm-2 text-nowrap"> <% unless defined?(skip_sort) %>
<%== checks_sort_links(:domain) %> <span class="sort-links mx-sm-2 text-nowrap">
</span> <%== checks_sort_links(:domain) %>
</span>
<% end %>
</th> </th>
<th scope="col"> <th scope="col">
<span class="d-none d-sm-inline"><%= t(".th.expiry_date") %></span> <span class="d-none d-sm-inline"><%= t(".th.expiry_date") %></span>
<span class="d-inline d-sm-none"><%= t(".th.expiry_date_short") %></span> <span class="d-inline d-sm-none"><%= t(".th.expiry_date_short") %></span>
<span class="sort-links mx-sm-2 text-nowrap"> <% unless defined?(skip_sort) %>
<%== checks_sort_links(:domain_expires_at) %> <span class="sort-links mx-sm-2 text-nowrap">
</span> <%== checks_sort_links(:domain_expires_at) %>
</span>
<% end %>
</th> </th>
<th scope="col" class="text-right"><%= t(".th.edit") %></th> <th scope="col" class="text-right"><%= t(".th.edit") %></th>
</tr> </tr>
@ -48,4 +52,4 @@
</table> </table>
</div> </div>
<%= paginate @checks %> <%= paginate checks unless defined?(skip_pagination)%>

View file

@ -3,16 +3,16 @@
<div class="container"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-12 col-lg-10"> <div class="col-12 col-lg-10">
<h1>Edit your check</h1> <h1><%= t(".title") %></h1>
<%= render "form", check: @check %> <%= render "form", check: @check, new_notification: @new_notification %>
</div> </div>
</div> </div>
<div class="row mt-5 justify-content-center"> <div class="row mt-5 justify-content-center">
<div class="col-12 col-lg-10"> <div class="col-12 col-lg-10">
<%= button_to("Delete", check_path(@check), class: "btn btn-danger", method: :delete, <%= button_to(t("helpers.submit.check.delete"), check_path(@check), class: "btn btn-danger", method: :delete,
data: { confirm: "Are you sure ?" }) %> data: { confirm: t(".destroy_confirmation") }) %>
</div> </div>
</div> </div>
</div> </div>

View file

@ -5,7 +5,7 @@
<div class="col-12 col-lg-10"> <div class="col-12 col-lg-10">
<h1><%= t(".#{@check.kind}.title") %></h1> <h1><%= t(".#{@check.kind}.title") %></h1>
<%= render "form", check: @check %> <%= render "form", check: @check, new_notification: @new_notification %>
</div> </div>
</div> </div>
</div> </div>

View file

@ -9,6 +9,9 @@
<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
</head> </head>
<body> <body>

View file

@ -0,0 +1,13 @@
<% # Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr> %>
<% # License: GNU AGPL-3+ (see full text in LICENSE file) %>
<%= simple_form_for(notification) do |f| %>
<%= f.input :recipient, as: :email,
input_html: { autocapitalize: :none, autocorrect: :off }
%>
<%= f.input :interval, as: :integer, required: true %>
<%= f.input :label, hint: t(".label_hint") %>
<%= f.button :submit, class: "btn-primary mt-3" %>
<% end %>

View file

@ -12,20 +12,16 @@
</div> </div>
<% end %> <% end %>
<div class="form-group <%= recipient_col_class %>"> <div class="form-group <%= notification_variable_col_class %>">
<%= f.input :recipient, as: :email, label: false %> <%= f.input :recipient, as: :email, label: false %>
</div> </div>
<div class="form-group col-md-2"> <div class="form-group col-md-2">
<%= f.input :interval, as: :integer, label: false %> <%= f.input :interval, as: :integer, label: false, required: true %>
</div> </div>
<div class="form-group col-md-1"> <div class="form-group <%= notification_variable_col_class %>">
<% if f.object.persisted? %> <%= f.input :label, label: false %>
<%= link_to check_notification_path(check, f.object), method: :delete, remote: true, class: "btn btn-danger" do %>
<%== Octicons::Octicon.new("x", width: 15, height: 20).to_svg %>
<% end %>
<% end %>
</div> </div>
</div> </div>
</fieldset> </fieldset>

View file

@ -7,11 +7,15 @@
</div> </div>
<% end %> <% end %>
<div class="<%= recipient_col_class %>"> <div class="<%= notification_variable_col_class %>">
<%= f.label :recipient %> <%= f.label :recipient %>
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
<%= f.label :interval %> <%= f.label :interval, required: true %>
</div>
<div class="<%= notification_variable_col_class %>">
<%= f.label :label %>
</div> </div>
</div> </div>

View file

@ -0,0 +1,46 @@
<% # Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr> %>
<% # License: GNU AGPL-3+ (see full text in LICENSE file) %>
<div class="mb-4 table-responsive">
<table class="table notifications-table">
<thead>
<tr>
<th scope="col">
<%= Notification.human_attribute_name("label") %>
</th>
<th scope="col">
<%= Notification.human_attribute_name("recipient") %>
</th>
<th scope="col">
<%= Notification.human_attribute_name("interval") %>
</th>
<th scope="col">
<%= Notification.human_attribute_name("checks_count") %>
</th>
<th scope="col" class="text-right"><%= t(".th.edit") %></th>
</tr>
</thead>
<tbody>
<% notifications.each do |notification| %>
<tr class="notification-row">
<td>
<strong><%= notification.label %></strong>
</td>
<td>
<%= notification.recipient %>
</td>
<td>
<%= t(".interval_in_days", count: notification.interval) %>
</td>
<td>
<%= notification.checks_count %>
</td>
<td class="action text-right">
<%= link_to edit_notification_path(notification), "data-turbolinks" => false do %>
<%== Octicons::Octicon.new("pencil").to_svg %>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>

View file

@ -1,3 +0,0 @@
<% # Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr> %>
<% # License: GNU AGPL-3+ (see full text in LICENSE file) %>
document.querySelector("[data-notification-id='<%= @notification.id %>']").remove();

View file

@ -0,0 +1,27 @@
<% # Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr> %>
<% # License: GNU AGPL-3+ (see full text in LICENSE file) %>
<div class="container">
<div class="row justify-content-center">
<div class="col-12 col-lg-10">
<h1><%= t(".title") %></h1>
<%= render "form", notification: @notification %>
</div>
</div>
<div class="row mt-5 justify-content-center">
<div class="col-12 col-lg-10">
<h4><%= t(".checks", count: @notification.checks_count) %></h4>
<% if @notification.checks_count.positive? %>
<%= render "checks/table", checks: @notification.checks, skip_sort: true, skip_pagination: true %>
<% end %>
</div>
</div>
<div class="row mt-5 mb-3 justify-content-center">
<div class="col-12 col-lg-10">
<%= button_to(t("helpers.submit.notification.delete"), notification_path(@notification), class: "btn btn-danger", method: :delete,
data: { confirm: t(".destroy_confirmation", count: @notification.checks_count) }) %>
</div>
</div>
</div>

View file

@ -0,0 +1,22 @@
<% # Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr> %>
<% # License: GNU AGPL-3+ (see full text in LICENSE file) %>
<div class="container-fluid">
<div class="row justify-content-center">
<div class="col-12 col-lg-10 col-xl-9">
<% if @notifications.empty? %>
<div class="alert alert-info">
<%= t(".no_notification_yet_html", new_path: new_notification_path) %>
</div>
<% else %>
<div class="row justify-content-md-end">
<%= link_to("Ajouter une notification", new_notification_path, class: "btn btn-primary") %>
</div>
<h1 class="mb-3 mb-sm-5"><%= t(".title") %></h1>
<%= render "table", notifications: @notifications %>
<% end %>
<div class="bd-callout bd-callout-info">
<p><%= t(".explanation") %></p>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,11 @@
<% # Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr> %>
<% # License: GNU AGPL-3+ (see full text in LICENSE file) %>
<div class="container">
<div class="row justify-content-center">
<div class="col-12 col-lg-10">
<h1><%= t(".title") %></h1>
<%= render "form", notification: @notification %>
</div>
</div>
</div>

View file

@ -2,7 +2,7 @@
<% # License: GNU AGPL-3+ (see full text in LICENSE file) %> <% # License: GNU AGPL-3+ (see full text in LICENSE file) %>
<nav class="navbar navbar-expand-lg navbar-dark justify-content-between"> <nav class="navbar navbar-expand-lg navbar-dark justify-content-between">
<div class="container-fluid"> <div class="container-fluid">
<%= link_to root_path , class: "navbar-brand" do %> <%= link_to (user_signed_in? ? checks_path : root_path), class: "navbar-brand" do %>
<%= image_tag 'chexpire10.png', width: 200, alt: 'chexpire logo' %> <%= image_tag 'chexpire10.png', width: 200, alt: 'chexpire logo' %>
<% end %> <% end %>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
@ -24,6 +24,9 @@
<li class="nav-item"> <li class="nav-item">
<%= link_to(t(".GitHub"), "https://github.com/Evolix/chexpire", class: "nav-link") %> <%= link_to(t(".GitHub"), "https://github.com/Evolix/chexpire", class: "nav-link") %>
</li> </li>
<li class="nav-item">
<%= link_to(t(".my_notifications"), notifications_path, class: "nav-link") %>
</li>
<% end %> <% end %>
</ul> </ul>

View file

@ -33,8 +33,29 @@ en:
notifications: notifications:
recipient: john@example.com recipient: john@example.com
helpers:
submit:
check:
create: "Create Check"
update: "Update Check"
delete: "Destroy Check"
notification:
create: "Create Notification"
update: "Update Notification"
delete: "Destroy Notification"
flashes: flashes:
user_not_authorized: "You are not authorized to access to this resource." user_not_authorized: "You are not authorized to access to this resource."
checks:
create: The check has been saved.
updated: The check has been updated.
invalid: Please check the form.
destroyed: The check has been destroyed.
notifications:
created: The notification has been created.
updated: The notification has been updated."
destroyed: The notification has been destroyed.
invalid: Please check the form.
notifications_mailer: notifications_mailer:
domain_expires_soon: domain_expires_soon:
@ -68,6 +89,7 @@ en:
my_checks: "My checks" my_checks: "My checks"
new_domain_check: "New domain check" new_domain_check: "New domain check"
new_ssl_check: "New SSL check" new_ssl_check: "New SSL check"
my_notifications: "My notifications"
sign_up: "Sign up" sign_up: "Sign up"
sign_in: "Log in" sign_in: "Log in"
sign_out: "Log out" sign_out: "Log out"
@ -104,7 +126,9 @@ en:
title: New domain check title: New domain check
ssl: ssl:
title: New SSL check title: New SSL check
edit:
title: Check edition
destroy_confirmation: Are you sure to destroy this check ?
create: create:
saved: "Your check has been saved." saved: "Your check has been saved."
invalid: "Please check the form." invalid: "Please check the form."
@ -127,7 +151,7 @@ en:
domain: Hostname domain: Hostname
notifications_hint: | notifications_hint: |
Receive notifications to warn you when our system detects that the Receive notifications to warn you when our system detects that the
expiration date is coming. The time is set in number of days. expiration date is coming.
table: table:
th: th:
@ -143,3 +167,36 @@ en:
zero: "Last check successful: today" zero: "Last check successful: today"
one: "Last check successful: yesterday" one: "Last check successful: yesterday"
other: "Last check successful %{count} days ago" other: "Last check successful %{count} days ago"
notifications:
index:
title: "List of your notifications"
no_notification_yet_html: |
You have not set up a notification yet.
<a href="%{new_path}">Create your first notification</a>.
explanation: |
For each of your checks,
you can associate one or more notifications that you will receive
by email at the interval of your choice before the expiration date.
table:
th:
edit: Edit
interval_in_days:
one: 1 day
other: "%{count} days"
edit:
title: "Edit the notification"
checks:
zero: No check associated check.
one: Check associated
other: Checks associated
destroy_confirmation:
zero: "Are you sure ?"
one: |
Are you sure ?
You won't receive this notification for the associated check.
other: |
Are you sure ?
You won't receive this notification for the associated checks.
form:
label_hint: This label allows you to identify this notification and associate it with a check.

View file

@ -13,8 +13,10 @@ fr:
domain_updated_at: "Date de modification" domain_updated_at: "Date de modification"
domain_expires_at: "Date d'expiration" domain_expires_at: "Date d'expiration"
notification: notification:
label: Étiquette
interval: Délai interval: Délai
recipient: Destinataire recipient: Destinataire
checks_count: Vérifications
user: user:
tos_accepted: "Conditions d'utilisation" tos_accepted: "Conditions d'utilisation"
notifications_enabled: "Notifications activées" notifications_enabled: "Notifications activées"
@ -25,8 +27,13 @@ fr:
helpers: helpers:
submit: submit:
check: check:
create: "Créer" create: "Créer la vérification"
update: "Valider" update: "Modifier la vérification"
delete: "Supprimer la vérification"
notification:
create: "Créer la notification"
update: "Modifier la notification"
delete: "Supprimer la notification"
page_entries_info: page_entries_info:
one_page: one_page:
display_entries: display_entries:
@ -71,6 +78,16 @@ fr:
flashes: flashes:
user_not_authorized: "Vous n'êtes pas autorisé•e à accéder à cette ressouce." user_not_authorized: "Vous n'êtes pas autorisé•e à accéder à cette ressouce."
checks:
create: La vérification est enregistrée.
updated: La vérification est mise à jour.
invalid: Veuillez vérifier le formulaire.
destroyed: La vérification est supprimée.
notifications:
created: La notification est créée.
updated: "La notification est mise à jour."
destroyed: La notification est supprimée.
invalid: Veuillez vérifier le formulaire.
notifications_mailer: notifications_mailer:
domain_expires_soon: domain_expires_soon:
@ -101,6 +118,7 @@ fr:
my_checks: "Mes vérifications" my_checks: "Mes vérifications"
new_domain_check: "Nouveau nom de domaine" new_domain_check: "Nouveau nom de domaine"
new_ssl_check: "Nouveau certificat SSL" new_ssl_check: "Nouveau certificat SSL"
my_notifications: "Mes notifications"
sign_up: "Enregistrement" sign_up: "Enregistrement"
sign_in: "Connexion" sign_in: "Connexion"
sign_out: "Déconnexion" sign_out: "Déconnexion"
@ -137,10 +155,9 @@ fr:
title: Nouvelle vérification d'un nom de domaine title: Nouvelle vérification d'un nom de domaine
ssl: ssl:
title: Nouvelle vérification d'un certificat SSL title: Nouvelle vérification d'un certificat SSL
edit:
create: title: Modification de la vérification
saved: La vérification est enregistrée. destroy_confirmation: Confirmez-vous la suppression de cette vérification ?
invalid: Veuillez vérifier le formulaire.
filters: filters:
kind_domain: Domaine kind_domain: Domaine
@ -160,7 +177,7 @@ fr:
domain: Nom d'hôte domain: Nom d'hôte
notifications_hint: | notifications_hint: |
Recevez des notifications pour vous avertir lorsque notre système détecte Recevez des notifications pour vous avertir lorsque notre système détecte
que la date d'expiration approche. Le délai est indiqué ennombre de jours. que la date d'expiration approche.
table: table:
th: th:
@ -176,3 +193,38 @@ fr:
zero: "Dernière vérification réussie : aujourd'hui" zero: "Dernière vérification réussie : aujourd'hui"
one: "Dernière vérification réussie : hier" one: "Dernière vérification réussie : hier"
other: "Dernière vérification réussie il y a %{count} jours" other: "Dernière vérification réussie il y a %{count} jours"
notifications:
index:
title: "Liste de vos notifications"
no_notification_yet_html: |
Vous n'avez pas encore créé de notification.
<a href="%{new_domain_path}">Créez votre première notification</a>.
explanation: |
Pour chacune de vos vérifications,
vous pouvez associer une ou plusieurs notifications que vous recevrez
par email un certain nombre de jours avant la date d'expiration.
table:
th:
edit: Modifier
interval_in_days:
one: 1 jour
other: "%{count} jours"
new:
title: Nouvelle notification
edit:
title: "Modification d'une notification"
checks:
zero: Aucune vérification associée.
one: Vérification associée
other: Vérifications associées
destroy_confirmation:
zero: "Confirmez-vous la suppression de cette notification ?"
one: |
Confirmez-vous la suppression ?
Vous ne recevrez plus la notification associée à cette vérification.
other: |
Confirmez-vous la suppression ?
Vous ne recevrez plus les notifications associées à ces vérifications.
form:
label_hint: Ce nom vous permet d'identifier cette notification pour l'associer à une vérification.

View file

@ -7,12 +7,13 @@ Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
resources :checks, except: [:show] do resources :checks, except: [:show] do
resources :notifications, only: [:destroy]
collection do collection do
post :supports, format: :json post :supports, format: :json
end end
end end
resources :notifications, except: [:show]
devise_for :users devise_for :users
root to: "pages#home" root to: "pages#home"
@ -22,7 +23,6 @@ end
# == Route Map # == Route Map
# #
# Prefix Verb URI Pattern Controller#Action # Prefix Verb URI Pattern Controller#Action
# check_notification DELETE /checks/:check_id/notifications/:id(.:format) notifications#destroy
# supports_checks POST /checks/supports(.:format) checks#supports # supports_checks POST /checks/supports(.:format) checks#supports
# checks GET /checks(.:format) checks#index # checks GET /checks(.:format) checks#index
# POST /checks(.:format) checks#create # POST /checks(.:format) checks#create
@ -31,6 +31,13 @@ end
# check PATCH /checks/:id(.:format) checks#update # check PATCH /checks/:id(.:format) checks#update
# PUT /checks/:id(.:format) checks#update # PUT /checks/:id(.:format) checks#update
# DELETE /checks/:id(.:format) checks#destroy # DELETE /checks/:id(.:format) checks#destroy
# notifications GET /notifications(.:format) notifications#index
# POST /notifications(.:format) notifications#create
# new_notification GET /notifications/new(.:format) notifications#new
# edit_notification GET /notifications/:id/edit(.:format) notifications#edit
# notification PATCH /notifications/:id(.:format) notifications#update
# PUT /notifications/:id(.:format) notifications#update
# DELETE /notifications/:id(.:format) notifications#destroy
# new_user_session GET /users/sign_in(.:format) devise/sessions#new # new_user_session GET /users/sign_in(.:format) devise/sessions#new
# user_session POST /users/sign_in(.:format) devise/sessions#create # user_session POST /users/sign_in(.:format) devise/sessions#create
# destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy # destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy
@ -56,7 +63,7 @@ end
# rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show # rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
# update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update # update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update
# rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create # rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create
# #
# Routes for LetterOpenerWeb::Engine: # Routes for LetterOpenerWeb::Engine:
# clear_letters DELETE /clear(.:format) letter_opener_web/letters#clear # clear_letters DELETE /clear(.:format) letter_opener_web/letters#clear
# delete_letter DELETE /:id(.:format) letter_opener_web/letters#destroy # delete_letter DELETE /:id(.:format) letter_opener_web/letters#destroy

View file

@ -1,3 +1,6 @@
# Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr>
# License: GNU AGPL-3+ (see full text in LICENSE file)
class AddConsecutiveFailuresToChecks < ActiveRecord::Migration[5.2] class AddConsecutiveFailuresToChecks < ActiveRecord::Migration[5.2]
def change def change
add_column :checks, :consecutive_failures, :integer, default: 0, null: false add_column :checks, :consecutive_failures, :integer, default: 0, null: false

View file

@ -0,0 +1,15 @@
# Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr>
# License: GNU AGPL-3+ (see full text in LICENSE file)
class CreateCheckNotifications < ActiveRecord::Migration[5.2]
def change
create_table :check_notifications do |t|
t.references :check, foreign_key: true
t.references :notification, foreign_key: true
t.integer :status, null: false, default: 0, limit: 1
t.datetime :sent_at
t.timestamps
end
end
end

View file

@ -0,0 +1,41 @@
# Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr>
# License: GNU AGPL-3+ (see full text in LICENSE file)
class AddFieldsToNotifications < ActiveRecord::Migration[5.2]
def change
add_reference :notifications, :user, foreign_key: true
add_column :notifications, :label, :string
add_column :notifications, :checks_count, :integer, default: 0, null: false
reversible do |dir|
dir.up do
# first set user & label for *all* notifications
Notification.find_each do |notification|
check = Check.find(notification.check_id) # check relation does not exist anymore
notification.user_id = check.user_id
notification.save!
end
# then build the equivalent check notification
Notification.find_each do |notification|
assoc_notification = Notification.where(
user_id: notification.user_id,
recipient: notification.recipient,
interval: notification.interval,
).order(checks_count: :desc).limit(1).first
CheckNotification.create!(
check_id: notification.check_id,
notification: assoc_notification,
status: notification.status,
sent_at: notification.sent_at
)
end
# last delete duplicate notification templates not used
Notification.where(checks_count: 0).destroy_all
end
end
end
end

View file

@ -0,0 +1,9 @@
# Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr>
# License: GNU AGPL-3+ (see full text in LICENSE file)
class RemoveObsoleteFieldsToNotifications < ActiveRecord::Migration[5.2]
def change
remove_column :notifications, :status, :integer, null: false, limit: 1, default: 0
remove_column :notifications, :sent_at, :datetime
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2018_08_29_134404) do ActiveRecord::Schema.define(version: 2018_08_30_083927) do
create_table "check_logs", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| create_table "check_logs", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.bigint "check_id" t.bigint "check_id"
@ -24,6 +24,17 @@ ActiveRecord::Schema.define(version: 2018_08_29_134404) do
t.index ["check_id"], name: "index_check_logs_on_check_id" t.index ["check_id"], name: "index_check_logs_on_check_id"
end end
create_table "check_notifications", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.bigint "check_id"
t.bigint "notification_id"
t.integer "status", limit: 1, default: 0, null: false
t.datetime "sent_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["check_id"], name: "index_check_notifications_on_check_id"
t.index ["notification_id"], name: "index_check_notifications_on_notification_id"
end
create_table "checks", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| create_table "checks", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.bigint "user_id" t.bigint "user_id"
t.integer "kind", null: false t.integer "kind", null: false
@ -38,8 +49,8 @@ ActiveRecord::Schema.define(version: 2018_08_29_134404) do
t.boolean "active", default: true, null: false t.boolean "active", default: true, null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.boolean "round_robin", default: true
t.integer "consecutive_failures", default: 0, null: false t.integer "consecutive_failures", default: 0, null: false
t.boolean "round_robin", default: true
t.integer "mode", default: 0, null: false t.integer "mode", default: 0, null: false
t.index ["user_id"], name: "index_checks_on_user_id" t.index ["user_id"], name: "index_checks_on_user_id"
end end
@ -49,11 +60,13 @@ ActiveRecord::Schema.define(version: 2018_08_29_134404) do
t.integer "channel", default: 0, null: false t.integer "channel", default: 0, null: false
t.string "recipient", null: false t.string "recipient", null: false
t.integer "interval", null: false t.integer "interval", null: false
t.integer "status", default: 0, null: false
t.datetime "sent_at"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.bigint "user_id"
t.string "label"
t.integer "checks_count", default: 0, null: false
t.index ["check_id"], name: "index_notifications_on_check_id" t.index ["check_id"], name: "index_notifications_on_check_id"
t.index ["user_id"], name: "index_notifications_on_user_id"
end end
create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
@ -82,6 +95,9 @@ ActiveRecord::Schema.define(version: 2018_08_29_134404) do
end end
add_foreign_key "check_logs", "checks" add_foreign_key "check_logs", "checks"
add_foreign_key "check_notifications", "checks"
add_foreign_key "check_notifications", "notifications"
add_foreign_key "checks", "users" add_foreign_key "checks", "users"
add_foreign_key "notifications", "checks" add_foreign_key "notifications", "checks"
add_foreign_key "notifications", "users"
end end

View file

@ -1,6 +1,3 @@
# Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr>
# License: GNU AGPL-3+ (see full text in LICENSE file)
require "test_helper" require "test_helper"
class NotificationsControllerTest < ActionDispatch::IntegrationTest class NotificationsControllerTest < ActionDispatch::IntegrationTest

View file

@ -0,0 +1,44 @@
# == Schema Information
#
# Table name: check_notifications
#
# id :bigint(8) not null, primary key
# sent_at :datetime
# status :integer default("pending"), not null
# created_at :datetime not null
# updated_at :datetime not null
# check_id :bigint(8)
# notification_id :bigint(8)
#
# Indexes
#
# index_check_notifications_on_check_id (check_id)
# index_check_notifications_on_notification_id (notification_id)
#
# Foreign Keys
#
# fk_rails_... (check_id => checks.id)
# fk_rails_... (notification_id => notifications.id)
#
FactoryBot.define do
factory :check_notification do
check
notification
status :pending
sent_at nil
trait :ongoing do
status :ongoing
end
trait :succeed do
status :succeed
sent_at { 1.day.ago }
end
trait :failed do
status :failed
end
end
end

View file

@ -86,7 +86,9 @@ FactoryBot.define do
trait :with_notifications do trait :with_notifications do
after :create do |check| after :create do |check|
create_list :notification, 2, check: check create_list :check_notification, 2,
check: check,
notification: build(:notification, user: check.user)
end end
end end
end end

View file

@ -1,53 +1,41 @@
# Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Jeremy Lecour <jlecour@evolix.fr>, 2018 Evolix <info@evolix.fr> # Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Jeremy Lecour <jlecour@evolix.fr>, 2018 Evolix <info@evolix.fr>
# License: GNU AGPL-3+ (see full text in LICENSE file) # License: GNU AGPL-3+ (see full text in LICENSE file)
# == Schema Information # == Schema Information
# #
# Table name: notifications # Table name: notifications
# #
# id :bigint(8) not null, primary key # id :bigint(8) not null, primary key
# channel :integer default("email"), not null # channel :integer default("email"), not null
# interval :integer not null # checks_count :integer default(0), not null
# recipient :string(255) not null # interval :integer not null
# sent_at :datetime # label :string(255)
# status :integer default("pending"), not null # recipient :string(255) not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# check_id :bigint(8) # check_id :bigint(8)
# user_id :bigint(8)
# #
# Indexes # Indexes
# #
# index_notifications_on_check_id (check_id) # index_notifications_on_check_id (check_id)
# index_notifications_on_user_id (user_id)
# #
# Foreign Keys # Foreign Keys
# #
# fk_rails_... (check_id => checks.id) # fk_rails_... (check_id => checks.id)
# fk_rails_... (user_id => users.id)
# #
FactoryBot.define do FactoryBot.define do
factory :notification do factory :notification do
check user
interval 30 interval 30
channel :email channel :email
label { "#{recipient} (#{interval})" }
recipient "recipient@domain.fr" recipient "recipient@domain.fr"
status :pending
sent_at nil
trait :email do trait :email do
channel :email channel :email
end end
trait :ongoing do
status :ongoing
end
trait :succeed do
status :succeed
sent_at { 1.day.ago }
end
trait :failed do
status :failed
end
end end
end end

View file

@ -6,10 +6,11 @@ require "test_helper"
class NotificationsMailerTest < ActionMailer::TestCase # rubocop:disable Metrics/ClassLength class NotificationsMailerTest < ActionMailer::TestCase # rubocop:disable Metrics/ClassLength
test "domain_expires_soon" do test "domain_expires_soon" do
check = create(:check, domain_expires_at: Time.new(2018, 6, 10, 12, 0, 5, "+02:00")) check = create(:check, domain_expires_at: Time.new(2018, 6, 10, 12, 0, 5, "+02:00"))
notification = build(:notification, interval: 10, check: check, recipient: "colin@example.org") notification = build(:notification, interval: 10, recipient: "colin@example.org")
check_notification = build(:check_notification, check: check, notification: notification)
Date.stub :today, Date.new(2018, 6, 2) do Date.stub :today, Date.new(2018, 6, 2) do
mail = NotificationsMailer.with(notification: notification).domain_expires_soon mail = NotificationsMailer.with(check_notification: check_notification).domain_expires_soon
assert_emails 1 do assert_emails 1 do
mail.deliver_now mail.deliver_now
@ -37,10 +38,11 @@ class NotificationsMailerTest < ActionMailer::TestCase # rubocop:disable Metrics
check = create(:check, check = create(:check,
domain_expires_at: Time.new(2018, 6, 10, 12, 0, 5, "+02:00"), domain_expires_at: Time.new(2018, 6, 10, 12, 0, 5, "+02:00"),
user: build(:user, :fr)) user: build(:user, :fr))
notification = build(:notification, interval: 10, check: check, recipient: "colin@example.org") notification = build(:notification, interval: 10, recipient: "colin@example.org")
check_notification = build(:check_notification, check: check, notification: notification)
Date.stub :today, Date.new(2018, 6, 2) do Date.stub :today, Date.new(2018, 6, 2) do
mail = NotificationsMailer.with(notification: notification).domain_expires_soon mail = NotificationsMailer.with(check_notification: check_notification).domain_expires_soon
assert_emails 1 do assert_emails 1 do
mail.deliver_now mail.deliver_now
@ -69,9 +71,9 @@ class NotificationsMailerTest < ActionMailer::TestCase # rubocop:disable Metrics
domain_expires_at: 1.week.from_now, domain_expires_at: 1.week.from_now,
comment: "My comment", comment: "My comment",
vendor: "The vendor") vendor: "The vendor")
notification = build(:notification, check: check) check_notification = build(:check_notification, check: check)
mail = NotificationsMailer.with(notification: notification).domain_expires_soon mail = NotificationsMailer.with(check_notification: check_notification).domain_expires_soon
parts = [mail.text_part.decode_body, mail.html_part.decode_body] parts = [mail.text_part.decode_body, mail.html_part.decode_body]
@ -87,9 +89,9 @@ class NotificationsMailerTest < ActionMailer::TestCase # rubocop:disable Metrics
comment: "My comment", comment: "My comment",
vendor: "The vendor", vendor: "The vendor",
user: build(:user, :fr)) user: build(:user, :fr))
notification = build(:notification, check: check) check_notification = build(:check_notification, check: check)
mail = NotificationsMailer.with(notification: notification).domain_expires_soon mail = NotificationsMailer.with(check_notification: check_notification).domain_expires_soon
parts = [mail.text_part.decode_body, mail.html_part.decode_body] parts = [mail.text_part.decode_body, mail.html_part.decode_body]
@ -158,10 +160,11 @@ class NotificationsMailerTest < ActionMailer::TestCase # rubocop:disable Metrics
test "ssl_expires_soon" do test "ssl_expires_soon" do
check = create(:check, :ssl, domain_expires_at: Time.new(2018, 6, 10, 12, 0, 5, "+02:00")) check = create(:check, :ssl, domain_expires_at: Time.new(2018, 6, 10, 12, 0, 5, "+02:00"))
notification = build(:notification, interval: 10, check: check, recipient: "colin@example.org") notification = build(:notification, interval: 10, recipient: "colin@example.org")
check_notification = build(:check_notification, check: check, notification: notification)
Date.stub :today, Date.new(2018, 6, 2) do Date.stub :today, Date.new(2018, 6, 2) do
mail = NotificationsMailer.with(notification: notification).ssl_expires_soon mail = NotificationsMailer.with(check_notification: check_notification).ssl_expires_soon
assert_emails 1 do assert_emails 1 do
mail.deliver_now mail.deliver_now
@ -190,10 +193,11 @@ class NotificationsMailerTest < ActionMailer::TestCase # rubocop:disable Metrics
check = create(:check, :ssl, check = create(:check, :ssl,
domain_expires_at: Time.new(2018, 6, 10, 12, 0, 5, "+02:00"), domain_expires_at: Time.new(2018, 6, 10, 12, 0, 5, "+02:00"),
user: build(:user, :fr)) user: build(:user, :fr))
notification = build(:notification, interval: 10, check: check, recipient: "colin@example.org") notification = build(:notification, interval: 10, recipient: "colin@example.org")
check_notification = build(:check_notification, check: check, notification: notification)
Date.stub :today, Date.new(2018, 6, 2) do Date.stub :today, Date.new(2018, 6, 2) do
mail = NotificationsMailer.with(notification: notification).ssl_expires_soon mail = NotificationsMailer.with(check_notification: check_notification).ssl_expires_soon
assert_emails 1 do assert_emails 1 do
mail.deliver_now mail.deliver_now

View file

@ -0,0 +1,30 @@
# == Schema Information
#
# Table name: check_notifications
#
# id :bigint(8) not null, primary key
# sent_at :datetime
# status :integer default("pending"), not null
# created_at :datetime not null
# updated_at :datetime not null
# check_id :bigint(8)
# notification_id :bigint(8)
#
# Indexes
#
# index_check_notifications_on_check_id (check_id)
# index_check_notifications_on_notification_id (notification_id)
#
# Foreign Keys
#
# fk_rails_... (check_id => checks.id)
# fk_rails_... (notification_id => notifications.id)
#
require "test_helper"
class CheckNotificationTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View file

@ -15,6 +15,7 @@
# kind :integer not null # kind :integer not null
# last_run_at :datetime # last_run_at :datetime
# last_success_at :datetime # last_success_at :datetime
# mode :integer default("auto"), not null
# round_robin :boolean default(TRUE) # round_robin :boolean default(TRUE)
# vendor :string(255) # vendor :string(255)
# created_at :datetime not null # created_at :datetime not null
@ -35,23 +36,23 @@ require "test_helper"
class CheckTest < ActiveSupport::TestCase class CheckTest < ActiveSupport::TestCase
test "notifications are resetted when domain expiration date has changed" do test "notifications are resetted when domain expiration date has changed" do
check = create(:check) check = create(:check)
notification = create(:notification, :succeed, check: check) check_notification = create(:check_notification, :succeed, check: check)
check.comment = "Will not reset because of this attribute" check.comment = "Will not reset because of this attribute"
check.save! check.save!
notification.reload check_notification.reload
assert notification.succeed? assert check_notification.succeed?
assert_not_nil notification.sent_at assert_not_nil check_notification.sent_at
check.domain_expires_at = 1.year.from_now check.domain_expires_at = 1.year.from_now
check.save! check.save!
notification.reload check_notification.reload
assert notification.pending? assert check_notification.pending?
assert_nil notification.sent_at assert_nil check_notification.sent_at
end end
test "days_from_last_success without any success" do test "days_from_last_success without any success" do

View file

@ -1,27 +1,29 @@
# Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Jeremy Lecour <jlecour@evolix.fr>, 2018 Evolix <info@evolix.fr> # Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Jeremy Lecour <jlecour@evolix.fr>, 2018 Evolix <info@evolix.fr>
# License: GNU AGPL-3+ (see full text in LICENSE file) # License: GNU AGPL-3+ (see full text in LICENSE file)
# == Schema Information # == Schema Information
# #
# Table name: notifications # Table name: notifications
# #
# id :bigint(8) not null, primary key # id :bigint(8) not null, primary key
# channel :integer default("email"), not null # channel :integer default("email"), not null
# interval :integer not null # checks_count :integer default(0), not null
# recipient :string(255) not null # interval :integer not null
# sent_at :datetime # label :string(255)
# status :integer default("pending"), not null # recipient :string(255) not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# check_id :bigint(8) # check_id :bigint(8)
# user_id :bigint(8)
# #
# Indexes # Indexes
# #
# index_notifications_on_check_id (check_id) # index_notifications_on_check_id (check_id)
# index_notifications_on_user_id (user_id)
# #
# Foreign Keys # Foreign Keys
# #
# fk_rails_... (check_id => checks.id) # fk_rails_... (check_id => checks.id)
# fk_rails_... (user_id => users.id)
# #
require "test_helper" require "test_helper"

View file

@ -0,0 +1,35 @@
# Copyright (C) 2018 Colin Darie <colin@darie.eu>, 2018 Evolix <info@evolix.fr>
# License: GNU AGPL-3+ (see full text in LICENSE file)
require "test_helper"
class CheckNotificationPolicyTest < ActiveSupport::TestCase
setup do
@owner, @other = create_list(:user, 2)
@check_notification = create(:check_notification, check: build(:check, user: @owner))
end
test "permit to check user" do
assert_permit @owner, @check_notification, :destroy
end
test "disallow to anonymous and other user" do
refute_permit @other, @check_notification, :destroy
refute_permit nil, @check_notification, :destroy
end
test "scope only to user checks" do
other_notifications = create_list(:check_notification, 2, check: build(:check, user: @other))
assert_empty Pundit.policy_scope!(nil, CheckNotification)
assert_equal [@check_notification], Pundit.policy_scope!(@owner, CheckNotification)
assert_equal other_notifications, Pundit.policy_scope!(@other, CheckNotification)
end
test "disabled actions" do
refute_permit @owner, @check_notification, :update
refute_permit @owner, @check_notification, :edit
refute_permit @owner, @check_notification, :create
refute_permit @owner, @check_notification, :index
end
end

View file

@ -6,30 +6,32 @@ require "test_helper"
class NotificationPolicyTest < ActiveSupport::TestCase class NotificationPolicyTest < ActiveSupport::TestCase
setup do setup do
@owner, @other = create_list(:user, 2) @owner, @other = create_list(:user, 2)
@notification = create(:notification, check: build(:check, user: @owner)) @notification = create(:notification, user: @owner)
end end
test "permit to check user" do test "create" do
assert_permit @other, Notification, :create
assert_permit @other, Notification, :new
end
test "permit to owner" do
assert_permit @owner, @notification, :edit
assert_permit @owner, @notification, :update
assert_permit @owner, @notification, :destroy assert_permit @owner, @notification, :destroy
end end
test "disallow to anonymous and other user" do test "disallow to anonymous and other user" do
refute_permit @other, @notification, :destroy %i[update edit destroy].each do |action|
refute_permit nil, @notification, :destroy refute_permit @other, @notification, action
refute_permit nil, @notification, action
end
end end
test "scope only to user checks" do test "scope only to owners" do
other_notifications = create_list(:notification, 2, check: build(:check, user: @other)) other_notifications = create_list(:notification, 2, user: @other)
assert_empty Pundit.policy_scope!(nil, Notification) assert_empty Pundit.policy_scope!(nil, Notification)
assert_equal [@notification], Pundit.policy_scope!(@owner, Notification) assert_equal [@notification], Pundit.policy_scope!(@owner, Notification)
assert_equal other_notifications, Pundit.policy_scope!(@other, Notification) assert_equal other_notifications, Pundit.policy_scope!(@other, Notification)
end end
test "disabled actions" do
refute_permit @owner, @notification, :update
refute_permit @owner, @notification, :edit
refute_permit @owner, @notification, :create
refute_permit @owner, @notification, :index
end
end end

View file

@ -8,8 +8,8 @@ module Notifier
class BaseTest < ActiveSupport::TestCase class BaseTest < ActiveSupport::TestCase
setup do setup do
class FakeChannel < Base class FakeChannel < Base
def supports?(notification) def supports?(check_notification)
notification.interval < 1_000 check_notification.notification.interval < 1_000
end end
def domain_notify_expires_soon(*); end def domain_notify_expires_soon(*); end
@ -19,45 +19,46 @@ module Notifier
end end
test "#notify change the status of the notification" do test "#notify change the status of the notification" do
notification = create(:notification) check_notification = create(:check_notification)
@channel.notify(notification) @channel.notify(check_notification)
notification.reload check_notification.reload
assert notification.ongoing? assert check_notification.ongoing?
assert_just_now notification.sent_at assert_just_now check_notification.sent_at
end end
test "#notify raises an exception for a non supported check kind" do test "#notify raises an exception for a non supported check kind" do
notification = Minitest::Mock.new check_notification = Minitest::Mock.new
notification.expect :ongoing!, true check_notification.expect :ongoing!, true
notification.expect :interval, 10 check_notification.expect :notification, OpenStruct.new(interval: 10)
check = Minitest::Mock.new check = Minitest::Mock.new
check.expect(:kind, :invalid_kind) check.expect(:kind, :invalid_kind)
check.expect(:kind, :invalid_kind) # twice (second call for exception message) check.expect(:kind, :invalid_kind) # twice (second call for exception message)
notification.expect :check, check check_notification.expect :check, check
notification.expect :check, check check_notification.expect :check, check
assert_raises ArgumentError do assert_raises ArgumentError do
@channel.notify(notification) @channel.notify(check_notification)
end end
check.verify check.verify
notification.verify check_notification.verify
end end
test "#notify does nothing when channel doesn't support a notification whatever the reason" do test "#notify does nothing when channel doesn't support a notification whatever the reason" do
notification = create(:notification, interval: 10_000) check_notification = create(:check_notification,
notification: build(:notification, interval: 10_000))
@channel.notify(notification) @channel.notify(check_notification)
notification.reload check_notification.reload
assert notification.pending? assert check_notification.pending?
assert_nil notification.sent_at assert_nil check_notification.sent_at
end end
end end
end end

View file

@ -5,10 +5,11 @@ require "test_helper"
module Notifier module Notifier
class ProcessorTest < ActiveSupport::TestCase class ProcessorTest < ActiveSupport::TestCase
# rubocop:disable Metrics/LineLength
test "#process_expires_soon sends an email for checks expiring soon" do test "#process_expires_soon sends an email for checks expiring soon" do
create_list(:notification, 3, :email, check: build(:check, :expires_next_week)) create_list(:check_notification, 3, notification: email_notification, check: build(:check, :expires_next_week))
create(:notification, :email, check: build(:check, :nil_dates)) create(:check_notification, notification: email_notification, check: build(:check, :nil_dates))
create(:notification, :email, check: build(:check, :inactive)) create(:check_notification, notification: email_notification, check: build(:check, :inactive))
processor = Processor.new processor = Processor.new
assert_difference "ActionMailer::Base.deliveries.size", +3 do assert_difference "ActionMailer::Base.deliveries.size", +3 do
@ -20,7 +21,7 @@ module Notifier
end end
test "#process_expires_soon respects the interval configuration between sends" do test "#process_expires_soon respects the interval configuration between sends" do
create_list(:notification, 3, :email, check: build(:check, :expires_next_week)) create_list(:check_notification, 3, notification: email_notification, check: build(:check, :expires_next_week))
test_interval_respected(:process_expires_soon, 3) test_interval_respected(:process_expires_soon, 3)
end end
@ -30,9 +31,14 @@ module Notifier
configuration.expect(:consecutive_failures, 4.2) configuration.expect(:consecutive_failures, 4.2)
end end
end end
# rubocop:enable Metrics/LineLength
private private
def email_notification
build(:notification, :email)
end
# rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/MethodLength
def test_interval_respected(process_method, count_expected) def test_interval_respected(process_method, count_expected)
configuration = Minitest::Mock.new configuration = Minitest::Mock.new

View file

@ -3,6 +3,7 @@
require "test_helper" require "test_helper"
# rubocop:disable Metrics/LineLength
module Notifier module Notifier
class ResolverTest < ActiveSupport::TestCase class ResolverTest < ActiveSupport::TestCase
setup do setup do
@ -10,74 +11,74 @@ module Notifier
end end
test "#notifications_expiring_soon ignores user having notification disabled" do test "#notifications_expiring_soon ignores user having notification disabled" do
n1 = create(:notification, check: build(:check, :expires_next_week)) n1 = create(:check_notification, check: build(:check, :expires_next_week))
n1.check.user.update_attribute(:notifications_enabled, false) n1.check.user.update_attribute(:notifications_enabled, false)
n2 = create(:notification, check: build(:check, :expires_next_week)) n2 = create(:check_notification, check: build(:check, :expires_next_week))
notifications = @resolver.notifications_expiring_soon check_notifications = @resolver.notifications_expiring_soon
assert_not_includes notifications, n1 assert_not_includes check_notifications, n1
assert_includes notifications, n2 assert_includes check_notifications, n2
end end
test "#notifications_expiring_soon ignores inactive checks" do test "#notifications_expiring_soon ignores inactive checks" do
n1 = create(:notification, check: build(:check, :expires_next_week, :inactive)) n1 = create(:check_notification, check: build(:check, :expires_next_week, :inactive))
n2 = create(:notification, check: build(:check, :expires_next_week)) n2 = create(:check_notification, check: build(:check, :expires_next_week))
notifications = @resolver.notifications_expiring_soon check_notifications = @resolver.notifications_expiring_soon
assert_not_includes notifications, n1 assert_not_includes check_notifications, n1
assert_includes notifications, n2 assert_includes check_notifications, n2
end end
test "#notifications_expiring_soon gets only checks inside interval" do test "#notifications_expiring_soon gets only checks inside interval" do
n1 = create(:notification, check: build(:check, :expires_next_week), interval: 6) n1 = create(:check_notification, check: build(:check, :expires_next_week), notification: build(:notification, interval: 6))
n2 = create(:notification, check: build(:check, :expires_next_week), interval: 7) n2 = create(:check_notification, check: build(:check, :expires_next_week), notification: build(:notification, interval: 7))
notifications = @resolver.notifications_expiring_soon check_notifications = @resolver.notifications_expiring_soon
assert_not_includes notifications, n1 assert_not_includes check_notifications, n1
assert_includes notifications, n2 assert_includes check_notifications, n2
end end
test "#notifications_expiring_soon can gets several notifications for a same check" do test "#notifications_expiring_soon can gets several notifications for a same check" do
check = create(:check, :expires_next_week) check = create(:check, :expires_next_week)
n1 = create(:notification, check: check, interval: 3) n1 = create(:check_notification, check: check, notification: build(:notification, interval: 3))
n2 = create(:notification, check: check, interval: 10) n2 = create(:check_notification, check: check, notification: build(:notification, interval: 10))
n3 = create(:notification, check: check, interval: 30) n3 = create(:check_notification, check: check, notification: build(:notification, interval: 30))
notifications = @resolver.notifications_expiring_soon check_notifications = @resolver.notifications_expiring_soon
assert_not_includes notifications, n1 assert_not_includes check_notifications, n1
assert_includes notifications, n2 assert_includes check_notifications, n2
assert_includes notifications, n3 assert_includes check_notifications, n3
end end
test "#notifications_expiring_soon takes care of the status" do test "#notifications_expiring_soon takes care of the status" do
check = create(:check, :expires_next_week) check = create(:check, :expires_next_week)
n1 = create(:notification, check: check) n1 = create(:check_notification, check: check)
n2 = create(:notification, :failed, check: check) n2 = create(:check_notification, :failed, check: check)
n3 = create(:notification, :ongoing, check: check) n3 = create(:check_notification, :ongoing, check: check)
n4 = create(:notification, :succeed, check: check) n4 = create(:check_notification, :succeed, check: check)
notifications = @resolver.notifications_expiring_soon check_notifications = @resolver.notifications_expiring_soon
assert_includes notifications, n1 assert_includes check_notifications, n1
assert_includes notifications, n2 assert_includes check_notifications, n2
assert_not_includes notifications, n3 assert_not_includes check_notifications, n3
assert_not_includes notifications, n4 assert_not_includes check_notifications, n4
end end
test "#notifications_expiring_soon ignores checks expired and without date" do test "#notifications_expiring_soon ignores checks expired and without date" do
n1 = create(:notification, check: build(:check, :expires_next_week)) n1 = create(:check_notification, check: build(:check, :expires_next_week))
n2 = create(:notification, check: build(:check, domain_expires_at: 1.week.ago)) n2 = create(:check_notification, check: build(:check, domain_expires_at: 1.week.ago))
n3 = create(:notification, check: build(:check, :nil_dates)) n3 = create(:check_notification, check: build(:check, :nil_dates))
notifications = @resolver.notifications_expiring_soon check_notifications = @resolver.notifications_expiring_soon
assert_includes notifications, n1 assert_includes check_notifications, n1
assert_not_includes notifications, n2 assert_not_includes check_notifications, n2
assert_not_includes notifications, n3 assert_not_includes check_notifications, n3
end end
test "#checks_recurrent_failures ignores inactive checks" do test "#checks_recurrent_failures ignores inactive checks" do
@ -116,3 +117,4 @@ module Notifier
end end
end end
end end
# rubocop:enable Metrics/LineLength

View file

@ -9,7 +9,7 @@ class ChecksTest < ApplicationSystemTestCase
login_as(@user) login_as(@user)
end end
test "create a check and a notification without kind" do test "create a check and a new notification without kind" do
visit new_check_path visit new_check_path
choose "domain" choose "domain"
@ -53,20 +53,34 @@ class ChecksTest < ApplicationSystemTestCase
fill_and_valid_new_check fill_and_valid_new_check
end end
test "remove a notification" do test "dissociate a notification" do
check = create(:check, :with_notifications, domain: "dom-with-notif.net", user: @user) check = create(:check, :with_notifications, user: @user)
notification = create(:notification, label: "label-notification", user: @user)
check.notifications << notification
visit edit_check_path(check) visit edit_check_path(check)
notification = check.notifications.first
selector = "[data-notification-id=\"#{notification.id}\"]" uncheck notification.label
assert_difference "Notification.where(check_id: #{check.id}).count", -1 do click_button "Update Check"
within selector do
find(".btn-danger").click
end
page.has_no_content?(selector) notification.reload
end assert_equal 0, notification.checks_count
assert_equal 2, check.check_notifications.count
end
test "associate a notification" do
check = create(:check, user: @user)
notification = create(:notification, label: "label-notification", user: @user)
visit edit_check_path(check)
check notification.label
click_button "Update Check"
notification.reload
assert_equal 1, notification.checks_count
assert_equal 1, check.check_notifications.count
end end
test "update a check" do test "update a check" do
@ -84,29 +98,6 @@ class ChecksTest < ApplicationSystemTestCase
assert_equal "My comment", check.comment assert_equal "My comment", check.comment
end end
test "add a notification" do
check = create(:check, :with_notifications, domain: "dom-with-notif.net", user: @user)
visit edit_check_path(check)
recipient = "recipient2@example.org"
fill_in("check[notifications_attributes][2][recipient]", with: recipient)
fill_in("check[notifications_attributes][2][interval]", with: 55)
assert_difference "Notification.where(check_id: #{check.id}).count", +1 do
click_button "Update Check"
assert_equal checks_path, page.current_path
end
assert page.has_css?(".alert-success")
notification = Notification.last
assert_equal recipient, notification.recipient
assert_equal 55, notification.interval
assert notification.email?
assert notification.pending?
end
test "list my checks" do test "list my checks" do
create(:check, :domain, domain: "dom.com", domain_expires_at: Time.new(2018, 7, 5, 12), user: @user) # rubocop:disable Metrics/LineLength create(:check, :domain, domain: "dom.com", domain_expires_at: Time.new(2018, 7, 5, 12), user: @user) # rubocop:disable Metrics/LineLength
create(:check, :ssl, domain: "ssldom.com", user: @user) create(:check, :ssl, domain: "ssldom.com", user: @user)
@ -286,6 +277,8 @@ class ChecksTest < ApplicationSystemTestCase
fill_in("check[domain]", with: domain) fill_in("check[domain]", with: domain)
recipient = "recipient@example.org" recipient = "recipient@example.org"
label = "my new notificatiion"
fill_in("check[notifications_attributes][0][label]", with: label)
fill_in("check[notifications_attributes][0][recipient]", with: recipient) fill_in("check[notifications_attributes][0][recipient]", with: recipient)
fill_in("check[notifications_attributes][0][interval]", with: 30) fill_in("check[notifications_attributes][0][interval]", with: 30)
@ -297,10 +290,14 @@ class ChecksTest < ApplicationSystemTestCase
assert page.has_content?(domain) assert page.has_content?(domain)
notification = Notification.last notification = Notification.last
assert_equal label, notification.label
assert_equal recipient, notification.recipient assert_equal recipient, notification.recipient
assert_equal 30, notification.interval assert_equal 30, notification.interval
assert notification.email? assert notification.email?
assert notification.pending?
check_notification = CheckNotification.last
assert check_notification.pending?
assert_nil check_notification.sent_at
end end
# rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/MethodLength

View file

@ -52,7 +52,7 @@ class UsersTest < ApplicationSystemTestCase
click_button I18n.t("devise.sessions.new.sign_in") click_button I18n.t("devise.sessions.new.sign_in")
assert_equal root_path, page.current_path assert_equal checks_path, page.current_path
assert page.has_content?(@user.email) assert page.has_content?(@user.email)
end end