Add automation run tracking with quota enforcement

- Add track-run action to ActivePieces smoothschedule piece
- Add webhook endpoint to receive run tracking from automations
- Update quota service to increment automation_runs_used count
- Add Celery task for async run tracking
- Update default flows to include track-run step

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-22 02:30:04 -05:00
parent dd24eede87
commit 28d6cee207
11 changed files with 627 additions and 160 deletions

View File

@@ -1,7 +1,7 @@
import { PieceAuth, createPiece } from '@activepieces/pieces-framework';
import { PieceCategory } from '@activepieces/shared';
import { createCustomApiCallAction } from '@activepieces/pieces-common';
import { createEventAction, findEventsAction, updateEventAction, cancelEventAction } from './lib/actions';
import { createEventAction, findEventsAction, updateEventAction, cancelEventAction, trackRunAction } from './lib/actions';
import { listResourcesAction } from './lib/actions/list-resources';
import { listServicesAction } from './lib/actions/list-services';
import { listInactiveCustomersAction } from './lib/actions/list-inactive-customers';
@@ -68,6 +68,7 @@ export const smoothSchedule = createPiece({
minimumSupportedRelease: '0.36.1',
authors: ['smoothschedule'],
actions: [
trackRunAction,
createEventAction,
updateEventAction,
cancelEventAction,

View File

@@ -6,3 +6,4 @@ export * from './list-resources';
export * from './list-services';
export * from './list-inactive-customers';
export * from './list-customers';
export * from './track-run';

View File

@@ -0,0 +1,86 @@
import { createAction } from '@activepieces/pieces-framework';
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
import { smoothScheduleAuth, SmoothScheduleAuth } from '../../index';
interface TrackRunResponse {
success: boolean;
runs_this_month: number;
limit: number;
remaining: number;
}
/**
* Track Automation Run Action
*
* This action should be placed at the beginning of each automation flow
* to track executions for quota management. It increments the run counter
* for the current flow and returns quota information.
*
* The action:
* 1. Gets the current flow ID from the context
* 2. Calls the SmoothSchedule track-run API endpoint
* 3. Returns quota usage information
*/
export const trackRunAction = createAction({
auth: smoothScheduleAuth,
name: 'track_run',
displayName: 'Track Run',
description:
'Track this automation execution for quota management. Place at the start of each flow.',
props: {},
async run(context) {
const auth = context.auth as SmoothScheduleAuth;
// Get the current flow ID from the Activepieces context
const flowId = context.flows.current.id;
// Build the URL for the track-run endpoint
// The track-run endpoint is at /api/activepieces/track-run/
const url = new URL(auth.props.baseUrl);
let hostHeader = `${url.hostname}${url.port ? ':' + url.port : ''}`;
// Map docker hostname to lvh.me (which Django recognizes)
if (url.hostname === 'django') {
hostHeader = `lvh.me${url.port ? ':' + url.port : ''}`;
}
const trackRunUrl = `${auth.props.baseUrl}/api/activepieces/track-run/`;
try {
const response = await httpClient.sendRequest<TrackRunResponse>({
method: HttpMethod.POST,
url: trackRunUrl,
body: {
flow_id: flowId,
},
headers: {
'X-Tenant': auth.props.subdomain,
Host: hostHeader,
'Content-Type': 'application/json',
},
});
return {
success: response.body.success,
runs_this_month: response.body.runs_this_month,
limit: response.body.limit,
remaining: response.body.remaining,
message:
response.body.limit < 0
? 'Unlimited automation runs'
: `${response.body.remaining} automation runs remaining this month`,
};
} catch (error) {
// Log the error but don't fail the flow - tracking is non-critical
console.error('Failed to track automation run:', error);
return {
success: false,
runs_this_month: -1,
limit: -1,
remaining: -1,
message: 'Failed to track run (flow will continue)',
error: error instanceof Error ? error.message : String(error),
};
}
},
});