feat: Add activity messages for linear actions (#11654)
This commit is contained in:
@@ -93,6 +93,8 @@ RSpec.describe 'Linear Integration API', type: :request do
|
||||
end
|
||||
|
||||
describe 'POST /api/v1/accounts/:account_id/integrations/linear/create_issue' do
|
||||
let(:inbox) { create(:inbox, account: account) }
|
||||
let(:conversation) { create(:conversation, account: account, inbox: inbox) }
|
||||
let(:issue_params) do
|
||||
{
|
||||
team_id: 'team1',
|
||||
@@ -101,32 +103,56 @@ RSpec.describe 'Linear Integration API', type: :request do
|
||||
assignee_id: 'user1',
|
||||
priority: 'high',
|
||||
state_id: 'state1',
|
||||
label_ids: ['label1']
|
||||
label_ids: ['label1'],
|
||||
conversation_id: conversation.display_id
|
||||
}
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
context 'when the issue is created successfully' do
|
||||
let(:created_issue) { { data: { 'id' => 'issue1', 'title' => 'Sample Issue' } } }
|
||||
let(:created_issue) { { data: { identifier: 'ENG-123', title: 'Sample Issue' } } }
|
||||
|
||||
it 'returns the created issue' do
|
||||
allow(processor_service).to receive(:create_issue).with(issue_params.stringify_keys).and_return(created_issue)
|
||||
|
||||
post "/api/v1/accounts/#{account.id}/integrations/linear/create_issue",
|
||||
params: issue_params,
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to include('Sample Issue')
|
||||
end
|
||||
|
||||
it 'creates activity message when conversation is provided' do
|
||||
allow(processor_service).to receive(:create_issue).with(issue_params.stringify_keys).and_return(created_issue)
|
||||
|
||||
expect do
|
||||
post "/api/v1/accounts/#{account.id}/integrations/linear/create_issue",
|
||||
params: issue_params,
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
end.to have_enqueued_job(Conversations::ActivityMessageJob)
|
||||
.with(conversation, {
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: :activity,
|
||||
content: "Linear issue ENG-123 was created by #{agent.name}"
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when issue creation fails' do
|
||||
it 'returns error message' do
|
||||
it 'returns error message and does not create activity message' do
|
||||
allow(processor_service).to receive(:create_issue).with(issue_params.stringify_keys).and_return(error: 'error message')
|
||||
post "/api/v1/accounts/#{account.id}/integrations/linear/create_issue",
|
||||
params: issue_params,
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect do
|
||||
post "/api/v1/accounts/#{account.id}/integrations/linear/create_issue",
|
||||
params: issue_params,
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
end.not_to have_enqueued_job(Conversations::ActivityMessageJob)
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response.body).to include('error message')
|
||||
end
|
||||
@@ -135,7 +161,7 @@ RSpec.describe 'Linear Integration API', type: :request do
|
||||
end
|
||||
|
||||
describe 'POST /api/v1/accounts/:account_id/integrations/linear/link_issue' do
|
||||
let(:issue_id) { 'issue1' }
|
||||
let(:issue_id) { 'ENG-456' }
|
||||
let(:conversation) { create(:conversation, account: account) }
|
||||
let(:link) { "#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/#{account.id}/conversations/#{conversation.display_id}" }
|
||||
let(:title) { 'Sample Issue' }
|
||||
@@ -144,24 +170,38 @@ RSpec.describe 'Linear Integration API', type: :request do
|
||||
context 'when the issue is linked successfully' do
|
||||
let(:linked_issue) { { data: { 'id' => 'issue1', 'link' => 'https://linear.app/issue1' } } }
|
||||
|
||||
it 'returns the linked issue' do
|
||||
it 'returns the linked issue and creates activity message' do
|
||||
allow(processor_service).to receive(:link_issue).with(link, issue_id, title).and_return(linked_issue)
|
||||
post "/api/v1/accounts/#{account.id}/integrations/linear/link_issue",
|
||||
params: { conversation_id: conversation.display_id, issue_id: issue_id, title: title },
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect do
|
||||
post "/api/v1/accounts/#{account.id}/integrations/linear/link_issue",
|
||||
params: { conversation_id: conversation.display_id, issue_id: issue_id, title: title },
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
end.to have_enqueued_job(Conversations::ActivityMessageJob)
|
||||
.with(conversation, {
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: :activity,
|
||||
content: "Linear issue ENG-456 was linked by #{agent.name}"
|
||||
})
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to include('https://linear.app/issue1')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when issue linking fails' do
|
||||
it 'returns error message' do
|
||||
it 'returns error message and does not create activity message' do
|
||||
allow(processor_service).to receive(:link_issue).with(link, issue_id, title).and_return(error: 'error message')
|
||||
post "/api/v1/accounts/#{account.id}/integrations/linear/link_issue",
|
||||
params: { conversation_id: conversation.display_id, issue_id: issue_id, title: title },
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect do
|
||||
post "/api/v1/accounts/#{account.id}/integrations/linear/link_issue",
|
||||
params: { conversation_id: conversation.display_id, issue_id: issue_id, title: title },
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
end.not_to have_enqueued_job(Conversations::ActivityMessageJob)
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response.body).to include('error message')
|
||||
end
|
||||
@@ -171,29 +211,45 @@ RSpec.describe 'Linear Integration API', type: :request do
|
||||
|
||||
describe 'POST /api/v1/accounts/:account_id/integrations/linear/unlink_issue' do
|
||||
let(:link_id) { 'attachment1' }
|
||||
let(:issue_id) { 'ENG-789' }
|
||||
let(:conversation) { create(:conversation, account: account) }
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
context 'when the issue is unlinked successfully' do
|
||||
let(:unlinked_issue) { { data: { 'id' => 'issue1', 'link' => 'https://linear.app/issue1' } } }
|
||||
|
||||
it 'returns the unlinked issue' do
|
||||
it 'returns the unlinked issue and creates activity message' do
|
||||
allow(processor_service).to receive(:unlink_issue).with(link_id).and_return(unlinked_issue)
|
||||
post "/api/v1/accounts/#{account.id}/integrations/linear/unlink_issue",
|
||||
params: { link_id: link_id },
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect do
|
||||
post "/api/v1/accounts/#{account.id}/integrations/linear/unlink_issue",
|
||||
params: { link_id: link_id, issue_id: issue_id, conversation_id: conversation.display_id },
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
end.to have_enqueued_job(Conversations::ActivityMessageJob)
|
||||
.with(conversation, {
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: :activity,
|
||||
content: "Linear issue ENG-789 was unlinked by #{agent.name}"
|
||||
})
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response.body).to include('https://linear.app/issue1')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when issue unlinking fails' do
|
||||
it 'returns error message' do
|
||||
it 'returns error message and does not create activity message' do
|
||||
allow(processor_service).to receive(:unlink_issue).with(link_id).and_return(error: 'error message')
|
||||
post "/api/v1/accounts/#{account.id}/integrations/linear/unlink_issue",
|
||||
params: { link_id: link_id },
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect do
|
||||
post "/api/v1/accounts/#{account.id}/integrations/linear/unlink_issue",
|
||||
params: { link_id: link_id, issue_id: issue_id, conversation_id: conversation.display_id },
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
end.not_to have_enqueued_job(Conversations::ActivityMessageJob)
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response.body).to include('error message')
|
||||
end
|
||||
|
||||
@@ -82,15 +82,27 @@ describe Integrations::Linear::ProcessorService do
|
||||
end
|
||||
let(:issue_response) do
|
||||
{
|
||||
'issueCreate' => { 'issue' => { 'id' => 'issue1', 'title' => 'Issue title' } }
|
||||
'issueCreate' => {
|
||||
'issue' => {
|
||||
'id' => 'issue1',
|
||||
'title' => 'Issue title',
|
||||
'identifier' => 'ENG-123'
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'when Linear client returns valid data' do
|
||||
it 'returns parsed issue data' do
|
||||
it 'returns parsed issue data with identifier' do
|
||||
allow(linear_client).to receive(:create_issue).with(params).and_return(issue_response)
|
||||
result = service.create_issue(params)
|
||||
expect(result).to eq({ data: { id: 'issue1', title: 'Issue title' } })
|
||||
expect(result).to eq({
|
||||
data: {
|
||||
id: 'issue1',
|
||||
title: 'Issue title',
|
||||
identifier: 'ENG-123'
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -133,13 +145,13 @@ describe Integrations::Linear::ProcessorService do
|
||||
|
||||
describe '#unlink_issue' do
|
||||
let(:link_id) { 'attachment1' }
|
||||
let(:unlink_response) { { data: { link_id: link_id } } }
|
||||
let(:linear_client_response) { { success: true } }
|
||||
|
||||
context 'when Linear client returns valid data' do
|
||||
it 'returns parsed unlink data' do
|
||||
allow(linear_client).to receive(:unlink_issue).with(link_id).and_return(unlink_response)
|
||||
it 'returns unlink data with link_id' do
|
||||
allow(linear_client).to receive(:unlink_issue).with(link_id).and_return(linear_client_response)
|
||||
result = service.unlink_issue(link_id)
|
||||
expect(result).to eq(unlink_response)
|
||||
expect(result).to eq({ data: { link_id: link_id } })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -207,4 +219,59 @@ describe Integrations::Linear::ProcessorService do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Tests specifically for activity message integration
|
||||
describe 'activity message data compatibility' do
|
||||
let(:linear_client_response) { { success: true } }
|
||||
|
||||
describe '#create_issue' do
|
||||
it 'includes identifier field needed for activity messages' do
|
||||
params = { title: 'Test Issue', team_id: 'team1' }
|
||||
response = {
|
||||
'issueCreate' => {
|
||||
'issue' => {
|
||||
'id' => 'internal_id_123',
|
||||
'title' => 'Test Issue',
|
||||
'identifier' => 'ENG-456'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allow(linear_client).to receive(:create_issue).with(params).and_return(response)
|
||||
result = service.create_issue(params)
|
||||
|
||||
expect(result[:data]).to have_key(:identifier)
|
||||
expect(result[:data][:identifier]).to eq('ENG-456')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#link_issue' do
|
||||
it 'returns issue_id in response for activity messages' do
|
||||
link = 'https://example.com'
|
||||
issue_id = 'ENG-789'
|
||||
title = 'Test Issue'
|
||||
response = {
|
||||
'attachmentLinkURL' => {
|
||||
'attachment' => { 'id' => 'attachment123' }
|
||||
}
|
||||
}
|
||||
|
||||
allow(linear_client).to receive(:link_issue).with(link, issue_id, title).and_return(response)
|
||||
result = service.link_issue(link, issue_id, title)
|
||||
|
||||
expect(result[:data][:id]).to eq(issue_id)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#unlink_issue' do
|
||||
it 'returns structured data for activity messages' do
|
||||
link_id = 'attachment456'
|
||||
|
||||
allow(linear_client).to receive(:unlink_issue).with(link_id).and_return(linear_client_response)
|
||||
result = service.unlink_issue(link_id)
|
||||
|
||||
expect(result).to eq({ data: { link_id: link_id } })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
174
spec/services/linear/activity_message_service_spec.rb
Normal file
174
spec/services/linear/activity_message_service_spec.rb
Normal file
@@ -0,0 +1,174 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Linear::ActivityMessageService, type: :service do
|
||||
let(:account) { create(:account) }
|
||||
let(:inbox) { create(:inbox, account: account) }
|
||||
let(:conversation) { create(:conversation, account: account, inbox: inbox) }
|
||||
let(:user) { create(:user, account: account) }
|
||||
|
||||
describe '#perform' do
|
||||
context 'when action_type is issue_created' do
|
||||
let(:service) do
|
||||
described_class.new(
|
||||
conversation: conversation,
|
||||
action_type: :issue_created,
|
||||
issue_data: { id: 'ENG-123' },
|
||||
user: user
|
||||
)
|
||||
end
|
||||
|
||||
it 'enqueues an activity message job' do
|
||||
expect do
|
||||
service.perform
|
||||
end.to have_enqueued_job(Conversations::ActivityMessageJob)
|
||||
.with(conversation, {
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: :activity,
|
||||
content: "Linear issue ENG-123 was created by #{user.name}"
|
||||
})
|
||||
end
|
||||
|
||||
it 'does not enqueue job when issue data lacks id' do
|
||||
service = described_class.new(
|
||||
conversation: conversation,
|
||||
action_type: :issue_created,
|
||||
issue_data: { title: 'Some issue' },
|
||||
user: user
|
||||
)
|
||||
|
||||
expect do
|
||||
service.perform
|
||||
end.not_to have_enqueued_job(Conversations::ActivityMessageJob)
|
||||
end
|
||||
|
||||
it 'does not enqueue job when issue_data is empty' do
|
||||
service = described_class.new(
|
||||
conversation: conversation,
|
||||
action_type: :issue_created,
|
||||
issue_data: {},
|
||||
user: user
|
||||
)
|
||||
|
||||
expect do
|
||||
service.perform
|
||||
end.not_to have_enqueued_job(Conversations::ActivityMessageJob)
|
||||
end
|
||||
|
||||
it 'does not enqueue job when conversation is nil' do
|
||||
service = described_class.new(
|
||||
conversation: nil,
|
||||
action_type: :issue_created,
|
||||
issue_data: { id: 'ENG-123' },
|
||||
user: user
|
||||
)
|
||||
|
||||
expect do
|
||||
service.perform
|
||||
end.not_to have_enqueued_job(Conversations::ActivityMessageJob)
|
||||
end
|
||||
|
||||
it 'does not enqueue job when user is nil' do
|
||||
service = described_class.new(
|
||||
conversation: conversation,
|
||||
action_type: :issue_created,
|
||||
issue_data: { id: 'ENG-123' },
|
||||
user: nil
|
||||
)
|
||||
|
||||
expect do
|
||||
service.perform
|
||||
end.not_to have_enqueued_job(Conversations::ActivityMessageJob)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when action_type is issue_linked' do
|
||||
let(:service) do
|
||||
described_class.new(
|
||||
conversation: conversation,
|
||||
action_type: :issue_linked,
|
||||
issue_data: { id: 'ENG-456' },
|
||||
user: user
|
||||
)
|
||||
end
|
||||
|
||||
it 'enqueues an activity message job' do
|
||||
expect do
|
||||
service.perform
|
||||
end.to have_enqueued_job(Conversations::ActivityMessageJob)
|
||||
.with(conversation, {
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: :activity,
|
||||
content: "Linear issue ENG-456 was linked by #{user.name}"
|
||||
})
|
||||
end
|
||||
|
||||
it 'does not enqueue job when issue data lacks id' do
|
||||
service = described_class.new(
|
||||
conversation: conversation,
|
||||
action_type: :issue_linked,
|
||||
issue_data: { title: 'Some issue' },
|
||||
user: user
|
||||
)
|
||||
|
||||
expect do
|
||||
service.perform
|
||||
end.not_to have_enqueued_job(Conversations::ActivityMessageJob)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when action_type is issue_unlinked' do
|
||||
let(:service) do
|
||||
described_class.new(
|
||||
conversation: conversation,
|
||||
action_type: :issue_unlinked,
|
||||
issue_data: { id: 'ENG-789' },
|
||||
user: user
|
||||
)
|
||||
end
|
||||
|
||||
it 'enqueues an activity message job' do
|
||||
expect do
|
||||
service.perform
|
||||
end.to have_enqueued_job(Conversations::ActivityMessageJob)
|
||||
.with(conversation, {
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: :activity,
|
||||
content: "Linear issue ENG-789 was unlinked by #{user.name}"
|
||||
})
|
||||
end
|
||||
|
||||
it 'does not enqueue job when issue data lacks id' do
|
||||
service = described_class.new(
|
||||
conversation: conversation,
|
||||
action_type: :issue_unlinked,
|
||||
issue_data: { title: 'Some issue' },
|
||||
user: user
|
||||
)
|
||||
|
||||
expect do
|
||||
service.perform
|
||||
end.not_to have_enqueued_job(Conversations::ActivityMessageJob)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when action_type is unknown' do
|
||||
let(:service) do
|
||||
described_class.new(
|
||||
conversation: conversation,
|
||||
action_type: :unknown_action,
|
||||
issue_data: { id: 'ENG-999' },
|
||||
user: user
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not enqueue job for unknown action types' do
|
||||
expect do
|
||||
service.perform
|
||||
end.not_to have_enqueued_job(Conversations::ActivityMessageJob)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user