Initial Redmine tooling and local plugin forks
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
class HelpdeskSearchController < ApplicationController
|
||||
unloadable
|
||||
|
||||
accept_api_auth :ticket_by_issue, :issues_by_contact, :messages_by_issue, :contact_timeline
|
||||
|
||||
before_filter :require_login
|
||||
|
||||
def usage
|
||||
# Human/browser probes often stop at /helpdesk_search/issues. Return a
|
||||
# machine-readable usage response instead of letting Rails raise a route
|
||||
# exception that looks like an application failure in production.log.
|
||||
render :json => {
|
||||
:helpdesk_search => {
|
||||
:ticket_by_issue => '/helpdesk_search/issues/:issue_id/ticket',
|
||||
:ticket_by_issue_alias => '/helpdesk_search/issues/:issue_id',
|
||||
:issues_by_contact => '/helpdesk_search/contacts/:contact_id/issues',
|
||||
:messages_by_issue => '/helpdesk_search/issues/:issue_id/messages',
|
||||
:contact_timeline => '/helpdesk_search/contacts/:contact_id/timeline'
|
||||
}
|
||||
}, :status => :bad_request
|
||||
end
|
||||
|
||||
def ticket_by_issue
|
||||
issue = Issue.find(params[:issue_id])
|
||||
return unless authorize_helpdesk_project!(issue.project)
|
||||
|
||||
ticket = HelpdeskTicket.where(:issue_id => issue.id).first
|
||||
unless ticket
|
||||
render_404
|
||||
return
|
||||
end
|
||||
|
||||
render :json => {:helpdesk_ticket => serialize_ticket(ticket)}
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def issues_by_contact
|
||||
contact = Contact.find(params[:contact_id])
|
||||
tickets = HelpdeskTicket.
|
||||
includes(:issue => [:project, :status, :tracker, :assigned_to]).
|
||||
where(:contact_id => contact.id).
|
||||
order("#{HelpdeskTicket.table_name}.ticket_date DESC").
|
||||
limit(api_limit)
|
||||
|
||||
render :json => {
|
||||
:contact_id => contact.id,
|
||||
:issues => tickets.map { |ticket| serialize_ticket_issue(ticket) }.compact
|
||||
}
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def messages_by_issue
|
||||
issue = Issue.find(params[:issue_id])
|
||||
return unless authorize_helpdesk_project!(issue.project)
|
||||
|
||||
messages = JournalMessage.
|
||||
includes(:contact, :journal).
|
||||
joins(:journal).
|
||||
where(:journals => {:journalized_type => 'Issue', :journalized_id => issue.id}).
|
||||
order("#{JournalMessage.table_name}.message_date ASC").
|
||||
limit(api_limit)
|
||||
|
||||
render :json => {
|
||||
:issue_id => issue.id,
|
||||
:journal_messages => messages.map { |message| serialize_journal_message(message) }
|
||||
}
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def contact_timeline
|
||||
contact = Contact.find(params[:contact_id])
|
||||
tickets = HelpdeskTicket.
|
||||
includes(:issue => [:project, :status, :tracker]).
|
||||
where(:contact_id => contact.id).
|
||||
order("#{HelpdeskTicket.table_name}.ticket_date DESC").
|
||||
limit(api_limit)
|
||||
|
||||
messages = JournalMessage.
|
||||
includes(:contact, :journal).
|
||||
where(:contact_id => contact.id).
|
||||
order("#{JournalMessage.table_name}.message_date DESC").
|
||||
limit(api_limit)
|
||||
|
||||
events = []
|
||||
tickets.each do |ticket|
|
||||
next unless visible_issue?(ticket.issue)
|
||||
events << serialize_ticket(ticket).merge(:type => 'helpdesk_ticket', :date => iso8601(ticket.ticket_date))
|
||||
end
|
||||
|
||||
messages.each do |message|
|
||||
issue = message_issue(message)
|
||||
next unless visible_issue?(issue)
|
||||
events << serialize_journal_message(message).merge(:type => 'journal_message', :date => iso8601(message.message_date))
|
||||
end
|
||||
|
||||
events.sort_by! { |event| event[:date].to_s }
|
||||
events.reverse!
|
||||
|
||||
render :json => {
|
||||
:contact_id => contact.id,
|
||||
:timeline => events.first(api_limit)
|
||||
}
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorize_helpdesk_project!(project)
|
||||
# Search endpoints expose customer/email context, so they reuse the native
|
||||
# per-project helpdesk visibility permission instead of adding a new role.
|
||||
return true if project && User.current.allowed_to?(:view_helpdesk_tickets, project)
|
||||
deny_access
|
||||
false
|
||||
end
|
||||
|
||||
def visible_issue?(issue)
|
||||
issue && issue.project && User.current.allowed_to?(:view_helpdesk_tickets, issue.project)
|
||||
end
|
||||
|
||||
def api_limit
|
||||
requested_limit = params[:limit].present? ? params[:limit].to_i : 100
|
||||
[[requested_limit, 1].max, 200].min
|
||||
end
|
||||
|
||||
def serialize_ticket_issue(ticket)
|
||||
issue = ticket.issue
|
||||
return nil unless visible_issue?(issue)
|
||||
|
||||
serialize_ticket(ticket).merge(
|
||||
:issue => {
|
||||
:id => issue.id,
|
||||
:project_id => issue.project_id,
|
||||
:tracker_id => issue.tracker_id,
|
||||
:tracker_name => issue.tracker.try(:name),
|
||||
:status_id => issue.status_id,
|
||||
:status_name => issue.status.try(:name),
|
||||
:assigned_to_id => issue.assigned_to_id,
|
||||
:assigned_to_name => issue.assigned_to.try(:name),
|
||||
:subject => issue.subject,
|
||||
:created_on => iso8601(issue.created_on),
|
||||
:updated_on => iso8601(issue.updated_on)
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def serialize_ticket(ticket)
|
||||
{
|
||||
:id => ticket.id,
|
||||
:issue_id => ticket.issue_id,
|
||||
:contact_id => ticket.contact_id,
|
||||
:message_id => ticket.message_id,
|
||||
:source => ticket.source,
|
||||
:is_incoming => ticket.is_incoming?,
|
||||
:from_address => ticket.from_address,
|
||||
:to_address => ticket.to_address,
|
||||
:cc_address => ticket.cc_address,
|
||||
:ticket_date => iso8601(ticket.ticket_date)
|
||||
}
|
||||
end
|
||||
|
||||
def serialize_journal_message(message)
|
||||
journal = message.journal
|
||||
issue = message_issue(message)
|
||||
|
||||
# Keep this API metadata-only. The external indexer can fetch journal.notes
|
||||
# with its own read policy; this endpoint should not leak message bodies,
|
||||
# private notes, attachments, or BCC addresses by accident.
|
||||
{
|
||||
:id => message.id,
|
||||
:journal_id => message.journal_id,
|
||||
:issue_id => issue.try(:id),
|
||||
:project_id => issue.try(:project_id),
|
||||
:contact_id => message.contact_id,
|
||||
:message_id => message.message_id,
|
||||
:source => message.source,
|
||||
:is_incoming => message.is_incoming?,
|
||||
:from_address => message.from_address,
|
||||
:to_address => message.to_address,
|
||||
:cc_address => message.cc_address,
|
||||
:has_bcc_address => message.bcc_address.present?,
|
||||
:message_date => iso8601(message.message_date),
|
||||
:journal_user_id => journal.try(:user_id),
|
||||
:journal_private_notes => journal.try(:private_notes?),
|
||||
:journal_has_notes => journal.try(:notes).present?
|
||||
}
|
||||
end
|
||||
|
||||
def message_issue(message)
|
||||
journal = message.journal
|
||||
return nil unless journal
|
||||
journal.try(:issue) || journal.try(:journalized)
|
||||
end
|
||||
|
||||
def iso8601(value)
|
||||
value.try(:utc).try(:iso8601)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user