diff --git a/app/controllers/api/v1/accounts/integrations/linear_controller.rb b/app/controllers/api/v1/accounts/integrations/linear_controller.rb index 5be1f5038..a8198a770 100644 --- a/app/controllers/api/v1/accounts/integrations/linear_controller.rb +++ b/app/controllers/api/v1/accounts/integrations/linear_controller.rb @@ -38,6 +38,17 @@ class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::Bas end end + def unlink_issue + link_id = params[:link_id] + issue = linear_processor_service.unlink_issue(link_id) + + if issue.is_a?(Hash) && issue[:error] + render json: { error: issue[:error] }, status: :unprocessable_entity + else + render json: issue, status: :ok + end + end + private def linear_processor_service @@ -45,6 +56,6 @@ class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::Bas end def permitted_params - params.permit(:team_id, :link, :issue_id, :title, :description, :assignee_id, :priority, label_ids: []) + params.permit(:team_id, :link, :issue_id, :link_id, :title, :description, :assignee_id, :priority, label_ids: []) end end diff --git a/config/routes.rb b/config/routes.rb index 96a39780a..eb57fe027 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -233,6 +233,7 @@ Rails.application.routes.draw do get :team_entites post :create_issue post :link_issue + post :unlink_issue end end end diff --git a/lib/integrations/linear/processor_service.rb b/lib/integrations/linear/processor_service.rb index 2315745e7..28ef9d638 100644 --- a/lib/integrations/linear/processor_service.rb +++ b/lib/integrations/linear/processor_service.rb @@ -36,7 +36,17 @@ class Integrations::Linear::ProcessorService { id: issue_id, - link: link + link: link, + link_id: response.with_indifferent_access[:attachmentLinkURL][:attachment][:id] + } + end + + def unlink_issue(link_id) + response = linear_client.unlink_issue(link_id) + return response if response[:error] + + { + link_id: link_id } end diff --git a/lib/linear.rb b/lib/linear.rb index 60f048ac8..424966a5c 100644 --- a/lib/linear.rb +++ b/lib/linear.rb @@ -53,6 +53,12 @@ class Linear execute_mutation(LinearMutations.issue_link(issue_id, link)) end + def unlink_issue(link_id) + raise ArgumentError, 'Missing link id' if link_id.blank? + + execute_mutation(LinearMutations.unlink_issue(link_id)) + end + private def validate_team_and_title(params) diff --git a/lib/linear_mutations.rb b/lib/linear_mutations.rb index 33ea2cf3c..e34fdcd43 100644 --- a/lib/linear_mutations.rb +++ b/lib/linear_mutations.rb @@ -44,4 +44,14 @@ module LinearMutations } GRAPHQL end + + def self.unlink_issue(link_id) + <<~GRAPHQL + mutation { + attachmentDelete(id: "#{link_id}") { + success + } + } + GRAPHQL + end end diff --git a/spec/controllers/api/v1/accounts/integrations/linear_controller_spec.rb b/spec/controllers/api/v1/accounts/integrations/linear_controller_spec.rb index b4dbf51c8..79c74713c 100644 --- a/spec/controllers/api/v1/accounts/integrations/linear_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/integrations/linear_controller_spec.rb @@ -155,4 +155,36 @@ RSpec.describe 'Linear Integration API', type: :request do end end end + + describe 'POST /api/v1/accounts/:account_id/integrations/linear/unlink_issue' do + let(:link_id) { 'attachment1' } + + context 'when it is an authenticated user' do + context 'when the issue is unlinked successfully' do + let(:unlinked_issue) { { 'id' => 'issue1', 'link' => 'https://linear.app/issue1' } } + + it 'returns the unlinked issue' 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(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 + 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(response).to have_http_status(:unprocessable_entity) + expect(response.body).to include('error message') + end + end + end + end end diff --git a/spec/lib/integrations/linear/processor_service_spec.rb b/spec/lib/integrations/linear/processor_service_spec.rb index 163e99f4b..470a7244e 100644 --- a/spec/lib/integrations/linear/processor_service_spec.rb +++ b/spec/lib/integrations/linear/processor_service_spec.rb @@ -107,11 +107,12 @@ describe Integrations::Linear::ProcessorService do describe '#link_issue' do let(:link) { 'https://example.com' } let(:issue_id) { 'issue1' } - let(:link_response) { { id: issue_id, link: link } } + let(:link_issue_response) { { id: issue_id, link: link, 'attachmentLinkURL': { 'attachment': { 'id': 'attachment1' } } } } + let(:link_response) { { id: issue_id, link: link, link_id: 'attachment1' } } context 'when Linear client returns valid data' do it 'returns parsed link data' do - allow(linear_client).to receive(:link_issue).with(link, issue_id).and_return(link_response) + allow(linear_client).to receive(:link_issue).with(link, issue_id).and_return(link_issue_response) result = service.link_issue(link, issue_id) expect(result).to eq(link_response) end diff --git a/spec/lib/linear_spec.rb b/spec/lib/linear_spec.rb index 67cfd91e9..943257995 100644 --- a/spec/lib/linear_spec.rb +++ b/spec/lib/linear_spec.rb @@ -211,4 +211,42 @@ describe Linear do end end end + + context 'when unlinking an issue' do + let(:link_id) { 'attachment1' } + + context 'when the API response is success' do + before do + linear_client.instance_variable_set(:@client, client) + stub_request(:post, url) + .to_return(status: 200, body: { data: { attachmentLinkURL: { id: 'attachment1' } } }.to_json) + end + + it 'unlinks an issue' do + response = linear_client.unlink_issue(link_id) + expect(response).to eq({ 'attachmentLinkURL' => { 'id' => 'attachment1' } }) + end + + context 'when the link_id is missing' do + let(:link_id) { '' } + + it 'raises an exception' do + expect { linear_client.unlink_issue(link_id) }.to raise_error(ArgumentError, 'Missing link id') + end + end + end + + context 'when the API response is an error' do + before do + linear_client.instance_variable_set(:@client, client) + stub_request(:post, url) + .to_return(status: 422, body: { errors: [{ message: 'Error unlinking issue' }] }.to_json) + end + + it 'raises an exception' do + response = linear_client.unlink_issue(link_id) + expect(response).to eq({ :error => 'Error: the server responded with status 422' }) + end + end + end end