class App include Mongoid::Document include Mongoid::Timestamps include Comparable field :name, :type => String field :api_key field :github_repo field :bitbucket_repo field :repository_branch field :resolve_errs_on_deploy, :type => Boolean, :default => false field :notify_all_users, :type => Boolean, :default => false field :notify_on_errs, :type => Boolean, :default => true field :notify_on_deploys, :type => Boolean, :default => false field :email_at_notices, :type => Array, :default => Errbit::Config.email_at_notices # Some legacy apps may have string as key instead of BSON::ObjectID identity :type => String embeds_many :watchers embeds_many :deploys embeds_one :issue_tracker embeds_one :notification_service has_many :problems, :inverse_of => :app, :dependent => :destroy before_validation :generate_api_key, :on => :create before_save :normalize_github_repo after_update :store_cached_attributes_on_problems validates_presence_of :name, :api_key validates_uniqueness_of :name, :allow_blank => true validates_uniqueness_of :api_key, :allow_blank => true validates_associated :watchers validate :check_issue_tracker accepts_nested_attributes_for :watchers, :allow_destroy => true, :reject_if => proc { |attrs| attrs[:user_id].blank? && attrs[:email].blank? } accepts_nested_attributes_for :issue_tracker, :allow_destroy => true, :reject_if => proc { |attrs| !IssueTracker.subclasses.map(&:to_s).include?(attrs[:type].to_s) } accepts_nested_attributes_for :notification_service, :allow_destroy => true, :reject_if => proc { |attrs| !NotificationService.subclasses.map(&:to_s).include?(attrs[:type].to_s) } # Processes a new error report. # # Accepts either XML or a hash with the following attributes: # # * :error_class - the class of error # * :message - the error message # * :backtrace - an array of stack trace lines # # * :request - a hash of values describing the request # * :server_environment - a hash of values describing the server environment # # * :api_key - the API key with which the error was reported # * :notifier - information to identify the source of the error report # def self.report_error!(*args) report = ErrorReport.new(*args) report.generate_notice! end # Processes a new error report. # # Accepts a hash with the following attributes: # # * :error_class - the class of error # * :message - the error message # * :backtrace - an array of stack trace lines # # * :request - a hash of values describing the request # * :server_environment - a hash of values describing the server environment # # * :notifier - information to identify the source of the error report # def report_error!(hash) report = ErrorReport.new(hash.merge(:api_key => api_key)) report.generate_notice! end def find_or_create_err!(attrs) Err.where(:fingerprint => attrs[:fingerprint]).first || problems.create!.errs.create!(attrs) end # Mongoid Bug: find(id) on association proxies returns an Enumerator def self.find_by_id!(app_id) find app_id end def self.find_by_api_key!(key) where(:api_key => key).first || raise(Mongoid::Errors::DocumentNotFound.new(self,key)) end def last_deploy_at (last_deploy = deploys.last) && last_deploy.created_at end # Legacy apps don't have notify_on_errs and notify_on_deploys params def notify_on_errs !(super == false) end alias :notify_on_errs? :notify_on_errs def notifiable? notify_on_errs? && notification_recipients.any? end def notify_on_deploys !(super == false) end alias :notify_on_deploys? :notify_on_deploys def repo_branch self.repository_branch.present? ? self.repository_branch : 'master' end def github_repo? self.github_repo.present? end def github_url "https://github.com/#{github_repo}" if github_repo? end def github_url_to_file(file) "#{github_url}/blob/#{repo_branch}/#{file}" end def bitbucket_repo? self.bitbucket_repo.present? end def bitbucket_url "https://bitbucket.org/#{bitbucket_repo}" if bitbucket_repo? end def bitbucket_url_to_file(file) "#{bitbucket_url}/src/#{repo_branch}/#{file}" end def issue_tracker_configured? !!(issue_tracker.class < IssueTracker && issue_tracker.configured?) end def notification_service_configured? !!(notification_service.class < NotificationService && notification_service.configured?) end def notification_recipients if notify_all_users (User.all.map(&:email).reject(&:blank?) + watchers.map(&:address)).uniq else watchers.map(&:address) end end # Copy app attributes from another app. def copy_attributes_from(app_id) if copy_app = App.where(:_id => app_id).first # Copy fields (copy_app.fields.keys - %w(_id name created_at updated_at)).each do |k| self.send("#{k}=", copy_app.send(k)) end # Clone the embedded objects that can be changed via apps/edit (ignore errs & deploys, etc.) %w(watchers issue_tracker notification_service).each do |relation| if obj = copy_app.send(relation) self.send("#{relation}=", obj.is_a?(Array) ? obj.map(&:clone) : obj.clone) end end end end def unresolved_count @unresolved_count ||= problems.unresolved.count end def problem_count @problem_count ||= problems.count end # Compare by number of unresolved errs, then problem counts. def <=>(other) (other.unresolved_count <=> unresolved_count).nonzero? || (other.problem_count <=> problem_count).nonzero? || name <=> other.name end def email_at_notices Errbit::Config.per_app_email_at_notices ? super : Errbit::Config.email_at_notices end protected def store_cached_attributes_on_problems problems.each(&:cache_app_attributes) end def generate_api_key self.api_key ||= SecureRandom.hex end def check_issue_tracker if issue_tracker.present? issue_tracker.valid? issue_tracker.errors.full_messages.each do |error| errors[:base] << error end if issue_tracker.errors end end def normalize_github_repo return if github_repo.blank? github_repo.strip! github_repo.sub!(/(git@|https?:\/\/)github\.com(\/|:)/, '') github_repo.sub!(/\.git$/, '') end end