feat(todoist): add app, authentication, docs (#826)
This commit is contained in:
100
packages/backend/src/apps/todoist/actions/create-task/index.ts
Normal file
100
packages/backend/src/apps/todoist/actions/create-task/index.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Create Task',
|
||||
key: 'createTask',
|
||||
description: 'Creates a Task in Todoist',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Project ID',
|
||||
key: 'projectId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listProjects',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Section ID',
|
||||
key: 'sectionId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: false,
|
||||
dependsOn: ['parameters.projectId'],
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listSections',
|
||||
},
|
||||
{
|
||||
name: 'parameters.projectId',
|
||||
value: '{parameters.projectId}',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Labels',
|
||||
key: 'labels',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description:
|
||||
'Labels to add to task (comma separated). Examples: "work" "work,imported"',
|
||||
},
|
||||
{
|
||||
label: 'Content',
|
||||
key: 'content',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
description:
|
||||
'Task content, may be markdown. Example: "Foo"',
|
||||
},
|
||||
{
|
||||
label: 'Description',
|
||||
key: 'description',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description:
|
||||
'Task description, may be markdown. Example: "Foo"',
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const requestPath = `/tasks`;
|
||||
const {
|
||||
projectId,
|
||||
sectionId,
|
||||
labels,
|
||||
content,
|
||||
description
|
||||
} = $.step.parameters;
|
||||
|
||||
const labelsArray = (labels as string).split(',')
|
||||
|
||||
const payload = {
|
||||
content,
|
||||
description: description || null,
|
||||
project_id: projectId || null,
|
||||
labels: labelsArray || null,
|
||||
section_id: sectionId || null,
|
||||
}
|
||||
|
||||
const response = await $.http.post(requestPath, payload);
|
||||
|
||||
$.setActionItem({ raw: response.data });
|
||||
},
|
||||
});
|
3
packages/backend/src/apps/todoist/actions/index.ts
Normal file
3
packages/backend/src/apps/todoist/actions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import createTask from './create-task';
|
||||
|
||||
export default [createTask];
|
14
packages/backend/src/apps/todoist/assets/favicon.svg
Normal file
14
packages/backend/src/apps/todoist/assets/favicon.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<path d="M224.001997,0 L31.9980026,0 C14.3579381,0.0394964443 0.0614809418,14.336846 0,32 L0,224 C0,241.6 14.3971038,256 31.9980026,256 L224.001997,256 C241.602896,256 256,241.6 256,224 L256,32 C256,14.4 241.602896,0 224.001997,0" fill="#E44332">
|
||||
|
||||
</path>
|
||||
<path d="M54.132778,120.802491 C58.5960224,118.196275 154.476075,62.477451 156.667847,61.1862981 C158.859619,59.9110855 158.97917,55.9898065 156.508446,54.5711324 C154.053661,53.1604284 149.391165,50.4824817 147.661658,49.4543415 C145.192242,48.0957707 142.191169,48.132074 139.755339,49.5499825 C138.527947,50.2672896 56.6035026,97.8486625 53.8697654,99.4107981 C50.5781227,101.291737 46.5372925,101.323617 43.2695601,99.4107981 L0,74.0181257 L0,95.6011002 C10.5205046,101.801822 36.7181549,117.200015 43.062338,120.826401 C46.8481256,122.978322 50.4745117,122.930502 54.1407481,120.802491" fill="#FFFFFF">
|
||||
|
||||
</path>
|
||||
<path d="M54.132778,161.609296 C58.5960224,159.00308 154.476075,103.284257 156.667847,101.993104 C158.859619,100.717891 158.97917,96.7966121 156.508446,95.377938 C154.053661,93.9672339 149.391165,91.2892873 147.661658,90.2611471 C145.192242,88.9025763 142.191169,88.9388796 139.755339,90.3567881 C138.527947,91.0740952 56.6035026,138.655468 53.8697654,140.217604 C50.5781227,142.098542 46.5372925,142.130423 43.2695601,140.217604 L0,114.824931 L0,136.407906 C10.5205046,142.608627 36.7181549,158.00682 43.062338,161.633206 C46.8481256,163.785128 50.4745117,163.737307 54.1407481,161.609296" fill="#FFFFFF">
|
||||
|
||||
</path>
|
||||
<path d="M54.132778,204.966527 C58.5960224,202.360311 154.476075,146.641487 156.667847,145.350335 C158.859619,144.075122 158.97917,140.153843 156.508446,138.735169 C154.053661,137.324465 149.391165,134.646518 147.661658,133.618378 C145.192242,132.259807 142.191169,132.29611 139.755339,133.714019 C138.527947,134.431326 56.6035026,182.012699 53.8697654,183.574835 C50.5781227,185.455773 46.5372925,185.487654 43.2695601,183.574835 L0,158.182162 L0,179.765137 C10.5205046,185.965858 36.7181549,201.364051 43.062338,204.990437 C46.8481256,207.142359 50.4745117,207.094538 54.1407481,204.966527" fill="#FFFFFF">
|
After Width: | Height: | Size: 2.4 KiB |
34
packages/backend/src/apps/todoist/auth/index.ts
Normal file
34
packages/backend/src/apps/todoist/auth/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import isStillVerified from './is-still-verified';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'screenName',
|
||||
label: 'Screen Name',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description:
|
||||
'Name your connection (only used for Automatisch UI).',
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'apiToken',
|
||||
label: 'API Token',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description:
|
||||
'Your Todoist API token. See https://todoist.com/app/settings/integrations/developer',
|
||||
clickToCopy: false,
|
||||
},
|
||||
],
|
||||
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
};
|
@@ -0,0 +1,9 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import verifyCredentials from './verify-credentials';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
await verifyCredentials($);
|
||||
return true;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
@@ -0,0 +1,7 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
await $.http.get('/projects');
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
12
packages/backend/src/apps/todoist/common/add-auth-header.ts
Normal file
12
packages/backend/src/apps/todoist/common/add-auth-header.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
if ($.auth.data?.apiToken) {
|
||||
const authorizationHeader = `Bearer ${$.auth.data.apiToken}`;
|
||||
requestConfig.headers.Authorization = authorizationHeader;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
5
packages/backend/src/apps/todoist/dynamic-data/index.ts
Normal file
5
packages/backend/src/apps/todoist/dynamic-data/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import listProjects from './list-projects';
|
||||
import listSections from './list-sections';
|
||||
import listLabels from './list-labels';
|
||||
|
||||
export default [listProjects, listSections, listLabels];
|
@@ -0,0 +1,19 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List labels',
|
||||
key: 'listLabels',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const response = await $.http.get('/labels');
|
||||
|
||||
response.data = response.data.map((label: { name: string }) => {
|
||||
return {
|
||||
value: label.name,
|
||||
name: label.name,
|
||||
};
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
};
|
@@ -0,0 +1,19 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List projects',
|
||||
key: 'listProjects',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const response = await $.http.get('/projects');
|
||||
|
||||
response.data = response.data.map((project: { id: string, name: string }) => {
|
||||
return {
|
||||
value: project.id,
|
||||
name: project.name,
|
||||
};
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
};
|
@@ -0,0 +1,23 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List sections',
|
||||
key: 'listSections',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const params = {
|
||||
project_id: ($.step.parameters.projectId as string),
|
||||
};
|
||||
|
||||
const response = await $.http.get('/sections', {params});
|
||||
|
||||
response.data = response.data.map((section: { id: string, name: string }) => {
|
||||
return {
|
||||
value: section.id,
|
||||
name: section.name,
|
||||
};
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
};
|
0
packages/backend/src/apps/todoist/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/todoist/index.d.ts
vendored
Normal file
22
packages/backend/src/apps/todoist/index.ts
Normal file
22
packages/backend/src/apps/todoist/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import addAuthHeader from './common/add-auth-header';
|
||||
import auth from './auth';
|
||||
import triggers from './triggers';
|
||||
import actions from './actions';
|
||||
import dynamicData from './dynamic-data';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Todoist',
|
||||
key: 'todoist',
|
||||
iconUrl: '{BASE_URL}/apps/todoist/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/todoist/connection',
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://todoist.com',
|
||||
apiBaseUrl: 'https://api.todoist.com/rest/v2',
|
||||
primaryColor: 'e44332',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
actions,
|
||||
dynamicData,
|
||||
});
|
@@ -0,0 +1,30 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const getActiveTasks = async ($: IGlobalVariable) => {
|
||||
|
||||
const params = {
|
||||
project_id: ($.step.parameters.projectId as string)?.trim(),
|
||||
section_id: ($.step.parameters.sectionId as string)?.trim(),
|
||||
label: ($.step.parameters.label as string)?.trim(),
|
||||
filter: ($.step.parameters.filter as string)?.trim(),
|
||||
};
|
||||
|
||||
const response = await $.http.get('/tasks', { params });
|
||||
|
||||
// todoist api doesn't offer sorting, so we inverse sort on id here
|
||||
response.data.sort((a: { id: number; }, b: { id: number; }) => {
|
||||
return b.id - a.id;
|
||||
})
|
||||
|
||||
for (const task of response.data) {
|
||||
$.pushTriggerItem({
|
||||
raw: task,
|
||||
meta:{
|
||||
internalId: task.id as string,
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export default getActiveTasks;
|
@@ -0,0 +1,80 @@
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
import getActiveTasks from './get-tasks';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'Get Active Tasks',
|
||||
key: 'getActiveTasks',
|
||||
pollInterval: 15,
|
||||
description: 'Triggers when new Task(s) are found',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Project ID',
|
||||
key: 'projectId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listProjects',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Section ID',
|
||||
key: 'sectionId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: false,
|
||||
dependsOn: ['parameters.projectId'],
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listSections',
|
||||
},
|
||||
{
|
||||
name: 'parameters.projectId',
|
||||
value: '{parameters.projectId}',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Label',
|
||||
key: 'label',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listLabels',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Filter',
|
||||
key: 'filter',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: false,
|
||||
description:
|
||||
'Limit queried tasks to this filter. Example: "Meeting & today"',
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
await getActiveTasks($);
|
||||
},
|
||||
});
|
3
packages/backend/src/apps/todoist/triggers/index.ts
Normal file
3
packages/backend/src/apps/todoist/triggers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import getTasks from './get-tasks';
|
||||
|
||||
export default [getTasks];
|
14
packages/docs/pages/apps/todoist/actions.md
Normal file
14
packages/docs/pages/apps/todoist/actions.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
favicon: /favicons/todoist.svg
|
||||
items:
|
||||
- name: Get Tasks
|
||||
desc: Finds tasks in Todoist, optionally matching specified parameters.
|
||||
- name: Create Task
|
||||
desc: Creates a task in Todoist.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import CustomListing from '../../components/CustomListing.vue'
|
||||
</script>
|
||||
|
||||
<CustomListing />
|
10
packages/docs/pages/apps/todoist/connection.md
Normal file
10
packages/docs/pages/apps/todoist/connection.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Todoist
|
||||
|
||||
:::info
|
||||
This page explains the steps you need to follow to set up the Todoist connection in Automatisch. If any of the steps are outdated, please let us know!
|
||||
:::
|
||||
|
||||
1. Go to the account [Integrations page](https://todoist.com/app/settings/integrations/developer) to copy your **API token**.
|
||||
1. Paste the **API token** value into Automatisch.
|
||||
1. Enter a memorable name for your connection in the **Screen Name** field.
|
||||
1. Click the **Submit** button on Automatisch.
|
14
packages/docs/pages/public/favicons/todoist.svg
Normal file
14
packages/docs/pages/public/favicons/todoist.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<path d="M224.001997,0 L31.9980026,0 C14.3579381,0.0394964443 0.0614809418,14.336846 0,32 L0,224 C0,241.6 14.3971038,256 31.9980026,256 L224.001997,256 C241.602896,256 256,241.6 256,224 L256,32 C256,14.4 241.602896,0 224.001997,0" fill="#E44332">
|
||||
|
||||
</path>
|
||||
<path d="M54.132778,120.802491 C58.5960224,118.196275 154.476075,62.477451 156.667847,61.1862981 C158.859619,59.9110855 158.97917,55.9898065 156.508446,54.5711324 C154.053661,53.1604284 149.391165,50.4824817 147.661658,49.4543415 C145.192242,48.0957707 142.191169,48.132074 139.755339,49.5499825 C138.527947,50.2672896 56.6035026,97.8486625 53.8697654,99.4107981 C50.5781227,101.291737 46.5372925,101.323617 43.2695601,99.4107981 L0,74.0181257 L0,95.6011002 C10.5205046,101.801822 36.7181549,117.200015 43.062338,120.826401 C46.8481256,122.978322 50.4745117,122.930502 54.1407481,120.802491" fill="#FFFFFF">
|
||||
|
||||
</path>
|
||||
<path d="M54.132778,161.609296 C58.5960224,159.00308 154.476075,103.284257 156.667847,101.993104 C158.859619,100.717891 158.97917,96.7966121 156.508446,95.377938 C154.053661,93.9672339 149.391165,91.2892873 147.661658,90.2611471 C145.192242,88.9025763 142.191169,88.9388796 139.755339,90.3567881 C138.527947,91.0740952 56.6035026,138.655468 53.8697654,140.217604 C50.5781227,142.098542 46.5372925,142.130423 43.2695601,140.217604 L0,114.824931 L0,136.407906 C10.5205046,142.608627 36.7181549,158.00682 43.062338,161.633206 C46.8481256,163.785128 50.4745117,163.737307 54.1407481,161.609296" fill="#FFFFFF">
|
||||
|
||||
</path>
|
||||
<path d="M54.132778,204.966527 C58.5960224,202.360311 154.476075,146.641487 156.667847,145.350335 C158.859619,144.075122 158.97917,140.153843 156.508446,138.735169 C154.053661,137.324465 149.391165,134.646518 147.661658,133.618378 C145.192242,132.259807 142.191169,132.29611 139.755339,133.714019 C138.527947,134.431326 56.6035026,182.012699 53.8697654,183.574835 C50.5781227,185.455773 46.5372925,185.487654 43.2695601,183.574835 L0,158.182162 L0,179.765137 C10.5205046,185.965858 36.7181549,201.364051 43.062338,204.990437 C46.8481256,207.142359 50.4745117,207.094538 54.1407481,204.966527" fill="#FFFFFF">
|
After Width: | Height: | Size: 2.4 KiB |
Reference in New Issue
Block a user