Skip to content
代码片段 群组 项目
提交 cf8d739e 编辑于 作者: Andy Soiron's avatar Andy Soiron
浏览文件

Merge branch '344856-add-incident-declare-slash-command-part-3' into 'master'

Make incident modal interactive and create incident

See merge request https://gitlab.com/gitlab-org/gitlab/-/merge_requests/103478



Merged-by: default avatarAndy Soiron <asoiron@gitlab.com>
Approved-by: default avatarAndy Soiron <asoiron@gitlab.com>
Co-authored-by: default avatarRajendra Kadam <rkadam@gitlab.com>
No related branches found
No related tags found
无相关合并请求
......@@ -19,6 +19,13 @@ def execute
# rubocop: disable CodeReuse/ActiveRecord
def find_chat_name
if @integration.nil?
return ChatName.find_by(
team_id: @params[:team_id],
chat_id: @params[:user_id]
)
end
ChatName.find_by(
integration: @integration,
team_id: @params[:team_id],
......
# frozen_string_literal: true
module Integrations
module SlackInteractions
module IncidentManagement
class IncidentModalSubmitService
include GitlabRoutingHelper
include Gitlab::Routing
IssueCreateError = Class.new(StandardError)
def initialize(params)
@params = params
@values = params.dig(:view, :state, :values)
@team_id = params.dig(:team, :id)
@user_id = params.dig(:user, :id)
end
attr_accessor :params, :values, :team_id, :user_id
def execute
create_response = Issues::CreateService.new(
project: project,
current_user: find_user.user,
params: incident_params,
spam_params: nil
).execute
raise IssueCreateError, create_response.errors.to_sentence if create_response.error?
incident = create_response.payload[:issue]
incident_link = incident_link_text(incident)
response = send_to_slack(incident_link)
return ServiceResponse.success(payload: { incident: incident }) if response['ok']
ServiceResponse.error(
message: _('Something went wrong when sending the incident link to Slack.'),
payload: response
).track_exception(
response: response.to_h,
slack_workspace_id: team_id,
slack_user_id: user_id
)
rescue StandardError => e
send_to_slack(_('There was a problem creating the incident. Please try again.'))
ServiceResponse
.error(
message: e.message
).track_exception(
slack_workspace_id: team_id,
slack_user_id: user_id,
as: e.class
)
end
private
def incident_params
{
"title": values.dig(:title_input, :title, :value),
"severity": severity,
"confidential": confidential?,
"description": description,
"issue_type": "incident"
}
end
def send_to_slack(text)
response_url = params.dig(:view, :private_metadata)
body = {
'replace_original': 'true',
'text': text
}
Gitlab::HTTP.post(
response_url,
body: Gitlab::Json.dump(body),
headers: { 'Content-Type' => 'application/json' }
)
end
def incident_link_text(incident)
"#{_('New incident has been created')}: <#{issue_url(incident)}|#{incident.to_reference} - #{incident.title}>"
end
def project
full_path = values.dig(:project_and_severity_selector, :project, :selected_option, :value)
Project.find_by_full_path(full_path)
end
def find_user
ChatNames::FindUserService.new(
nil,
{ team_id: team_id, user_id: user_id }
).execute
end
def description
description = values.dig(:incident_description, :description, :value)
zoom_link = values.dig(:zoom, :link, :value)
return description if zoom_link.blank?
"#{description} \n/zoom #{zoom_link}"
end
def confidential?
values.dig(:confidentiality, :confidential, :selected_options).present?
end
def severity
values.dig(:project_and_severity_selector, :severity, :selected_option, :value) || 'unknown'
end
end
end
end
end
......@@ -10,7 +10,8 @@ class SlackInteractivityWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
INTERACTIONS = {
'view_closed' => SlackInteractions::IncidentManagement::IncidentModalClosedService
'view_closed' => SlackInteractions::IncidentManagement::IncidentModalClosedService,
'view_submission' => SlackInteractions::IncidentManagement::IncidentModalSubmitService
}.freeze
feature_category :integrations
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Integrations::SlackInteractions::IncidentManagement::IncidentModalSubmitService,
feature_category: :incident_management do
describe '#execute' do
let_it_be(:slack_installation) { create(:slack_integration) }
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:api_url) { 'https://api.slack.com/id/1234' }
let_it_be(:chat_name) do
create(:chat_name,
user: user,
team_id: slack_installation.team_id,
chat_id: slack_installation.user_id,
integration: slack_installation.integration
)
end
# Setting below params as they are optional, have added values wherever required in specs
let(:zoom_link) { '' }
let(:severity) { {} }
let(:confidential_selected_options) { [] }
let(:confidential) { { selected_options: confidential_selected_options } }
let(:title) { 'Incident title' }
let(:zoom) do
{
link: {
value: zoom_link
}
}
end
let(:params) do
{
team: {
id: slack_installation.team_id
},
user: {
id: slack_installation.user_id
},
view: {
private_metadata: api_url,
state: {
values: {
title_input: {
title: {
value: title
}
},
incident_description: {
description: {
value: 'Incident description'
}
},
project_and_severity_selector: {
project: {
selected_option: {
value: project.full_path
}
},
severity: severity
},
confidentiality: {
confidential: confidential
},
zoom: zoom
}
}
}
}
end
subject(:execute_service) { described_class.new(params).execute }
shared_examples 'error in creation' do |error_message|
it 'returns error and raises exception' do
expect(::Gitlab::ErrorTracking).to receive(:track_exception)
.with(
described_class::IssueCreateError.new(error_message),
{
slack_workspace_id: slack_installation.team_id,
slack_user_id: slack_installation.user_id
}
)
expect(Gitlab::HTTP).to receive(:post)
.with(
api_url,
body: Gitlab::Json.dump(
{
'replace_original': 'true',
'text': 'There was a problem creating the incident. Please try again.'
}
),
headers: { 'Content-Type' => 'application/json' }
)
response = execute_service
expect(response).to be_error
expect(response.message).to eq(error_message)
end
end
context 'when user has permissions to create incidents' do
let(:api_response) { '{"ok":true}' }
before do
project.add_developer(user)
stub_request(:post, api_url)
.to_return(body: api_response, headers: { 'Content-Type' => 'application/json' })
end
context 'with non-optional params' do
it 'creates incident' do
response = execute_service
incident = response[:incident]
expect(response).to be_success
expect(incident).not_to be_nil
expect(incident.description).to eq('Incident description')
expect(incident.author).to eq(user)
expect(incident.severity).to eq('unknown')
expect(incident.confidential).to be_falsey
end
it 'sends incident link to slack' do
execute_service
expect(WebMock).to have_requested(:post, api_url)
end
end
context 'with zoom_link' do
let(:zoom_link) { 'https://gitlab.zoom.us/j/1234' }
it 'sets zoom link as quick action' do
incident = execute_service[:incident]
zoom_meeting = ZoomMeeting.find_by_issue_id(incident.id)
expect(incident.description).to eq("Incident description")
expect(zoom_meeting.url).to eq(zoom_link)
end
end
context 'with confidential and severity' do
let(:confidential_selected_options) { ['confidential'] }
let(:severity) do
{
selected_option: {
value: 'high'
}
}
end
it 'sets confidential and severity' do
incident = execute_service[:incident]
expect(incident.confidential).to be_truthy
expect(incident.severity).to eq('high')
end
end
context 'when response is not ok' do
let(:api_response) { '{"ok":false}' }
it 'returns error response and tracks the exception' do
expect(::Gitlab::ErrorTracking).to receive(:track_exception)
.with(
StandardError.new('Something went wrong when sending the incident link to Slack.'),
{
response: { 'ok' => false },
slack_workspace_id: slack_installation.team_id,
slack_user_id: slack_installation.user_id
}
)
execute_service
end
end
context 'when incident creation fails' do
let(:title) { '' }
it_behaves_like 'error in creation', "Title can't be blank"
end
end
context 'when user does not have permission to create incidents' do
it_behaves_like 'error in creation', 'Operation not allowed'
end
end
end
......@@ -3,19 +3,23 @@
require 'spec_helper'
RSpec.describe Integrations::SlackInteractivityWorker, :clean_gitlab_redis_shared_state do
describe '.interaction?' do
subject { described_class.interaction?(slack_interaction) }
context 'when slack_interaction is known' do
let(:slack_interaction) { 'view_closed' }
using RSpec::Parameterized::TableSyntax
it { is_expected.to be_truthy }
end
let_it_be(:slack_integration) { create(:slack_integration) }
context 'when slack_interaction is not known' do
let(:slack_interaction) { 'foo' }
describe '.interaction?' do
context 'when slack_interaction is known/unknown' do
where(:slack_interaction, :result) do
'view_closed' | true
'view_submission' | true
'foo' | false
end
it { is_expected.to be_falsey }
with_them do
it 'returns correct result' do
expect(described_class.interaction?(slack_interaction)).to be(result)
end
end
end
end
......@@ -30,8 +34,6 @@
end
let(:worker) { described_class.new }
let(:slack_interaction) { 'view_closed' }
let(:service_class) { ::Integrations::SlackInteractions::IncidentManagement::IncidentModalClosedService }
let(:args) do
{
......@@ -43,13 +45,16 @@
let(:params) do
{
user: {
id: 'U0123ABCDEF'
id: slack_integration.user_id
},
team: {
id: 'T0123A456BC'
id: slack_integration.team_id
},
view: {
private_metadata: 'https://response.slack.com/id/123'
private_metadata: 'https://response.slack.com/id/123',
state: {
values: {}
}
}
}
end
......@@ -57,28 +62,52 @@
shared_examples 'logs extra metadata on done' do
specify do
expect(worker).to receive(:log_extra_metadata_on_done).with(:slack_interaction, slack_interaction)
expect(worker).to receive(:log_extra_metadata_on_done).with(:slack_user_id, 'U0123ABCDEF')
expect(worker).to receive(:log_extra_metadata_on_done).with(:slack_workspace_id, 'T0123A456BC')
expect(worker).to receive(:log_extra_metadata_on_done).with(:slack_user_id, slack_integration.user_id)
expect(worker).to receive(:log_extra_metadata_on_done).with(:slack_workspace_id, slack_integration.team_id)
worker.perform(args)
end
end
it 'executes the correct service' do
expect_next_instance_of(service_class, params) do |service|
expect(service).to receive(:execute).and_return(ServiceResponse.success)
context 'when view is closed' do
let(:slack_interaction) { 'view_closed' }
it 'executes the correct service' do
view_closed_service = described_class::INTERACTIONS['view_closed']
expect_next_instance_of(view_closed_service, params) do |service|
expect(service).to receive(:execute).and_return(ServiceResponse.success)
end
worker.perform(args)
end
worker.perform(args)
it_behaves_like 'logs extra metadata on done'
end
it_behaves_like 'logs extra metadata on done'
context 'when view is submitted' do
let(:slack_interaction) { 'view_submission' }
it 'executes the submission service' do
view_submission_service = described_class::INTERACTIONS['view_submission']
expect_next_instance_of(view_submission_service, params) do |service|
expect(service).to receive(:execute).and_return(ServiceResponse.success)
end
worker.perform(args)
end
it_behaves_like 'logs extra metadata on done'
end
context 'when slack_interaction is not known' do
let(:slack_interaction) { 'foo' }
it 'does not execute the service class' do
expect(service_class).not_to receive(:new)
it 'does not execute a service class' do
described_class::INTERACTIONS.each_value do |service_class|
expect(service_class).not_to receive(:new)
end
worker.perform(args)
end
......
......@@ -27129,6 +27129,9 @@ msgstr ""
msgid "New incident"
msgstr ""
 
msgid "New incident has been created"
msgstr ""
msgid "New issue"
msgstr ""
 
......@@ -38676,6 +38679,9 @@ msgstr ""
msgid "Something went wrong when reordering designs. Please try again"
msgstr ""
 
msgid "Something went wrong when sending the incident link to Slack."
msgstr ""
msgid "Something went wrong while adding timeline event."
msgstr ""
 
......@@ -41497,6 +41503,9 @@ msgstr ""
msgid "There was a problem communicating with your device."
msgstr ""
 
msgid "There was a problem creating the incident. Please try again."
msgstr ""
msgid "There was a problem fetching CRM contacts."
msgstr ""
 
......@@ -40,6 +40,14 @@
expect(chat_name.reload.last_used_at).to eq(time)
end
context 'when integration is not passed' do
it 'returns chat name' do
requested_chat_name = described_class.new(nil, params).execute
expect(requested_chat_name).to eq(chat_name)
end
end
end
context 'when different user is requested' do
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册