Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add APIs for linear integration #9346

Merged
merged 67 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
ead7cc0
feat: add liner integration
muhsin-k May 2, 2024
68cb6b1
feat: add teams end point
muhsin-k May 2, 2024
0fafe37
chore: code cleanup
muhsin-k May 2, 2024
97f96db
Create linear_spec.rb
muhsin-k May 3, 2024
c5e3466
chore: add linear schema
muhsin-k May 3, 2024
7e61397
fix: linear spec
scmmishra May 3, 2024
e58f1ad
fix: specs
muhsin-k May 3, 2024
1bba61a
Merge branch 'develop' into feat/linear-backend
muhsin-k May 3, 2024
243834c
feat: add team entities API
muhsin-k May 6, 2024
8f7162d
Merge branch 'develop' into feat/linear-backend
muhsin-k May 6, 2024
0bf4fc9
chore: add ProcessorService spec
muhsin-k May 6, 2024
dd2938d
Merge branch 'feat/linear-backend' of https://github.com/chatwoot/cha…
muhsin-k May 6, 2024
8e5cabd
Merge branch 'develop' into feat/linear-backend
muhsin-k May 6, 2024
68a7fce
Merge branch 'develop' into feat/linear-backend
muhsin-k May 6, 2024
3595903
Merge branch 'develop' into feat/linear-backend
muhsin-k May 7, 2024
ee184d9
chore: cleanup code
muhsin-k May 7, 2024
0a45b7e
chore: add feature flag linear_integration
muhsin-k May 7, 2024
b11f4d5
Update linear_controller.rb
muhsin-k May 7, 2024
e098e46
Merge branch 'develop' into feat/linear-backend
muhsin-k May 8, 2024
17b61ad
chore: add specs
muhsin-k May 8, 2024
2ae79bd
chore: add specs
muhsin-k May 8, 2024
5b7af73
Merge branch 'feat/linear-backend' of https://github.com/chatwoot/cha…
muhsin-k May 8, 2024
c0d985a
chore: make applications component in vue 3 syntax
muhsin-k May 8, 2024
2bad7f4
Merge branch 'develop' into feat/linear-backend
muhsin-k May 8, 2024
2130a2e
Merge branch 'feat/linear-backend' of https://github.com/chatwoot/cha…
muhsin-k May 8, 2024
bf40242
Merge branch 'feat/linear-backend' of https://github.com/chatwoot/cha…
muhsin-k May 8, 2024
792bc5c
Merge branch 'feat/linear-backend' of https://github.com/chatwoot/cha…
muhsin-k May 8, 2024
3c4a7bb
Merge branch 'develop' into feat/linear-backend
muhsin-k May 9, 2024
2960bcd
Merge branch 'develop' into feat/linear-backend
muhsin-k May 9, 2024
0268d54
Merge branch 'develop' into feat/linear-backend
muhsin-k May 9, 2024
1ddc05d
feat: add create issue API
muhsin-k May 9, 2024
e567be8
chore: issue input validation
muhsin-k May 9, 2024
fc41669
feat: add link issue API
muhsin-k May 9, 2024
429cfd0
feat: add specs for create and link issue
muhsin-k May 9, 2024
2b7c972
feat: add specs for linear processor
muhsin-k May 9, 2024
3e4f43d
chore: add specs for create and link apis
muhsin-k May 9, 2024
df29b43
feat: unlink issue api
muhsin-k May 9, 2024
5e1dbfb
feat: search issue API
muhsin-k May 9, 2024
4645e57
feat: add linked issue api
muhsin-k May 9, 2024
775e825
Merge branch 'develop' into feat/linear-backend
muhsin-k May 9, 2024
793420c
Merge branch 'develop' into feat/linear-backend
muhsin-k May 9, 2024
12b3323
Merge branch 'develop' into feat/linear-backend
muhsin-k May 10, 2024
b25e395
Merge branch 'develop' into feat/linear-backend
muhsin-k May 10, 2024
4d47fc1
chore: rename from team_entites to team_entities
muhsin-k May 10, 2024
ecce6e0
chore: update link issue API
muhsin-k May 10, 2024
5d48c2b
chore: add more fields in get issue querey
muhsin-k May 10, 2024
d356067
Merge branch 'develop' into feat/linear-backend
muhsin-k May 13, 2024
01ddb61
chore: add url in issue
muhsin-k May 16, 2024
b663f32
Merge branch 'develop' into feat/linear-backend
muhsin-k May 16, 2024
31732a1
chore: replace graphlient with HTTParty
muhsin-k May 16, 2024
669a138
chore: team validation
muhsin-k May 16, 2024
08c487d
chore: update issue query
muhsin-k May 17, 2024
b40c311
Merge branch 'develop' into feat/linear-backend
muhsin-k May 17, 2024
2378efd
Update linear_queries.rb
muhsin-k May 17, 2024
c26e0cc
Merge branch 'feat/linear-backend' of https://github.com/chatwoot/cha…
muhsin-k May 17, 2024
d169d97
Merge branch 'develop' into feat/linear-backend
muhsin-k May 17, 2024
ca8dc37
chore: update linear config
muhsin-k May 17, 2024
cfb1ae0
Merge branch 'develop' into feat/linear-backend
muhsin-k May 20, 2024
e8f5e35
chore: add title in link issue
muhsin-k May 21, 2024
efb18c1
chore: add identifier in search query
muhsin-k May 21, 2024
903ec12
chore: add identifier in search query
muhsin-k May 21, 2024
4974716
Merge branch 'feat/linear-backend' of https://github.com/chatwoot/cha…
muhsin-k May 21, 2024
23ac2d9
chore: remove unused code
muhsin-k May 21, 2024
a0d8555
Merge branch 'develop' into feat/linear-backend
muhsin-k May 22, 2024
dd78655
chore: review fixes
muhsin-k May 22, 2024
aefe3a1
chore: rename linked_issues route
muhsin-k May 22, 2024
52d4a81
Merge branch 'develop' into feat/linear-backend
muhsin-k May 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
93 changes: 93 additions & 0 deletions app/controllers/api/v1/accounts/integrations/linear_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::BaseController
before_action :fetch_conversation, only: [:link_issue, :linked_issues]

def teams
teams = linear_processor_service.teams
if teams[:error]
render json: { error: teams[:error] }, status: :unprocessable_entity
else
render json: teams[:data], status: :ok
end
end

def team_entities
team_id = permitted_params[:team_id]
team_entities = linear_processor_service.team_entities(team_id)
if team_entities[:error]
render json: { error: team_entities[:error] }, status: :unprocessable_entity
else
render json: team_entities[:data], status: :ok
end
end

def create_issue
issue = linear_processor_service.create_issue(permitted_params)
if issue[:error]
render json: { error: issue[:error] }, status: :unprocessable_entity
else
render json: issue[:data], status: :ok
end
end

def link_issue
issue_id = permitted_params[:issue_id]
title = permitted_params[:title]
issue = linear_processor_service.link_issue(conversation_link, issue_id, title)
if issue[:error]
render json: { error: issue[:error] }, status: :unprocessable_entity
else
render json: issue[:data], status: :ok
end
end

def unlink_issue
link_id = permitted_params[:link_id]
issue = linear_processor_service.unlink_issue(link_id)

if issue[:error]
render json: { error: issue[:error] }, status: :unprocessable_entity
else
render json: issue[:data], status: :ok
end
end

def linked_issues
issues = linear_processor_service.linked_issues(conversation_link)

if issues[:error]
render json: { error: issues[:error] }, status: :unprocessable_entity
else
render json: issues[:data], status: :ok
end
end

def search_issue
render json: { error: 'Specify search string with parameter q' }, status: :unprocessable_entity if params[:q].blank? && return

term = params[:q]
issues = linear_processor_service.search_issue(term)
if issues[:error]
render json: { error: issues[:error] }, status: :unprocessable_entity
else
render json: issues[:data], status: :ok
end
end

private

def conversation_link
"#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/#{Current.account.id}/conversations/#{@conversation.display_id}"
end

def fetch_conversation
@conversation = Current.account.conversations.find_by!(display_id: permitted_params[:conversation_id])
end

def linear_processor_service
Integrations::Linear::ProcessorService.new(account: Current.account)
end

def permitted_params
params.permit(:team_id, :conversation_id, :issue_id, :link_id, :title, :description, :assignee_id, :priority, label_ids: [])
end
end
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
<template>
<div class="flex-shrink flex-grow overflow-auto p-4">
<div class="flex-grow flex-shrink p-4 overflow-auto">
<div class="flex flex-col">
<div v-if="uiFlags.isFetching" class="my-0 mx-auto">
<div v-if="uiFlags.isFetching" class="mx-auto my-0">
<woot-loading-state :message="$t('INTEGRATION_APPS.FETCHING')" />
</div>

<div v-else class="w-full">
<div>
<div
v-for="item in integrationsList"
v-for="item in enabledIntegrations"
:key="item.id"
class="bg-white dark:bg-slate-800 border border-solid border-slate-75 dark:border-slate-700/50 rounded-sm mb-4 p-4"
class="p-4 mb-4 bg-white border border-solid rounded-sm dark:bg-slate-800 border-slate-75 dark:border-slate-700/50"
>
<integration-item
:integration-id="item.id"
Expand All @@ -25,22 +25,38 @@
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';

<script setup>
import { useStoreGetters, useStore } from 'dashboard/composables/store';
import { computed, onMounted } from 'vue';
import IntegrationItem from './IntegrationItem.vue';
const store = useStore();
const getters = useStoreGetters();

const uiFlags = getters['integrations/getUIFlags'];

const accountId = getters.getCurrentAccountId;

const integrationList = computed(() => {
return getters['integrations/getAppIntegrations'].value;
});

const isLinearIntegrationEnabled = computed(() => {
return getters['accounts/isFeatureEnabledonAccount'].value(
accountId.value,
'linear_integration'
);
});
const enabledIntegrations = computed(() => {
if (!isLinearIntegrationEnabled.value) {
return integrationList.value.filter(
integration => integration.id !== 'linear'
);
}
return integrationList.value;
});

export default {
components: {
IntegrationItem,
},
computed: {
...mapGetters({
uiFlags: 'labels/getUIFlags',
integrationsList: 'integrations/getAppIntegrations',
}),
},
mounted() {
this.$store.dispatch('integrations/get');
},
};
onMounted(() => {
store.dispatch('integrations/get');
});
</script>
10 changes: 7 additions & 3 deletions app/javascript/dashboard/store/modules/integrations.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ const state = {
};

const isAValidAppIntegration = integration => {
return ['dialogflow', 'dyte', 'google_translate', 'openai'].includes(
integration.id
);
return [
'dialogflow',
'dyte',
'google_translate',
'openai',
'linear',
].includes(integration.id);
};
export const getters = {
getIntegrations($state) {
Expand Down
2 changes: 2 additions & 0 deletions config/features.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,5 @@
- name: help_center_embedding_search
enabled: false
premium: true
- name: linear_integration
enabled: false
24 changes: 24 additions & 0 deletions config/integration/apps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,27 @@ openai:
},
]
visible_properties: ['api_key', 'label_suggestion']
linear:
id: linear
logo: linear.png
i18n_key: linear
action: /linear
hook_type: account
allow_multiple_hooks: false
settings_json_schema: {
"type": "object",
"properties": {
"api_key": { "type": "string" },
},
"required": ["api_key"],
"additionalProperties": false,
}
settings_form_schema: [
{
"label": "API Key",
"type": "text",
"name": "api_key",
"validation": "required",
},
]
visible_properties: []
3 changes: 3 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ en:
openai:
name: "OpenAI"
description: "Integrate powerful AI features into Chatwoot by leveraging the GPT models from OpenAI."
linear:
name: "Linear"
description: "Create Linear issues from conversations, or link existing ones for seamless tracking."
public_portal:
search:
search_placeholder: Search for article by title or body...
Expand Down
11 changes: 11 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,17 @@
post :add_participant_to_meeting
end
end
resource :linear, controller: 'linear', only: [] do
collection do
get :teams
get :team_entities
post :create_issue
post :link_issue
post :unlink_issue
get :search_issue
get :linked_issues
end
end
end
resources :working_hours, only: [:update]

Expand Down
82 changes: 82 additions & 0 deletions lib/integrations/linear/processor_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
class Integrations::Linear::ProcessorService
pattr_initialize [:account!]

def teams
response = linear_client.teams
return { error: response[:error] } if response[:error]

{ data: response['teams']['nodes'].map(&:as_json) }
end

def team_entities(team_id)
response = linear_client.team_entities(team_id)
return response if response[:error]

{
data: {
users: response['users']['nodes'].map(&:as_json),
projects: response['projects']['nodes'].map(&:as_json),
states: response['workflowStates']['nodes'].map(&:as_json),
labels: response['issueLabels']['nodes'].map(&:as_json)
}
}
end

def create_issue(params)
response = linear_client.create_issue(params)
return response if response[:error]

{
data: { id: response['issueCreate']['issue']['id'],
title: response['issueCreate']['issue']['title'] }
}
end

def link_issue(link, issue_id, title)
response = linear_client.link_issue(link, issue_id, title)
return response if response[:error]

{
data: {
id: issue_id,
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]

{
data: { link_id: link_id }
}
end

def search_issue(term)
response = linear_client.search_issue(term)

return response if response[:error]

{ data: response['searchIssues']['nodes'].map(&:as_json) }
end

def linked_issues(url)
response = linear_client.linked_issues(url)
return response if response[:error]

{ data: response['attachmentsForURL']['nodes'].map(&:as_json) }
end

private

def linear_hook
@linear_hook ||= account.hooks.find_by!(app_id: 'linear')
end

def linear_client
credentials = linear_hook.settings
@linear_client ||= Linear.new(credentials['api_key'])
end
end