Commit 47bf8064 authored by Andreas Brandl's avatar Andreas Brandl

Merge branch 'issue_2030' into 'master'

Allow to use issue templates on service desk

See merge request gitlab-org/gitlab!19515
parents e1557877 cf72a5a2
# frozen_string_literal: true
class CreateServiceDeskSettings < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
create_table :service_desk_settings, id: false do |t|
t.references :project,
primary_key: true,
default: nil,
null: false,
index: false,
foreign_key: { on_delete: :cascade }
t.string :issue_template_key, limit: 255
end
end
end
...@@ -3486,6 +3486,10 @@ ActiveRecord::Schema.define(version: 2019_11_15_091425) do ...@@ -3486,6 +3486,10 @@ ActiveRecord::Schema.define(version: 2019_11_15_091425) do
t.index ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true t.index ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true
end end
create_table "service_desk_settings", primary_key: "project_id", id: :bigint, default: nil, force: :cascade do |t|
t.string "issue_template_key", limit: 255
end
create_table "services", id: :serial, force: :cascade do |t| create_table "services", id: :serial, force: :cascade do |t|
t.string "type" t.string "type"
t.string "title" t.string "title"
...@@ -4509,6 +4513,7 @@ ActiveRecord::Schema.define(version: 2019_11_15_091425) do ...@@ -4509,6 +4513,7 @@ ActiveRecord::Schema.define(version: 2019_11_15_091425) do
add_foreign_key "scim_oauth_access_tokens", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "scim_oauth_access_tokens", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "self_managed_prometheus_alert_events", "environments", on_delete: :cascade add_foreign_key "self_managed_prometheus_alert_events", "environments", on_delete: :cascade
add_foreign_key "self_managed_prometheus_alert_events", "projects", on_delete: :cascade add_foreign_key "self_managed_prometheus_alert_events", "projects", on_delete: :cascade
add_foreign_key "service_desk_settings", "projects", on_delete: :cascade
add_foreign_key "services", "projects", name: "fk_71cce407f9", on_delete: :cascade add_foreign_key "services", "projects", name: "fk_71cce407f9", on_delete: :cascade
add_foreign_key "slack_integrations", "services", on_delete: :cascade add_foreign_key "slack_integrations", "services", on_delete: :cascade
add_foreign_key "smartcard_identities", "users", on_delete: :cascade add_foreign_key "smartcard_identities", "users", on_delete: :cascade
......
...@@ -10,6 +10,8 @@ class Projects::ServiceDeskController < Projects::ApplicationController ...@@ -10,6 +10,8 @@ class Projects::ServiceDeskController < Projects::ApplicationController
def update def update
Projects::UpdateService.new(project, current_user, { service_desk_enabled: params[:service_desk_enabled] }).execute Projects::UpdateService.new(project, current_user, { service_desk_enabled: params[:service_desk_enabled] }).execute
ServiceDeskSetting.update_template_key_for(project: project, issue_template_key: params[:issue_template_key])
json_response json_response
end end
...@@ -17,8 +19,15 @@ class Projects::ServiceDeskController < Projects::ApplicationController ...@@ -17,8 +19,15 @@ class Projects::ServiceDeskController < Projects::ApplicationController
def json_response def json_response
respond_to do |format| respond_to do |format|
service_desk_settings = project.service_desk_setting
service_desk_attributes = service_desk_attributes =
{ service_desk_address: project.service_desk_address, service_desk_enabled: project.service_desk_enabled } {
service_desk_address: project.service_desk_address,
service_desk_enabled: project.service_desk_enabled,
issue_template_key: service_desk_settings&.issue_template_key,
template_file_missing: service_desk_settings&.issue_template_missing?
}
format.json { render json: service_desk_attributes } format.json { render json: service_desk_attributes }
end end
......
...@@ -48,6 +48,7 @@ module EE ...@@ -48,6 +48,7 @@ module EE
has_one :gitlab_slack_application_service has_one :gitlab_slack_application_service
has_one :alerts_service has_one :alerts_service
has_one :service_desk_setting, class_name: 'ServiceDeskSetting'
has_one :tracing_setting, class_name: 'ProjectTracingSetting' has_one :tracing_setting, class_name: 'ProjectTracingSetting'
has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting' has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting'
has_one :incident_management_setting, inverse_of: :project, class_name: 'IncidentManagement::ProjectIncidentManagementSetting' has_one :incident_management_setting, inverse_of: :project, class_name: 'IncidentManagement::ProjectIncidentManagementSetting'
......
# frozen_string_literal: true
class ServiceDeskSetting < ApplicationRecord
include Gitlab::Utils::StrongMemoize
belongs_to :project
validates :project_id, presence: true
validate :valid_issue_template
def self.update_template_key_for(project:, issue_template_key:)
return unless issue_template_key
settings = safe_find_or_create_by!(project_id: project.id)
settings.update!(issue_template_key: issue_template_key)
settings
end
def issue_template_content
strong_memoize(:issue_template_content) do
next unless issue_template_key.present?
Gitlab::Template::IssueTemplate.find(issue_template_key, project).content
rescue ::Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
end
end
def issue_template_missing?
issue_template_key.present? && !issue_template_content.present?
end
def valid_issue_template
if issue_template_missing?
errors.add(:issue_template_key, "Issue template empty or not found")
end
end
end
---
title: Use issue templates on service desk(backend)
merge_request: 19515
author:
type: added
...@@ -9,6 +9,7 @@ module Gitlab ...@@ -9,6 +9,7 @@ module Gitlab
module EE module EE
class ServiceDeskHandler < BaseHandler class ServiceDeskHandler < BaseHandler
include ReplyProcessing include ReplyProcessing
include Gitlab::Utils::StrongMemoize
HANDLER_REGEX = /\A#{HANDLER_ACTION_BASE_REGEX}-issue-\z/.freeze HANDLER_REGEX = /\A#{HANDLER_ACTION_BASE_REGEX}-issue-\z/.freeze
HANDLER_REGEX_LEGACY = /\A(?<project_path>[^\+]*)\z/.freeze HANDLER_REGEX_LEGACY = /\A(?<project_path>[^\+]*)\z/.freeze
...@@ -62,18 +63,59 @@ module Gitlab ...@@ -62,18 +63,59 @@ module Gitlab
project, project,
User.support_bot, User.support_bot,
title: issue_title, title: issue_title,
description: message_including_reply, description: message_including_template,
confidential: true, confidential: true,
service_desk_reply_to: from_address service_desk_reply_to: from_address
).execute ).execute
raise InvalidIssueError unless @issue.persisted? raise InvalidIssueError unless @issue.persisted?
if service_desk_setting&.issue_template_missing?
create_template_not_found_note(@issue)
end
end end
def send_thank_you_email! def send_thank_you_email!
Notify.service_desk_thank_you_email(@issue.id).deliver_later! Notify.service_desk_thank_you_email(@issue.id).deliver_later!
end end
def message_including_template
description = message_including_reply
template_content = service_desk_setting&.issue_template_content
if template_content.present?
description += " \n" + template_content
end
description
end
def service_desk_setting
strong_memoize(:service_desk_setting) do
project.service_desk_setting
end
end
def create_template_not_found_note(issue)
issue_template_key = service_desk_setting&.issue_template_key
warning_note = <<-MD.strip_heredoc
WARNING: The template file #{issue_template_key}.md used for service desk issues is empty or could not be found.
Please check service desk settings and update the file to be used.
MD
note_params = {
noteable: issue,
note: warning_note
}
::Notes::CreateService.new(
project,
User.support_bot,
note_params
).execute
end
def from_address def from_address
(mail.reply_to || []).first || mail.from.first || mail.sender (mail.reply_to || []).first || mail.from.first || mail.sender
end end
......
...@@ -37,6 +37,30 @@ describe Projects::ServiceDeskController do ...@@ -37,6 +37,30 @@ describe Projects::ServiceDeskController do
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
end end
context 'when issue template is present' do
it 'returns template_file_missing as false' do
template_path = '.gitlab/issue_templates/service_desk.md'
project.repository.create_file(user, template_path, 'text from template', message: 'message', branch_name: 'master')
ServiceDeskSetting.update_template_key_for(project: project, issue_template_key: 'service_desk')
get :show, params: { namespace_id: project.namespace.to_param, project_id: project }, format: :json
response_hash = JSON.parse(response.body)
expect(response_hash['template_file_missing']).to eq(false)
end
end
context 'when issue template file becomes outdated' do
it 'returns template_file_missing as true' do
service = ServiceDeskSetting.new(project_id: project.id, issue_template_key: 'deleted')
service.save(validate: false)
get :show, params: { namespace_id: project.namespace.to_param, project_id: project }, format: :json
expect(json_response['template_file_missing']).to eq(true)
end
end
end end
describe 'PUT service desk properties' do describe 'PUT service desk properties' do
...@@ -50,6 +74,20 @@ describe Projects::ServiceDeskController do ...@@ -50,6 +74,20 @@ describe Projects::ServiceDeskController do
expect(response.status).to eq(200) expect(response.status).to eq(200)
end end
it 'sets issue_template_key' do
template_path = '.gitlab/issue_templates/service_desk.md'
project.repository.create_file(user, template_path, 'template text', message: 'message', branch_name: 'master')
ServiceDeskSetting.update_template_key_for(project: project, issue_template_key: 'service_desk')
put :update, params: { namespace_id: project.namespace.to_param, project_id: project, issue_template_key: 'service_desk' }, format: :json
settings = project.service_desk_setting
expect(settings).to be_present
expect(settings.issue_template_key).to eq('service_desk')
expect(json_response['template_file_missing']).to eq(false)
expect(json_response['issue_template_key']).to eq('service_desk')
end
context 'when user cannot admin the project' do context 'when user cannot admin the project' do
let(:other_user) { create(:user) } let(:other_user) { create(:user) }
......
...@@ -11,11 +11,11 @@ describe Gitlab::Email::Handler::EE::ServiceDeskHandler do ...@@ -11,11 +11,11 @@ describe Gitlab::Email::Handler::EE::ServiceDeskHandler do
end end
let(:email_raw) { email_fixture('emails/service_desk.eml', dir: 'ee') } let(:email_raw) { email_fixture('emails/service_desk.eml', dir: 'ee') }
let(:namespace) { create(:namespace, name: "email") } let_it_be(:namespace) { create(:namespace, name: "email") }
let(:expected_description) { "Service desk stuff!\n\n```\na = b\n```\n\n![image](uploads/image.png)" } let(:expected_description) { "Service desk stuff!\n\n```\na = b\n```\n\n![image](uploads/image.png)" }
context 'service desk is enabled for the project' do context 'service desk is enabled for the project' do
let(:project) { create(:project, :public, namespace: namespace, path: 'test', service_desk_enabled: true) } let_it_be(:project) { create(:project, :repository, :public, namespace: namespace, path: 'test', service_desk_enabled: true) }
before do before do
allow(::EE::Gitlab::ServiceDesk).to receive(:enabled?).and_return(true) allow(::EE::Gitlab::ServiceDesk).to receive(:enabled?).and_return(true)
...@@ -52,6 +52,63 @@ describe Gitlab::Email::Handler::EE::ServiceDeskHandler do ...@@ -52,6 +52,63 @@ describe Gitlab::Email::Handler::EE::ServiceDeskHandler do
it_behaves_like 'a new issue request' it_behaves_like 'a new issue request'
end end
context 'when using issue templates' do
let_it_be(:user) { create(:user) }
before do
setup_attachment
end
context 'and template is present' do
it 'appends template text to issue description' do
template_path = '.gitlab/issue_templates/service_desk.md'
project.repository.create_file(user, template_path, 'text from template', message: 'message', branch_name: 'master')
ServiceDeskSetting.update_template_key_for(project: project, issue_template_key: 'service_desk')
receiver.execute
issue_description = Issue.last.description
expect(issue_description).to include(expected_description)
expect(issue_description.lines.last).to eq('text from template')
end
end
context 'and template cannot be found' do
before do
service = ServiceDeskSetting.new(project_id: project.id, issue_template_key: 'unknown')
service.save(validate: false)
end
it 'does not append template text to issue description' do
receiver.execute
new_issue = Issue.last
expect(new_issue.description).to eq(expected_description.strip)
end
it 'creates support bot note on issue' do
receiver.execute
note = Note.last
expect(note.note).to include("WARNING: The template file unknown.md used for service desk issues is empty or could not be found.")
expect(note.author).to eq(User.support_bot)
end
it 'does not send warning note email' do
ActionMailer::Base.deliveries = []
perform_enqueued_jobs do
expect { receiver.execute }.to change { ActionMailer::Base.deliveries.size }.by(1)
end
# Only sends created issue email
expect(ActionMailer::Base.deliveries.last.text_part.body).to include("Thank you for your support request!")
end
end
end
end end
describe '#can_handle?' do describe '#can_handle?' do
......
# frozen_string_literal: true
require 'spec_helper'
describe ServiceDeskSetting do
describe 'validations' do
it { is_expected.to validate_presence_of(:project_id) }
end
describe 'associations' do
it { is_expected.to belong_to(:project) }
end
describe '.update_template_key_for' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository) }
context 'when template exists' do
it 'updates issue_template_key' do
template_path = '.gitlab/issue_templates/service_desk.md'
project.repository.create_file(user, template_path, 'Template text', message: 'message', branch_name: 'master')
described_class.update_template_key_for(project: project, issue_template_key: 'service_desk')
expect(project.service_desk_setting.issue_template_key).to eq('service_desk')
end
end
context 'when template does not exist' do
it 'raises error' do
expect do
described_class.update_template_key_for(project: project, issue_template_key: 'unknown')
end.to raise_error(ActiveRecord::RecordInvalid)
end
end
end
end
...@@ -275,3 +275,4 @@ ee: ...@@ -275,3 +275,4 @@ ee:
- :unprotect_access_levels - :unprotect_access_levels
- protected_environments: - protected_environments:
- :deploy_access_levels - :deploy_access_levels
- :service_desk_setting
...@@ -432,6 +432,7 @@ project: ...@@ -432,6 +432,7 @@ project:
- downstream_projects - downstream_projects
- upstream_project_subscriptions - upstream_project_subscriptions
- downstream_project_subscriptions - downstream_project_subscriptions
- service_desk_setting
award_emoji: award_emoji:
- awardable - awardable
- user - user
......
...@@ -762,3 +762,6 @@ ZoomMeeting: ...@@ -762,3 +762,6 @@ ZoomMeeting:
- url - url
- created_at - created_at
- updated_at - updated_at
ServiceDeskSetting:
- project_id
- issue_template_key
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment