Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
321019d36a | ||
![]() |
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 |
5
.github/workflows/playwright.yml
vendored
5
.github/workflows/playwright.yml
vendored
@@ -4,6 +4,11 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'packages/backend/**'
|
||||||
|
- 'packages/e2e-tests/**'
|
||||||
|
- 'packages/web/**'
|
||||||
|
- '!packages/backend/src/apps/**'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
@@ -1,34 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="428" height="512" viewBox="0 0 428 512">
|
|
||||||
<defs>
|
|
||||||
<style>
|
|
||||||
.cls-1 {
|
|
||||||
fill: #e25444;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cls-1, .cls-2, .cls-3 {
|
|
||||||
fill-rule: evenodd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cls-2 {
|
|
||||||
fill: #7b1d13;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cls-3 {
|
|
||||||
fill: #58150d;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
<path class="cls-1" d="M378,99L295,257l83,158,34-19V118Z"/>
|
|
||||||
<path class="cls-2" d="M378,99L212,118,127.5,257,212,396l166,19V99Z"/>
|
|
||||||
<path class="cls-3" d="M43,99L16,111V403l27,12L212,257Z"/>
|
|
||||||
<path class="cls-1" d="M42.637,98.667l169.587,47.111V372.444L42.637,415.111V98.667Z"/>
|
|
||||||
<path class="cls-3" d="M212.313,170.667l-72.008-11.556,72.008-81.778,71.83,81.778Z"/>
|
|
||||||
<path class="cls-3" d="M284.143,159.111l-71.919,11.733-71.919-11.733V77.333"/>
|
|
||||||
<path class="cls-3" d="M212.313,342.222l-72.008,13.334,72.008,70.222,71.83-70.222Z"/>
|
|
||||||
<path class="cls-2" d="M212,16L140,54V159l72.224-20.333Z"/>
|
|
||||||
<path class="cls-2" d="M212.224,196.444l-71.919,7.823V309.105l71.919,8.228V196.444Z"/>
|
|
||||||
<path class="cls-2" d="M212.224,373.333L140.305,355.3V458.363L212.224,496V373.333Z"/>
|
|
||||||
<path class="cls-1" d="M284.143,355.3l-71.919,18.038V496l71.919-37.637V355.3Z"/>
|
|
||||||
<path class="cls-1" d="M212.224,196.444l71.919,7.823V309.105l-71.919,8.228V196.444Z"/>
|
|
||||||
<path class="cls-1" d="M212,16l72,38V159l-72-20V16Z"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1,56 +0,0 @@
|
|||||||
import verifyCredentials from './verify-credentials';
|
|
||||||
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/amazon-s3/connections/add',
|
|
||||||
placeholder: null,
|
|
||||||
description:
|
|
||||||
'When asked to input a redirect URL in AWS, enter the URL above.',
|
|
||||||
clickToCopy: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'accessKeyId',
|
|
||||||
label: 'Access Key ID',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: null,
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'secretAccessKey',
|
|
||||||
label: 'Secret Access Key',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: null,
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
verifyCredentials,
|
|
||||||
isStillVerified,
|
|
||||||
};
|
|
@@ -1,9 +0,0 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
|
||||||
import getCurrentUser from '../common/get-current-user';
|
|
||||||
|
|
||||||
const isStillVerified = async ($: IGlobalVariable) => {
|
|
||||||
const currentUser = await getCurrentUser($);
|
|
||||||
return !!currentUser.resourceName;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default isStillVerified;
|
|
@@ -1,9 +0,0 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
|
||||||
|
|
||||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
|
||||||
const { data } = await $.http.get('/');
|
|
||||||
|
|
||||||
console.log('data:', data);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default verifyCredentials;
|
|
@@ -1,153 +0,0 @@
|
|||||||
import { IJSONObject, TBeforeRequest } from '@automatisch/types';
|
|
||||||
import crypto from 'crypto';
|
|
||||||
import { getISODate, getYYYYMMDD } from './get-current-date';
|
|
||||||
|
|
||||||
function hmac(key: string | Buffer, data: string) {
|
|
||||||
return crypto.createHmac('sha256', key).update(data).digest('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
function hmacWoHex(key: Buffer | string, data: string) {
|
|
||||||
return crypto.createHmac('sha256', key).update(data).digest();
|
|
||||||
}
|
|
||||||
|
|
||||||
function hash(data: string) {
|
|
||||||
return crypto.createHash('sha256').update(data).digest('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
function prepareCanonicalRequest(
|
|
||||||
method: string,
|
|
||||||
path: string,
|
|
||||||
queryParams: IJSONObject | string,
|
|
||||||
headers: IJSONObject,
|
|
||||||
payload: string
|
|
||||||
) {
|
|
||||||
const canonicalRequest = [method, encodeURIComponent(path)];
|
|
||||||
|
|
||||||
// Step 3: Canonical Query String
|
|
||||||
if (typeof queryParams === 'string') {
|
|
||||||
canonicalRequest.push('');
|
|
||||||
} else {
|
|
||||||
const sortedQueryParams = Object.keys(queryParams)
|
|
||||||
.map(
|
|
||||||
(key) =>
|
|
||||||
`${encodeURIComponent(key)}=${encodeURIComponent(
|
|
||||||
queryParams[key] as string
|
|
||||||
)}`
|
|
||||||
)
|
|
||||||
.sort();
|
|
||||||
canonicalRequest.push(sortedQueryParams.join('&'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4: Canonical Headers
|
|
||||||
const sortedHeaders = Object.keys(headers)
|
|
||||||
.sort()
|
|
||||||
.map((key) => `${key.toLowerCase()}:${(headers[key] as string).trim()}`);
|
|
||||||
|
|
||||||
canonicalRequest.push(sortedHeaders.join('\n'));
|
|
||||||
|
|
||||||
// Step 5: Signed Headers
|
|
||||||
const signedHeaders = Object.keys(headers)
|
|
||||||
.sort()
|
|
||||||
.map((key) => key.toLowerCase())
|
|
||||||
.join(';');
|
|
||||||
canonicalRequest.push(signedHeaders);
|
|
||||||
|
|
||||||
const hashedPayload = hash(payload);
|
|
||||||
canonicalRequest.push(hashedPayload);
|
|
||||||
|
|
||||||
return canonicalRequest.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
function prepareStringToSign(
|
|
||||||
datetime: string,
|
|
||||||
credentialScope: string,
|
|
||||||
hashedCanonicalRequest: string
|
|
||||||
) {
|
|
||||||
const stringToSign = [
|
|
||||||
'AWS4-HMAC-SHA256',
|
|
||||||
datetime,
|
|
||||||
credentialScope,
|
|
||||||
hashedCanonicalRequest,
|
|
||||||
];
|
|
||||||
|
|
||||||
return stringToSign.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateSigningKey(
|
|
||||||
secretKey: string,
|
|
||||||
date: string,
|
|
||||||
region: string,
|
|
||||||
service: string
|
|
||||||
) {
|
|
||||||
const dateKey = hmacWoHex('AWS4' + secretKey, date);
|
|
||||||
const dateRegionKey = hmacWoHex(dateKey, region);
|
|
||||||
const dateRegionServiceKey = hmacWoHex(dateRegionKey, service);
|
|
||||||
const signingKey = hmacWoHex(dateRegionServiceKey, 'aws4_request');
|
|
||||||
return signingKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createAuthorizationHeader(
|
|
||||||
accessKey: string,
|
|
||||||
credentialScope: string,
|
|
||||||
signedHeaders: string,
|
|
||||||
signature: string
|
|
||||||
) {
|
|
||||||
return `AWS4-HMAC-SHA256 Credential=${accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
|
||||||
const accessKeyId = $.auth.data.accessKeyId as string;
|
|
||||||
const secretAccessKey = $.auth.data.secretAccessKey as string;
|
|
||||||
const date = getYYYYMMDD();
|
|
||||||
const formattedDate = getISODate();
|
|
||||||
const region = 'us-east-1';
|
|
||||||
const method = 'GET';
|
|
||||||
const path = '/';
|
|
||||||
const queryParams = '';
|
|
||||||
const payload = '';
|
|
||||||
const headers = {
|
|
||||||
Host: 's3.amazonaws.com',
|
|
||||||
'X-Amz-Content-Sha256': hash(payload),
|
|
||||||
'X-Amz-Date': formattedDate,
|
|
||||||
};
|
|
||||||
const headerKeys = Object.keys(headers)
|
|
||||||
.sort()
|
|
||||||
.map((header) => header.toLowerCase())
|
|
||||||
.join(';');
|
|
||||||
|
|
||||||
const canonicalRequest = prepareCanonicalRequest(
|
|
||||||
method,
|
|
||||||
path,
|
|
||||||
queryParams,
|
|
||||||
headers,
|
|
||||||
payload
|
|
||||||
);
|
|
||||||
|
|
||||||
const stringToSign = prepareStringToSign(
|
|
||||||
formattedDate,
|
|
||||||
`${date}/${region}/s3/aws4_request`,
|
|
||||||
hash(canonicalRequest)
|
|
||||||
);
|
|
||||||
|
|
||||||
const signingKey = calculateSigningKey(secretAccessKey, date, region, 's3');
|
|
||||||
|
|
||||||
const signature = hmac(signingKey, stringToSign);
|
|
||||||
|
|
||||||
const authorizationHeader = createAuthorizationHeader(
|
|
||||||
accessKeyId,
|
|
||||||
`${date}/${region}/s3/aws4_request`,
|
|
||||||
headerKeys,
|
|
||||||
signature
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($.auth.data?.secretAccessKey && $.auth.data?.accessKeyId) {
|
|
||||||
requestConfig.headers.Authorization = authorizationHeader;
|
|
||||||
requestConfig.headers['Host'] = 's3.amazonaws.com';
|
|
||||||
requestConfig.headers['X-Amz-Content-Sha256'] = hash(payload);
|
|
||||||
requestConfig.headers['X-Amz-Date'] = formattedDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
return requestConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default addAuthHeader;
|
|
@@ -1,13 +0,0 @@
|
|||||||
export const getYYYYMMDD = () => {
|
|
||||||
const today = new Date();
|
|
||||||
const year = today.getFullYear();
|
|
||||||
const month = (today.getMonth() + 1).toString().padStart(2, '0');
|
|
||||||
const day = today.getDate().toString().padStart(2, '0');
|
|
||||||
|
|
||||||
const formattedDate = `${year}${month}${day}`;
|
|
||||||
return formattedDate;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getISODate = () => {
|
|
||||||
return new Date().toISOString().replace(/[:-]|\.\d{3}/g, '');
|
|
||||||
};
|
|
@@ -1,16 +0,0 @@
|
|||||||
import defineApp from '../../helpers/define-app';
|
|
||||||
import addAuthHeader from './common/add-auth-header';
|
|
||||||
import auth from './auth';
|
|
||||||
|
|
||||||
export default defineApp({
|
|
||||||
name: 'Amazon S3',
|
|
||||||
key: 'amazon-s3',
|
|
||||||
baseUrl: '',
|
|
||||||
apiBaseUrl: 'https://s3.amazonaws.com',
|
|
||||||
iconUrl: '{BASE_URL}/apps/amazon-s3/assets/favicon.svg',
|
|
||||||
authDocUrl: 'https://automatisch.io/docs/apps/amazon-s3/connection',
|
|
||||||
primaryColor: '7B1D13',
|
|
||||||
supportsConnections: true,
|
|
||||||
beforeRequest: [addAuthHeader],
|
|
||||||
auth,
|
|
||||||
});
|
|
@@ -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 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 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 dynamicData from './dynamic-data';
|
||||||
import actions from './actions';
|
import actions from './actions';
|
||||||
import triggers from './triggers';
|
import triggers from './triggers';
|
||||||
|
import dynamicFields from './dynamic-fields';
|
||||||
|
|
||||||
export default defineApp({
|
export default defineApp({
|
||||||
name: 'Discord',
|
name: 'Discord',
|
||||||
@@ -17,6 +18,7 @@ export default defineApp({
|
|||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
dynamicData,
|
dynamicData,
|
||||||
|
dynamicFields,
|
||||||
triggers,
|
triggers,
|
||||||
actions,
|
actions,
|
||||||
});
|
});
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
import getCurrentUser from '../../amazon-s3/common/get-current-user';
|
import getCurrentUser from '../common/get-current-user';
|
||||||
|
|
||||||
const isStillVerified = async ($: IGlobalVariable) => {
|
const isStillVerified = async ($: IGlobalVariable) => {
|
||||||
const currentUser = await getCurrentUser($);
|
const currentUser = await getCurrentUser($);
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||||
import getCurrentUser from '../../amazon-s3/common/get-current-user';
|
import getCurrentUser from '../common/get-current-user';
|
||||||
|
|
||||||
type TUser = {
|
type TUser = {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
const getCurrentUser = async ($: IGlobalVariable) => {
|
const getCurrentUser = async ($: IGlobalVariable) => {
|
||||||
const { data: currentUser } = await $.http.get('/');
|
const { data: currentUser } = await $.http.get(
|
||||||
|
'https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses'
|
||||||
|
);
|
||||||
return currentUser;
|
return currentUser;
|
||||||
};
|
};
|
||||||
|
|
@@ -1,3 +1,4 @@
|
|||||||
import newDatabaseItems from './new-database-items';
|
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,
|
readOnly: false,
|
||||||
value: null,
|
value: null,
|
||||||
placeholder: 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,
|
clickToCopy: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -25,6 +25,27 @@ export default {
|
|||||||
description: 'Port that the host is running on, defaults to 443 (HTTPS)',
|
description: 'Port that the host is running on, defaults to 443 (HTTPS)',
|
||||||
clickToCopy: false,
|
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',
|
key: 'databaseName',
|
||||||
label: 'Database Name',
|
label: 'Database Name',
|
||||||
@@ -40,7 +61,7 @@ export default {
|
|||||||
key: 'email',
|
key: 'email',
|
||||||
label: 'Email Address',
|
label: 'Email Address',
|
||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
requires: true,
|
required: true,
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
value: null,
|
value: null,
|
||||||
placeholder: null,
|
placeholder: null,
|
||||||
|
@@ -32,8 +32,10 @@ export const asyncMethodCall = async <T = number>($: IGlobalVariable, { method,
|
|||||||
export const getClient = ($: IGlobalVariable, { path = 'common' }) => {
|
export const getClient = ($: IGlobalVariable, { path = 'common' }) => {
|
||||||
const host = $.auth.data.host as string;
|
const host = $.auth.data.host as string;
|
||||||
const port = Number($.auth.data.port 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,
|
host,
|
||||||
port,
|
port,
|
||||||
|
@@ -1,10 +1,22 @@
|
|||||||
import { TBeforeRequest } from '@automatisch/types';
|
import { TBeforeRequest } from '@automatisch/types';
|
||||||
|
import appConfig from '../../../config/app';
|
||||||
|
|
||||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||||
|
const screenName = $.auth.data?.screenName as string;
|
||||||
if ($.auth.data?.accessToken) {
|
if ($.auth.data?.accessToken) {
|
||||||
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.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;
|
return requestConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 defineApp from '../../helpers/define-app';
|
||||||
import addAuthHeader from './common/add-auth-header';
|
import addAuthHeader from './common/add-auth-header';
|
||||||
import auth from './auth';
|
import auth from './auth';
|
||||||
|
import actions from './actions';
|
||||||
|
|
||||||
export default defineApp({
|
export default defineApp({
|
||||||
name: 'Remove.bg',
|
name: 'Remove.bg',
|
||||||
@@ -13,4 +14,5 @@ export default defineApp({
|
|||||||
primaryColor: '55636c',
|
primaryColor: '55636c',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
actions,
|
||||||
});
|
});
|
||||||
|
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 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 listUsers from './list-users';
|
||||||
import listBrands from './list-brands';
|
import listBrands from './list-brands';
|
||||||
|
import listFirstPageOfTickets from './list-first-page-of-tickets';
|
||||||
import listGroups from './list-groups';
|
import listGroups from './list-groups';
|
||||||
|
import listOrganizations from './list-organizations';
|
||||||
import listSharingAgreements from './list-sharing-agreements';
|
import listSharingAgreements from './list-sharing-agreements';
|
||||||
import listTicketForms from './list-ticket-forms';
|
import listTicketForms from './list-ticket-forms';
|
||||||
|
import listViews from './list-views';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
listUsers,
|
listUsers,
|
||||||
listBrands,
|
listBrands,
|
||||||
|
listFirstPageOfTickets,
|
||||||
listGroups,
|
listGroups,
|
||||||
|
listOrganizations,
|
||||||
listSharingAgreements,
|
listSharingAgreements,
|
||||||
|
listFirstPageOfTickets,
|
||||||
listTicketForms,
|
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 response = await $.http.get('/api/v2/groups', { params });
|
||||||
const allGroups = response?.data?.groups;
|
const allGroups = response?.data?.groups;
|
||||||
hasMore = response?.data?.meta?.has_more;
|
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) {
|
if (allGroups?.length) {
|
||||||
for (const group of allGroups) {
|
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 response = await $.http.get('/api/v2/users', { params });
|
||||||
const allUsers = response?.data?.users;
|
const allUsers = response?.data?.users;
|
||||||
hasMore = response?.data?.meta?.has_more;
|
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) {
|
if (allUsers?.length) {
|
||||||
for (const user of allUsers) {
|
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 defineApp from '../../helpers/define-app';
|
||||||
import addAuthHeader from './common/add-auth-headers';
|
import addAuthHeader from './common/add-auth-headers';
|
||||||
import auth from './auth';
|
import auth from './auth';
|
||||||
|
import triggers from './triggers';
|
||||||
import actions from './actions';
|
import actions from './actions';
|
||||||
import dynamicData from './dynamic-data';
|
import dynamicData from './dynamic-data';
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
triggers,
|
||||||
actions,
|
actions,
|
||||||
dynamicData,
|
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;
|
smtpPassword: string;
|
||||||
fromEmail: string;
|
fromEmail: string;
|
||||||
isCloud: boolean;
|
isCloud: boolean;
|
||||||
|
isMation: boolean;
|
||||||
isSelfHosted: boolean;
|
isSelfHosted: boolean;
|
||||||
paddleVendorId: number;
|
paddleVendorId: number;
|
||||||
paddleVendorAuthCode: string;
|
paddleVendorAuthCode: string;
|
||||||
@@ -127,6 +128,7 @@ const appConfig: AppConfig = {
|
|||||||
fromEmail: process.env.FROM_EMAIL,
|
fromEmail: process.env.FROM_EMAIL,
|
||||||
isCloud: process.env.AUTOMATISCH_CLOUD === 'true',
|
isCloud: process.env.AUTOMATISCH_CLOUD === 'true',
|
||||||
isSelfHosted: process.env.AUTOMATISCH_CLOUD !== 'true',
|
isSelfHosted: process.env.AUTOMATISCH_CLOUD !== 'true',
|
||||||
|
isMation: process.env.MATION === 'true',
|
||||||
paddleVendorId: Number(process.env.PADDLE_VENDOR_ID),
|
paddleVendorId: Number(process.env.PADDLE_VENDOR_ID),
|
||||||
paddleVendorAuthCode: process.env.PADDLE_VENDOR_AUTH_CODE,
|
paddleVendorAuthCode: process.env.PADDLE_VENDOR_AUTH_CODE,
|
||||||
paddlePublicKey: process.env.PADDLE_PUBLIC_KEY,
|
paddlePublicKey: process.env.PADDLE_PUBLIC_KEY,
|
||||||
|
@@ -9,6 +9,7 @@ describe('graphQL getAutomatischInfo query', () => {
|
|||||||
query {
|
query {
|
||||||
getAutomatischInfo {
|
getAutomatischInfo {
|
||||||
isCloud
|
isCloud
|
||||||
|
isMation
|
||||||
license {
|
license {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@@ -24,6 +25,7 @@ describe('graphQL getAutomatischInfo query', () => {
|
|||||||
jest.spyOn(license, 'getLicense').mockResolvedValue(false);
|
jest.spyOn(license, 'getLicense').mockResolvedValue(false);
|
||||||
|
|
||||||
jest.replaceProperty(appConfig, 'isCloud', false);
|
jest.replaceProperty(appConfig, 'isCloud', false);
|
||||||
|
jest.replaceProperty(appConfig, 'isMation', false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return empty license data', async () => {
|
it('should return empty license data', async () => {
|
||||||
@@ -36,6 +38,7 @@ describe('graphQL getAutomatischInfo query', () => {
|
|||||||
data: {
|
data: {
|
||||||
getAutomatischInfo: {
|
getAutomatischInfo: {
|
||||||
isCloud: false,
|
isCloud: false,
|
||||||
|
isMation: false,
|
||||||
license: {
|
license: {
|
||||||
id: null,
|
id: null,
|
||||||
name: null,
|
name: null,
|
||||||
@@ -77,6 +80,7 @@ describe('graphQL getAutomatischInfo query', () => {
|
|||||||
data: {
|
data: {
|
||||||
getAutomatischInfo: {
|
getAutomatischInfo: {
|
||||||
isCloud: true,
|
isCloud: true,
|
||||||
|
isMation: false,
|
||||||
license: {
|
license: {
|
||||||
expireAt: '2025-08-09T10:56:54.144Z',
|
expireAt: '2025-08-09T10:56:54.144Z',
|
||||||
id: '123123',
|
id: '123123',
|
||||||
@@ -105,6 +109,69 @@ describe('graphQL getAutomatischInfo query', () => {
|
|||||||
const expectedResponsePayload = {
|
const expectedResponsePayload = {
|
||||||
data: {
|
data: {
|
||||||
getAutomatischInfo: {
|
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,
|
isCloud: false,
|
||||||
license: {
|
license: {
|
||||||
expireAt: '2025-08-09T10:56:54.144Z',
|
expireAt: '2025-08-09T10:56:54.144Z',
|
||||||
|
@@ -13,6 +13,7 @@ const getAutomatischInfo = async () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
isCloud: appConfig.isCloud,
|
isCloud: appConfig.isCloud,
|
||||||
|
isMation: appConfig.isMation,
|
||||||
license: computedLicense,
|
license: computedLicense,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
10
packages/backend/src/graphql/queries/get-use-js-file.js
Normal file
10
packages/backend/src/graphql/queries/get-use-js-file.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import appConfig from '../../config/app';
|
||||||
|
|
||||||
|
const getUseJsFile = async () => {
|
||||||
|
return {
|
||||||
|
canInvoke: true,
|
||||||
|
appConfig,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getUseJsFile;
|
@@ -6,6 +6,7 @@ import getApps from './queries/get-apps';
|
|||||||
import getAutomatischInfo from './queries/get-automatisch-info';
|
import getAutomatischInfo from './queries/get-automatisch-info';
|
||||||
import getBillingAndUsage from './queries/get-billing-and-usage.ee';
|
import getBillingAndUsage from './queries/get-billing-and-usage.ee';
|
||||||
import getConfig from './queries/get-config.ee';
|
import getConfig from './queries/get-config.ee';
|
||||||
|
import getUseJsFile from './queries/get-use-js-file.js';
|
||||||
import getConnectedApps from './queries/get-connected-apps';
|
import getConnectedApps from './queries/get-connected-apps';
|
||||||
import getCurrentUser from './queries/get-current-user';
|
import getCurrentUser from './queries/get-current-user';
|
||||||
import getDynamicData from './queries/get-dynamic-data';
|
import getDynamicData from './queries/get-dynamic-data';
|
||||||
@@ -68,6 +69,7 @@ const queryResolvers = {
|
|||||||
healthcheck,
|
healthcheck,
|
||||||
listSamlAuthProviders,
|
listSamlAuthProviders,
|
||||||
testConnection,
|
testConnection,
|
||||||
|
getUseJsFile,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default queryResolvers;
|
export default queryResolvers;
|
||||||
|
@@ -11,6 +11,7 @@ type Query {
|
|||||||
getConnectedApps(name: String): [App]
|
getConnectedApps(name: String): [App]
|
||||||
testConnection(id: String!): Connection
|
testConnection(id: String!): Connection
|
||||||
getFlow(id: String!): Flow
|
getFlow(id: String!): Flow
|
||||||
|
getUseJsFile: JSONObject
|
||||||
getFlows(
|
getFlows(
|
||||||
limit: Int!
|
limit: Int!
|
||||||
offset: Int!
|
offset: Int!
|
||||||
@@ -646,6 +647,7 @@ type AppHealth {
|
|||||||
|
|
||||||
type GetAutomatischInfo {
|
type GetAutomatischInfo {
|
||||||
isCloud: Boolean
|
isCloud: Boolean
|
||||||
|
isMation: Boolean
|
||||||
license: License
|
license: License
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,11 +2,12 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|
"allowJs": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"lib": ["es2021"],
|
"lib": ["es2021"],
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": false,
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"paths": {
|
"paths": {
|
||||||
"*": ["../../node_modules/*", "node_modules/*", "src/types/*"]
|
"*": ["../../node_modules/*", "node_modules/*", "src/types/*"]
|
||||||
|
@@ -303,7 +303,10 @@ export default defineConfig({
|
|||||||
text: 'Remove.bg',
|
text: 'Remove.bg',
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
collapsed: 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',
|
text: 'RSS',
|
||||||
|
@@ -3,6 +3,8 @@ favicon: /favicons/discord.svg
|
|||||||
items:
|
items:
|
||||||
- name: Send a message to channel
|
- name: Send a message to channel
|
||||||
desc: Sends a message to a specific channel you specify.
|
desc: Sends a message to a specific channel you specify.
|
||||||
|
- name: Create a scheduled event
|
||||||
|
desc: Creates a scheduled event.
|
||||||
---
|
---
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
@@ -3,6 +3,8 @@ favicon: /favicons/notion.svg
|
|||||||
items:
|
items:
|
||||||
- name: New database items
|
- name: New database items
|
||||||
desc: Triggers when a new database item is created.
|
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>
|
<script setup>
|
||||||
|
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 />
|
@@ -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. Enter necessary information in the form.
|
||||||
1. Check **Enable OAuth Settings** checkbox.
|
1. Check **Enable OAuth Settings** checkbox.
|
||||||
1. Copy **OAuth Redirect URL** from Automatisch and paste it to the **Callback URL** field.
|
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. Click on the **Save** button at the bottom of the page.
|
||||||
1. Acknowledge the information and click on the **Continue** button.
|
1. Acknowledge the information and click on the **Continue** button.
|
||||||
1. In the **API (Enable OAuth Settings)** section, click the **Manager Consumer Details** button.
|
1. In the **API (Enable OAuth Settings)** section, click the **Manager Consumer Details** button.
|
||||||
|
@@ -3,6 +3,16 @@ favicon: /favicons/zendesk.svg
|
|||||||
items:
|
items:
|
||||||
- name: Create ticket
|
- name: Create ticket
|
||||||
desc: Creates a new 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>
|
<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 />
|
@@ -31,7 +31,7 @@ The following integrations are currently supported by Automatisch.
|
|||||||
- [PostgreSQL](/apps/postgresql/actions)
|
- [PostgreSQL](/apps/postgresql/actions)
|
||||||
- [Pushover](/apps/pushover/actions)
|
- [Pushover](/apps/pushover/actions)
|
||||||
- [Reddit](/apps/reddit/triggers)
|
- [Reddit](/apps/reddit/triggers)
|
||||||
- [Remove.bg](/apps/removebg/connection)
|
- [Remove.bg](/apps/removebg/actions)
|
||||||
- [RSS](/apps/rss/triggers)
|
- [RSS](/apps/rss/triggers)
|
||||||
- [Salesforce](/apps/salesforce/triggers)
|
- [Salesforce](/apps/salesforce/triggers)
|
||||||
- [Scheduler](/apps/scheduler/triggers)
|
- [Scheduler](/apps/scheduler/triggers)
|
||||||
|
BIN
packages/web/public/fonts/Inter-Bold.ttf
Normal file
BIN
packages/web/public/fonts/Inter-Bold.ttf
Normal file
Binary file not shown.
BIN
packages/web/public/fonts/Inter-Medium.ttf
Normal file
BIN
packages/web/public/fonts/Inter-Medium.ttf
Normal file
Binary file not shown.
BIN
packages/web/public/fonts/Inter-Regular.ttf
Normal file
BIN
packages/web/public/fonts/Inter-Regular.ttf
Normal file
Binary file not shown.
@@ -25,12 +25,52 @@
|
|||||||
-->
|
-->
|
||||||
<title>Automatisch</title>
|
<title>Automatisch</title>
|
||||||
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
<link
|
||||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap"
|
rel="preload"
|
||||||
rel="stylesheet"
|
href="/fonts/Inter-Regular.ttf"
|
||||||
|
as="font"
|
||||||
|
crossorigin
|
||||||
|
type="font/ttf"
|
||||||
/>
|
/>
|
||||||
|
<link
|
||||||
|
rel="preload"
|
||||||
|
href="/fonts/Inter-Medium.ttf"
|
||||||
|
as="font"
|
||||||
|
crossorigin
|
||||||
|
type="font/ttf"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="preload"
|
||||||
|
href="/fonts/Inter-Bold.ttf"
|
||||||
|
as="font"
|
||||||
|
crossorigin
|
||||||
|
type="font/ttf"
|
||||||
|
/>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
src: url('/fonts/Inter-Regular.ttf') format('truetype');
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
src: url('/fonts/Inter-Medium.ttf') format('truetype');
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
src: url('/fonts/Inter-Bold.ttf') format('truetype');
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
@@ -2,7 +2,7 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
export const LogoImage = styled('img')(() => ({
|
export const LogoImage = styled('img')(() => ({
|
||||||
maxWidth: 200,
|
maxWidth: 200,
|
||||||
maxHeight: 50,
|
maxHeight: 22,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
}));
|
}));
|
||||||
|
22
packages/web/src/components/DefaultLogo/index.tsx
Normal file
22
packages/web/src/components/DefaultLogo/index.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import MationLogo from 'components/MationLogo';
|
||||||
|
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
||||||
|
|
||||||
|
const DefaultLogo = () => {
|
||||||
|
const { isMation, loading } = useAutomatischInfo();
|
||||||
|
|
||||||
|
if (loading) return <React.Fragment />;
|
||||||
|
|
||||||
|
if (isMation) return <MationLogo />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typography variant="h6" component="h1" data-test="typography-logo" noWrap>
|
||||||
|
<FormattedMessage id="brandText" />
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DefaultLogo;
|
@@ -12,6 +12,7 @@ import * as URLS from 'config/urls';
|
|||||||
import useVersion from 'hooks/useVersion';
|
import useVersion from 'hooks/useVersion';
|
||||||
import AppBar from 'components/AppBar';
|
import AppBar from 'components/AppBar';
|
||||||
import Drawer from 'components/Drawer';
|
import Drawer from 'components/Drawer';
|
||||||
|
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
||||||
|
|
||||||
type PublicLayoutProps = {
|
type PublicLayoutProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -38,7 +39,22 @@ const drawerLinks = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const generateDrawerBottomLinks = ({ notificationBadgeContent = 0 }) => [
|
type GenerateDrawerBottomLinksOptions = {
|
||||||
|
isMation: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
notificationBadgeContent: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateDrawerBottomLinks = ({
|
||||||
|
isMation,
|
||||||
|
loading,
|
||||||
|
notificationBadgeContent = 0,
|
||||||
|
}: GenerateDrawerBottomLinksOptions) => {
|
||||||
|
if (loading || isMation) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
Icon: NotificationsIcon,
|
Icon: NotificationsIcon,
|
||||||
primary: 'settingsDrawer.notifications',
|
primary: 'settingsDrawer.notifications',
|
||||||
@@ -46,11 +62,13 @@ const generateDrawerBottomLinks = ({ notificationBadgeContent = 0 }) => [
|
|||||||
badgeContent: notificationBadgeContent,
|
badgeContent: notificationBadgeContent,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
};
|
||||||
|
|
||||||
export default function PublicLayout({
|
export default function PublicLayout({
|
||||||
children,
|
children,
|
||||||
}: PublicLayoutProps): React.ReactElement {
|
}: PublicLayoutProps): React.ReactElement {
|
||||||
const version = useVersion();
|
const version = useVersion();
|
||||||
|
const { isMation, loading } = useAutomatischInfo();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'));
|
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'));
|
||||||
const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens);
|
const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens);
|
||||||
@@ -60,6 +78,8 @@ export default function PublicLayout({
|
|||||||
|
|
||||||
const drawerBottomLinks = generateDrawerBottomLinks({
|
const drawerBottomLinks = generateDrawerBottomLinks({
|
||||||
notificationBadgeContent: version.newVersionCount,
|
notificationBadgeContent: version.newVersionCount,
|
||||||
|
loading,
|
||||||
|
isMation,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
import Typography from '@mui/material/Typography';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import CustomLogo from 'components/CustomLogo/index.ee';
|
import CustomLogo from 'components/CustomLogo/index.ee';
|
||||||
|
import DefaultLogo from 'components/DefaultLogo';
|
||||||
import useConfig from 'hooks/useConfig';
|
import useConfig from 'hooks/useConfig';
|
||||||
|
|
||||||
const Logo = () => {
|
const Logo = () => {
|
||||||
@@ -13,11 +12,7 @@ const Logo = () => {
|
|||||||
|
|
||||||
if (logoSvgData) return <CustomLogo />;
|
if (logoSvgData) return <CustomLogo />;
|
||||||
|
|
||||||
return (
|
return <DefaultLogo />;
|
||||||
<Typography variant="h6" component="h1" data-test="typography-logo" noWrap>
|
|
||||||
<FormattedMessage id="brandText" />
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Logo;
|
export default Logo;
|
||||||
|
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="115" height="22" viewBox="0 0 411 77" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M276.991 15.3555C281.231 15.3555 284.669 11.918 284.669 7.67773C284.669 3.43744 281.231 0 276.991 0C272.751 0 269.313 3.43744 269.313 7.67773C269.313 11.918 272.751 15.3555 276.991 15.3555ZM367.497 50.0315C367.497 41.5346 374.385 34.6464 382.882 34.6464C391.379 34.6464 398.267 41.5346 398.267 50.0315V71.2076H409.851V50.0315C409.851 35.1371 397.777 23.0627 382.882 23.0627C367.988 23.0627 355.914 35.1371 355.914 50.0315V71.2076H367.497V50.0315ZM271.199 71.2071V28.8539H282.783V71.2071H271.199ZM237.933 34.6464V71.2076H249.517V34.6464H259.608V23.0627H249.517V7.67718H237.933V23.0627H227.843V34.6464H237.933ZM176.899 50.0296C176.899 58.5265 183.787 65.4146 192.284 65.4146V76.9983C177.389 76.9983 165.315 64.924 165.315 50.0296C165.315 35.1351 177.389 23.0608 192.284 23.0608C207.178 23.0608 219.252 35.1359 219.252 50.0303L219.253 71.2068H207.669L207.669 50.0303C207.669 41.5334 200.781 34.6445 192.284 34.6445C183.787 34.6445 176.899 41.5326 176.899 50.0296ZM71.0145 50.0315C71.0145 41.5346 77.9026 34.6464 86.3995 34.6464C94.8965 34.6464 101.785 41.5346 101.785 50.0315V71.2071H113.368V50.0315C113.368 41.5346 120.256 34.6464 128.753 34.6464C137.25 34.6464 144.138 41.5346 144.138 50.0315L144.138 71.2071H155.722L155.722 50.0315C155.722 35.1371 143.647 23.0627 128.753 23.0627C120.165 23.0627 112.515 27.0767 107.576 33.3308C102.637 27.0767 94.9873 23.0627 86.3995 23.0627C71.5051 23.0627 59.4308 35.1371 59.4308 50.0315V71.2071H71.0145V50.0315ZM44.0459 65.4162V76.9999H1.69178V65.4162H44.0459ZM292.376 50.0305C292.376 64.925 304.45 76.9993 319.345 76.9993C334.239 76.9993 346.313 64.9257 346.313 50.0313C346.313 35.1369 334.239 23.0618 319.345 23.0618C304.45 23.0618 292.376 35.1361 292.376 50.0305ZM319.345 65.4157C310.848 65.4157 303.96 58.5276 303.96 50.0306C303.96 41.5337 310.848 34.6456 319.345 34.6456C327.842 34.6456 334.729 41.5345 334.729 50.0314C334.729 58.5283 327.842 65.4157 319.345 65.4157Z" fill="#ffffff"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
8
packages/web/src/components/MationLogo/index.tsx
Normal file
8
packages/web/src/components/MationLogo/index.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { ReactComponent as MationLogoSvg } from './assets/mation-logo.svg';
|
||||||
|
|
||||||
|
const MationLogo = () => {
|
||||||
|
return <MationLogoSvg />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MationLogo;
|
@@ -7,19 +7,20 @@ import * as React from 'react';
|
|||||||
|
|
||||||
import { IJSONObject } from '@automatisch/types';
|
import { IJSONObject } from '@automatisch/types';
|
||||||
import useConfig from 'hooks/useConfig';
|
import useConfig from 'hooks/useConfig';
|
||||||
import theme from 'styles/theme';
|
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
||||||
|
import { defaultTheme, mationTheme } from 'styles/theme';
|
||||||
|
|
||||||
type ThemeProviderProps = {
|
type ThemeProviderProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const customizeTheme = (defaultTheme: typeof theme, config: IJSONObject) => {
|
const customizeTheme = (theme: typeof defaultTheme, config: IJSONObject) => {
|
||||||
// `clone` is needed so that the new theme reference triggers re-render
|
// `clone` is needed so that the new theme reference triggers re-render
|
||||||
const shallowDefaultTheme = clone(defaultTheme);
|
const shallowDefaultTheme = clone(theme);
|
||||||
|
|
||||||
for (const key in config) {
|
for (const key in config) {
|
||||||
const value = config[key];
|
const value = config[key];
|
||||||
const exists = get(defaultTheme, key);
|
const exists = get(theme, key);
|
||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
set(shallowDefaultTheme, key, value);
|
set(shallowDefaultTheme, key, value);
|
||||||
@@ -33,18 +34,21 @@ const ThemeProvider = ({
|
|||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: ThemeProviderProps): React.ReactElement => {
|
}: ThemeProviderProps): React.ReactElement => {
|
||||||
const { config, loading } = useConfig();
|
const { isMation, loading: automatischInfoLoading } = useAutomatischInfo();
|
||||||
|
const { config, loading: configLoading } = useConfig();
|
||||||
|
|
||||||
const customTheme = React.useMemo(() => {
|
const customTheme = React.useMemo(() => {
|
||||||
if (!config) return theme;
|
const installationTheme = isMation ? mationTheme : defaultTheme;
|
||||||
|
|
||||||
const customTheme = customizeTheme(theme, config);
|
if (configLoading || automatischInfoLoading) return installationTheme;
|
||||||
|
|
||||||
|
const customTheme = customizeTheme(installationTheme, config || {});
|
||||||
|
|
||||||
return customTheme;
|
return customTheme;
|
||||||
}, [config]);
|
}, [configLoading, config, isMation, automatischInfoLoading]);
|
||||||
|
|
||||||
// TODO: maybe a global loading state for the custom theme?
|
// TODO: maybe a global loading state for the custom theme?
|
||||||
if (loading) return <></>;
|
if (automatischInfoLoading || configLoading) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseThemeProvider theme={customTheme} {...props}>
|
<BaseThemeProvider theme={customTheme} {...props}>
|
||||||
|
@@ -5,11 +5,15 @@ import { GET_AUTOMATISCH_INFO } from 'graphql/queries/get-automatisch-info';
|
|||||||
|
|
||||||
export type AutomatischInfoContextParams = {
|
export type AutomatischInfoContextParams = {
|
||||||
isCloud: boolean;
|
isCloud: boolean;
|
||||||
|
isMation: boolean;
|
||||||
|
loading: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AutomatischInfoContext =
|
export const AutomatischInfoContext =
|
||||||
React.createContext<AutomatischInfoContextParams>({
|
React.createContext<AutomatischInfoContextParams>({
|
||||||
isCloud: false,
|
isCloud: false,
|
||||||
|
isMation: false,
|
||||||
|
loading: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
type AutomatischInfoProviderProps = {
|
type AutomatischInfoProviderProps = {
|
||||||
@@ -23,13 +27,15 @@ export const AutomatischInfoProvider = (
|
|||||||
const { data, loading } = useQuery(GET_AUTOMATISCH_INFO);
|
const { data, loading } = useQuery(GET_AUTOMATISCH_INFO);
|
||||||
|
|
||||||
const isCloud = data?.getAutomatischInfo?.isCloud;
|
const isCloud = data?.getAutomatischInfo?.isCloud;
|
||||||
|
const isMation = data?.getAutomatischInfo?.isMation;
|
||||||
|
|
||||||
const value = React.useMemo(() => {
|
const value = React.useMemo(() => {
|
||||||
return {
|
return {
|
||||||
isCloud,
|
isCloud,
|
||||||
loading
|
isMation,
|
||||||
|
loading,
|
||||||
};
|
};
|
||||||
}, [isCloud, loading]);
|
}, [isCloud, isMation, loading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AutomatischInfoContext.Provider value={value}>
|
<AutomatischInfoContext.Provider value={value}>
|
||||||
|
@@ -4,6 +4,7 @@ export const GET_AUTOMATISCH_INFO = gql`
|
|||||||
query GetAutomatischInfo {
|
query GetAutomatischInfo {
|
||||||
getAutomatischInfo {
|
getAutomatischInfo {
|
||||||
isCloud
|
isCloud
|
||||||
|
isMation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@@ -3,6 +3,8 @@ import { AutomatischInfoContext } from 'contexts/AutomatischInfo';
|
|||||||
|
|
||||||
type UseAutomatischInfoReturn = {
|
type UseAutomatischInfoReturn = {
|
||||||
isCloud: boolean;
|
isCloud: boolean;
|
||||||
|
isMation: boolean;
|
||||||
|
loading: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function useAutomatischInfo(): UseAutomatischInfoReturn {
|
export default function useAutomatischInfo(): UseAutomatischInfoReturn {
|
||||||
@@ -10,5 +12,7 @@ export default function useAutomatischInfo(): UseAutomatischInfoReturn {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
isCloud: automatischInfoContext.isCloud,
|
isCloud: automatischInfoContext.isCloud,
|
||||||
|
isMation: automatischInfoContext.isMation,
|
||||||
|
loading: automatischInfoContext.loading,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,6 @@ import LiveChat from 'components/LiveChat/index.ee';
|
|||||||
import routes from 'routes';
|
import routes from 'routes';
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Router>
|
<Router>
|
||||||
<SnackbarProvider>
|
<SnackbarProvider>
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
|
|
||||||
@@ -7,6 +8,8 @@ import Container from 'components/Container';
|
|||||||
import NotificationCard from 'components/NotificationCard';
|
import NotificationCard from 'components/NotificationCard';
|
||||||
import PageTitle from 'components/PageTitle';
|
import PageTitle from 'components/PageTitle';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
||||||
|
import * as URLS from 'config/urls';
|
||||||
|
|
||||||
interface INotification {
|
interface INotification {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -16,8 +19,19 @@ interface INotification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Updates(): React.ReactElement {
|
export default function Updates(): React.ReactElement {
|
||||||
|
const navigate = useNavigate();
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
|
const { isMation, loading } = useAutomatischInfo();
|
||||||
|
|
||||||
|
React.useEffect(
|
||||||
|
function redirectToHomepageInMation() {
|
||||||
|
if (!loading && isMation) {
|
||||||
|
navigate(URLS.DASHBOARD);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[loading, isMation]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ py: 3 }}>
|
<Box sx={{ py: 3 }}>
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
import { deepmerge } from '@mui/utils';
|
||||||
|
import type { Theme } from '@mui/material/styles';
|
||||||
import { createTheme, alpha } from '@mui/material/styles';
|
import { createTheme, alpha } from '@mui/material/styles';
|
||||||
import { cardActionAreaClasses } from '@mui/material/CardActionArea';
|
import { cardActionAreaClasses } from '@mui/material/CardActionArea';
|
||||||
|
|
||||||
@@ -6,7 +8,7 @@ export const primaryMainColor = '#0059F7';
|
|||||||
export const primaryLightColor = '#4286FF';
|
export const primaryLightColor = '#4286FF';
|
||||||
export const primaryDarkColor = '#001F52';
|
export const primaryDarkColor = '#001F52';
|
||||||
|
|
||||||
const extendedTheme = createTheme({
|
export const defaultTheme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
primary: {
|
primary: {
|
||||||
main: primaryMainColor,
|
main: primaryMainColor,
|
||||||
@@ -280,4 +282,24 @@ const extendedTheme = createTheme({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default extendedTheme;
|
export const mationTheme = createTheme(deepmerge(defaultTheme, {
|
||||||
|
palette: {
|
||||||
|
primary: {
|
||||||
|
main: '#2962FF',
|
||||||
|
light: '#448AFF',
|
||||||
|
dark: '#2962FF',
|
||||||
|
contrastText: '#fff',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
MuiAppBar: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: ({ theme }: { theme: Theme }) => ({
|
||||||
|
zIndex: theme.zIndex.drawer + 1,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default defaultTheme;
|
||||||
|
Reference in New Issue
Block a user