Compare commits
66 Commits
v0.10.0
...
custom-ais
Author | SHA1 | Date | |
---|---|---|---|
![]() |
74494989d2 | ||
![]() |
e122ad4178 | ||
![]() |
a8b85cdb0d | ||
![]() |
d070e976b0 | ||
![]() |
0caf6bfabb | ||
![]() |
b842d7938f | ||
![]() |
cebbf84375 | ||
![]() |
8608431490 | ||
![]() |
78ba18b176 | ||
![]() |
f8c30c8526 | ||
![]() |
693c9b85a5 | ||
![]() |
70bb7defd1 | ||
![]() |
160377ca31 | ||
![]() |
2c0ce77a4e | ||
![]() |
77fbb0c9da | ||
![]() |
5971425d23 | ||
![]() |
aefff5c861 | ||
![]() |
a296b5e645 | ||
![]() |
eb486a3a07 | ||
![]() |
062b8521ba | ||
![]() |
1b07f3195a | ||
![]() |
dfa7d4cb8d | ||
![]() |
a14dd9666c | ||
![]() |
b07bd4374f | ||
![]() |
b4e12b0ea8 | ||
![]() |
ee5c17bb85 | ||
![]() |
16c9d3400c | ||
![]() |
4dd994348d | ||
![]() |
f0cbfafc24 | ||
![]() |
d3f38f5488 | ||
![]() |
737090a67a | ||
![]() |
4f66a4d090 | ||
![]() |
df54f909c1 | ||
![]() |
772b195eca | ||
![]() |
87866e34ed | ||
![]() |
c98ac05097 | ||
![]() |
36f991b6f9 | ||
![]() |
a81c5164fc | ||
![]() |
5942482690 | ||
![]() |
4f538ca2fc | ||
![]() |
9f2281a3e2 | ||
![]() |
b0d2f28c78 | ||
![]() |
d4380a4426 | ||
![]() |
ae2738d4cc | ||
![]() |
aa5ae028b2 | ||
![]() |
7ab8c76aa0 | ||
![]() |
8075b65e14 | ||
![]() |
073ce3bf1b | ||
![]() |
80fcbfe01b | ||
![]() |
dba0041e5f | ||
![]() |
b8a44afd25 | ||
![]() |
e2445bf585 | ||
![]() |
50706c524e | ||
![]() |
11e0cb9398 | ||
![]() |
1e82e40802 | ||
![]() |
ff00644e62 | ||
![]() |
97bcd3792b | ||
![]() |
5738a09771 | ||
![]() |
c461cc4878 | ||
![]() |
878fab347a | ||
![]() |
354b331b08 | ||
![]() |
3b9aadb90f | ||
![]() |
7193d018ce | ||
![]() |
d5cea034ac | ||
![]() |
a2760c10b3 | ||
![]() |
3593cf3808 |
5
.github/workflows/playwright.yml
vendored
5
.github/workflows/playwright.yml
vendored
@@ -4,6 +4,11 @@ on:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
paths:
|
||||
- 'packages/backend/**'
|
||||
- 'packages/e2e-tests/**'
|
||||
- 'packages/web/**'
|
||||
- '!packages/backend/src/apps/**'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
|
@@ -38,7 +38,7 @@
|
||||
"@types/xmlrpc": "^1.3.7",
|
||||
"accounting": "^0.4.1",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"axios": "0.24.0",
|
||||
"axios": "1.6.0",
|
||||
"bcrypt": "^5.0.1",
|
||||
"bullmq": "^3.0.0",
|
||||
"copyfiles": "^2.4.1",
|
||||
|
3
packages/backend/src/apps/azure-openai/actions/index.ts
Normal file
3
packages/backend/src/apps/azure-openai/actions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import sendPrompt from './send-prompt';
|
||||
|
||||
export default [sendPrompt];
|
@@ -0,0 +1,87 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
const castFloatOrUndefined = (value: string | null) => {
|
||||
return value === '' ? undefined : parseFloat(value);
|
||||
}
|
||||
|
||||
export default defineAction({
|
||||
name: 'Send prompt',
|
||||
key: 'sendPrompt',
|
||||
description: 'Creates a completion for the provided prompt and parameters.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Prompt',
|
||||
key: 'prompt',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
description: 'The text to analyze.'
|
||||
},
|
||||
{
|
||||
label: 'Temperature',
|
||||
key: 'temperature',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'What sampling temperature to use, between 0 and 2. Higher values means the model will take more risks. Try 0.9 for more creative applications, and 0 (argmax sampling) for ones with a well-defined answer. We generally recommend altering this or Top P but not both.'
|
||||
},
|
||||
{
|
||||
label: 'Maximum tokens',
|
||||
key: 'maxTokens',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'The maximum number of tokens to generate in the completion.'
|
||||
},
|
||||
{
|
||||
label: 'Stop Sequence',
|
||||
key: 'stopSequence',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'Single stop sequence where the API will stop generating further tokens. The returned text will not contain the stop sequence.'
|
||||
},
|
||||
{
|
||||
label: 'Top P',
|
||||
key: 'topP',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or temperature but not both.'
|
||||
},
|
||||
{
|
||||
label: 'Frequency Penalty',
|
||||
key: 'frequencyPenalty',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: `Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.`
|
||||
},
|
||||
{
|
||||
label: 'Presence Penalty',
|
||||
key: 'presencePenalty',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: `Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.`
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const payload = {
|
||||
model: $.step.parameters.model as string,
|
||||
prompt: $.step.parameters.prompt as string,
|
||||
temperature: castFloatOrUndefined($.step.parameters.temperature as string),
|
||||
max_tokens: castFloatOrUndefined($.step.parameters.maxTokens as string),
|
||||
stop: ($.step.parameters.stopSequence as string || null),
|
||||
top_p: castFloatOrUndefined($.step.parameters.topP as string),
|
||||
frequency_penalty: castFloatOrUndefined($.step.parameters.frequencyPenalty as string),
|
||||
presence_penalty: castFloatOrUndefined($.step.parameters.presencePenalty as string),
|
||||
};
|
||||
const { data } = await $.http.post(`/deployments/${$.auth.data.deploymentId}/completions`, payload);
|
||||
|
||||
$.setActionItem({
|
||||
raw: data,
|
||||
});
|
||||
},
|
||||
});
|
@@ -0,0 +1,6 @@
|
||||
<svg width="256px" height="260px" viewBox="0 0 256 260" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<title>OpenAI</title>
|
||||
<g>
|
||||
<path d="M239.183914,106.202783 C245.054304,88.5242096 243.02228,69.1733805 233.607599,53.0998864 C219.451678,28.4588021 190.999703,15.7836129 163.213007,21.739505 C147.554077,4.32145883 123.794909,-3.42398554 100.87901,1.41873898 C77.9631105,6.26146349 59.3690093,22.9572536 52.0959621,45.2214219 C33.8436494,48.9644867 18.0901721,60.392749 8.86672513,76.5818033 C-5.443491,101.182962 -2.19544431,132.215255 16.8986662,153.320094 C11.0060865,170.990656 13.0197283,190.343991 22.4238231,206.422991 C36.5975553,231.072344 65.0680342,243.746566 92.8695738,237.783372 C105.235639,251.708249 123.001113,259.630942 141.623968,259.52692 C170.105359,259.552169 195.337611,241.165718 204.037777,214.045661 C222.28734,210.296356 238.038489,198.869783 247.267014,182.68528 C261.404453,158.127515 258.142494,127.262775 239.183914,106.202783 L239.183914,106.202783 Z M141.623968,242.541207 C130.255682,242.559177 119.243876,238.574642 110.519381,231.286197 L112.054146,230.416496 L163.724595,200.590881 C166.340648,199.056444 167.954321,196.256818 167.970781,193.224005 L167.970781,120.373788 L189.815614,133.010026 C190.034132,133.121423 190.186235,133.330564 190.224885,133.572774 L190.224885,193.940229 C190.168603,220.758427 168.442166,242.484864 141.623968,242.541207 Z M37.1575749,197.93062 C31.456498,188.086359 29.4094818,176.546984 31.3766237,165.342426 L32.9113895,166.263285 L84.6329973,196.088901 C87.2389349,197.618207 90.4682717,197.618207 93.0742093,196.088901 L156.255402,159.663793 L156.255402,184.885111 C156.243557,185.149771 156.111725,185.394602 155.89729,185.550176 L103.561776,215.733903 C80.3054953,229.131632 50.5924954,221.165435 37.1575749,197.93062 Z M23.5493181,85.3811273 C29.2899861,75.4733097 38.3511911,67.9162648 49.1287482,64.0478825 L49.1287482,125.438515 C49.0891492,128.459425 50.6965386,131.262556 53.3237748,132.754232 L116.198014,169.025864 L94.3531808,181.662102 C94.1132325,181.789434 93.8257461,181.789434 93.5857979,181.662102 L41.3526015,151.529534 C18.1419426,138.076098 10.1817681,108.385562 23.5493181,85.125333 L23.5493181,85.3811273 Z M203.0146,127.075598 L139.935725,90.4458545 L161.7294,77.8607748 C161.969348,77.7334434 162.256834,77.7334434 162.496783,77.8607748 L214.729979,108.044502 C231.032329,117.451747 240.437294,135.426109 238.871504,154.182739 C237.305714,172.939368 225.050719,189.105572 207.414262,195.67963 L207.414262,134.288998 C207.322521,131.276867 205.650697,128.535853 203.0146,127.075598 Z M224.757116,94.3850867 L223.22235,93.4642272 L171.60306,63.3828173 C168.981293,61.8443751 165.732456,61.8443751 163.110689,63.3828173 L99.9806554,99.8079259 L99.9806554,74.5866077 C99.9533004,74.3254088 100.071095,74.0701869 100.287609,73.9215426 L152.520805,43.7889738 C168.863098,34.3743518 189.174256,35.2529043 204.642579,46.0434841 C220.110903,56.8340638 227.949269,75.5923959 224.757116,94.1804513 L224.757116,94.3850867 Z M88.0606409,139.097931 L66.2158076,126.512851 C65.9950399,126.379091 65.8450965,126.154176 65.8065367,125.898945 L65.8065367,65.684966 C65.8314495,46.8285367 76.7500605,29.6846032 93.8270852,21.6883055 C110.90411,13.6920079 131.063833,16.2835462 145.5632,28.338998 L144.028434,29.2086986 L92.3579852,59.0343142 C89.7419327,60.5687513 88.1282597,63.3683767 88.1117998,66.4011901 L88.0606409,139.097931 Z M99.9294965,113.5185 L128.06687,97.3011417 L156.255402,113.5185 L156.255402,145.953218 L128.169187,162.170577 L99.9806554,145.953218 L99.9294965,113.5185 Z" fill="#000000"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.6 KiB |
58
packages/backend/src/apps/azure-openai/auth/index.ts
Normal file
58
packages/backend/src/apps/azure-openai/auth/index.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
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:
|
||||
'Screen name of your connection to be used on Automatisch UI.',
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'yourResourceName',
|
||||
label: 'Your Resource Name',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: 'The name of your Azure OpenAI Resource.',
|
||||
docUrl: 'https://automatisch.io/docs/azure-openai#your-resource-name',
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'deploymentId',
|
||||
label: 'Deployment ID',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: 'The deployment name you chose when you deployed the model.',
|
||||
docUrl: 'https://automatisch.io/docs/azure-openai#deployment-id',
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'apiKey',
|
||||
label: 'API Key',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: 'Azure OpenAI API key of your account.',
|
||||
docUrl: 'https://automatisch.io/docs/azure-openai#api-key',
|
||||
clickToCopy: false,
|
||||
},
|
||||
],
|
||||
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
};
|
@@ -0,0 +1,8 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
await $.http.get('/fine_tuning/jobs');
|
||||
return true;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
@@ -0,0 +1,7 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
await $.http.get('/fine_tuning/jobs');
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
@@ -0,0 +1,15 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
if ($.auth.data?.apiKey) {
|
||||
requestConfig.headers['api-key'] = $.auth.data.apiKey as string;
|
||||
}
|
||||
|
||||
requestConfig.params = {
|
||||
'api-version': '2023-10-01-preview'
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
@@ -0,0 +1,13 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const setBaseUrl: TBeforeRequest = ($, requestConfig) => {
|
||||
const yourResourceName = $.auth.data.yourResourceName as string;
|
||||
|
||||
if (yourResourceName) {
|
||||
requestConfig.baseURL = `https://${yourResourceName}.openai.azure.com/openai`;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default setBaseUrl;
|
0
packages/backend/src/apps/azure-openai/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/azure-openai/index.d.ts
vendored
Normal file
19
packages/backend/src/apps/azure-openai/index.ts
Normal file
19
packages/backend/src/apps/azure-openai/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import setBaseUrl from './common/set-base-url';
|
||||
import addAuthHeader from './common/add-auth-header';
|
||||
import auth from './auth';
|
||||
import actions from './actions';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Azure OpenAI',
|
||||
key: 'azure-openai',
|
||||
baseUrl: 'https://azure.microsoft.com/en-us/products/ai-services/openai-service',
|
||||
apiBaseUrl: '',
|
||||
iconUrl: '{BASE_URL}/apps/azure-openai/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/azure-openai/connection',
|
||||
primaryColor: '000000',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
});
|
@@ -0,0 +1,102 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Create a scheduled event',
|
||||
key: 'createScheduledEvent',
|
||||
description: 'Creates a scheduled event',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Type',
|
||||
key: 'entityType',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
options: [
|
||||
{ label: 'Stage channel', value: 1 },
|
||||
{ label: 'Voice channel', value: 2 },
|
||||
{ label: 'External', value: 3 }
|
||||
],
|
||||
additionalFields: {
|
||||
type: 'query',
|
||||
name: 'getDynamicFields',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listExternalScheduledEventFields',
|
||||
},
|
||||
{
|
||||
name: 'parameters.entityType',
|
||||
value: '{parameters.entityType}',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Name',
|
||||
key: 'name',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Description',
|
||||
key: 'description',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Image',
|
||||
key: 'image',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
description: 'Image as DataURI scheme [data:image/<jpeg/png/gif>;base64,BASE64_ENCODED_<JPEG/PNG/GIF>_IMAGE_DATA]',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
type entity_metadata = {
|
||||
location: string
|
||||
}
|
||||
|
||||
type guild_event = {
|
||||
channel_id: number,
|
||||
name: string,
|
||||
privacy_level: number,
|
||||
scheduled_start_time: string,
|
||||
scheduled_end_time?: string,
|
||||
description?: string,
|
||||
entity_type?: number,
|
||||
entity_metadata?: entity_metadata,
|
||||
image?: string, //data:image/jpeg;base64,BASE64_ENCODED_JPEG_IMAGE_DATA
|
||||
}
|
||||
|
||||
|
||||
const data: guild_event = {
|
||||
channel_id: $.step.parameters.channel_id as number,
|
||||
name: $.step.parameters.name as string,
|
||||
privacy_level: 2,
|
||||
scheduled_start_time: $.step.parameters.scheduledStartTime as string,
|
||||
scheduled_end_time: $.step.parameters.scheduledEndTime as string,
|
||||
description: $.step.parameters.description as string,
|
||||
entity_type: $.step.parameters.entityType as number,
|
||||
image: $.step.parameters.image as string,
|
||||
};
|
||||
|
||||
const isExternal = $.step.parameters.entityType === 3;
|
||||
if (isExternal) {
|
||||
data.entity_metadata = {
|
||||
location: $.step.parameters.location as string,
|
||||
};
|
||||
data.channel_id = null;
|
||||
}
|
||||
|
||||
const response = await $.http?.post(
|
||||
`/guilds/${$.auth.data.guildId}/scheduled-events`,
|
||||
data
|
||||
);
|
||||
|
||||
$.setActionItem({ raw: response.data });
|
||||
},
|
||||
});
|
@@ -1,3 +1,4 @@
|
||||
import sendMessageToChannel from './send-message-to-channel';
|
||||
import createScheduledEvent from './create-scheduled-event';
|
||||
|
||||
export default [sendMessageToChannel];
|
||||
export default [sendMessageToChannel, createScheduledEvent];
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import listChannels from './list-channels';
|
||||
import listVoiceChannels from './list-voice-channels';
|
||||
|
||||
export default [listChannels];
|
||||
export default [listChannels, listVoiceChannels];
|
||||
|
@@ -0,0 +1,34 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List voice channels',
|
||||
key: 'listVoiceChannels',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const channels: {
|
||||
data: IJSONObject[];
|
||||
error: IJSONObject | null;
|
||||
} = {
|
||||
data: [],
|
||||
error: null,
|
||||
};
|
||||
|
||||
const response = await $.http.get(
|
||||
`/guilds/${$.auth.data.guildId}/channels`
|
||||
);
|
||||
|
||||
channels.data = response.data
|
||||
.filter((channel: IJSONObject) => {
|
||||
// filter in voice and stage channels only
|
||||
return channel.type === 2 || channel.type === 13;
|
||||
})
|
||||
.map((channel: IJSONObject) => {
|
||||
return {
|
||||
value: channel.id,
|
||||
name: channel.name,
|
||||
};
|
||||
});
|
||||
|
||||
return channels;
|
||||
},
|
||||
};
|
@@ -0,0 +1,3 @@
|
||||
import listExternalScheduledEventFields from './list-external-scheduled-event-fields';
|
||||
|
||||
export default [listExternalScheduledEventFields];
|
@@ -0,0 +1,83 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
export default {
|
||||
name: 'List external scheduled event fields',
|
||||
key: 'listExternalScheduledEventFields',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const isExternal = $.step.parameters.entityType === 3;
|
||||
|
||||
if (isExternal) {
|
||||
return [
|
||||
{
|
||||
label: 'Location',
|
||||
key: 'location',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'The location of the event (1-100 characters). This will be omitted if type is NOT EXTERNAL',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Start-Time',
|
||||
key: 'scheduledStartTime',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'The time the event will start [ISO8601]',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'End-Time',
|
||||
key: 'scheduledEndTime',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'The time the event will end [ISO8601]. This will be omitted if type is NOT EXTERNAL',
|
||||
variables: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: 'Channel',
|
||||
key: 'channel_id',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
description: 'Pick a voice or stage channel to link the event to. This will be omitted if type is EXTERNAL',
|
||||
variables: true,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listVoiceChannels',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Location',
|
||||
key: 'location',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
description: 'The location of the event (1-100 characters). This will be omitted if type is NOT EXTERNAL',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Start-Time',
|
||||
key: 'scheduledStartTime',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'The time the event will start [ISO8601]',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'End-Time',
|
||||
key: 'scheduledEndTime',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
description: 'The time the event will end [ISO8601]. This will be omitted if type is NOT EXTERNAL',
|
||||
variables: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
@@ -4,6 +4,7 @@ import auth from './auth';
|
||||
import dynamicData from './dynamic-data';
|
||||
import actions from './actions';
|
||||
import triggers from './triggers';
|
||||
import dynamicFields from './dynamic-fields';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Discord',
|
||||
@@ -17,6 +18,7 @@ export default defineApp({
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
dynamicData,
|
||||
dynamicFields,
|
||||
triggers,
|
||||
actions,
|
||||
});
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import newDatabaseItems from './new-database-items';
|
||||
import updatedDatabaseItems from './updated-database-items';
|
||||
|
||||
export default [newDatabaseItems];
|
||||
export default [newDatabaseItems, updatedDatabaseItems];
|
||||
|
@@ -0,0 +1,33 @@
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
import updatedDatabaseItems from './updated-database-items';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'Updated database items',
|
||||
key: 'updatedDatabaseItems',
|
||||
pollInterval: 15,
|
||||
description:
|
||||
'Triggers when there is an update to an item in a chosen database',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Database',
|
||||
key: 'databaseId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listDatabases',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
await updatedDatabaseItems($);
|
||||
},
|
||||
});
|
@@ -0,0 +1,51 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
type DatabaseItem = {
|
||||
id: string;
|
||||
last_edited_time: string;
|
||||
};
|
||||
|
||||
type ResponseData = {
|
||||
results: DatabaseItem[];
|
||||
next_cursor?: string;
|
||||
};
|
||||
|
||||
type Payload = {
|
||||
sorts: [
|
||||
{
|
||||
timestamp: 'created_time' | 'last_edited_time';
|
||||
direction: 'ascending' | 'descending';
|
||||
}
|
||||
];
|
||||
start_cursor?: string;
|
||||
};
|
||||
|
||||
const updatedDatabaseItems = async ($: IGlobalVariable) => {
|
||||
const payload: Payload = {
|
||||
sorts: [
|
||||
{
|
||||
timestamp: 'last_edited_time',
|
||||
direction: 'descending',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const databaseId = $.step.parameters.databaseId as string;
|
||||
const path = `/v1/databases/${databaseId}/query`;
|
||||
do {
|
||||
const response = await $.http.post<ResponseData>(path, payload);
|
||||
|
||||
payload.start_cursor = response.data.next_cursor;
|
||||
|
||||
for (const databaseItem of response.data.results) {
|
||||
$.pushTriggerItem({
|
||||
raw: databaseItem,
|
||||
meta: {
|
||||
internalId: `${databaseItem.id}-${databaseItem.last_edited_time}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
} while (payload.start_cursor);
|
||||
};
|
||||
|
||||
export default updatedDatabaseItems;
|
@@ -11,7 +11,7 @@ export default {
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: 'Host name of your Odoo Server',
|
||||
description: 'Host name of your Odoo Server (e.g. sub.domain.com without the protocol)',
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
@@ -25,6 +25,27 @@ export default {
|
||||
description: 'Port that the host is running on, defaults to 443 (HTTPS)',
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'secure',
|
||||
label: 'Secure',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: 'true',
|
||||
description: 'True if the host communicates via secure protocol.',
|
||||
variables: false,
|
||||
clickToCopy: false,
|
||||
options: [
|
||||
{
|
||||
label: 'True',
|
||||
value: 'true',
|
||||
},
|
||||
{
|
||||
label: 'False',
|
||||
value: 'false',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'databaseName',
|
||||
label: 'Database Name',
|
||||
@@ -40,7 +61,7 @@ export default {
|
||||
key: 'email',
|
||||
label: 'Email Address',
|
||||
type: 'string' as const,
|
||||
requires: true,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
|
@@ -32,8 +32,10 @@ export const asyncMethodCall = async <T = number>($: IGlobalVariable, { method,
|
||||
export const getClient = ($: IGlobalVariable, { path = 'common' }) => {
|
||||
const host = $.auth.data.host as string;
|
||||
const port = Number($.auth.data.port as string);
|
||||
const secure = $.auth.data.secure === 'true';
|
||||
const createClientFunction = secure ? xmlrpc.createSecureClient : xmlrpc.createClient;
|
||||
|
||||
return xmlrpc.createClient(
|
||||
return createClientFunction(
|
||||
{
|
||||
host,
|
||||
port,
|
||||
|
@@ -105,7 +105,7 @@ export default defineAction({
|
||||
description: `Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.`
|
||||
},
|
||||
{
|
||||
label: 'presencePenalty',
|
||||
label: 'Presence Penalty',
|
||||
key: 'presencePenalty',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
|
@@ -75,7 +75,7 @@ export default defineAction({
|
||||
description: `Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.`
|
||||
},
|
||||
{
|
||||
label: 'presencePenalty',
|
||||
label: 'Presence Penalty',
|
||||
key: 'presencePenalty',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
|
@@ -0,0 +1,53 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
import { URLSearchParams } from 'url';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Create link post',
|
||||
key: 'createLinkPost',
|
||||
description: 'Create a new link post within a subreddit.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Title',
|
||||
key: 'title',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description:
|
||||
'Heading for the recent post. Limited to 300 characters or less.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Subreddit',
|
||||
key: 'subreddit',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'The subreddit for posting. Note: Exclude /r/.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Url',
|
||||
key: 'url',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: '',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const { title, subreddit, url } = $.step.parameters;
|
||||
|
||||
const params = new URLSearchParams({
|
||||
kind: 'link',
|
||||
api_type: 'json',
|
||||
title: title as string,
|
||||
sr: subreddit as string,
|
||||
url: url as string,
|
||||
});
|
||||
|
||||
const { data } = await $.http.post('/api/submit', params.toString());
|
||||
|
||||
$.setActionItem({
|
||||
raw: data,
|
||||
});
|
||||
},
|
||||
});
|
3
packages/backend/src/apps/reddit/actions/index.ts
Normal file
3
packages/backend/src/apps/reddit/actions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import createLinkPost from './create-link-post';
|
||||
|
||||
export default [createLinkPost];
|
1
packages/backend/src/apps/reddit/assets/favicon.svg
Normal file
1
packages/backend/src/apps/reddit/assets/favicon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" class="_1O4jTk-dZ-VIxsCuYB6OR8 " width="40" height="48" ><g><circle fill="#FF4500" cx="10" cy="10" r="10"></circle><path fill="#FFFFFF" d="M16.67,10A1.46,1.46,0,0,0,14.2,9a7.12,7.12,0,0,0-3.85-1.23L11,4.65,13.14,5.1a1,1,0,1,0,.13-0.61L10.82,4a0.31,0.31,0,0,0-.37.24L9.71,7.71a7.14,7.14,0,0,0-3.9,1.23A1.46,1.46,0,1,0,4.2,11.33a2.87,2.87,0,0,0,0,.44c0,2.24,2.61,4.06,5.83,4.06s5.83-1.82,5.83-4.06a2.87,2.87,0,0,0,0-.44A1.46,1.46,0,0,0,16.67,10Zm-10,1a1,1,0,1,1,1,1A1,1,0,0,1,6.67,11Zm5.81,2.75a3.84,3.84,0,0,1-2.47.77,3.84,3.84,0,0,1-2.47-.77,0.27,0.27,0,0,1,.38-0.38A3.27,3.27,0,0,0,10,14a3.28,3.28,0,0,0,2.09-.61A0.27,0.27,0,1,1,12.48,13.79Zm-0.18-1.71a1,1,0,1,1,1-1A1,1,0,0,1,12.29,12.08Z"></path></g></svg>
|
After Width: | Height: | Size: 813 B |
26
packages/backend/src/apps/reddit/auth/generate-auth-url.ts
Normal file
26
packages/backend/src/apps/reddit/auth/generate-auth-url.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||
import { URLSearchParams } from 'url';
|
||||
import authScope from '../common/auth-scope';
|
||||
|
||||
export default async function generateAuthUrl($: IGlobalVariable) {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const redirectUri = oauthRedirectUrlField.value as string;
|
||||
const state = Math.random().toString() as string;
|
||||
const searchParams = new URLSearchParams({
|
||||
client_id: $.auth.data.clientId as string,
|
||||
response_type: 'code',
|
||||
redirect_uri: redirectUri,
|
||||
duration: 'permanent',
|
||||
scope: authScope.join(' '),
|
||||
state,
|
||||
});
|
||||
|
||||
const url = `https://www.reddit.com/api/v1/authorize?${searchParams.toString()}`;
|
||||
|
||||
await $.auth.set({
|
||||
url,
|
||||
originalState: state,
|
||||
});
|
||||
}
|
48
packages/backend/src/apps/reddit/auth/index.ts
Normal file
48
packages/backend/src/apps/reddit/auth/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import generateAuthUrl from './generate-auth-url';
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import refreshToken from './refresh-token';
|
||||
import isStillVerified from './is-still-verified';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'oAuthRedirectUrl',
|
||||
label: 'OAuth Redirect URL',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: true,
|
||||
value: '{WEB_APP_URL}/app/reddit/connections/add',
|
||||
placeholder: null,
|
||||
description:
|
||||
'When asked to input a redirect URL in Reddit, enter the URL above.',
|
||||
clickToCopy: true,
|
||||
},
|
||||
{
|
||||
key: 'clientId',
|
||||
label: 'Client ID',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'clientSecret',
|
||||
label: 'Client Secret',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
],
|
||||
|
||||
generateAuthUrl,
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
refreshToken,
|
||||
};
|
@@ -0,0 +1,9 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import getCurrentUser from '../common/get-current-user';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
const currentUser = await getCurrentUser($);
|
||||
return !!currentUser.id;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
29
packages/backend/src/apps/reddit/auth/refresh-token.ts
Normal file
29
packages/backend/src/apps/reddit/auth/refresh-token.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { URLSearchParams } from 'node:url';
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const refreshToken = async ($: IGlobalVariable) => {
|
||||
const headers = {
|
||||
Authorization: `Basic ${Buffer.from(
|
||||
$.auth.data.clientId + ':' + $.auth.data.clientSecret
|
||||
).toString('base64')}`,
|
||||
};
|
||||
const params = new URLSearchParams({
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: $.auth.data.refreshToken as string,
|
||||
});
|
||||
|
||||
const { data } = await $.http.post(
|
||||
'https://www.reddit.com/api/v1/access_token',
|
||||
params.toString(),
|
||||
{ headers }
|
||||
);
|
||||
|
||||
await $.auth.set({
|
||||
accessToken: data.access_token,
|
||||
expiresIn: data.expires_in,
|
||||
scope: data.scope,
|
||||
tokenType: data.token_type,
|
||||
});
|
||||
};
|
||||
|
||||
export default refreshToken;
|
48
packages/backend/src/apps/reddit/auth/verify-credentials.ts
Normal file
48
packages/backend/src/apps/reddit/auth/verify-credentials.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||
import getCurrentUser from '../common/get-current-user';
|
||||
import { URLSearchParams } from 'url';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
if ($.auth.data.originalState !== $.auth.data.state) {
|
||||
throw new Error(`The 'state' parameter does not match.`);
|
||||
}
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const redirectUri = oauthRedirectUrlField.value as string;
|
||||
const headers = {
|
||||
Authorization: `Basic ${Buffer.from(
|
||||
$.auth.data.clientId + ':' + $.auth.data.clientSecret
|
||||
).toString('base64')}`,
|
||||
};
|
||||
const params = new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
code: $.auth.data.code as string,
|
||||
redirect_uri: redirectUri,
|
||||
});
|
||||
|
||||
const { data } = await $.http.post(
|
||||
'https://www.reddit.com/api/v1/access_token',
|
||||
params.toString(),
|
||||
{ headers }
|
||||
);
|
||||
|
||||
await $.auth.set({
|
||||
accessToken: data.access_token,
|
||||
tokenType: data.token_type,
|
||||
});
|
||||
|
||||
const currentUser = await getCurrentUser($);
|
||||
const screenName = currentUser?.name;
|
||||
|
||||
await $.auth.set({
|
||||
clientId: $.auth.data.clientId,
|
||||
clientSecret: $.auth.data.clientSecret,
|
||||
scope: $.auth.data.scope,
|
||||
expiresIn: data.expires_in,
|
||||
refreshToken: data.refresh_token,
|
||||
screenName,
|
||||
});
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
23
packages/backend/src/apps/reddit/common/add-auth-header.ts
Normal file
23
packages/backend/src/apps/reddit/common/add-auth-header.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
import appConfig from '../../../config/app';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
const screenName = $.auth.data?.screenName as string;
|
||||
if ($.auth.data?.accessToken) {
|
||||
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
|
||||
}
|
||||
|
||||
if (screenName) {
|
||||
requestConfig.headers[
|
||||
'User-Agent'
|
||||
] = `web:automatisch:${appConfig.version} (by /u/${screenName})`;
|
||||
} else {
|
||||
requestConfig.headers[
|
||||
'User-Agent'
|
||||
] = `web:automatisch:${appConfig.version}`;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
3
packages/backend/src/apps/reddit/common/auth-scope.ts
Normal file
3
packages/backend/src/apps/reddit/common/auth-scope.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
const authScope: string[] = ['identity', 'read', 'account', 'submit'];
|
||||
|
||||
export default authScope;
|
@@ -0,0 +1,8 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const getCurrentUser = async ($: IGlobalVariable) => {
|
||||
const { data: currentUser } = await $.http.get('/api/v1/me');
|
||||
return currentUser;
|
||||
};
|
||||
|
||||
export default getCurrentUser;
|
0
packages/backend/src/apps/reddit/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/reddit/index.d.ts
vendored
Normal file
20
packages/backend/src/apps/reddit/index.ts
Normal file
20
packages/backend/src/apps/reddit/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
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';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Reddit',
|
||||
key: 'reddit',
|
||||
baseUrl: 'https://www.reddit.com',
|
||||
apiBaseUrl: 'https://oauth.reddit.com',
|
||||
iconUrl: '{BASE_URL}/apps/reddit/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/reddit/connection',
|
||||
primaryColor: 'FF4500',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
actions,
|
||||
});
|
3
packages/backend/src/apps/reddit/triggers/index.ts
Normal file
3
packages/backend/src/apps/reddit/triggers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import newPostsMatchingSearch from './new-posts-matching-search';
|
||||
|
||||
export default [newPostsMatchingSearch];
|
@@ -0,0 +1,48 @@
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'New posts matching search',
|
||||
key: 'newPostsMatchingSearch',
|
||||
pollInterval: 15,
|
||||
description: 'Triggers when a search string matches a new post.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Search Query',
|
||||
key: 'searchQuery',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description:
|
||||
'The term or expression to look for, restricted to 512 characters. If your query contains periods (e.g., automatisch.io), ensure it is enclosed in quotes ("automatisch.io").',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const { searchQuery } = $.step.parameters;
|
||||
const params = {
|
||||
q: searchQuery,
|
||||
type: 'link',
|
||||
sort: 'new',
|
||||
limit: 100,
|
||||
after: undefined as unknown as string,
|
||||
};
|
||||
|
||||
do {
|
||||
const { data } = await $.http.get('/search', {
|
||||
params,
|
||||
});
|
||||
params.after = data.data.after;
|
||||
|
||||
if (data.data.children?.length) {
|
||||
for (const item of data.data.children) {
|
||||
$.pushTriggerItem({
|
||||
raw: item,
|
||||
meta: {
|
||||
internalId: item.data.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (params.after);
|
||||
},
|
||||
});
|
3
packages/backend/src/apps/removebg/actions/index.ts
Normal file
3
packages/backend/src/apps/removebg/actions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import removeImageBackground from './remove-image-background';
|
||||
|
||||
export default [removeImageBackground];
|
@@ -0,0 +1,82 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Remove image background',
|
||||
key: 'removeImageBackground',
|
||||
description:
|
||||
'Removes the background of an image.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Image file',
|
||||
key: 'imageFileB64',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
description: 'Provide a JPG or PNG file in Base64 format, up to 12 MB (see remove.bg/supported-images)',
|
||||
},
|
||||
{
|
||||
label: 'Size',
|
||||
key: 'size',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
value: 'auto',
|
||||
options: [
|
||||
{ label: 'Auto', value: 'auto' },
|
||||
{ label: 'Preview (up to 0.25 megapixels)', value: 'preview' },
|
||||
{ label: 'Full (up to 10 megapixels)', value: 'full' },
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Background color',
|
||||
key: 'bgColor',
|
||||
type: 'string' as const,
|
||||
description: 'Adds a solid color background. Can be a hex color code (e.g. 81d4fa, fff) or a color name (e.g. green)',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
label: 'Background image URL',
|
||||
key: 'bgImageUrl',
|
||||
type: 'string' as const,
|
||||
description: 'Adds a background image from a URL.',
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
label: 'Output image format',
|
||||
key: 'outputFormat',
|
||||
type: 'dropdown' as const,
|
||||
description: 'Note: Use PNG to preserve transparency',
|
||||
required: true,
|
||||
value: 'auto',
|
||||
options: [
|
||||
{ label: 'Auto', value: 'auto' },
|
||||
{ label: 'PNG', value: 'png' },
|
||||
{ label: 'JPG', value: 'jpg' },
|
||||
{ label: 'ZIP', value: 'zip' }
|
||||
]
|
||||
}
|
||||
],
|
||||
async run($) {
|
||||
const imageFileB64 = $.step.parameters.imageFileB64 as string;
|
||||
const size = $.step.parameters.size as string;
|
||||
const bgColor = $.step.parameters.bgColor as string;
|
||||
const bgImageUrl = $.step.parameters.bgImageUrl as string;
|
||||
const outputFormat = $.step.parameters.outputFormat as string;
|
||||
|
||||
const body = JSON.stringify({
|
||||
image_file_b64: imageFileB64,
|
||||
size: size,
|
||||
bg_color: bgColor,
|
||||
bg_image_url: bgImageUrl,
|
||||
format: outputFormat
|
||||
});
|
||||
|
||||
const response = await $.http.post('/removebg', body, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
$.setActionItem({ raw: response.data });
|
||||
}
|
||||
});
|
@@ -1,6 +1,7 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import addAuthHeader from './common/add-auth-header';
|
||||
import auth from './auth';
|
||||
import actions from './actions';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Remove.bg',
|
||||
@@ -13,4 +14,5 @@ export default defineApp({
|
||||
primaryColor: '55636c',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
});
|
||||
|
@@ -0,0 +1,4 @@
|
||||
import sendPrompt from './send-prompt';
|
||||
import sendChatPrompt from './send-chat-prompt';
|
||||
|
||||
export default [sendChatPrompt, sendPrompt];
|
@@ -0,0 +1,137 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
type TMessage = {
|
||||
role: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const castFloatOrUndefined = (value: string | null) => {
|
||||
return value === '' ? undefined : parseFloat(value);
|
||||
}
|
||||
|
||||
export default defineAction({
|
||||
name: 'Send chat prompt',
|
||||
key: 'sendChatPrompt',
|
||||
description: 'Creates a completion for the provided prompt and parameters.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Model',
|
||||
key: 'model',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listModels',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Messages',
|
||||
key: 'messages',
|
||||
type: 'dynamic' as const,
|
||||
required: true,
|
||||
description: 'Add or remove messages as needed',
|
||||
value: [{ role: 'system', body: '' }],
|
||||
fields: [
|
||||
{
|
||||
label: 'Role',
|
||||
key: 'role',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
options: [
|
||||
{
|
||||
label: 'System',
|
||||
value: 'system',
|
||||
},
|
||||
{
|
||||
label: 'User',
|
||||
value: 'user',
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Content',
|
||||
key: 'content',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Temperature',
|
||||
key: 'temperature',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'What sampling temperature to use. Higher values mean the model will take more risk. Try 0.9 for more creative applications, and 0 for ones with a well-defined answer. We generally recommend altering this or Top P but not both.'
|
||||
},
|
||||
{
|
||||
label: 'Maximum tokens',
|
||||
key: 'maxTokens',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'The maximum number of tokens to generate in the completion.'
|
||||
},
|
||||
{
|
||||
label: 'Stop Sequence',
|
||||
key: 'stopSequence',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'Single stop sequence where the API will stop generating further tokens. The returned text will not contain the stop sequence.'
|
||||
},
|
||||
{
|
||||
label: 'Top P',
|
||||
key: 'topP',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with Top P probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.'
|
||||
},
|
||||
{
|
||||
label: 'Frequency Penalty',
|
||||
key: 'frequencyPenalty',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: `Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.`
|
||||
},
|
||||
{
|
||||
label: 'Presence Penalty',
|
||||
key: 'presencePenalty',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: `Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.`
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const payload = {
|
||||
model: $.step.parameters.model as string,
|
||||
temperature: castFloatOrUndefined($.step.parameters.temperature as string),
|
||||
max_tokens: castFloatOrUndefined($.step.parameters.maxTokens as string),
|
||||
stop: ($.step.parameters.stopSequence as string || null),
|
||||
top_p: castFloatOrUndefined($.step.parameters.topP as string),
|
||||
frequency_penalty: castFloatOrUndefined($.step.parameters.frequencyPenalty as string),
|
||||
presence_penalty: castFloatOrUndefined($.step.parameters.presencePenalty as string),
|
||||
messages: ($.step.parameters.messages as TMessage[]).map(message => ({
|
||||
role: message.role,
|
||||
content: message.content,
|
||||
})),
|
||||
};
|
||||
const { data } = await $.http.post('/v1/chat/completions', payload);
|
||||
|
||||
$.setActionItem({
|
||||
raw: data,
|
||||
});
|
||||
},
|
||||
});
|
@@ -0,0 +1,104 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
const castFloatOrUndefined = (value: string | null) => {
|
||||
return value === '' ? undefined : parseFloat(value);
|
||||
}
|
||||
|
||||
export default defineAction({
|
||||
name: 'Send prompt',
|
||||
key: 'sendPrompt',
|
||||
description: 'Creates a completion for the provided prompt and parameters.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Model',
|
||||
key: 'model',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listModels',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Prompt',
|
||||
key: 'prompt',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
description: 'The text to analyze.'
|
||||
},
|
||||
{
|
||||
label: 'Temperature',
|
||||
key: 'temperature',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'What sampling temperature to use. Higher values mean the model will take more risk. Try 0.9 for more creative applications, and 0 for ones with a well-defined answer. We generally recommend altering this or Top P but not both.'
|
||||
},
|
||||
{
|
||||
label: 'Maximum tokens',
|
||||
key: 'maxTokens',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'The maximum number of tokens to generate in the completion.'
|
||||
},
|
||||
{
|
||||
label: 'Stop Sequence',
|
||||
key: 'stopSequence',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'Single stop sequence where the API will stop generating further tokens. The returned text will not contain the stop sequence.'
|
||||
},
|
||||
{
|
||||
label: 'Top P',
|
||||
key: 'topP',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with Top P probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.'
|
||||
},
|
||||
{
|
||||
label: 'Frequency Penalty',
|
||||
key: 'frequencyPenalty',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: `Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.`
|
||||
},
|
||||
{
|
||||
label: 'Presence Penalty',
|
||||
key: 'presencePenalty',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: `Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.`
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const payload = {
|
||||
model: $.step.parameters.model as string,
|
||||
prompt: $.step.parameters.prompt as string,
|
||||
temperature: castFloatOrUndefined($.step.parameters.temperature as string),
|
||||
max_tokens: castFloatOrUndefined($.step.parameters.maxTokens as string),
|
||||
stop: ($.step.parameters.stopSequence as string || null),
|
||||
top_p: castFloatOrUndefined($.step.parameters.topP as string),
|
||||
frequency_penalty: castFloatOrUndefined($.step.parameters.frequencyPenalty as string),
|
||||
presence_penalty: castFloatOrUndefined($.step.parameters.presencePenalty as string),
|
||||
};
|
||||
const { data } = await $.http.post('/v1/completions', payload);
|
||||
|
||||
$.setActionItem({
|
||||
raw: data,
|
||||
});
|
||||
},
|
||||
});
|
@@ -0,0 +1,6 @@
|
||||
<svg width="256px" height="260px" viewBox="0 0 256 260" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<title>OpenAI</title>
|
||||
<g>
|
||||
<path d="M239.183914,106.202783 C245.054304,88.5242096 243.02228,69.1733805 233.607599,53.0998864 C219.451678,28.4588021 190.999703,15.7836129 163.213007,21.739505 C147.554077,4.32145883 123.794909,-3.42398554 100.87901,1.41873898 C77.9631105,6.26146349 59.3690093,22.9572536 52.0959621,45.2214219 C33.8436494,48.9644867 18.0901721,60.392749 8.86672513,76.5818033 C-5.443491,101.182962 -2.19544431,132.215255 16.8986662,153.320094 C11.0060865,170.990656 13.0197283,190.343991 22.4238231,206.422991 C36.5975553,231.072344 65.0680342,243.746566 92.8695738,237.783372 C105.235639,251.708249 123.001113,259.630942 141.623968,259.52692 C170.105359,259.552169 195.337611,241.165718 204.037777,214.045661 C222.28734,210.296356 238.038489,198.869783 247.267014,182.68528 C261.404453,158.127515 258.142494,127.262775 239.183914,106.202783 L239.183914,106.202783 Z M141.623968,242.541207 C130.255682,242.559177 119.243876,238.574642 110.519381,231.286197 L112.054146,230.416496 L163.724595,200.590881 C166.340648,199.056444 167.954321,196.256818 167.970781,193.224005 L167.970781,120.373788 L189.815614,133.010026 C190.034132,133.121423 190.186235,133.330564 190.224885,133.572774 L190.224885,193.940229 C190.168603,220.758427 168.442166,242.484864 141.623968,242.541207 Z M37.1575749,197.93062 C31.456498,188.086359 29.4094818,176.546984 31.3766237,165.342426 L32.9113895,166.263285 L84.6329973,196.088901 C87.2389349,197.618207 90.4682717,197.618207 93.0742093,196.088901 L156.255402,159.663793 L156.255402,184.885111 C156.243557,185.149771 156.111725,185.394602 155.89729,185.550176 L103.561776,215.733903 C80.3054953,229.131632 50.5924954,221.165435 37.1575749,197.93062 Z M23.5493181,85.3811273 C29.2899861,75.4733097 38.3511911,67.9162648 49.1287482,64.0478825 L49.1287482,125.438515 C49.0891492,128.459425 50.6965386,131.262556 53.3237748,132.754232 L116.198014,169.025864 L94.3531808,181.662102 C94.1132325,181.789434 93.8257461,181.789434 93.5857979,181.662102 L41.3526015,151.529534 C18.1419426,138.076098 10.1817681,108.385562 23.5493181,85.125333 L23.5493181,85.3811273 Z M203.0146,127.075598 L139.935725,90.4458545 L161.7294,77.8607748 C161.969348,77.7334434 162.256834,77.7334434 162.496783,77.8607748 L214.729979,108.044502 C231.032329,117.451747 240.437294,135.426109 238.871504,154.182739 C237.305714,172.939368 225.050719,189.105572 207.414262,195.67963 L207.414262,134.288998 C207.322521,131.276867 205.650697,128.535853 203.0146,127.075598 Z M224.757116,94.3850867 L223.22235,93.4642272 L171.60306,63.3828173 C168.981293,61.8443751 165.732456,61.8443751 163.110689,63.3828173 L99.9806554,99.8079259 L99.9806554,74.5866077 C99.9533004,74.3254088 100.071095,74.0701869 100.287609,73.9215426 L152.520805,43.7889738 C168.863098,34.3743518 189.174256,35.2529043 204.642579,46.0434841 C220.110903,56.8340638 227.949269,75.5923959 224.757116,94.1804513 L224.757116,94.3850867 Z M88.0606409,139.097931 L66.2158076,126.512851 C65.9950399,126.379091 65.8450965,126.154176 65.8065367,125.898945 L65.8065367,65.684966 C65.8314495,46.8285367 76.7500605,29.6846032 93.8270852,21.6883055 C110.90411,13.6920079 131.063833,16.2835462 145.5632,28.338998 L144.028434,29.2086986 L92.3579852,59.0343142 C89.7419327,60.5687513 88.1282597,63.3683767 88.1117998,66.4011901 L88.0606409,139.097931 Z M99.9294965,113.5185 L128.06687,97.3011417 L156.255402,113.5185 L156.255402,145.953218 L128.169187,162.170577 L99.9806554,145.953218 L99.9294965,113.5185 Z" fill="#000000"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.6 KiB |
44
packages/backend/src/apps/self-hosted-llm/auth/index.ts
Normal file
44
packages/backend/src/apps/self-hosted-llm/auth/index.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
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:
|
||||
'Screen name of your connection to be used on Automatisch UI.',
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'apiUrl',
|
||||
label: 'API URL',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
docUrl: 'https://automatisch.io/docs/self-hosted-llm#api-url',
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'apiKey',
|
||||
label: 'API Key',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
docUrl: 'https://automatisch.io/docs/self-hosted-llm#api-key',
|
||||
clickToCopy: false,
|
||||
},
|
||||
],
|
||||
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
};
|
@@ -0,0 +1,8 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
const r = await $.http.get('/v1/models');
|
||||
return true;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
@@ -0,0 +1,7 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
await $.http.get('/v1/models');
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
@@ -0,0 +1,11 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
if ($.auth.data?.apiKey) {
|
||||
requestConfig.headers.Authorization = `Bearer ${$.auth.data.apiKey}`;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
@@ -0,0 +1,11 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const setBaseUrl: TBeforeRequest = ($, requestConfig) => {
|
||||
if ($.auth.data.apiUrl) {
|
||||
requestConfig.baseURL = $.auth.data.apiUrl as string;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default setBaseUrl;
|
@@ -0,0 +1,3 @@
|
||||
import listModels from './list-models';
|
||||
|
||||
export default [listModels];
|
@@ -0,0 +1,19 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List models',
|
||||
key: 'listModels',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const response = await $.http.get('/v1/models');
|
||||
|
||||
const models = response.data.data.map((model: { id: string }) => {
|
||||
return {
|
||||
value: model.id,
|
||||
name: model.id,
|
||||
};
|
||||
});
|
||||
|
||||
return { data: models };
|
||||
},
|
||||
};
|
0
packages/backend/src/apps/self-hosted-llm/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/self-hosted-llm/index.d.ts
vendored
Normal file
21
packages/backend/src/apps/self-hosted-llm/index.ts
Normal file
21
packages/backend/src/apps/self-hosted-llm/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import addAuthHeader from './common/add-auth-header';
|
||||
import setBaseUrl from './common/set-base-url';
|
||||
import auth from './auth';
|
||||
import actions from './actions';
|
||||
import dynamicData from './dynamic-data';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Self-hosted LLM',
|
||||
key: 'self-hosted-llm',
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
iconUrl: '{BASE_URL}/apps/self-hosted-llm/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/self-hosted-llm/connection',
|
||||
primaryColor: '000000',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
dynamicData,
|
||||
});
|
@@ -1,3 +1,4 @@
|
||||
import newBankTransactions from './new-bank-transactions';
|
||||
import newPayments from './new-payments';
|
||||
|
||||
export default [newBankTransactions];
|
||||
export default [newBankTransactions, newPayments];
|
||||
|
109
packages/backend/src/apps/xero/triggers/new-payments/index.ts
Normal file
109
packages/backend/src/apps/xero/triggers/new-payments/index.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
|
||||
type Params = {
|
||||
page: number;
|
||||
order: string;
|
||||
where?: string;
|
||||
};
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'New payments',
|
||||
key: 'newPayments',
|
||||
pollInterval: 15,
|
||||
description: 'Triggers when a new payment is received.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Organization',
|
||||
key: 'organizationId',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
description: '',
|
||||
variables: true,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listOrganizations',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Payment Type',
|
||||
key: 'paymentType',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
description: '',
|
||||
variables: true,
|
||||
value: '',
|
||||
options: [
|
||||
{ label: 'Accounts Receivable', value: 'ACCRECPAYMENT' },
|
||||
{ label: 'Accounts Payable', value: 'ACCPAYPAYMENT' },
|
||||
{
|
||||
label: 'Accounts Receivable Credit (Refund)',
|
||||
value: 'ARCREDITPAYMENT',
|
||||
},
|
||||
{
|
||||
label: 'Accounts Payable Credit (Refund)',
|
||||
value: 'APCREDITPAYMENT',
|
||||
},
|
||||
{
|
||||
label: 'Accounts Receivable Overpayment (Refund)',
|
||||
value: 'AROVERPAYMENTPAYMENT',
|
||||
},
|
||||
{
|
||||
label: 'Accounts Receivable Prepayment (Refund)',
|
||||
value: 'ARPREPAYMENTPAYMENT',
|
||||
},
|
||||
{
|
||||
label: 'Accounts Payable Prepayment (Refund)',
|
||||
value: 'APPREPAYMENTPAYMENT',
|
||||
},
|
||||
{
|
||||
label: 'Accounts Payable Overpayment (Refund)',
|
||||
value: 'APOVERPAYMENTPAYMENT',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const paymentType = $.step.parameters.paymentType;
|
||||
|
||||
const params: Params = {
|
||||
page: 1,
|
||||
order: 'Date DESC',
|
||||
};
|
||||
|
||||
if (paymentType) {
|
||||
params.where = `PaymentType="${paymentType}"`;
|
||||
}
|
||||
|
||||
let nextPage = false;
|
||||
do {
|
||||
const { data } = await $.http.get('/api.xro/2.0/Payments', {
|
||||
params,
|
||||
});
|
||||
params.page = params.page + 1;
|
||||
|
||||
if (data.Payments?.length) {
|
||||
for (const payment of data.Payments) {
|
||||
$.pushTriggerItem({
|
||||
raw: payment,
|
||||
meta: {
|
||||
internalId: payment.PaymentID,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (data.Payments?.length === 100) {
|
||||
nextPage = true;
|
||||
} else {
|
||||
nextPage = false;
|
||||
}
|
||||
} while (nextPage);
|
||||
},
|
||||
});
|
102
packages/backend/src/apps/zendesk/actions/create-user/fields.ts
Normal file
102
packages/backend/src/apps/zendesk/actions/create-user/fields.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
export const fields = [
|
||||
{
|
||||
label: 'Name',
|
||||
key: 'name',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
label: 'Email',
|
||||
key: 'email',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
description:
|
||||
'It is essential to be distinctive. Zendesk prohibits the existence of identical users sharing the same email address.',
|
||||
},
|
||||
{
|
||||
label: 'Details',
|
||||
key: 'details',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
label: 'Notes',
|
||||
key: 'notes',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description:
|
||||
'Within this field, you have the capability to save any remarks or comments you may have concerning the user.',
|
||||
},
|
||||
{
|
||||
label: 'Phone',
|
||||
key: 'phone',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description:
|
||||
"The user's contact number should be entered in the following format: +1 (555) 123-4567.",
|
||||
},
|
||||
{
|
||||
label: 'Tags',
|
||||
key: 'tags',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'A comma separated list of tags.',
|
||||
},
|
||||
{
|
||||
label: 'Role',
|
||||
key: 'role',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description:
|
||||
"It can take on one of the designated roles: 'end-user', 'agent', or 'admin'. If a different value is set or none is specified, the default is 'end-user.'",
|
||||
},
|
||||
{
|
||||
label: 'Organization',
|
||||
key: 'organizationId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'Assign this user to a specific organization.',
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listOrganizations',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'External Id',
|
||||
key: 'externalId',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description:
|
||||
'An exclusive external identifier; you can utilize this to link organizations with an external record.',
|
||||
},
|
||||
{
|
||||
label: 'Verified',
|
||||
key: 'verified',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
description:
|
||||
"Specify if you can verify that the user's assertion of their identity is accurate.",
|
||||
variables: true,
|
||||
options: [
|
||||
{ label: 'True', value: 'true' },
|
||||
{ label: 'False', value: 'false' },
|
||||
],
|
||||
},
|
||||
];
|
@@ -0,0 +1,53 @@
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
import { fields } from './fields';
|
||||
|
||||
type Payload = {
|
||||
user: IJSONObject;
|
||||
};
|
||||
|
||||
export default defineAction({
|
||||
name: 'Create user',
|
||||
key: 'createUser',
|
||||
description: 'Creates a new user.',
|
||||
arguments: fields,
|
||||
|
||||
async run($) {
|
||||
const {
|
||||
name,
|
||||
email,
|
||||
details,
|
||||
notes,
|
||||
phone,
|
||||
role,
|
||||
organizationId,
|
||||
externalId,
|
||||
verified,
|
||||
} = $.step.parameters;
|
||||
|
||||
const tags = $.step.parameters.tags as string;
|
||||
const formattedTags = tags.split(',');
|
||||
|
||||
const payload: Payload = {
|
||||
user: {
|
||||
name,
|
||||
email,
|
||||
details,
|
||||
notes,
|
||||
phone,
|
||||
organization_id: organizationId,
|
||||
external_id: externalId,
|
||||
verified: verified || 'false',
|
||||
tags: formattedTags,
|
||||
},
|
||||
};
|
||||
|
||||
if (role) {
|
||||
payload.user.role = role;
|
||||
}
|
||||
|
||||
const response = await $.http.post('/api/v2/users', payload);
|
||||
|
||||
$.setActionItem({ raw: response.data });
|
||||
},
|
||||
});
|
@@ -0,0 +1,35 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Delete ticket',
|
||||
key: 'deleteTicket',
|
||||
description: 'Deletes an existing ticket.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Ticket',
|
||||
key: 'ticketId',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
description: 'Select the ticket you want to delete.',
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listFirstPageOfTickets',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const ticketId = $.step.parameters.ticketId;
|
||||
|
||||
const response = await $.http.delete(`/api/v2/tickets/${ticketId}`);
|
||||
|
||||
$.setActionItem({ raw: { data: response.data } });
|
||||
},
|
||||
});
|
@@ -0,0 +1,43 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Delete user',
|
||||
key: 'deleteUser',
|
||||
description: 'Deletes an existing user.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'User',
|
||||
key: 'userId',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
description: 'Select the user you want to modify.',
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listUsers',
|
||||
},
|
||||
{
|
||||
name: 'parameters.showUserRole',
|
||||
value: 'true',
|
||||
},
|
||||
{
|
||||
name: 'parameters.includeAllUsers',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const userId = $.step.parameters.userId;
|
||||
|
||||
const response = await $.http.delete(`/api/v2/users/${userId}`);
|
||||
|
||||
$.setActionItem({ raw: response.data });
|
||||
},
|
||||
});
|
@@ -0,0 +1,32 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Find ticket',
|
||||
key: 'findTicket',
|
||||
description: 'Finds an existing ticket.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Query',
|
||||
key: 'query',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
description:
|
||||
'Write a search string that specifies the way we will search for the ticket in Zendesk.',
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const query = $.step.parameters.query;
|
||||
|
||||
const params = {
|
||||
query: `type:ticket ${query}`,
|
||||
sort_by: 'created_at',
|
||||
sort_order: 'desc',
|
||||
};
|
||||
|
||||
const response = await $.http.get('/api/v2/search', { params });
|
||||
|
||||
$.setActionItem({ raw: response.data.results[0] });
|
||||
},
|
||||
});
|
@@ -1,3 +1,15 @@
|
||||
import createTicket from './create-ticket';
|
||||
import createUser from './create-user';
|
||||
import deleteTicket from './delete-ticket';
|
||||
import deleteUser from './delete-user';
|
||||
import findTicket from './find-ticket';
|
||||
import updateTicket from './update-ticket';
|
||||
|
||||
export default [createTicket];
|
||||
export default [
|
||||
createTicket,
|
||||
createUser,
|
||||
deleteTicket,
|
||||
deleteUser,
|
||||
findTicket,
|
||||
updateTicket,
|
||||
];
|
||||
|
@@ -0,0 +1,167 @@
|
||||
export const fields = [
|
||||
{
|
||||
label: 'Ticket',
|
||||
key: 'ticketId',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
description: 'Select the ticket you want to change.',
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listFirstPageOfTickets',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Subject',
|
||||
key: 'subject',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
label: 'Assignee',
|
||||
key: 'assigneeId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description:
|
||||
'Note: An error occurs if the assignee is not in the default group (or the specific group chosen below).',
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listUsers',
|
||||
},
|
||||
{
|
||||
name: 'parameters.showUserRole',
|
||||
value: 'true',
|
||||
},
|
||||
{
|
||||
name: 'parameters.includeAdmins',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Group',
|
||||
key: 'groupId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'Allocate this ticket to a specific group.',
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listGroups',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'New Status',
|
||||
key: 'status',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: '',
|
||||
options: [
|
||||
{ label: 'New', value: 'new' },
|
||||
{ label: 'Open', value: 'open' },
|
||||
{ label: 'Pending', value: 'pending' },
|
||||
{ label: 'Hold', value: 'hold' },
|
||||
{ label: 'Solved', value: 'solved' },
|
||||
{ label: 'Closed', value: 'closed' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'New comment to add to the ticket',
|
||||
key: 'comment',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
label: 'Should the first comment be public?',
|
||||
key: 'publicOrNot',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: '',
|
||||
options: [
|
||||
{ label: 'Yes', value: 'yes' },
|
||||
{ label: 'No', value: 'no' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Tags',
|
||||
key: 'tags',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'A comma separated list of tags.',
|
||||
},
|
||||
{
|
||||
label: 'Type',
|
||||
key: 'type',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: '',
|
||||
options: [
|
||||
{ label: 'Problem', value: 'problem' },
|
||||
{ label: 'Incident', value: 'incident' },
|
||||
{ label: 'Question', value: 'question' },
|
||||
{ label: 'Task', value: 'task' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Priority',
|
||||
key: 'priority',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: '',
|
||||
options: [
|
||||
{ label: 'Urgent', value: 'urgent' },
|
||||
{ label: 'High', value: 'high' },
|
||||
{ label: 'Normal', value: 'normal' },
|
||||
{ label: 'Low', value: 'low' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Submitter',
|
||||
key: 'submitterId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: '',
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listUsers',
|
||||
},
|
||||
{
|
||||
name: 'parameters.includeAdmins',
|
||||
value: 'false',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
@@ -0,0 +1,57 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
import { fields } from './fields';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import omitBy from 'lodash/omitBy';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Update ticket',
|
||||
key: 'updateTicket',
|
||||
description: 'Modify the status of an existing ticket or append comments.',
|
||||
arguments: fields,
|
||||
|
||||
async run($) {
|
||||
const {
|
||||
ticketId,
|
||||
subject,
|
||||
assigneeId,
|
||||
groupId,
|
||||
status,
|
||||
comment,
|
||||
publicOrNot,
|
||||
type,
|
||||
priority,
|
||||
submitterId,
|
||||
} = $.step.parameters;
|
||||
|
||||
const tags = $.step.parameters.tags as string;
|
||||
const formattedTags = tags.split(',');
|
||||
|
||||
const payload = {
|
||||
subject,
|
||||
assignee_id: assigneeId,
|
||||
group_id: groupId,
|
||||
status,
|
||||
comment: {
|
||||
body: comment,
|
||||
public: publicOrNot,
|
||||
},
|
||||
tags: formattedTags,
|
||||
type,
|
||||
priority,
|
||||
submitter_id: submitterId,
|
||||
};
|
||||
|
||||
const fieldsToRemoveIfEmpty = ['group_id', 'status', 'type', 'priority'];
|
||||
|
||||
const filteredPayload = omitBy(
|
||||
payload,
|
||||
(value, key) => fieldsToRemoveIfEmpty.includes(key) && isEmpty(value)
|
||||
);
|
||||
|
||||
const response = await $.http.put(`/api/v2/tickets/${ticketId}`, {
|
||||
ticket: filteredPayload,
|
||||
});
|
||||
|
||||
$.setActionItem({ raw: response.data });
|
||||
},
|
||||
});
|
@@ -1,13 +1,20 @@
|
||||
import listUsers from './list-users';
|
||||
import listBrands from './list-brands';
|
||||
import listFirstPageOfTickets from './list-first-page-of-tickets';
|
||||
import listGroups from './list-groups';
|
||||
import listOrganizations from './list-organizations';
|
||||
import listSharingAgreements from './list-sharing-agreements';
|
||||
import listTicketForms from './list-ticket-forms';
|
||||
import listViews from './list-views';
|
||||
|
||||
export default [
|
||||
listUsers,
|
||||
listBrands,
|
||||
listFirstPageOfTickets,
|
||||
listGroups,
|
||||
listOrganizations,
|
||||
listSharingAgreements,
|
||||
listFirstPageOfTickets,
|
||||
listTicketForms,
|
||||
listViews,
|
||||
];
|
||||
|
@@ -0,0 +1,33 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List first page of tickets',
|
||||
key: 'listFirstPageOfTickets',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const tickets: {
|
||||
data: IJSONObject[];
|
||||
} = {
|
||||
data: [],
|
||||
};
|
||||
|
||||
const params = {
|
||||
'page[size]': 100,
|
||||
sort: '-id',
|
||||
};
|
||||
|
||||
const response = await $.http.get('/api/v2/tickets', { params });
|
||||
const allTickets = response.data.tickets;
|
||||
|
||||
if (allTickets?.length) {
|
||||
for (const ticket of allTickets) {
|
||||
tickets.data.push({
|
||||
value: ticket.id,
|
||||
name: ticket.subject,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return tickets;
|
||||
},
|
||||
};
|
@@ -21,7 +21,7 @@ export default {
|
||||
const response = await $.http.get('/api/v2/groups', { params });
|
||||
const allGroups = response?.data?.groups;
|
||||
hasMore = response?.data?.meta?.has_more;
|
||||
params['page[after]'] = response.data.links?.after_cursor;
|
||||
params['page[after]'] = response.data.meta?.after_cursor;
|
||||
|
||||
if (allGroups?.length) {
|
||||
for (const group of allGroups) {
|
||||
|
@@ -0,0 +1,38 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List organizations',
|
||||
key: 'listOrganizations',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const organizations: {
|
||||
data: IJSONObject[];
|
||||
} = {
|
||||
data: [],
|
||||
};
|
||||
let hasMore;
|
||||
|
||||
const params = {
|
||||
'page[size]': 100,
|
||||
'page[after]': undefined as unknown as string,
|
||||
};
|
||||
|
||||
do {
|
||||
const response = await $.http.get('/api/v2/organizations', { params });
|
||||
const allOrganizations = response?.data?.organizations;
|
||||
hasMore = response?.data?.meta?.has_more;
|
||||
params['page[after]'] = response.data.meta?.after_cursor;
|
||||
|
||||
if (allOrganizations?.length) {
|
||||
for (const organization of allOrganizations) {
|
||||
organizations.data.push({
|
||||
value: organization.id,
|
||||
name: organization.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (hasMore);
|
||||
|
||||
return organizations;
|
||||
},
|
||||
};
|
@@ -25,7 +25,7 @@ export default {
|
||||
const response = await $.http.get('/api/v2/users', { params });
|
||||
const allUsers = response?.data?.users;
|
||||
hasMore = response?.data?.meta?.has_more;
|
||||
params['page[after]'] = response.data.links?.after_cursor;
|
||||
params['page[after]'] = response.data.meta?.after_cursor;
|
||||
|
||||
if (allUsers?.length) {
|
||||
for (const user of allUsers) {
|
||||
|
@@ -0,0 +1,38 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List views',
|
||||
key: 'listViews',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const views: {
|
||||
data: IJSONObject[];
|
||||
} = {
|
||||
data: [],
|
||||
};
|
||||
let hasMore;
|
||||
|
||||
const params = {
|
||||
'page[size]': 100,
|
||||
'page[after]': undefined as unknown as string,
|
||||
};
|
||||
|
||||
do {
|
||||
const response = await $.http.get('/api/v2/views', { params });
|
||||
const allViews = response?.data?.views;
|
||||
hasMore = response?.data?.meta?.has_more;
|
||||
params['page[after]'] = response.data.meta?.after_cursor;
|
||||
|
||||
if (allViews?.length) {
|
||||
for (const view of allViews) {
|
||||
views.data.push({
|
||||
value: view.id,
|
||||
name: view.title,
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (hasMore);
|
||||
|
||||
return views;
|
||||
},
|
||||
};
|
@@ -1,6 +1,7 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import addAuthHeader from './common/add-auth-headers';
|
||||
import auth from './auth';
|
||||
import triggers from './triggers';
|
||||
import actions from './actions';
|
||||
import dynamicData from './dynamic-data';
|
||||
|
||||
@@ -15,6 +16,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
actions,
|
||||
dynamicData,
|
||||
});
|
||||
|
4
packages/backend/src/apps/zendesk/triggers/index.ts
Normal file
4
packages/backend/src/apps/zendesk/triggers/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import newTickets from './new-tickets';
|
||||
import newUsers from './new-users';
|
||||
|
||||
export default [newTickets, newUsers];
|
@@ -0,0 +1,59 @@
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'New tickets',
|
||||
key: 'newTickets',
|
||||
pollInterval: 15,
|
||||
description: 'Triggers when a new ticket is created in a specific view.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'View',
|
||||
key: 'viewId',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listViews',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const viewId = $.step.parameters.viewId;
|
||||
|
||||
const params = {
|
||||
'page[size]': 100,
|
||||
'page[after]': undefined as unknown as string,
|
||||
sort_by: 'nice_id',
|
||||
sort_order: 'desc',
|
||||
};
|
||||
let hasMore;
|
||||
|
||||
do {
|
||||
const response = await $.http.get(`/api/v2/views/${viewId}/tickets`, {
|
||||
params,
|
||||
});
|
||||
const allTickets = response?.data?.tickets;
|
||||
hasMore = response?.data?.meta?.has_more;
|
||||
params['page[after]'] = response.data.meta?.after_cursor;
|
||||
|
||||
if (allTickets?.length) {
|
||||
for (const ticket of allTickets) {
|
||||
$.pushTriggerItem({
|
||||
raw: ticket,
|
||||
meta: {
|
||||
internalId: ticket.id.toString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (hasMore);
|
||||
},
|
||||
});
|
@@ -0,0 +1,83 @@
|
||||
import Crypto from 'crypto';
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'New users',
|
||||
key: 'newUsers',
|
||||
type: 'webhook',
|
||||
description: 'Triggers upon the creation of a new user.',
|
||||
|
||||
async run($) {
|
||||
const dataItem = {
|
||||
raw: $.request.body,
|
||||
meta: {
|
||||
internalId: Crypto.randomUUID(),
|
||||
},
|
||||
};
|
||||
|
||||
$.pushTriggerItem(dataItem);
|
||||
},
|
||||
|
||||
async testRun($) {
|
||||
const params = {
|
||||
query: 'type:user',
|
||||
sort_by: 'created_at',
|
||||
sort_order: 'desc',
|
||||
};
|
||||
|
||||
const response = await $.http.get('/api/v2/search', { params });
|
||||
|
||||
const lastUser = response.data.results[0];
|
||||
|
||||
const computedWebhookEvent = {
|
||||
id: Crypto.randomUUID(),
|
||||
time: lastUser.created_at,
|
||||
type: 'zen:event-type:user.created',
|
||||
event: {},
|
||||
detail: {
|
||||
id: lastUser.id,
|
||||
role: lastUser.role,
|
||||
email: lastUser.email,
|
||||
created_at: lastUser.created_at,
|
||||
updated_at: lastUser.updated_at,
|
||||
external_id: lastUser.external_id,
|
||||
organization_id: lastUser.organization_id,
|
||||
default_group_id: lastUser.default_group_id,
|
||||
},
|
||||
subject: `zen:user:${lastUser.id}`,
|
||||
account_id: '',
|
||||
zendesk_event_version: '2022-11-06',
|
||||
};
|
||||
|
||||
const dataItem = {
|
||||
raw: computedWebhookEvent,
|
||||
meta: {
|
||||
internalId: computedWebhookEvent.id,
|
||||
},
|
||||
};
|
||||
|
||||
$.pushTriggerItem(dataItem);
|
||||
},
|
||||
|
||||
async registerHook($) {
|
||||
const payload = {
|
||||
webhook: {
|
||||
name: `Flow ID: ${$.flow.id}`,
|
||||
status: 'active',
|
||||
subscriptions: ['zen:event-type:user.created'],
|
||||
endpoint: $.webhookUrl,
|
||||
http_method: 'POST',
|
||||
request_format: 'json',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await $.http.post('/api/v2/webhooks', payload);
|
||||
const id = response.data.webhook.id;
|
||||
|
||||
await $.flow.setRemoteWebhookId(id);
|
||||
},
|
||||
|
||||
async unregisterHook($) {
|
||||
await $.http.delete(`/api/v2/webhooks/${$.flow.remoteWebhookId}`);
|
||||
},
|
||||
});
|
@@ -49,6 +49,7 @@ type AppConfig = {
|
||||
smtpPassword: string;
|
||||
fromEmail: string;
|
||||
isCloud: boolean;
|
||||
isMation: boolean;
|
||||
isSelfHosted: boolean;
|
||||
paddleVendorId: number;
|
||||
paddleVendorAuthCode: string;
|
||||
@@ -127,6 +128,7 @@ const appConfig: AppConfig = {
|
||||
fromEmail: process.env.FROM_EMAIL,
|
||||
isCloud: process.env.AUTOMATISCH_CLOUD === 'true',
|
||||
isSelfHosted: process.env.AUTOMATISCH_CLOUD !== 'true',
|
||||
isMation: process.env.MATION === 'true',
|
||||
paddleVendorId: Number(process.env.PADDLE_VENDOR_ID),
|
||||
paddleVendorAuthCode: process.env.PADDLE_VENDOR_AUTH_CODE,
|
||||
paddlePublicKey: process.env.PADDLE_PUBLIC_KEY,
|
||||
|
@@ -9,6 +9,7 @@ describe('graphQL getAutomatischInfo query', () => {
|
||||
query {
|
||||
getAutomatischInfo {
|
||||
isCloud
|
||||
isMation
|
||||
license {
|
||||
id
|
||||
name
|
||||
@@ -24,6 +25,7 @@ describe('graphQL getAutomatischInfo query', () => {
|
||||
jest.spyOn(license, 'getLicense').mockResolvedValue(false);
|
||||
|
||||
jest.replaceProperty(appConfig, 'isCloud', false);
|
||||
jest.replaceProperty(appConfig, 'isMation', false);
|
||||
});
|
||||
|
||||
it('should return empty license data', async () => {
|
||||
@@ -36,6 +38,7 @@ describe('graphQL getAutomatischInfo query', () => {
|
||||
data: {
|
||||
getAutomatischInfo: {
|
||||
isCloud: false,
|
||||
isMation: false,
|
||||
license: {
|
||||
id: null,
|
||||
name: null,
|
||||
@@ -77,6 +80,7 @@ describe('graphQL getAutomatischInfo query', () => {
|
||||
data: {
|
||||
getAutomatischInfo: {
|
||||
isCloud: true,
|
||||
isMation: false,
|
||||
license: {
|
||||
expireAt: '2025-08-09T10:56:54.144Z',
|
||||
id: '123123',
|
||||
@@ -105,6 +109,69 @@ describe('graphQL getAutomatischInfo query', () => {
|
||||
const expectedResponsePayload = {
|
||||
data: {
|
||||
getAutomatischInfo: {
|
||||
isCloud: false,
|
||||
isMation: false,
|
||||
license: {
|
||||
expireAt: '2025-08-09T10:56:54.144Z',
|
||||
id: '123123',
|
||||
name: 'Test License',
|
||||
verified: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(response.body).toEqual(expectedResponsePayload);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and with mation flag enabled', () => {
|
||||
beforeEach(async () => {
|
||||
jest.replaceProperty(appConfig, 'isCloud', false);
|
||||
jest.replaceProperty(appConfig, 'isMation', true);
|
||||
});
|
||||
|
||||
it('should return all license data', async () => {
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.send({ query })
|
||||
.expect(200);
|
||||
|
||||
const expectedResponsePayload = {
|
||||
data: {
|
||||
getAutomatischInfo: {
|
||||
isCloud: false,
|
||||
isMation: true,
|
||||
license: {
|
||||
expireAt: '2025-08-09T10:56:54.144Z',
|
||||
id: '123123',
|
||||
name: 'Test License',
|
||||
verified: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(response.body).toEqual(expectedResponsePayload);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and with mation flag disabled', () => {
|
||||
beforeEach(async () => {
|
||||
jest.replaceProperty(appConfig, 'isCloud', false);
|
||||
jest.replaceProperty(appConfig, 'isMation', false);
|
||||
});
|
||||
|
||||
it('should return all license data', async () => {
|
||||
const response = await request(app)
|
||||
.post('/graphql')
|
||||
.send({ query })
|
||||
.expect(200);
|
||||
|
||||
const expectedResponsePayload = {
|
||||
data: {
|
||||
getAutomatischInfo: {
|
||||
isMation: false,
|
||||
isCloud: false,
|
||||
license: {
|
||||
expireAt: '2025-08-09T10:56:54.144Z',
|
||||
|
@@ -13,6 +13,7 @@ const getAutomatischInfo = async () => {
|
||||
|
||||
return {
|
||||
isCloud: appConfig.isCloud,
|
||||
isMation: appConfig.isMation,
|
||||
license: computedLicense,
|
||||
};
|
||||
};
|
||||
|
@@ -646,6 +646,7 @@ type AppHealth {
|
||||
|
||||
type GetAutomatischInfo {
|
||||
isCloud: Boolean
|
||||
isMation: Boolean
|
||||
license: License
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { IHttpClientParams } from '@automatisch/types';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
import { InternalAxiosRequestConfig } from 'axios';
|
||||
import { URL } from 'node:url';
|
||||
export { AxiosInstance as IHttpClient } from 'axios';
|
||||
|
||||
@@ -7,8 +7,8 @@ import HttpError from '../../errors/http';
|
||||
import axios from '../axios-with-proxy';
|
||||
|
||||
const removeBaseUrlForAbsoluteUrls = (
|
||||
requestConfig: AxiosRequestConfig
|
||||
): AxiosRequestConfig => {
|
||||
requestConfig: InternalAxiosRequestConfig
|
||||
): InternalAxiosRequestConfig => {
|
||||
try {
|
||||
const url = new URL(requestConfig.url);
|
||||
requestConfig.baseURL = url.origin;
|
||||
@@ -30,12 +30,21 @@ export default function createHttpClient({
|
||||
});
|
||||
|
||||
instance.interceptors.request.use(
|
||||
(requestConfig: AxiosRequestConfig): AxiosRequestConfig => {
|
||||
(requestConfig: InternalAxiosRequestConfig): InternalAxiosRequestConfig => {
|
||||
const newRequestConfig = removeBaseUrlForAbsoluteUrls(requestConfig);
|
||||
|
||||
return beforeRequest.reduce((newConfig, beforeRequestFunc) => {
|
||||
const result = beforeRequest.reduce((newConfig, beforeRequestFunc) => {
|
||||
return beforeRequestFunc($, newConfig);
|
||||
}, newRequestConfig);
|
||||
|
||||
/**
|
||||
* axios seems to want InternalAxiosRequestConfig returned not AxioRequestConfig
|
||||
* anymore even though requests do require AxiosRequestConfig.
|
||||
*
|
||||
* Since both interfaces are very similar (InternalAxiosRequestConfig
|
||||
* extends AxiosRequestConfig), we can utilize an assertion below
|
||||
**/
|
||||
return result as InternalAxiosRequestConfig;
|
||||
}
|
||||
);
|
||||
|
||||
|
@@ -289,11 +289,24 @@ export default defineConfig({
|
||||
{ text: 'Connection', link: '/apps/pushover/connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Reddit',
|
||||
collapsible: true,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Triggers', link: '/apps/reddit/triggers' },
|
||||
{ text: 'Actions', link: '/apps/reddit/actions' },
|
||||
{ text: 'Connection', link: '/apps/reddit/connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Remove.bg',
|
||||
collapsible: true,
|
||||
collapsed: true,
|
||||
items: [{ text: 'Connection', link: '/apps/removebg/connection' }],
|
||||
items: [
|
||||
{ text: 'Actions', link: '/apps/removebg/actions' },
|
||||
{ text: 'Connection', link: '/apps/removebg/connection' }
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'RSS',
|
||||
@@ -453,6 +466,15 @@ export default defineConfig({
|
||||
{ text: 'Connection', link: '/apps/wordpress/connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Xero',
|
||||
collapsible: true,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Triggers', link: '/apps/xero/triggers' },
|
||||
{ text: 'Connection', link: '/apps/xero/connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Youtube',
|
||||
collapsible: true,
|
||||
|
@@ -3,6 +3,8 @@ favicon: /favicons/discord.svg
|
||||
items:
|
||||
- name: Send a message to channel
|
||||
desc: Sends a message to a specific channel you specify.
|
||||
- name: Create a scheduled event
|
||||
desc: Creates a scheduled event.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
|
@@ -3,6 +3,8 @@ favicon: /favicons/notion.svg
|
||||
items:
|
||||
- name: New database items
|
||||
desc: Triggers when a new database item is created.
|
||||
- name: Updated database items
|
||||
desc: Triggers when there is an update to an item in a chosen database.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
|
12
packages/docs/pages/apps/reddit/actions.md
Normal file
12
packages/docs/pages/apps/reddit/actions.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
favicon: /favicons/reddit.svg
|
||||
items:
|
||||
- name: Create link post
|
||||
desc: Create a new link post within a subreddit.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import CustomListing from '../../components/CustomListing.vue'
|
||||
</script>
|
||||
|
||||
<CustomListing />
|
15
packages/docs/pages/apps/reddit/connection.md
Normal file
15
packages/docs/pages/apps/reddit/connection.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Reddit
|
||||
|
||||
:::info
|
||||
This page explains the steps you need to follow to set up the Reddit
|
||||
connection in Automatisch. If any of the steps are outdated, please let us know!
|
||||
:::
|
||||
|
||||
1. Go to [Reddit apps page](https://www.reddit.com/prefs/apps).
|
||||
2. Click on the **"are you a developer? create an app..."** button in order to create an app.
|
||||
3. Fill the **Name** field and choose **web app**.
|
||||
4. Copy **OAuth Redirect URL** from Automatisch to **redirect uri** field.
|
||||
5. Click on the **create app** button.
|
||||
6. Copy the client id below **web app** text to the `Client ID` field on Automatisch.
|
||||
7. Copy the **secret** value to the `Client Secret` field on Automatisch.
|
||||
8. Start using Reddit integration with Automatisch!
|
12
packages/docs/pages/apps/reddit/triggers.md
Normal file
12
packages/docs/pages/apps/reddit/triggers.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
favicon: /favicons/reddit.svg
|
||||
items:
|
||||
- name: New posts matching search
|
||||
desc: Triggers when a search string matches a new post.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import CustomListing from '../../components/CustomListing.vue'
|
||||
</script>
|
||||
|
||||
<CustomListing />
|
12
packages/docs/pages/apps/removebg/actions.md
Normal file
12
packages/docs/pages/apps/removebg/actions.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
favicon: /favicons/removebg.svg
|
||||
items:
|
||||
- name: Remove Image Background
|
||||
desc: Remove backgrounds 100% automatically in 5 seconds with one click.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import CustomListing from '../../components/CustomListing.vue'
|
||||
</script>
|
||||
|
||||
<CustomListing />
|
@@ -5,6 +5,8 @@ items:
|
||||
desc: Creates an attachment of a specified object by given parent ID.
|
||||
- name: Find record
|
||||
desc: Finds a record of a specified object by a field and value.
|
||||
- name: Execute query
|
||||
desc: Executes a SOQL query in Salesforce.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
|
@@ -12,7 +12,9 @@ connection in Automatisch. If any of the steps are outdated, please let us know!
|
||||
1. Enter necessary information in the form.
|
||||
1. Check **Enable OAuth Settings** checkbox.
|
||||
1. Copy **OAuth Redirect URL** from Automatisch and paste it to the **Callback URL** field.
|
||||
1. Add any scopes you plan to use in the **Selected OAuth Scopes** section.
|
||||
1. Add any scopes you plan to use in the **Selected OAuth Scopes** section. We suggest `full` and `refresh_token, offline_access` scopes.
|
||||
1. Uncheck "Require Proof Key for Code Exchange (PKCE) Extension for Supported Authorization Flows" checkbox.
|
||||
1. Check "Enable Authorization Code and Credentials Flow" checkbox
|
||||
1. Click on the **Save** button at the bottom of the page.
|
||||
1. Acknowledge the information and click on the **Continue** button.
|
||||
1. In the **API (Enable OAuth Settings)** section, click the **Manager Consumer Details** button.
|
||||
|
@@ -3,6 +3,8 @@ favicon: /favicons/xero.svg
|
||||
items:
|
||||
- name: New bank transactions
|
||||
desc: Triggers when a new bank transaction occurs.
|
||||
- name: New payments
|
||||
desc: Triggers when a new payment is received.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
|
@@ -3,6 +3,16 @@ favicon: /favicons/zendesk.svg
|
||||
items:
|
||||
- name: Create ticket
|
||||
desc: Creates a new ticket.
|
||||
- name: Create user
|
||||
desc: Creates a new user.
|
||||
- name: Delete ticket
|
||||
desc: Deletes an existing ticket.
|
||||
- name: Delete user
|
||||
desc: Deletes an existing user.
|
||||
- name: Find ticket
|
||||
desc: Finds an existing ticket.
|
||||
- name: Update ticket
|
||||
desc: Modify the status of an existing ticket or append comments.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
|
14
packages/docs/pages/apps/zendesk/triggers.md
Normal file
14
packages/docs/pages/apps/zendesk/triggers.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
favicon: /favicons/zendesk.svg
|
||||
items:
|
||||
- name: New tickets
|
||||
desc: Triggers when a new ticket is created in a specific view.
|
||||
- name: New users
|
||||
desc: Triggers upon the creation of a new user.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import CustomListing from '../../components/CustomListing.vue'
|
||||
</script>
|
||||
|
||||
<CustomListing />
|
@@ -30,7 +30,8 @@ The following integrations are currently supported by Automatisch.
|
||||
- [Placetel](/apps/placetel/triggers)
|
||||
- [PostgreSQL](/apps/postgresql/actions)
|
||||
- [Pushover](/apps/pushover/actions)
|
||||
- [Remove.bg](/apps/removebg/connection)
|
||||
- [Reddit](/apps/reddit/triggers)
|
||||
- [Remove.bg](/apps/removebg/actions)
|
||||
- [RSS](/apps/rss/triggers)
|
||||
- [Salesforce](/apps/salesforce/triggers)
|
||||
- [Scheduler](/apps/scheduler/triggers)
|
||||
@@ -48,5 +49,6 @@ The following integrations are currently supported by Automatisch.
|
||||
- [Typeform](/apps/typeform/triggers)
|
||||
- [Webhooks](/apps/webhooks/triggers)
|
||||
- [WordPress](/apps/wordpress/triggers)
|
||||
- [Xero](/apps/xero/triggers)
|
||||
- [Youtube](/apps/youtube/triggers)
|
||||
- [Zendesk](/apps/zendesk/actions)
|
||||
|
1
packages/docs/pages/public/favicons/reddit.svg
Normal file
1
packages/docs/pages/public/favicons/reddit.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" class="_1O4jTk-dZ-VIxsCuYB6OR8 " width="40" height="48" ><g><circle fill="#FF4500" cx="10" cy="10" r="10"></circle><path fill="#FFFFFF" d="M16.67,10A1.46,1.46,0,0,0,14.2,9a7.12,7.12,0,0,0-3.85-1.23L11,4.65,13.14,5.1a1,1,0,1,0,.13-0.61L10.82,4a0.31,0.31,0,0,0-.37.24L9.71,7.71a7.14,7.14,0,0,0-3.9,1.23A1.46,1.46,0,1,0,4.2,11.33a2.87,2.87,0,0,0,0,.44c0,2.24,2.61,4.06,5.83,4.06s5.83-1.82,5.83-4.06a2.87,2.87,0,0,0,0-.44A1.46,1.46,0,0,0,16.67,10Zm-10,1a1,1,0,1,1,1,1A1,1,0,0,1,6.67,11Zm5.81,2.75a3.84,3.84,0,0,1-2.47.77,3.84,3.84,0,0,1-2.47-.77,0.27,0.27,0,0,1,.38-0.38A3.27,3.27,0,0,0,10,14a3.28,3.28,0,0,0,2.09-.61A0.27,0.27,0,1,1,12.48,13.79Zm-0.18-1.71a1,1,0,1,1,1-1A1,1,0,0,1,12.29,12.08Z"></path></g></svg>
|
After Width: | Height: | Size: 813 B |
@@ -2,12 +2,12 @@ const { AuthenticatedPage } = require('../authenticated-page');
|
||||
const { RoleConditionsModal } = require('./role-conditions-modal');
|
||||
|
||||
export class AdminCreateRolePage extends AuthenticatedPage {
|
||||
screenshotPath = '/admin/create-role'
|
||||
screenshotPath = '/admin/create-role';
|
||||
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
constructor (page) {
|
||||
constructor(page) {
|
||||
super(page);
|
||||
this.nameInput = page.getByTestId('name-input');
|
||||
this.descriptionInput = page.getByTestId('description-input');
|
||||
@@ -15,27 +15,28 @@ export class AdminCreateRolePage extends AuthenticatedPage {
|
||||
this.connectionRow = page.getByTestId('Connection-permission-row');
|
||||
this.executionRow = page.getByTestId('Execution-permission-row');
|
||||
this.flowRow = page.getByTestId('Flow-permission-row');
|
||||
this.pageTitle = page.getByTestId('create-role-title');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {('Connection'|'Execution'|'Flow')} subject
|
||||
* @param {('Connection'|'Execution'|'Flow')} subject
|
||||
*/
|
||||
getRoleConditionsModal (subject) {
|
||||
getRoleConditionsModal(subject) {
|
||||
return new RoleConditionsModal(this.page, subject);
|
||||
}
|
||||
|
||||
async getPermissionConfigs () {
|
||||
async getPermissionConfigs() {
|
||||
const subjects = ['Connection', 'Flow', 'Execution'];
|
||||
const permissionConfigs = [];
|
||||
for (let subject of subjects) {
|
||||
const row = this.getSubjectRow(subject);
|
||||
const actionInputs = await this.getRowInputs(row);
|
||||
Object.keys(actionInputs).forEach(action => {
|
||||
Object.keys(actionInputs).forEach((action) => {
|
||||
permissionConfigs.push({
|
||||
action,
|
||||
locator: actionInputs[action],
|
||||
subject,
|
||||
row
|
||||
row,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -43,50 +44,50 @@ export class AdminCreateRolePage extends AuthenticatedPage {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {(
|
||||
* 'Connection' | 'Flow' | 'Execution'
|
||||
* )} subject
|
||||
* )} subject
|
||||
*/
|
||||
getSubjectRow (subject) {
|
||||
const k = `${subject.toLowerCase()}Row`
|
||||
getSubjectRow(subject) {
|
||||
const k = `${subject.toLowerCase()}Row`;
|
||||
if (this[k]) {
|
||||
return this[k]
|
||||
return this[k];
|
||||
} else {
|
||||
throw 'Unknown row'
|
||||
throw 'Unknown row';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@playwright/test').Locator} row
|
||||
* @param {import('@playwright/test').Locator} row
|
||||
*/
|
||||
async getRowInputs (row) {
|
||||
async getRowInputs(row) {
|
||||
const inputs = {
|
||||
// settingsButton: row.getByTestId('permission-settings-button')
|
||||
}
|
||||
};
|
||||
for (let input of ['create', 'read', 'update', 'delete', 'publish']) {
|
||||
const testId = `${input}-checkbox`
|
||||
if (await row.getByTestId(testId).count() > 0) {
|
||||
const testId = `${input}-checkbox`;
|
||||
if ((await row.getByTestId(testId).count()) > 0) {
|
||||
inputs[input] = row.getByTestId(testId).locator('input');
|
||||
}
|
||||
}
|
||||
return inputs
|
||||
return inputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@playwright/test').Locator} row
|
||||
* @param {import('@playwright/test').Locator} row
|
||||
*/
|
||||
async clickPermissionSettings (row) {
|
||||
async clickPermissionSettings(row) {
|
||||
await row.getByTestId('permission-settings-button').click();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} subject
|
||||
*
|
||||
* @param {string} subject
|
||||
* @param {'create'|'read'|'update'|'delete'|'publish'} action
|
||||
* @param {boolean} val
|
||||
* @param {boolean} val
|
||||
*/
|
||||
async updateAction (subject, action, val) {
|
||||
async updateAction(subject, action, val) {
|
||||
const row = await this.getSubjectRow(subject);
|
||||
const inputs = await this.getRowInputs(row);
|
||||
if (inputs[action]) {
|
||||
@@ -100,7 +101,7 @@ export class AdminCreateRolePage extends AuthenticatedPage {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error(`${subject} does not have action ${action}`)
|
||||
throw new Error(`${subject} does not have action ${action}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,24 +7,25 @@ export class AdminCreateUserPage extends AuthenticatedPage {
|
||||
/**
|
||||
* @param {import('@playwright/test').Page} page
|
||||
*/
|
||||
constructor (page) {
|
||||
constructor(page) {
|
||||
super(page);
|
||||
this.fullNameInput = page.getByTestId('full-name-input');
|
||||
this.emailInput = page.getByTestId('email-input');
|
||||
this.passwordInput = page.getByTestId('password-input');
|
||||
this.roleInput = page.getByTestId('role.id-autocomplete');
|
||||
this.createButton = page.getByTestId('create-button');
|
||||
this.pageTitle = page.getByTestId('create-user-title');
|
||||
}
|
||||
|
||||
seed (seed) {
|
||||
seed(seed) {
|
||||
faker.seed(seed || 0);
|
||||
}
|
||||
|
||||
generateUser () {
|
||||
generateUser() {
|
||||
return {
|
||||
fullName: faker.person.fullName(),
|
||||
email: faker.internet.email().toLowerCase(),
|
||||
password: faker.internet.password()
|
||||
}
|
||||
password: faker.internet.password(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,5 +5,6 @@ export class AdminEditRolePage extends AdminCreateRolePage {
|
||||
super(page);
|
||||
delete this.createButton;
|
||||
this.updateButton = page.getByTestId('update-button');
|
||||
this.pageTitle = page.getByTestId('edit-role-title');
|
||||
}
|
||||
}
|
@@ -15,6 +15,7 @@ export class AdminEditUserPage extends AuthenticatedPage {
|
||||
this.emailInput = page.getByTestId('email-input');
|
||||
this.roleInput = page.getByTestId('role.id-autocomplete');
|
||||
this.updateButton = page.getByTestId('update-button');
|
||||
this.pageTitle = page.getByTestId('edit-user-title');
|
||||
}
|
||||
|
||||
generateUser () {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user