Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(flow): add virtual paused/published/draft status #1050

Merged
merged 2 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/backend/src/config/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type AppConfig = {
smtpPassword: string;
fromEmail: string;
isCloud: boolean;
isSelfHosted: boolean;
paddleVendorId: number;
paddleVendorAuthCode: string;
paddlePublicKey: string;
Expand Down Expand Up @@ -110,6 +111,7 @@ const appConfig: AppConfig = {
smtpPassword: process.env.SMTP_PASSWORD,
fromEmail: process.env.FROM_EMAIL,
isCloud: process.env.AUTOMATISCH_CLOUD === 'true',
isSelfHosted: process.env.AUTOMATISCH_CLOUD !== 'true',
paddleVendorId: Number(process.env.PADDLE_VENDOR_ID),
paddleVendorAuthCode: process.env.PADDLE_VENDOR_AUTH_CODE,
paddlePublicKey: process.env.PADDLE_PUBLIC_KEY,
Expand Down
19 changes: 13 additions & 6 deletions packages/backend/src/controllers/webhooks/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,24 @@ import Flow from '../../models/flow';
import { processTrigger } from '../../services/trigger';
import actionQueue from '../../queues/action';
import globalVariable from '../../helpers/global-variable';
import { REMOVE_AFTER_30_DAYS_OR_150_JOBS, REMOVE_AFTER_7_DAYS_OR_50_JOBS } from '../../helpers/remove-job-configuration';
import QuotaExceededError from '../../errors/quote-exceeded';
import {
REMOVE_AFTER_30_DAYS_OR_150_JOBS,
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
} from '../../helpers/remove-job-configuration';

export default async (request: IRequest, response: Response) => {
const flow = await Flow.query()
.findById(request.params.flowId)
.throwIfNotFound();

const user = await flow.$relatedQuery('user');

const testRun = !flow.active;
const quotaExceeded = !testRun && !(await user.isAllowedToRunFlows());

if (!testRun) {
await flow.throwIfQuotaExceeded();
if (quotaExceeded) {
throw new QuotaExceededError();
}

const triggerStep = await flow.getTriggerStep();
Expand Down Expand Up @@ -58,7 +65,7 @@ export default async (request: IRequest, response: Response) => {
headers: request.headers,
body: request.body,
query: request.query,
}
};

rawInternalId = JSON.stringify(payload);
}
Expand All @@ -74,7 +81,7 @@ export default async (request: IRequest, response: Response) => {
flowId: flow.id,
stepId: triggerStep.id,
triggerItem,
testRun
testRun,
});

if (testRun) {
Expand All @@ -93,7 +100,7 @@ export default async (request: IRequest, response: Response) => {
const jobOptions = {
removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS,
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS,
}
};

await actionQueue.add(jobName, jobPayload, jobOptions);

Expand Down
7 changes: 7 additions & 0 deletions packages/backend/src/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -250,13 +250,20 @@ type FlowEdge {
node: Flow
}

enum FlowStatus {
paused
published
draft
}

type Flow {
id: String
name: String
active: Boolean
steps: [Step]
createdAt: String
updatedAt: String
status: FlowStatus
}

type Execution {
Expand Down
55 changes: 28 additions & 27 deletions packages/backend/src/models/flow.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { ValidationError } from 'objection';
import type { ModelOptions, QueryContext } from 'objection';
import appConfig from '../config/app';
import type {
ModelOptions,
QueryContext,
StaticHookArguments,
} from 'objection';
import ExtendedQueryBuilder from './query-builder';
import Base from './base';
import Step from './step';
import User from './user';
import Execution from './execution';
import Telemetry from '../helpers/telemetry';
import QuotaExceededError from '../errors/quote-exceeded';

class Flow extends Base {
id!: string;
name!: string;
userId!: string;
active: boolean;
status: 'paused' | 'published' | 'draft';
steps: Step[];
published_at: string;
remoteWebhookId: string;
Expand Down Expand Up @@ -65,6 +68,26 @@ class Flow extends Base {
},
});

static async afterFind(args: StaticHookArguments<any>): Promise<any> {
const { result } = args;

const referenceFlow = result[0];

if (referenceFlow) {
const shouldBePaused = await referenceFlow.isPaused();

for (const flow of result) {
if (!flow.active) {
flow.status = 'draft';
} else if (flow.active && shouldBePaused) {
flow.status = 'paused';
} else {
flow.status = 'published';
}
}
}
}

async lastInternalId() {
const lastExecution = await this.$relatedQuery('executions')
.orderBy('created_at', 'desc')
Expand Down Expand Up @@ -132,31 +155,9 @@ class Flow extends Base {
});
}

async checkIfQuotaExceeded() {
if (!appConfig.isCloud) return;

async isPaused() {
const user = await this.$relatedQuery('user');
const usageData = await user.$relatedQuery('currentUsageData');

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 QuotaExceededError();
}

return this;
return await user.isAllowedToRunFlows();
}
}

Expand Down
19 changes: 0 additions & 19 deletions packages/backend/src/models/usage-data.ee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { raw } from 'objection';
import Base from './base';
import User from './user';
import Subscription from './subscription.ee';
import { getPlanById } from '../helpers/billing/plans.ee';

class UsageData extends Base {
id!: string;
Expand Down Expand Up @@ -47,24 +46,6 @@ class UsageData extends Base {
},
});

async checkIfLimitExceeded() {
const user = await this.$relatedQuery('user');

if (await user.inTrial()) {
return false;
}

const subscription = await this.$relatedQuery('subscription');

if (!subscription.isActive) {
return true;
}

const plan = subscription.plan;

return this.consumedTaskCount >= plan.quota;
}

async increaseConsumedTaskCountByOne() {
return await this.$query().patch({
consumedTaskCount: raw('consumed_task_count + 1'),
Expand Down
36 changes: 30 additions & 6 deletions packages/backend/src/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,18 +165,24 @@ class User extends Base {
this.trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate();
}

async hasActiveSubscription() {
if (!appConfig.isCloud) {
return false;
async isAllowedToRunFlows() {
if (appConfig.isSelfHosted) {
return true;
}

const subscription = await this.$relatedQuery('currentSubscription');
if (await this.inTrial()) {
return true;
}

return subscription?.isActive;
if ((await this.hasActiveSubscription()) && (await this.withinLimits())) {
return true;
}

return false;
}

async inTrial() {
if (!appConfig.isCloud) {
if (appConfig.isSelfHosted) {
return false;
}

Expand All @@ -196,6 +202,24 @@ class User extends Base {
return now < expiryDate;
}

async hasActiveSubscription() {
if (!appConfig.isCloud) {
return false;
}

const subscription = await this.$relatedQuery('currentSubscription');

return subscription?.isActive;
}

async withinLimits() {
const currentSubscription = await this.$relatedQuery('currentSubscription');
const plan = currentSubscription.plan;
const currentUsageData = await this.$relatedQuery('currentUsageData');

return currentUsageData.consumedTaskCount >= plan.quota;
}

async $beforeInsert(queryContext: QueryContext) {
await super.$beforeInsert(queryContext);
await this.generateHash();
Expand Down
6 changes: 3 additions & 3 deletions packages/backend/src/workers/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ export const worker = new Worker(
const { flowId } = job.data;

const flow = await Flow.query().findById(flowId).throwIfNotFound();
const user = await flow.$relatedQuery('user');
const allowedToRunFlows = await user.isAllowedToRunFlows();

const quotaExceeded = await flow.checkIfQuotaExceeded();

if (quotaExceeded) {
if (!allowedToRunFlows) {
return;
}

Expand Down
1 change: 1 addition & 0 deletions packages/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export interface IFlow {
name: string;
userId: string;
active: boolean;
status: 'paused' | 'published' | 'draft';
steps: IStep[];
createdAt: string;
updatedAt: string;
Expand Down
24 changes: 22 additions & 2 deletions packages/web/src/components/FlowRow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,26 @@ type FlowRowProps = {
flow: IFlow;
};

function getFlowStatusTranslationKey(status: IFlow["status"]): string {
if (status === 'published') {
return 'flow.published';
} else if (status === 'paused') {
return 'flow.paused';
}

return 'flow.draft';
}

function getFlowStatusColor(status: IFlow["status"]): 'default' | 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning' {
if (status === 'published') {
return 'success';
} else if (status === 'paused') {
return 'error';
}

return 'info';
}

export default function FlowRow(props: FlowRowProps): React.ReactElement {
const formatMessage = useFormatMessage();
const contextButtonRef = React.useRef<HTMLButtonElement | null>(null);
Expand Down Expand Up @@ -76,10 +96,10 @@ export default function FlowRow(props: FlowRowProps): React.ReactElement {
<ContextMenu>
<Chip
size="small"
color={flow?.active ? 'success' : 'info'}
color={getFlowStatusColor(flow?.status)}
variant={flow?.active ? 'filled' : 'outlined'}
label={formatMessage(
flow?.active ? 'flow.published' : 'flow.draft'
getFlowStatusTranslationKey(flow?.status)
)}
/>

Expand Down
1 change: 1 addition & 0 deletions packages/web/src/graphql/queries/get-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const GET_FLOW = gql`
id
name
active
status
steps {
id
type
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/graphql/queries/get-flows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const GET_FLOWS = gql`
createdAt
updatedAt
active
status
steps {
iconUrl
}
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"flow.active": "ON",
"flow.inactive": "OFF",
"flow.published": "Published",
"flow.paused": "Paused",
"flow.draft": "Draft",
"flow.successfullyDeleted": "The flow and associated executions have been deleted.",
"flowEditor.publish": "PUBLISH",
Expand Down