From 12e34013f8daa38f919d4aa55a6b3de76937454b Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 8 Mar 2023 18:35:51 +0000 Subject: [PATCH 01/20] refactor: default noSsr to true in useMediaQuery --- packages/web/src/components/AppBar/index.tsx | 4 +--- packages/web/src/components/ConditionalIconButton/index.tsx | 4 +--- packages/web/src/components/Drawer/index.tsx | 4 +--- packages/web/src/components/Layout/index.tsx | 4 +--- packages/web/src/components/SettingsLayout/index.tsx | 4 +--- packages/web/src/pages/Application/index.tsx | 4 +--- packages/web/src/styles/theme.ts | 5 +++++ 7 files changed, 11 insertions(+), 18 deletions(-) diff --git a/packages/web/src/components/AppBar/index.tsx b/packages/web/src/components/AppBar/index.tsx index 56d52bd3..cf32768a 100644 --- a/packages/web/src/components/AppBar/index.tsx +++ b/packages/web/src/components/AppBar/index.tsx @@ -29,9 +29,7 @@ export default function AppBar(props: AppBarProps): React.ReactElement { const { drawerOpen, onDrawerOpen, onDrawerClose, maxWidth = false } = props; const theme = useTheme(); - const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'), { - noSsr: true, - }); + const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md')); const [accountMenuAnchorElement, setAccountMenuAnchorElement] = React.useState(null); diff --git a/packages/web/src/components/ConditionalIconButton/index.tsx b/packages/web/src/components/ConditionalIconButton/index.tsx index 4c72a4a9..dd8bcaf4 100644 --- a/packages/web/src/components/ConditionalIconButton/index.tsx +++ b/packages/web/src/components/ConditionalIconButton/index.tsx @@ -10,9 +10,7 @@ import { IconButton } from './style'; export default function ConditionalIconButton(props: any): React.ReactElement { const { icon, ...buttonProps } = props; const theme = useTheme(); - const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'), { - noSsr: true, - }); + const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md')); if (matchSmallScreens) { return ( diff --git a/packages/web/src/components/Drawer/index.tsx b/packages/web/src/components/Drawer/index.tsx index 936a1853..9d677bf1 100644 --- a/packages/web/src/components/Drawer/index.tsx +++ b/packages/web/src/components/Drawer/index.tsx @@ -31,9 +31,7 @@ type DrawerProps = { export default function Drawer(props: DrawerProps): React.ReactElement { const { links = [], bottomLinks = [], ...drawerProps } = props; const theme = useTheme(); - const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'), { - noSsr: true, - }); + const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md')); const formatMessage = useFormatMessage(); const closeOnClick = (event: React.SyntheticEvent) => { diff --git a/packages/web/src/components/Layout/index.tsx b/packages/web/src/components/Layout/index.tsx index 05e3838c..09c738de 100644 --- a/packages/web/src/components/Layout/index.tsx +++ b/packages/web/src/components/Layout/index.tsx @@ -52,9 +52,7 @@ export default function PublicLayout({ }: PublicLayoutProps): React.ReactElement { const version = useVersion(); const theme = useTheme(); - const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'), { - noSsr: true, - }); + const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg')); const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens); const openDrawer = () => setDrawerOpen(true); diff --git a/packages/web/src/components/SettingsLayout/index.tsx b/packages/web/src/components/SettingsLayout/index.tsx index 488349ea..ddeb90da 100644 --- a/packages/web/src/components/SettingsLayout/index.tsx +++ b/packages/web/src/components/SettingsLayout/index.tsx @@ -49,9 +49,7 @@ export default function SettingsLayout({ }: SettingsLayoutProps): React.ReactElement { const { isCloud } = useAutomatischInfo(); const theme = useTheme(); - const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'), { - noSsr: true, - }); + const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg')); const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens); const openDrawer = () => setDrawerOpen(true); diff --git a/packages/web/src/pages/Application/index.tsx b/packages/web/src/pages/Application/index.tsx index f3db7ad9..2bee8da6 100644 --- a/packages/web/src/pages/Application/index.tsx +++ b/packages/web/src/pages/Application/index.tsx @@ -51,9 +51,7 @@ const ReconnectConnection = (props: any): React.ReactElement => { export default function Application(): React.ReactElement | null { const theme = useTheme(); - const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'), { - noSsr: true, - }); + const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md')); const formatMessage = useFormatMessage(); const connectionsPathMatch = useMatch({ path: URLS.APP_CONNECTIONS_PATTERN, diff --git a/packages/web/src/styles/theme.ts b/packages/web/src/styles/theme.ts index 4624bb5b..11a8fe7c 100644 --- a/packages/web/src/styles/theme.ts +++ b/packages/web/src/styles/theme.ts @@ -251,6 +251,11 @@ const extendedTheme = createTheme({ }), }, }, + MuiUseMediaQuery: { + defaultProps: { + noSsr: true, + }, + }, MuiTab: { styleOverrides: { root: ({ theme }) => ({ From 1669708041c1fa5ca6ed6135135e62f00ca73091 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 8 Mar 2023 18:37:32 +0000 Subject: [PATCH 02/20] feat: show usage alert as of threshold --- packages/web/src/components/AppBar/index.tsx | 3 + .../src/components/UsageAlert/index.ee.tsx | 57 +++++++++++++++++++ packages/web/src/config/app.ts | 1 + packages/web/src/hooks/useUsageAlert.ee.ts | 46 +++++++++++++++ packages/web/src/locales/en.json | 6 +- 5 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 packages/web/src/components/UsageAlert/index.ee.tsx create mode 100644 packages/web/src/hooks/useUsageAlert.ee.ts diff --git a/packages/web/src/components/AppBar/index.tsx b/packages/web/src/components/AppBar/index.tsx index cf32768a..dfb4f4d1 100644 --- a/packages/web/src/components/AppBar/index.tsx +++ b/packages/web/src/components/AppBar/index.tsx @@ -12,6 +12,7 @@ import AccountCircleIcon from '@mui/icons-material/AccountCircle'; import * as URLS from 'config/urls'; import AccountDropdownMenu from 'components/AccountDropdownMenu'; +import UsageAlert from 'components/UsageAlert/index.ee'; import Container from 'components/Container'; import { FormattedMessage } from 'react-intl'; import { Link } from './style'; @@ -81,6 +82,8 @@ export default function AppBar(props: AppBarProps): React.ReactElement { + + ( + (props, ref) => , +); + +export default function UsageAlert() { + const formatMessage = useFormatMessage(); + const usageAlert = useUsageAlert(); + + if (!usageAlert.showAlert) return (); + + return ( + + + + + {usageAlert.alertMessage} + + + + + + + + + ); +} diff --git a/packages/web/src/config/app.ts b/packages/web/src/config/app.ts index 184dbe2b..ea01596c 100644 --- a/packages/web/src/config/app.ts +++ b/packages/web/src/config/app.ts @@ -7,6 +7,7 @@ const config: Config = { graphqlUrl: process.env.REACT_APP_GRAPHQL_URL as string, notificationsUrl: process.env.REACT_APP_NOTIFICATIONS_URL as string, chatwootBaseUrl: 'https://app.chatwoot.com', + supportEmailAddress: 'support@automatisch.io' }; export default config; diff --git a/packages/web/src/hooks/useUsageAlert.ee.ts b/packages/web/src/hooks/useUsageAlert.ee.ts new file mode 100644 index 00000000..2bfd802a --- /dev/null +++ b/packages/web/src/hooks/useUsageAlert.ee.ts @@ -0,0 +1,46 @@ +import useFormatMessage from './useFormatMessage'; +import useUsageData from './useUsageData.ee'; +import usePaymentPortalUrl from './usePaymentPortalUrl.ee'; + +type UseUsageAlertReturn = { + showAlert: boolean; + hasExceededLimit?: boolean; + alertMessage?: string; + url?: string; + consumptionPercentage?: number; +}; + +export default function useUsageAlert(): UseUsageAlertReturn { + const { url, loading: paymentPortalUrlLoading } = usePaymentPortalUrl(); + const { + allowedTaskCount, + consumedTaskCount, + nextResetAt, + loading: usageDataLoading + } = useUsageData(); + const formatMessage = useFormatMessage(); + + if (paymentPortalUrlLoading || usageDataLoading) { + return { showAlert: false }; + } + + const hasLoaded = !paymentPortalUrlLoading || usageDataLoading; + const withinUsageThreshold = consumedTaskCount > allowedTaskCount * 0.7; + const consumptionPercentage = consumedTaskCount / allowedTaskCount * 100; + const showAlert = hasLoaded && withinUsageThreshold; + const hasExceededLimit = consumedTaskCount >= allowedTaskCount; + + const alertMessage = formatMessage('usageAlert.informationText', { + allowedTaskCount, + consumedTaskCount, + relativeResetDate: nextResetAt?.toRelative(), + }); + + return { + showAlert, + hasExceededLimit, + alertMessage, + consumptionPercentage, + url, + }; +} diff --git a/packages/web/src/locales/en.json b/packages/web/src/locales/en.json index ddfda549..dee7e9cd 100644 --- a/packages/web/src/locales/en.json +++ b/packages/web/src/locales/en.json @@ -136,5 +136,7 @@ "resetPasswordForm.submit": "Reset password", "resetPasswordForm.passwordFieldLabel": "Password", "resetPasswordForm.confirmPasswordFieldLabel": "Confirm password", - "resetPasswordForm.passwordUpdated": "The password has been updated. Now, you can login." -} + "resetPasswordForm.passwordUpdated": "The password has been updated. Now, you can login.", + "usageAlert.informationText": "Tasks: {consumedTaskCount}/{allowedTaskCount} (Resets {relativeResetDate})", + "usageAlert.viewPlans": "View plans" +} \ No newline at end of file From e67adf87b281811511b1cfd9f64d62f15902588b Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 8 Mar 2023 18:37:42 +0000 Subject: [PATCH 03/20] fix: hide chatwoot in small screens --- .../src/components/LiveChat/Chatwoot.ee.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/web/src/components/LiveChat/Chatwoot.ee.tsx b/packages/web/src/components/LiveChat/Chatwoot.ee.tsx index a45f7f3e..67182189 100644 --- a/packages/web/src/components/LiveChat/Chatwoot.ee.tsx +++ b/packages/web/src/components/LiveChat/Chatwoot.ee.tsx @@ -1,4 +1,6 @@ import * as React from 'react'; +import { useTheme } from '@mui/material/styles'; +import useMediaQuery from '@mui/material/useMediaQuery'; import appConfig from 'config/app'; import useCurrentUser from 'hooks/useCurrentUser'; @@ -8,7 +10,9 @@ type ChatwootProps = { } const Chatwoot = ({ ready }: ChatwootProps) => { + const theme = useTheme(); const currentUser = useCurrentUser(); + const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md')); React.useEffect(function initiateChatwoot() { window.chatwootSDK.run({ @@ -30,8 +34,18 @@ const Chatwoot = ({ ready }: ChatwootProps) => { name: currentUser.fullName, }); - window.$chatwoot.toggleBubbleVisibility("show"); - }, [currentUser, ready]); + if (!matchSmallScreens) { + window.$chatwoot.toggleBubbleVisibility("show"); + } + }, [currentUser, ready, matchSmallScreens]); + + React.useLayoutEffect(function hideChatwoot() { + if (matchSmallScreens) { + window.$chatwoot?.toggleBubbleVisibility('hide'); + } else { + window.$chatwoot?.toggleBubbleVisibility('show'); + } + }, [matchSmallScreens]) return ( From c81db8ae192c83f8e3f63353c15a54d78d1c5c74 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 9 Mar 2023 11:23:29 +0000 Subject: [PATCH 04/20] feat: disable non-cloud features --- .../web/src/components/LoginForm/index.tsx | 10 ++++--- packages/web/src/hooks/useCloud.ts | 15 +++++++++- packages/web/src/index.tsx | 30 ++++++++++--------- .../web/src/pages/ForgotPassword/index.ee.tsx | 4 +++ .../web/src/pages/ResetPassword/index.ee.tsx | 4 +++ packages/web/src/pages/SignUp/index.ee.tsx | 6 +++- 6 files changed, 49 insertions(+), 20 deletions(-) diff --git a/packages/web/src/components/LoginForm/index.tsx b/packages/web/src/components/LoginForm/index.tsx index 7070da10..1c30640a 100644 --- a/packages/web/src/components/LoginForm/index.tsx +++ b/packages/web/src/components/LoginForm/index.tsx @@ -7,6 +7,7 @@ import Typography from '@mui/material/Typography'; import LoadingButton from '@mui/lab/LoadingButton'; import useAuthentication from 'hooks/useAuthentication'; +import useCloud from 'hooks/useCloud'; import * as URLS from 'config/urls'; import { LOGIN } from 'graphql/mutations/login'; import Form from 'components/Form'; @@ -14,6 +15,7 @@ import TextField from 'components/TextField'; import useFormatMessage from 'hooks/useFormatMessage'; function LoginForm() { + const isCloud = useCloud(); const navigate = useNavigate(); const formatMessage = useFormatMessage(); const authentication = useAuthentication(); @@ -76,13 +78,13 @@ function LoginForm() { sx={{ mb: 1 }} /> - {formatMessage('loginForm.forgotPasswordText')} - + } - + {isCloud && {formatMessage('loginForm.noAccount')}   {formatMessage('loginForm.signUp')} - + } ); diff --git a/packages/web/src/hooks/useCloud.ts b/packages/web/src/hooks/useCloud.ts index 43c1360f..bb0226e7 100644 --- a/packages/web/src/hooks/useCloud.ts +++ b/packages/web/src/hooks/useCloud.ts @@ -1,7 +1,20 @@ +import { useNavigate } from 'react-router-dom'; + import useAutomatischInfo from './useAutomatischInfo'; -export default function useCloud(): boolean { +type UseCloudOptions = { + redirect?: boolean; +} + +export default function useCloud(options?: UseCloudOptions): boolean { + const redirect = options?.redirect || false; + const { isCloud } = useAutomatischInfo(); + const navigate = useNavigate(); + + if (isCloud === false && redirect) { + navigate('/'); + } return isCloud; } diff --git a/packages/web/src/index.tsx b/packages/web/src/index.tsx index 502bd72e..c2cd7099 100644 --- a/packages/web/src/index.tsx +++ b/packages/web/src/index.tsx @@ -12,21 +12,23 @@ import routes from 'routes'; import reportWebVitals from './reportWebVitals'; ReactDOM.render( - - - - - - - {routes} + + + + + + + + {routes} - - - - - - - , + + + + + + + + , document.getElementById('root') ); diff --git a/packages/web/src/pages/ForgotPassword/index.ee.tsx b/packages/web/src/pages/ForgotPassword/index.ee.tsx index 4b75e703..1c1a27cf 100644 --- a/packages/web/src/pages/ForgotPassword/index.ee.tsx +++ b/packages/web/src/pages/ForgotPassword/index.ee.tsx @@ -1,9 +1,13 @@ import * as React from 'react'; import Box from '@mui/material/Box'; + +import useCloud from 'hooks/useCloud'; import Container from 'components/Container'; import ForgotPasswordForm from 'components/ForgotPasswordForm/index.ee'; export default function ForgotPassword(): React.ReactElement { + useCloud({ redirect: true }); + return ( diff --git a/packages/web/src/pages/ResetPassword/index.ee.tsx b/packages/web/src/pages/ResetPassword/index.ee.tsx index eb41213e..8dfb0664 100644 --- a/packages/web/src/pages/ResetPassword/index.ee.tsx +++ b/packages/web/src/pages/ResetPassword/index.ee.tsx @@ -1,9 +1,13 @@ import * as React from 'react'; import Box from '@mui/material/Box'; + +import useCloud from 'hooks/useCloud'; import Container from 'components/Container'; import ResetPasswordForm from 'components/ResetPasswordForm/index.ee'; export default function ResetPassword(): React.ReactElement { + useCloud({ redirect: true }); + return ( diff --git a/packages/web/src/pages/SignUp/index.ee.tsx b/packages/web/src/pages/SignUp/index.ee.tsx index afe51ddb..7d314164 100644 --- a/packages/web/src/pages/SignUp/index.ee.tsx +++ b/packages/web/src/pages/SignUp/index.ee.tsx @@ -1,9 +1,13 @@ import * as React from 'react'; import Box from '@mui/material/Box'; + +import useCloud from 'hooks/useCloud'; import Container from 'components/Container'; import SignUpForm from 'components/SignUpForm/index.ee'; -export default function Login(): React.ReactElement { +export default function SignUp(): React.ReactElement { + useCloud({ redirect: true }); + return ( From 8098a7ee5dd6480fd5edbd07bae019e9b3aa3e98 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 8 Mar 2023 20:22:06 +0000 Subject: [PATCH 05/20] feat: add checkIfLimitExceeded in UsageData model --- packages/backend/src/models/usage-data.ee.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/backend/src/models/usage-data.ee.ts b/packages/backend/src/models/usage-data.ee.ts index 558fe036..2e508f12 100644 --- a/packages/backend/src/models/usage-data.ee.ts +++ b/packages/backend/src/models/usage-data.ee.ts @@ -1,12 +1,14 @@ import { raw } from 'objection'; import Base from './base'; import User from './user'; +import PaymentPlan from './payment-plan.ee'; class UsageData extends Base { id!: string; userId!: string; consumedTaskCount!: number; nextResetAt!: string; + paymentPlan?: PaymentPlan; static tableName = 'usage_data'; @@ -31,8 +33,22 @@ class UsageData extends Base { to: 'users.id', }, }, + paymentPlan: { + relation: Base.BelongsToOneRelation, + modelClass: PaymentPlan, + join: { + from: 'usage_data.user_id', + to: 'payment_plans.user_id', + }, + }, }); + async checkIfLimitExceeded() { + const paymentPlan = await this.$relatedQuery('paymentPlan'); + + return this.consumedTaskCount >= paymentPlan.taskCount; + } + async increaseConsumedTaskCountByOne() { return await this.$query().patch({ consumedTaskCount: raw('consumed_task_count + 1') }); } From 92d1ed65ff5bfdb3974db45b6116d5d77a55cd71 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 8 Mar 2023 20:23:05 +0000 Subject: [PATCH 06/20] feat: expose errors from webhooks --- packages/backend/src/routes/webhooks.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/routes/webhooks.ts b/packages/backend/src/routes/webhooks.ts index cdf8f705..3bda1138 100644 --- a/packages/backend/src/routes/webhooks.ts +++ b/packages/backend/src/routes/webhooks.ts @@ -1,5 +1,6 @@ -import express, { Router } from 'express'; +import express, { Response, Router, NextFunction, RequestHandler } from 'express'; import multer from 'multer'; + import { IRequest } from '@automatisch/types'; import appConfig from '../config/app'; import webhookHandler from '../controllers/webhooks/handler'; @@ -16,9 +17,17 @@ router.use(express.text({ }, })); -router.get('/:flowId', webhookHandler); -router.put('/:flowId', webhookHandler); -router.patch('/:flowId', webhookHandler); -router.post('/:flowId', webhookHandler); +const exposeError = (handler: RequestHandler) => async (req: IRequest, res: Response, next: NextFunction) => { + try { + await handler(req, res, next); + } catch (err) { + res.send(err); + } +} + +router.get('/:flowId', exposeError(webhookHandler)); +router.put('/:flowId', exposeError(webhookHandler)); +router.patch('/:flowId', exposeError(webhookHandler)); +router.post('/:flowId', exposeError(webhookHandler)); export default router; From 54e68f62527e73bec3fad550944fb6cb91749e37 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 8 Mar 2023 20:24:59 +0000 Subject: [PATCH 07/20] feat: skip processing tasks over task quota --- .../backend/src/controllers/webhooks/handler.ts | 5 +++++ packages/backend/src/models/flow.ts | 16 ++++++++++++++++ packages/backend/src/routes/webhooks.ts | 2 +- packages/backend/src/services/action.ts | 13 ++++++++++--- packages/backend/src/services/flow.ts | 10 ++++++++-- 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/controllers/webhooks/handler.ts b/packages/backend/src/controllers/webhooks/handler.ts index ed8684dd..5ac8a160 100644 --- a/packages/backend/src/controllers/webhooks/handler.ts +++ b/packages/backend/src/controllers/webhooks/handler.ts @@ -14,6 +14,11 @@ export default async (request: IRequest, response: Response) => { .throwIfNotFound(); const testRun = !flow.active; + + if (!testRun) { + await flow.throwIfQuotaExceeded(); + } + const triggerStep = await flow.getTriggerStep(); const triggerCommand = await triggerStep.getTriggerCommand(); const app = await triggerStep.getApp(); diff --git a/packages/backend/src/models/flow.ts b/packages/backend/src/models/flow.ts index e0868b97..7a96b6c1 100644 --- a/packages/backend/src/models/flow.ts +++ b/packages/backend/src/models/flow.ts @@ -1,5 +1,6 @@ import { ValidationError } from 'objection'; import type { ModelOptions, QueryContext } from 'objection'; +import appConfig from '../config/app'; import ExtendedQueryBuilder from './query-builder'; import Base from './base'; import Step from './step'; @@ -129,6 +130,21 @@ class Flow extends Base { type: 'trigger', }); } + + async throwIfQuotaExceeded() { + if (!appConfig.isCloud) return; + + const user = await this.$relatedQuery('user'); + const usageData = await user.$relatedQuery('usageData'); + + const hasExceeded = await usageData.checkIfLimitExceeded(); + + if (hasExceeded) { + throw new Error('The allowed task quota has been exhausted!'); + } + + return this; + } } export default Flow; diff --git a/packages/backend/src/routes/webhooks.ts b/packages/backend/src/routes/webhooks.ts index 3bda1138..98191a0a 100644 --- a/packages/backend/src/routes/webhooks.ts +++ b/packages/backend/src/routes/webhooks.ts @@ -21,7 +21,7 @@ const exposeError = (handler: RequestHandler) => async (req: IRequest, res: Resp try { await handler(req, res, next); } catch (err) { - res.send(err); + res.status(422).send(err); } } diff --git a/packages/backend/src/services/action.ts b/packages/backend/src/services/action.ts index f1d13373..79af8b9f 100644 --- a/packages/backend/src/services/action.ts +++ b/packages/backend/src/services/action.ts @@ -1,3 +1,4 @@ +import appConfig from '../config/app'; import Step from '../models/step'; import Flow from '../models/flow'; import Execution from '../models/execution'; @@ -17,17 +18,23 @@ type ProcessActionOptions = { export const processAction = async (options: ProcessActionOptions) => { const { flowId, stepId, executionId } = options; - const step = await Step.query().findById(stepId).throwIfNotFound(); + const flow = await Flow.query().findById(flowId).throwIfNotFound(); const execution = await Execution.query() .findById(executionId) .throwIfNotFound(); + if (!execution.testRun) { + await flow.throwIfQuotaExceeded(); + } + + const step = await Step.query().findById(stepId).throwIfNotFound(); + const $ = await globalVariable({ - flow: await Flow.query().findById(flowId).throwIfNotFound(), + flow, app: await step.getApp(), step: step, connection: await step.$relatedQuery('connection'), - execution: execution, + execution, }); const priorExecutionSteps = await ExecutionStep.query().where({ diff --git a/packages/backend/src/services/flow.ts b/packages/backend/src/services/flow.ts index 9ff6d6fc..4ace0599 100644 --- a/packages/backend/src/services/flow.ts +++ b/packages/backend/src/services/flow.ts @@ -1,3 +1,4 @@ +import appConfig from '../config/app'; import Flow from '../models/flow'; import globalVariable from '../helpers/global-variable'; import EarlyExitError from '../errors/early-exit'; @@ -10,7 +11,12 @@ type ProcessFlowOptions = { }; export const processFlow = async (options: ProcessFlowOptions) => { - const flow = await Flow.query().findById(options.flowId).throwIfNotFound(); + const { testRun, flowId } = options; + const flow = await Flow.query().findById(flowId).throwIfNotFound(); + + if (!testRun) { + await flow.throwIfQuotaExceeded(); + } const triggerStep = await flow.getTriggerStep(); const triggerCommand = await triggerStep.getTriggerCommand(); @@ -20,7 +26,7 @@ export const processFlow = async (options: ProcessFlowOptions) => { connection: await triggerStep.$relatedQuery('connection'), app: await triggerStep.getApp(), step: triggerStep, - testRun: options.testRun, + testRun, }); try { From ae3512fecf5d7ba02165c01cb6a50007a9dffd6d Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 9 Mar 2023 15:13:50 +0000 Subject: [PATCH 08/20] feat: don't interrupt execution due to quota --- packages/backend/src/models/flow.ts | 14 +++++++++++++- packages/backend/src/services/action.ts | 5 ----- packages/backend/src/services/flow.ts | 6 ------ packages/backend/src/workers/flow.ts | 7 +++++++ 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/packages/backend/src/models/flow.ts b/packages/backend/src/models/flow.ts index 7a96b6c1..90abceef 100644 --- a/packages/backend/src/models/flow.ts +++ b/packages/backend/src/models/flow.ts @@ -131,7 +131,7 @@ class Flow extends Base { }); } - async throwIfQuotaExceeded() { + async checkIfQuotaExceeded() { if (!appConfig.isCloud) return; const user = await this.$relatedQuery('user'); @@ -139,6 +139,18 @@ class Flow extends Base { const hasExceeded = await usageData.checkIfLimitExceeded(); + if (hasExceeded) { + return true; + } + + return false; + } + + async throwIfQuotaExceeded() { + if (!appConfig.isCloud) return; + + const hasExceeded = await this.checkIfQuotaExceeded(); + if (hasExceeded) { throw new Error('The allowed task quota has been exhausted!'); } diff --git a/packages/backend/src/services/action.ts b/packages/backend/src/services/action.ts index 79af8b9f..0b1caf51 100644 --- a/packages/backend/src/services/action.ts +++ b/packages/backend/src/services/action.ts @@ -1,4 +1,3 @@ -import appConfig from '../config/app'; import Step from '../models/step'; import Flow from '../models/flow'; import Execution from '../models/execution'; @@ -23,10 +22,6 @@ export const processAction = async (options: ProcessActionOptions) => { .findById(executionId) .throwIfNotFound(); - if (!execution.testRun) { - await flow.throwIfQuotaExceeded(); - } - const step = await Step.query().findById(stepId).throwIfNotFound(); const $ = await globalVariable({ diff --git a/packages/backend/src/services/flow.ts b/packages/backend/src/services/flow.ts index 4ace0599..a0b97baf 100644 --- a/packages/backend/src/services/flow.ts +++ b/packages/backend/src/services/flow.ts @@ -1,4 +1,3 @@ -import appConfig from '../config/app'; import Flow from '../models/flow'; import globalVariable from '../helpers/global-variable'; import EarlyExitError from '../errors/early-exit'; @@ -13,11 +12,6 @@ type ProcessFlowOptions = { export const processFlow = async (options: ProcessFlowOptions) => { const { testRun, flowId } = options; const flow = await Flow.query().findById(flowId).throwIfNotFound(); - - if (!testRun) { - await flow.throwIfQuotaExceeded(); - } - const triggerStep = await flow.getTriggerStep(); const triggerCommand = await triggerStep.getTriggerCommand(); diff --git a/packages/backend/src/workers/flow.ts b/packages/backend/src/workers/flow.ts index 7ca05782..04d4bf00 100644 --- a/packages/backend/src/workers/flow.ts +++ b/packages/backend/src/workers/flow.ts @@ -12,6 +12,13 @@ export const worker = new Worker( const { flowId } = job.data; const flow = await Flow.query().findById(flowId).throwIfNotFound(); + + const quotaExceeded = await flow.checkIfQuotaExceeded(); + + if (quotaExceeded) { + return; + } + const triggerStep = await flow.getTriggerStep(); const { data, error } = await processFlow({ flowId }); From 37e0091ef0bede9af10976633be7fcaa63ed5820 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 9 Mar 2023 15:17:05 +0000 Subject: [PATCH 09/20] feat: open payment portal in same window --- packages/web/src/components/UsageAlert/index.ee.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/web/src/components/UsageAlert/index.ee.tsx b/packages/web/src/components/UsageAlert/index.ee.tsx index fe9a4653..ee6e391b 100644 --- a/packages/web/src/components/UsageAlert/index.ee.tsx +++ b/packages/web/src/components/UsageAlert/index.ee.tsx @@ -9,10 +9,6 @@ import LinearProgress from '@mui/material/LinearProgress'; import useFormatMessage from 'hooks/useFormatMessage'; import useUsageAlert from 'hooks/useUsageAlert.ee'; -const LinkBehavior = React.forwardRef( - (props, ref) => , -); - export default function UsageAlert() { const formatMessage = useFormatMessage(); const usageAlert = useUsageAlert(); @@ -38,7 +34,6 @@ export default function UsageAlert() {