feat: Add activity messages for linear actions (#11654)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_conversation, only: [:link_issue, :linked_issues]
|
||||
before_action :fetch_conversation, only: [:create_issue, :link_issue, :unlink_issue, :linked_issues]
|
||||
before_action :fetch_hook, only: [:destroy]
|
||||
|
||||
def destroy
|
||||
@@ -31,6 +31,12 @@ class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::Bas
|
||||
if issue[:error]
|
||||
render json: { error: issue[:error] }, status: :unprocessable_entity
|
||||
else
|
||||
Linear::ActivityMessageService.new(
|
||||
conversation: @conversation,
|
||||
action_type: :issue_created,
|
||||
issue_data: { id: issue[:data][:identifier] },
|
||||
user: Current.user
|
||||
).perform
|
||||
render json: issue[:data], status: :ok
|
||||
end
|
||||
end
|
||||
@@ -42,17 +48,30 @@ class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::Bas
|
||||
if issue[:error]
|
||||
render json: { error: issue[:error] }, status: :unprocessable_entity
|
||||
else
|
||||
Linear::ActivityMessageService.new(
|
||||
conversation: @conversation,
|
||||
action_type: :issue_linked,
|
||||
issue_data: { id: issue_id },
|
||||
user: Current.user
|
||||
).perform
|
||||
render json: issue[:data], status: :ok
|
||||
end
|
||||
end
|
||||
|
||||
def unlink_issue
|
||||
link_id = permitted_params[:link_id]
|
||||
issue_id = permitted_params[:issue_id]
|
||||
issue = linear_processor_service.unlink_issue(link_id)
|
||||
|
||||
if issue[:error]
|
||||
render json: { error: issue[:error] }, status: :unprocessable_entity
|
||||
else
|
||||
Linear::ActivityMessageService.new(
|
||||
conversation: @conversation,
|
||||
action_type: :issue_unlinked,
|
||||
issue_data: { id: issue_id },
|
||||
user: Current.user
|
||||
).perform
|
||||
render json: issue[:data], status: :ok
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,9 +33,11 @@ class LinearAPI extends ApiClient {
|
||||
);
|
||||
}
|
||||
|
||||
unlinkIssue(linkId) {
|
||||
unlinkIssue(linkId, issueIdentifier, conversationId) {
|
||||
return axios.post(`${this.url}/unlink_issue`, {
|
||||
link_id: linkId,
|
||||
issue_id: issueIdentifier,
|
||||
conversation_id: conversationId,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,19 @@ describe('#linearAPI', () => {
|
||||
issueData
|
||||
);
|
||||
});
|
||||
|
||||
it('creates a valid request with conversation_id', () => {
|
||||
const issueData = {
|
||||
title: 'New Issue',
|
||||
description: 'Issue description',
|
||||
conversation_id: 123,
|
||||
};
|
||||
LinearAPIClient.createIssue(issueData);
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/linear/create_issue',
|
||||
issueData
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('link_issue', () => {
|
||||
@@ -120,6 +133,18 @@ describe('#linearAPI', () => {
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('creates a valid request with title', () => {
|
||||
LinearAPIClient.link_issue(1, 'ENG-123', 'Sample Issue');
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/linear/link_issue',
|
||||
{
|
||||
issue_id: 'ENG-123',
|
||||
conversation_id: 1,
|
||||
title: 'Sample Issue',
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLinkedIssue', () => {
|
||||
@@ -164,12 +189,26 @@ describe('#linearAPI', () => {
|
||||
window.axios = originalAxios;
|
||||
});
|
||||
|
||||
it('creates a valid request', () => {
|
||||
LinearAPIClient.unlinkIssue(1);
|
||||
it('creates a valid request with link_id only', () => {
|
||||
LinearAPIClient.unlinkIssue('link123');
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/linear/unlink_issue',
|
||||
{
|
||||
link_id: 1,
|
||||
link_id: 'link123',
|
||||
issue_id: undefined,
|
||||
conversation_id: undefined,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('creates a valid request with all parameters', () => {
|
||||
LinearAPIClient.unlinkIssue('link123', 'ENG-456', 789);
|
||||
expect(axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/linear/unlink_issue',
|
||||
{
|
||||
link_id: 'link123',
|
||||
issue_id: 'ENG-456',
|
||||
conversation_id: 789,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -183,13 +183,18 @@ const createIssue = async () => {
|
||||
state_id: formState.stateId || undefined,
|
||||
priority: formState.priority || undefined,
|
||||
label_ids: formState.labelId ? [formState.labelId] : undefined,
|
||||
conversation_id: props.conversationId,
|
||||
};
|
||||
|
||||
try {
|
||||
isCreating.value = true;
|
||||
const response = await LinearAPI.createIssue(payload);
|
||||
const { id: issueId } = response.data;
|
||||
await LinearAPI.link_issue(props.conversationId, issueId, props.title);
|
||||
const { identifier: issueIdentifier } = response.data;
|
||||
await LinearAPI.link_issue(
|
||||
props.conversationId,
|
||||
issueIdentifier,
|
||||
props.title
|
||||
);
|
||||
useAlert(t('INTEGRATION_SETTINGS.LINEAR.ADD_OR_LINK.CREATE_SUCCESS'));
|
||||
useTrack(LINEAR_EVENTS.CREATE_ISSUE);
|
||||
onClose();
|
||||
|
||||
@@ -46,9 +46,9 @@ const loadLinkedIssues = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const unlinkIssue = async linkId => {
|
||||
const unlinkIssue = async (linkId, issueIdentifier) => {
|
||||
try {
|
||||
await LinearAPI.unlinkIssue(linkId);
|
||||
await LinearAPI.unlinkIssue(linkId, issueIdentifier, props.conversationId);
|
||||
useTrack(LINEAR_EVENTS.UNLINK_ISSUE);
|
||||
linkedIssues.value = linkedIssues.value.filter(
|
||||
issue => issue.id !== linkId
|
||||
@@ -110,7 +110,7 @@ onMounted(() => {
|
||||
<LinearIssueItem
|
||||
v-for="linkedIssue in linkedIssues"
|
||||
:key="linkedIssue.id"
|
||||
class="pt-3 px-4 pb-4 border-b border-n-weak last:border-b-0"
|
||||
class="px-4 pt-3 pb-4 border-b border-n-weak last:border-b-0"
|
||||
:linked-issue="linkedIssue"
|
||||
@unlink-issue="unlinkIssue"
|
||||
/>
|
||||
|
||||
@@ -14,6 +14,8 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['unlinkIssue']);
|
||||
|
||||
const { linkedIssue } = props;
|
||||
|
||||
const priorityMap = {
|
||||
1: 'Urgent',
|
||||
2: 'High',
|
||||
@@ -21,7 +23,7 @@ const priorityMap = {
|
||||
4: 'Low',
|
||||
};
|
||||
|
||||
const issue = computed(() => props.linkedIssue.issue);
|
||||
const issue = computed(() => linkedIssue.issue);
|
||||
|
||||
const assignee = computed(() => {
|
||||
const assigneeDetails = issue.value.assignee;
|
||||
@@ -37,7 +39,7 @@ const labels = computed(() => issue.value.labels?.nodes || []);
|
||||
const priorityLabel = computed(() => priorityMap[issue.value.priority]);
|
||||
|
||||
const unlinkIssue = () => {
|
||||
emit('unlinkIssue', props.linkedIssue.id);
|
||||
emit('unlinkIssue', linkedIssue.id, linkedIssue.issue.identifier);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ const onSearch = async value => {
|
||||
isFetching.value = true;
|
||||
const response = await LinearAPI.searchIssues(value);
|
||||
issues.value = response.data.map(issue => ({
|
||||
id: issue.id,
|
||||
id: issue.identifier,
|
||||
name: `${issue.identifier} ${issue.title}`,
|
||||
icon: 'status',
|
||||
iconColor: issue.state.color,
|
||||
|
||||
41
app/services/linear/activity_message_service.rb
Normal file
41
app/services/linear/activity_message_service.rb
Normal file
@@ -0,0 +1,41 @@
|
||||
class Linear::ActivityMessageService
|
||||
attr_reader :conversation, :action_type, :issue_data, :user
|
||||
|
||||
def initialize(conversation:, action_type:, user:, issue_data: {})
|
||||
@conversation = conversation
|
||||
@action_type = action_type
|
||||
@issue_data = issue_data
|
||||
@user = user
|
||||
end
|
||||
|
||||
def perform
|
||||
return unless conversation && issue_data[:id] && user
|
||||
|
||||
content = generate_activity_content
|
||||
return unless content
|
||||
|
||||
::Conversations::ActivityMessageJob.perform_later(conversation, activity_message_params(content))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_activity_content
|
||||
case action_type.to_sym
|
||||
when :issue_created
|
||||
I18n.t('conversations.activity.linear.issue_created', user_name: user.name, issue_id: issue_data[:id])
|
||||
when :issue_linked
|
||||
I18n.t('conversations.activity.linear.issue_linked', user_name: user.name, issue_id: issue_data[:id])
|
||||
when :issue_unlinked
|
||||
I18n.t('conversations.activity.linear.issue_unlinked', user_name: user.name, issue_id: issue_data[:id])
|
||||
end
|
||||
end
|
||||
|
||||
def activity_message_params(content)
|
||||
{
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: :activity,
|
||||
content: content
|
||||
}
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user