Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8156b8b356 | ||
![]() |
3a2cbae0a0 | ||
![]() |
0ad8da097b | ||
![]() |
e2dcdd2811 | ||
![]() |
8074f9146b | ||
![]() |
df24bac913 | ||
![]() |
4d4091adcc | ||
![]() |
cac54c41a1 | ||
![]() |
130931d7af | ||
![]() |
d35b08b35e | ||
![]() |
82031da6a6 | ||
![]() |
9df5ee7b11 | ||
![]() |
2ed1a57cd9 | ||
![]() |
101450cba6 | ||
![]() |
6bab5b3f7c |
@@ -29,7 +29,6 @@ rm -rf .env
|
|||||||
echo "
|
echo "
|
||||||
PORT=$WEB_PORT
|
PORT=$WEB_PORT
|
||||||
REACT_APP_GRAPHQL_URL=http://localhost:$BACKEND_PORT/graphql
|
REACT_APP_GRAPHQL_URL=http://localhost:$BACKEND_PORT/graphql
|
||||||
REACT_APP_NOTIFICATIONS_URL=https://notifications.automatisch.io
|
|
||||||
" >> .env
|
" >> .env
|
||||||
cd $CURRENT_DIR
|
cd $CURRENT_DIR
|
||||||
|
|
||||||
|
60
.github/workflows/playwright.yml
vendored
60
.github/workflows/playwright.yml
vendored
@@ -1,31 +1,87 @@
|
|||||||
name: Automatisch UI Tests
|
name: Automatisch UI Tests
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 12 * * *'
|
- cron: '0 12 * * *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
ENCRYPTION_KEY: sample_encryption_key
|
||||||
|
WEBHOOK_SECRET_KEY: sample_webhook_secret_key
|
||||||
|
APP_SECRET_KEY: sample_app_secret_key
|
||||||
|
POSTGRES_HOST: localhost
|
||||||
|
POSTGRES_DATABASE: automatisch
|
||||||
|
POSTGRES_PORT: 5432
|
||||||
|
POSTGRES_USERNAME: automatisch_user
|
||||||
|
POSTGRES_PASSWORD: automatisch_password
|
||||||
|
REDIS_HOST: localhost
|
||||||
|
APP_ENV: production
|
||||||
|
LICENSE_KEY: ${{ secrets.E2E_LICENSE_KEY }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:14.5-alpine
|
||||||
|
env:
|
||||||
|
POSTGRES_DB: automatisch
|
||||||
|
POSTGRES_USER: automatisch_user
|
||||||
|
POSTGRES_PASSWORD: automatisch_password
|
||||||
|
options: >-
|
||||||
|
--health-cmd "pg_isready -U automatisch_user -d automatisch"
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
redis:
|
||||||
|
image: redis:7.0.4-alpine
|
||||||
|
options: >-
|
||||||
|
--health-cmd "redis-cli ping"
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: yarn && yarn lerna bootstrap
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
run: yarn playwright install --with-deps
|
run: yarn playwright install --with-deps
|
||||||
|
- name: Build Automatisch
|
||||||
|
run: yarn lerna run --scope=@*/{web,backend,cli} build
|
||||||
|
env:
|
||||||
|
# Keep this until we clean up warnings in build processes
|
||||||
|
CI: false
|
||||||
|
- name: Migrate database
|
||||||
|
working-directory: ./packages/backend
|
||||||
|
run: yarn db:migrate --migrations-directory ./dist/src/db/migrations
|
||||||
|
- name: Seed user
|
||||||
|
working-directory: ./packages/backend
|
||||||
|
run: yarn db:seed:user &
|
||||||
|
- name: Run Automatisch
|
||||||
|
run: yarn start &
|
||||||
|
working-directory: ./packages/backend
|
||||||
|
- name: Run Automatisch worker
|
||||||
|
run: node dist/src/worker.js &
|
||||||
|
working-directory: ./packages/backend
|
||||||
- name: Run Playwright tests
|
- name: Run Playwright tests
|
||||||
working-directory: ./packages/e2e-tests
|
working-directory: ./packages/e2e-tests
|
||||||
env:
|
env:
|
||||||
LOGIN_EMAIL: ${{ secrets.LOGIN_EMAIL }}
|
LOGIN_EMAIL: ${{ secrets.LOGIN_EMAIL }}
|
||||||
LOGIN_PASSWORD: ${{ secrets.LOGIN_PASSWORD }}
|
LOGIN_PASSWORD: ${{ secrets.LOGIN_PASSWORD }}
|
||||||
|
BASE_URL: ${{ vars.E2E_BASE_URL }}
|
||||||
run: yarn test
|
run: yarn test
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: playwright-report
|
name: playwright-report
|
||||||
path: test-results/
|
path: ./packages/e2e-tests/test-results/**/*
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -1,4 +1,7 @@
|
|||||||
{
|
{
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,7 @@ WORKDIR /automatisch
|
|||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk --no-cache add --virtual build-dependencies python3 build-base && \
|
apk --no-cache add --virtual build-dependencies python3 build-base && \
|
||||||
yarn global add @automatisch/cli@0.9.1 --network-timeout 1000000 && \
|
yarn global add @automatisch/cli@0.9.3 --network-timeout 1000000 && \
|
||||||
rm -rf /usr/local/share/.cache/ && \
|
rm -rf /usr/local/share/.cache/ && \
|
||||||
apk del build-dependencies
|
apk del build-dependencies
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM automatischio/automatisch:0.9.1
|
FROM automatischio/automatisch:0.9.3
|
||||||
WORKDIR /automatisch
|
WORKDIR /automatisch
|
||||||
|
|
||||||
RUN apk add --no-cache openssl dos2unix
|
RUN apk add --no-cache openssl dos2unix
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"command": {
|
"command": {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/backend",
|
"name": "@automatisch/backend",
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"license": "See LICENSE file",
|
"license": "See LICENSE file",
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"prebuild": "rm -rf ./dist"
|
"prebuild": "rm -rf ./dist"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@automatisch/web": "^0.9.1",
|
"@automatisch/web": "^0.9.3",
|
||||||
"@bull-board/express": "^3.10.1",
|
"@bull-board/express": "^3.10.1",
|
||||||
"@casl/ability": "^6.5.0",
|
"@casl/ability": "^6.5.0",
|
||||||
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
"url": "https://github.com/automatisch/automatisch/issues"
|
"url": "https://github.com/automatisch/automatisch/issues"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@automatisch/types": "^0.9.1",
|
"@automatisch/types": "^0.9.3",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/bull": "^3.15.8",
|
"@types/bull": "^3.15.8",
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
|
@@ -4,6 +4,7 @@ import htmlToMarkdown from './transformers/html-to-markdown';
|
|||||||
import markdownToHtml from './transformers/markdown-to-html';
|
import markdownToHtml from './transformers/markdown-to-html';
|
||||||
import useDefaultValue from './transformers/use-default-value';
|
import useDefaultValue from './transformers/use-default-value';
|
||||||
import extractEmailAddress from './transformers/extract-email-address';
|
import extractEmailAddress from './transformers/extract-email-address';
|
||||||
|
import extractNumber from './transformers/extract-number';
|
||||||
|
|
||||||
const transformers = {
|
const transformers = {
|
||||||
capitalize,
|
capitalize,
|
||||||
@@ -11,6 +12,7 @@ const transformers = {
|
|||||||
markdownToHtml,
|
markdownToHtml,
|
||||||
useDefaultValue,
|
useDefaultValue,
|
||||||
extractEmailAddress,
|
extractEmailAddress,
|
||||||
|
extractNumber,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defineAction({
|
export default defineAction({
|
||||||
@@ -32,6 +34,7 @@ export default defineAction({
|
|||||||
{ label: 'Convert Markdown to HTML', value: 'markdownToHtml' },
|
{ label: 'Convert Markdown to HTML', value: 'markdownToHtml' },
|
||||||
{ label: 'Use Default Value', value: 'useDefaultValue' },
|
{ label: 'Use Default Value', value: 'useDefaultValue' },
|
||||||
{ label: 'Extract Email Address', value: 'extractEmailAddress' },
|
{ label: 'Extract Email Address', value: 'extractEmailAddress' },
|
||||||
|
{ label: 'Extract Number', value: 'extractNumber' },
|
||||||
],
|
],
|
||||||
additionalFields: {
|
additionalFields: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
|
@@ -0,0 +1,26 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
|
const extractNumber = ($: IGlobalVariable) => {
|
||||||
|
const input = $.step.parameters.input as string;
|
||||||
|
|
||||||
|
// Example numbers that's supported:
|
||||||
|
// 123
|
||||||
|
// -123
|
||||||
|
// 123456
|
||||||
|
// -123456
|
||||||
|
// 121,234
|
||||||
|
// -121,234
|
||||||
|
// 121.234
|
||||||
|
// -121.234
|
||||||
|
// 1,234,567.89
|
||||||
|
// -1,234,567.89
|
||||||
|
// 1.234.567,89
|
||||||
|
// -1.234.567,89
|
||||||
|
|
||||||
|
const numberRegexp = /-?((\d{1,3})+\.?,?)+/g;
|
||||||
|
|
||||||
|
const numbers = input.match(numberRegexp);
|
||||||
|
return numbers ? numbers[0] : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export default extractNumber;
|
@@ -4,6 +4,7 @@ import htmlToMarkdown from './options/html-to-markdown';
|
|||||||
import markdownToHtml from './options/markdown-to-html';
|
import markdownToHtml from './options/markdown-to-html';
|
||||||
import useDefaultValue from './options/use-default-value';
|
import useDefaultValue from './options/use-default-value';
|
||||||
import extractEmailAddress from './options/extract-email-address';
|
import extractEmailAddress from './options/extract-email-address';
|
||||||
|
import extractNumber from './options/extract-number';
|
||||||
|
|
||||||
const options: IJSONObject = {
|
const options: IJSONObject = {
|
||||||
capitalize,
|
capitalize,
|
||||||
@@ -11,6 +12,7 @@ const options: IJSONObject = {
|
|||||||
markdownToHtml,
|
markdownToHtml,
|
||||||
useDefaultValue,
|
useDefaultValue,
|
||||||
extractEmailAddress,
|
extractEmailAddress,
|
||||||
|
extractNumber,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
const extractNumber = [
|
||||||
|
{
|
||||||
|
label: 'Input',
|
||||||
|
key: 'input',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
description: 'Text that will be searched for a number.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default extractNumber;
|
@@ -4,9 +4,13 @@ import bcrypt from 'bcrypt';
|
|||||||
|
|
||||||
const getInternalId = async (item: IJSONObject): Promise<string> => {
|
const getInternalId = async (item: IJSONObject): Promise<string> => {
|
||||||
if (item.guid) {
|
if (item.guid) {
|
||||||
return item.guid.toString();
|
return typeof item.guid === 'object'
|
||||||
|
? (item.guid as IJSONObject)['#text'].toString()
|
||||||
|
: item.guid.toString();
|
||||||
} else if (item.id) {
|
} else if (item.id) {
|
||||||
return item.id.toString();
|
return typeof item.id === 'object'
|
||||||
|
? (item.id as IJSONObject)['#text'].toString()
|
||||||
|
: item.id.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return await hashItem(JSON.stringify(item));
|
return await hashItem(JSON.stringify(item));
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
await knex('permissions')
|
||||||
|
.where(knex.raw('conditions::text'), '=', knex.raw("'{}'::text"))
|
||||||
|
.update('conditions', JSON.stringify([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(): Promise<void> {
|
||||||
|
// void
|
||||||
|
}
|
15
packages/backend/src/graphql/queries/get-notifications.ts
Normal file
15
packages/backend/src/graphql/queries/get-notifications.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import axios from '../../helpers/axios-with-proxy';
|
||||||
|
|
||||||
|
const NOTIFICATIONS_URL = 'https://notifications.automatisch.io/notifications.json';
|
||||||
|
|
||||||
|
const getNotifications = async () => {
|
||||||
|
try {
|
||||||
|
const { data: notifications = [] } = await axios.get(NOTIFICATIONS_URL);
|
||||||
|
|
||||||
|
return notifications;
|
||||||
|
} catch (err) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getNotifications;
|
@@ -16,13 +16,14 @@ import getExecutions from './queries/get-executions';
|
|||||||
import getFlow from './queries/get-flow';
|
import getFlow from './queries/get-flow';
|
||||||
import getFlows from './queries/get-flows';
|
import getFlows from './queries/get-flows';
|
||||||
import getInvoices from './queries/get-invoices.ee';
|
import getInvoices from './queries/get-invoices.ee';
|
||||||
|
import getNotifications from './queries/get-notifications';
|
||||||
import getPaddleInfo from './queries/get-paddle-info.ee';
|
import getPaddleInfo from './queries/get-paddle-info.ee';
|
||||||
import getPaymentPlans from './queries/get-payment-plans.ee';
|
import getPaymentPlans from './queries/get-payment-plans.ee';
|
||||||
import getPermissionCatalog from './queries/get-permission-catalog.ee';
|
import getPermissionCatalog from './queries/get-permission-catalog.ee';
|
||||||
import getRole from './queries/get-role.ee';
|
import getRole from './queries/get-role.ee';
|
||||||
import getRoles from './queries/get-roles.ee';
|
import getRoles from './queries/get-roles.ee';
|
||||||
import getSamlAuthProvider from './queries/get-saml-auth-provider.ee';
|
|
||||||
import getSamlAuthProviderRoleMappings from './queries/get-saml-auth-provider-role-mappings.ee';
|
import getSamlAuthProviderRoleMappings from './queries/get-saml-auth-provider-role-mappings.ee';
|
||||||
|
import getSamlAuthProvider from './queries/get-saml-auth-provider.ee';
|
||||||
import getStepWithTestExecutions from './queries/get-step-with-test-executions';
|
import getStepWithTestExecutions from './queries/get-step-with-test-executions';
|
||||||
import getSubscriptionStatus from './queries/get-subscription-status.ee';
|
import getSubscriptionStatus from './queries/get-subscription-status.ee';
|
||||||
import getTrialStatus from './queries/get-trial-status.ee';
|
import getTrialStatus from './queries/get-trial-status.ee';
|
||||||
@@ -51,6 +52,7 @@ const queryResolvers = {
|
|||||||
getFlow,
|
getFlow,
|
||||||
getFlows,
|
getFlows,
|
||||||
getInvoices,
|
getInvoices,
|
||||||
|
getNotifications,
|
||||||
getPaddleInfo,
|
getPaddleInfo,
|
||||||
getPaymentPlans,
|
getPaymentPlans,
|
||||||
getPermissionCatalog,
|
getPermissionCatalog,
|
||||||
|
@@ -46,6 +46,7 @@ type Query {
|
|||||||
getPermissionCatalog: PermissionCatalog
|
getPermissionCatalog: PermissionCatalog
|
||||||
getRole(id: String!): Role
|
getRole(id: String!): Role
|
||||||
getRoles: [Role]
|
getRoles: [Role]
|
||||||
|
getNotifications: [Notification]
|
||||||
getSamlAuthProvider: SamlAuthProvider
|
getSamlAuthProvider: SamlAuthProvider
|
||||||
getSamlAuthProviderRoleMappings(id: String!): [SamlAuthProvidersRoleMapping]
|
getSamlAuthProviderRoleMappings(id: String!): [SamlAuthProvidersRoleMapping]
|
||||||
getSubscriptionStatus: GetSubscriptionStatus
|
getSubscriptionStatus: GetSubscriptionStatus
|
||||||
@@ -787,6 +788,13 @@ input UpdateAppAuthClientInput {
|
|||||||
active: Boolean
|
active: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Notification {
|
||||||
|
name: String
|
||||||
|
createdAt: String
|
||||||
|
documentationUrl: String
|
||||||
|
description: String
|
||||||
|
}
|
||||||
|
|
||||||
schema {
|
schema {
|
||||||
query: Query
|
query: Query
|
||||||
mutation: Mutation
|
mutation: Mutation
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { rule, shield, allow } from 'graphql-shield';
|
import { allow, rule, shield } from 'graphql-shield';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import User from '../models/user';
|
|
||||||
import appConfig from '../config/app';
|
import appConfig from '../config/app';
|
||||||
|
import User from '../models/user';
|
||||||
|
|
||||||
const isAuthenticated = rule()(async (_parent, _args, req) => {
|
const isAuthenticated = rule()(async (_parent, _args, req) => {
|
||||||
const token = req.headers['authorization'];
|
const token = req.headers['authorization'];
|
||||||
@@ -34,15 +34,16 @@ const authentication = shield(
|
|||||||
Query: {
|
Query: {
|
||||||
'*': isAuthenticated,
|
'*': isAuthenticated,
|
||||||
getAutomatischInfo: allow,
|
getAutomatischInfo: allow,
|
||||||
listSamlAuthProviders: allow,
|
|
||||||
healthcheck: allow,
|
|
||||||
getConfig: allow,
|
getConfig: allow,
|
||||||
|
getNotifications: allow,
|
||||||
|
healthcheck: allow,
|
||||||
|
listSamlAuthProviders: allow,
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
'*': isAuthenticated,
|
'*': isAuthenticated,
|
||||||
registerUser: allow,
|
|
||||||
forgotPassword: allow,
|
forgotPassword: allow,
|
||||||
login: allow,
|
login: allow,
|
||||||
|
registerUser: allow,
|
||||||
resetPassword: allow,
|
resetPassword: allow,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
// TODO: replace with axios-with-proxy
|
|
||||||
import axios from 'axios';
|
|
||||||
import appConfig from '../config/app';
|
|
||||||
import memoryCache from 'memory-cache';
|
import memoryCache from 'memory-cache';
|
||||||
|
import appConfig from '../config/app';
|
||||||
|
import axios from './axios-with-proxy';
|
||||||
|
|
||||||
const CACHE_DURATION = 1000 * 60 * 60 * 24; // 24 hours in milliseconds
|
const CACHE_DURATION = 1000 * 60 * 60 * 24; // 24 hours in milliseconds
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/cli",
|
"name": "@automatisch/cli",
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"license": "See LICENSE file",
|
"license": "See LICENSE file",
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"version": "oclif readme && git add README.md"
|
"version": "oclif readme && git add README.md"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@automatisch/backend": "^0.9.1",
|
"@automatisch/backend": "^0.9.3",
|
||||||
"@oclif/core": "^1",
|
"@oclif/core": "^1",
|
||||||
"@oclif/plugin-help": "^5",
|
"@oclif/plugin-help": "^5",
|
||||||
"@oclif/plugin-plugins": "^2.0.1",
|
"@oclif/plugin-plugins": "^2.0.1",
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/docs",
|
"name": "@automatisch/docs",
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"license": "See LICENSE file",
|
"license": "See LICENSE file",
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@@ -8,7 +8,11 @@ const { LoginPage } = require('./login-page');
|
|||||||
|
|
||||||
exports.test = test.extend({
|
exports.test = test.extend({
|
||||||
page: async ({ page }, use) => {
|
page: async ({ page }, use) => {
|
||||||
await new LoginPage(page).login();
|
const loginPage = new LoginPage(page);
|
||||||
|
await loginPage.login();
|
||||||
|
|
||||||
|
await expect(loginPage.loginButton).not.toBeVisible();
|
||||||
|
await expect(page).toHaveURL('/flows');
|
||||||
|
|
||||||
await use(page);
|
await use(page);
|
||||||
},
|
},
|
||||||
@@ -29,6 +33,19 @@ exports.test = test.extend({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
exports.publicTest = test.extend({
|
||||||
|
page: async ({ page }, use) => {
|
||||||
|
await use(page);
|
||||||
|
},
|
||||||
|
loginPage: async ({ page }, use) => {
|
||||||
|
const loginPage = new LoginPage(page);
|
||||||
|
|
||||||
|
await loginPage.open();
|
||||||
|
|
||||||
|
await use(loginPage);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
expect.extend({
|
expect.extend({
|
||||||
toBeClickableLink: async (locator) => {
|
toBeClickableLink: async (locator) => {
|
||||||
await expect(locator).not.toHaveAttribute('aria-disabled', 'true');
|
await expect(locator).not.toHaveAttribute('aria-disabled', 'true');
|
||||||
|
@@ -17,13 +17,18 @@ export class LoginPage extends BasePage {
|
|||||||
this.loginButton = this.page.getByTestId('login-button');
|
this.loginButton = this.page.getByTestId('login-button');
|
||||||
}
|
}
|
||||||
|
|
||||||
async login() {
|
async open() {
|
||||||
|
return await this.page.goto(this.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(
|
||||||
|
email = process.env.LOGIN_EMAIL,
|
||||||
|
password = process.env.LOGIN_PASSWORD
|
||||||
|
) {
|
||||||
await this.page.goto(this.path);
|
await this.page.goto(this.path);
|
||||||
await this.emailTextField.fill(process.env.LOGIN_EMAIL);
|
await this.emailTextField.fill(email);
|
||||||
await this.passwordTextField.fill(process.env.LOGIN_PASSWORD);
|
await this.passwordTextField.fill(password);
|
||||||
|
|
||||||
await this.loginButton.click();
|
await this.loginButton.click();
|
||||||
|
|
||||||
await expect(this.loginButton).not.toBeVisible();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/e2e-tests",
|
"name": "@automatisch/e2e-tests",
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"license": "See LICENSE file",
|
"license": "See LICENSE file",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
|
@@ -16,20 +16,18 @@ module.exports = defineConfig({
|
|||||||
fullyParallel: true,
|
fullyParallel: true,
|
||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
/* Retry on CI only */
|
retries: 0,
|
||||||
retries: process.env.CI ? 2 : 0,
|
|
||||||
/* Opt out of parallel tests on CI. */
|
/* Opt out of parallel tests on CI. */
|
||||||
workers: process.env.CI ? 1 : undefined,
|
workers: process.env.CI ? 1 : undefined,
|
||||||
/* Timeout threshold for each test */
|
/* Timeout threshold for each test */
|
||||||
timeout: 120000,
|
timeout: 30000,
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
reporter: process.env.CI ? 'github' : 'html',
|
reporter: process.env.CI ? 'github' : 'html',
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
use: {
|
use: {
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
baseURL: process.env.CI
|
baseURL: process.env.BASE_URL
|
||||||
? 'https://sandbox.automatisch.io'
|
|| 'http://localhost:3001',
|
||||||
: 'http://localhost:3001',
|
|
||||||
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
@@ -39,7 +37,7 @@ module.exports = defineConfig({
|
|||||||
|
|
||||||
expect: {
|
expect: {
|
||||||
/* Timeout threshold for each assertion */
|
/* Timeout threshold for each assertion */
|
||||||
timeout: 30000,
|
timeout: 10000,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
/* Configure projects for major browsers */
|
||||||
|
@@ -6,7 +6,8 @@ test.describe('Apps page', () => {
|
|||||||
await applicationsPage.drawerLink.click();
|
await applicationsPage.drawerLink.click();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('displays applications', async ({ applicationsPage }) => {
|
// no connected application exists in an empty account
|
||||||
|
test.skip('displays no applications', async ({ applicationsPage }) => {
|
||||||
await applicationsPage.page.getByTestId('apps-loader').waitFor({
|
await applicationsPage.page.getByTestId('apps-loader').waitFor({
|
||||||
state: 'detached',
|
state: 'detached',
|
||||||
});
|
});
|
||||||
|
22
packages/e2e-tests/tests/authentication/login.spec.js
Normal file
22
packages/e2e-tests/tests/authentication/login.spec.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// @ts-check
|
||||||
|
const { publicTest, test, expect } = require('../../fixtures/index');
|
||||||
|
|
||||||
|
publicTest.describe('Login page', () => {
|
||||||
|
publicTest('shows login form', async ({ loginPage }) => {
|
||||||
|
await loginPage.emailTextField.waitFor({ state: 'attached' });
|
||||||
|
await loginPage.passwordTextField.waitFor({ state: 'attached' });
|
||||||
|
await loginPage.loginButton.waitFor({ state: 'attached' });
|
||||||
|
});
|
||||||
|
|
||||||
|
publicTest('lets user login', async ({ loginPage }) => {
|
||||||
|
await loginPage.login();
|
||||||
|
|
||||||
|
await expect(loginPage.page).toHaveURL('/flows');
|
||||||
|
});
|
||||||
|
|
||||||
|
publicTest(`doesn't let un-existing user login`, async ({ loginPage }) => {
|
||||||
|
await loginPage.login('nonexisting@automatisch.io', 'sample');
|
||||||
|
|
||||||
|
await expect(loginPage.page).toHaveURL('/login');
|
||||||
|
});
|
||||||
|
});
|
@@ -1,7 +1,8 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
const { test, expect } = require('../../fixtures/index');
|
const { test, expect } = require('../../fixtures/index');
|
||||||
|
|
||||||
test.describe('Executions page', () => {
|
// no execution data exists in an empty account
|
||||||
|
test.describe.skip('Executions page', () => {
|
||||||
test.beforeEach(async ({ page, executionsPage }) => {
|
test.beforeEach(async ({ page, executionsPage }) => {
|
||||||
await page.getByTestId('executions-page-drawer-link').click();
|
await page.getByTestId('executions-page-drawer-link').click();
|
||||||
await page.getByTestId('execution-row').first().click();
|
await page.getByTestId('execution-row').first().click();
|
||||||
|
@@ -6,7 +6,8 @@ test.describe('Executions page', () => {
|
|||||||
await page.getByTestId('executions-page-drawer-link').click();
|
await page.getByTestId('executions-page-drawer-link').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('displays executions', async ({ page, executionsPage }) => {
|
// no executions exist in an empty account
|
||||||
|
test.skip('displays executions', async ({ page, executionsPage }) => {
|
||||||
await page.getByTestId('executions-loader').waitFor({
|
await page.getByTestId('executions-loader').waitFor({
|
||||||
state: 'detached',
|
state: 'detached',
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
const { test, expect } = require('../../fixtures/index');
|
const { test, expect } = require('../../fixtures/index');
|
||||||
|
|
||||||
test.describe('User interface page', () => {
|
test.describe.skip('User interface page', () => {
|
||||||
test.beforeEach(async ({ userInterfacePage }) => {
|
test.beforeEach(async ({ userInterfacePage }) => {
|
||||||
await userInterfacePage.profileMenuButton.click();
|
await userInterfacePage.profileMenuButton.click();
|
||||||
await userInterfacePage.adminMenuItem.click();
|
await userInterfacePage.adminMenuItem.click();
|
||||||
|
7
packages/types/index.d.ts
vendored
7
packages/types/index.d.ts
vendored
@@ -448,6 +448,13 @@ type AppAuthClient = {
|
|||||||
formattedAuthDefaults: IJSONObject;
|
formattedAuthDefaults: IJSONObject;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Notification = {
|
||||||
|
name: string;
|
||||||
|
createdAt: string;
|
||||||
|
documentationUrl: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
declare module 'axios' {
|
declare module 'axios' {
|
||||||
interface AxiosResponse {
|
interface AxiosResponse {
|
||||||
httpError?: IJSONObject;
|
httpError?: IJSONObject;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/types",
|
"name": "@automatisch/types",
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"license": "See LICENSE file",
|
"license": "See LICENSE file",
|
||||||
"description": "Type definitions for automatisch",
|
"description": "Type definitions for automatisch",
|
||||||
"homepage": "https://github.com/automatisch/automatisch",
|
"homepage": "https://github.com/automatisch/automatisch",
|
||||||
|
@@ -2,4 +2,3 @@ PORT=3001
|
|||||||
REACT_APP_GRAPHQL_URL=http://localhost:3000/graphql
|
REACT_APP_GRAPHQL_URL=http://localhost:3000/graphql
|
||||||
# HTTPS=true
|
# HTTPS=true
|
||||||
REACT_APP_BASE_URL=http://localhost:3001
|
REACT_APP_BASE_URL=http://localhost:3001
|
||||||
REACT_APP_NOTIFICATIONS_URL=https://notifications.automatisch.io
|
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/web",
|
"name": "@automatisch/web",
|
||||||
"version": "0.9.1",
|
"version": "0.9.3",
|
||||||
"license": "See LICENSE file",
|
"license": "See LICENSE file",
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.6.9",
|
"@apollo/client": "^3.6.9",
|
||||||
"@automatisch/types": "^0.9.1",
|
"@automatisch/types": "^0.9.3",
|
||||||
"@casl/ability": "^6.5.0",
|
"@casl/ability": "^6.5.0",
|
||||||
"@casl/react": "^3.1.0",
|
"@casl/react": "^3.1.0",
|
||||||
"@emotion/react": "^11.4.1",
|
"@emotion/react": "^11.4.1",
|
||||||
|
@@ -2,7 +2,6 @@ type Config = {
|
|||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
graphqlUrl: string;
|
graphqlUrl: string;
|
||||||
notificationsUrl: string;
|
|
||||||
chatwootBaseUrl: string;
|
chatwootBaseUrl: string;
|
||||||
supportEmailAddress: string;
|
supportEmailAddress: string;
|
||||||
};
|
};
|
||||||
@@ -10,7 +9,6 @@ type Config = {
|
|||||||
const config: Config = {
|
const config: Config = {
|
||||||
baseUrl: process.env.REACT_APP_BASE_URL as string,
|
baseUrl: process.env.REACT_APP_BASE_URL as string,
|
||||||
graphqlUrl: process.env.REACT_APP_GRAPHQL_URL as string,
|
graphqlUrl: process.env.REACT_APP_GRAPHQL_URL as string,
|
||||||
notificationsUrl: process.env.REACT_APP_NOTIFICATIONS_URL as string,
|
|
||||||
chatwootBaseUrl: 'https://app.chatwoot.com',
|
chatwootBaseUrl: 'https://app.chatwoot.com',
|
||||||
supportEmailAddress: 'support@automatisch.io',
|
supportEmailAddress: 'support@automatisch.io',
|
||||||
};
|
};
|
||||||
|
12
packages/web/src/graphql/queries/get-notifications.ts
Normal file
12
packages/web/src/graphql/queries/get-notifications.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const GET_NOTIFICATIONS = gql`
|
||||||
|
query GetNotifications {
|
||||||
|
getNotifications {
|
||||||
|
name
|
||||||
|
createdAt
|
||||||
|
documentationUrl
|
||||||
|
description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
@@ -1,26 +1,20 @@
|
|||||||
import * as React from 'react';
|
import { useQuery } from '@apollo/client';
|
||||||
import appConfig from 'config/app';
|
import type { Notification } from '@automatisch/types';
|
||||||
|
|
||||||
interface INotification {
|
import { GET_NOTIFICATIONS } from 'graphql/queries/get-notifications';
|
||||||
name: string;
|
|
||||||
createdAt: string;
|
type UseNotificationsReturn = {
|
||||||
documentationUrl: string;
|
notifications: Notification[];
|
||||||
description: string;
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useNotifications(): INotification[] {
|
export default function useNotifications(): UseNotificationsReturn {
|
||||||
const [notifications, setNotifications] = React.useState<INotification[]>([]);
|
const { data, loading } = useQuery(GET_NOTIFICATIONS);
|
||||||
|
|
||||||
React.useEffect(() => {
|
const notifications = data?.getNotifications || [];
|
||||||
fetch(`${appConfig.notificationsUrl}/notifications.json`)
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((notifications) => {
|
|
||||||
if (Array.isArray(notifications) && notifications.length) {
|
|
||||||
setNotifications(notifications);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return notifications;
|
return {
|
||||||
|
loading,
|
||||||
|
notifications,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@@ -10,7 +10,7 @@ type TVersionInfo = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function useVersion(): TVersionInfo {
|
export default function useVersion(): TVersionInfo {
|
||||||
const notifications = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
const { data } = useQuery(HEALTHCHECK, { fetchPolicy: 'cache-and-network' });
|
const { data } = useQuery(HEALTHCHECK, { fetchPolicy: 'cache-and-network' });
|
||||||
const version = data?.healthcheck.version;
|
const version = data?.healthcheck.version;
|
||||||
|
|
||||||
|
@@ -17,7 +17,7 @@ interface INotification {
|
|||||||
|
|
||||||
export default function Updates(): React.ReactElement {
|
export default function Updates(): React.ReactElement {
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const notifications = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ py: 3 }}>
|
<Box sx={{ py: 3 }}>
|
||||||
|
Reference in New Issue
Block a user