Compare commits

...

101 Commits

Author SHA1 Message Date
Matt Aitken 7bea9ebfb0 A task for reproducing a race condition with checkpoints
🚀 Publish Trigger.dev Docker / units (push) Failing after 11m17s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 11m17s
🚀 Publish Trigger.dev Docker / publish (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-infra (push) Has been skipped
2024-09-12 11:25:09 +01:00
Matt Aitken 639525935c Added more logs when resuming a dependency, added the runId 2024-09-12 11:24:06 +01:00
Matt Aitken 92c7341bd3 Ignore /packages/cli-v3/src/package.json 2024-09-11 17:54:49 +01:00
James Ritchie 9c61a25402 Docs/task examples (#1293)
* WIP first example task guide

* Added example link to github repo

* Created a top level Examples docs section

* Renamed side menu to examples for now
2024-09-11 17:37:49 +01:00
James Ritchie 972b5e34b8 Docs/more improvements (#1292)
* WIP creating a CLI section

* WIP adding more CLI commands in a new section

* New CLI section with introduction and more CLI commands

* More improvement to the CLI options

* Each CLI command has its own page

* fixed broken links

* Removed outdated copy
2024-09-11 17:14:39 +01:00
Eric Allam 47ca24aab8 Update new build sytem docs 2024-09-11 15:57:22 +01:00
Eric Allam 8578c9b281 v3: new build system fixes round 2 (#1283)
* Fixed empty env vars overriding in dev runs

* Don’t import package.json anymore

* fix node10 moduleResolution in @trigger.dev/core

* Support self-hosters pushing to a custom registry when running deploy

* dev: Fixed stuck runs when a child run fails with a process exit

* Make some doc notes about known issues and docker hub private repos

* Fix --project-ref when running deploy

* Fix —config option when deploying

* Fixing the flushing/killing process with the new build system

* Add monorepo-react-email e2e test fixture

* Fix issue with emitDecoratorMetadata and tsconfigs with extends

* Got the emit decorator metadata fixture working

* Fixed typechecking yarn e2e CLI tests in monorepos

* Add remote forced externals system, in case we come across another package that cannot be bundled (spurred on by header-generator)

* Remote externals now powered by JSON Hero to be easier to update

* resolve config source files

* Add a —javascript option to init, defaults to typescript

* Add support for prisma typed sql

* Remove msw and retry.interceptFetch

* Add missing code to the openai retries example

* Don’t generate the v3 catalog prisma client during CI

* Fixed v3-catalog task imports

* Remove interceptor usage in task file

* Only import import-in-the-middle hook if there are instrumented packages

* Fix yarn.lock file
2024-09-11 15:48:20 +01:00
Matt Aitken 8bb81ec73f The usage tasks table was showing double all values if you had a staging env… 🤦‍♂️ 2024-09-11 11:39:08 +01:00
Matt Aitken c46e3d528e Fix for BigInt values getting stripped from log properties 2024-09-11 11:20:22 +01:00
Matt Aitken 93bc32ca87 Checkpoint restoring should of triggerAndWait should use the child run 2024-09-10 15:46:24 +01:00
Matt Aitken 36397d0cb1 Added more logging when finalizing runs and when messages don’t exist in marqs 2024-09-10 14:52:32 +01:00
nicktrn 90bf01fb23 Decrease cpu requests by 25% 2024-09-10 14:28:05 +01:00
Matt Peck 15ed74b63e Allow custom docker network (#1291) 2024-09-09 22:54:27 +01:00
Matt Aitken 392453e8a6 Frozen run fixes (#1286)
* When resuming a batch, only do marqs operations once

* Made TaskRunDependency clearer in the Prisma schema

* New ResumeDependentParentsService service, use it from checkpoints

* WIP on making resuming more robust

* Turn the declarative schedules off because they make debugging other runs painful

* Resuming batches when there’s an attempt is working

* If there’s no attempt then create one

* Added a log if there are no span events to complete

* If Graphile addJob doesn’t return a row, log and return undefined. No throw

* Pass prisma into the ResumeDependentParentsService

* Removed the todos

* Pass Prisma through to the checkpoint service

* Fix for not checking the batch item correctly

* Fix for when a log flush times out and the process is checkpointed

* Fix for when a log flush times out and the process is checkpointed

* Another test run that does batches with failed subtasks

* Don’t call ResumeTaskRunDependenciesService anymore (we have a new service)

* Only resume if the run is in a final state

* If an attempt doesn’t exist, fix for creating queue with sanitized name

* If DEV then don’t resume using marqs/batches. The CLI manages it

* We don’t need to check the run status again, it’s in the main function now

* Added TaskRunAttempt taskRunId index

* Only allow calling ResumeDependentParentsService with a run ID

* Put the flushing back to what it was
2024-09-09 16:03:36 +01:00
Elie Steinbock e5f0aafb14 Include uploadthing in .env.example for references (#1288) 2024-09-08 10:28:16 +01:00
Elie Steinbock 05cdc6993c Remove astro as recommended extension (#1285) 2024-09-06 16:36:15 +01:00
James Ritchie 569dcf4ef4 Docs improvements (#1284)
* Separated the triggering docs page into 2 sections

* Added link to GitHub Actions from deploy page

* Improved the batchTriggerAndWait docs for handling errors

* Added docs for using google credential files

* Improved the triggering page copy some more

* Improved the google credentials copy
2024-09-06 14:26:58 +01:00
Eric Allam 87e46d497b projects now have a dedicated build server (#1281) 2024-09-06 09:36:03 +01:00
James Ritchie 022bbe1371 Fixed type issues with emails 2024-09-05 17:22:58 +01:00
James Ritchie 3e0881c2e9 Added project name to run alert email 2024-09-05 15:33:55 +01:00
Matt Aitken ec3c300cac Put the database command back in with wal_level logical 2024-09-05 14:18:21 +01:00
Matt Aitken 3f182cd145 Latest lockfile 2024-09-05 14:18:01 +01:00
Matt Aitken 09e6f58e84 Revert "ElectricSQL run page and react-window-splitter (#1277)"
This reverts commit 5231381717.

# Conflicts:
#	pnpm-lock.yaml
2024-09-05 14:13:16 +01:00
Eric Allam aa79b01f7e Update docs with new 0.0.0-prerelease-20240905123715 prerelease 2024-09-05 13:46:24 +01:00
Eric Allam f040417440 v3: new build system fixes (#1278)
* Support custom config file names & paths

* Fix entry point paths on windows

* Support custom conditions

Add support for custom conditions (for bundling and running), to support being able to import `ai/rsc` with the “react-server” condition.

- Fixed an issue where symlinking unresolvable externals after rebuilding caused the build to hang
- Fixed an issue with external not working with subpath exports (e.g. “ai” would not match “ai/rsc”)
- Protect better against build extensions breaking builds

* Add changeset

* Fix passing CLI process.env down to the task processes

* Remove unused import

* reviving the e2e CLI tests

* Another attempt at fixing windows

* yet another windows attempt (yawa)

* Output index child stdout and stderr (yawa)

* normalize import paths for windows

* Added some logging (yawa)

* normalize the loader path as well

* Added some logging to figure out why the entry points aren’t being found on windows

* Fix for entry point detection on windows

* Normalize runner import loader path

* Normalize import paths in dev and make sure rewritten build manifest paths are correct on windows as well

* Various cleanup after windows fixes

* Remove the webapp e2e for now

* Add node10 type resolution support for subpath exports (still does not actually import in Node 10 or Browserify/Parcel)

https://github.com/andrewbranch/example-subpath-exports-ts-compat

* init using templates again but downloaded from the repo this time

* Adding init schedule example

* Support for js init

* init now working with js

* Fix issues with links in terminals that don’t support them. Also skip update check of version starts with 0.0.0
2024-09-05 12:56:23 +01:00
Matt Aitken 5231381717 ElectricSQL run page and react-window-splitter (#1277)
* WIP on using react-window-splitter

* WIP with new resizable panels and SSR

* Use the cookie package

* Resizable storybook page

* Increase indexing memory limit

* Fixed v2 usage meter displaying when on paid plan (#1255)

* Fixed v2 usage meter displaying when on paid plan

* Show the free usage panel only for v3 projects

* Concurrency page and more accurate tracking (#1252)

* Initial TaskRunConcurrencyTracker implementation

* MARQS calls a subscriber to events

* When enqueuing add the extra required metadata

* Track concurrency per environment for tasks too

* Admin page for global concurrency

* Use the new concurrency tracker on the tasks page

* Useful performance test task

* getAllTaskIdentifiers()

* New page for concurrency

* BackgroundWorkerTask index for quick lookup of task identifiers

* Added a way to get concurrency for environments

* Added upgrade/request more concurrency button

* Queued task column working

* Use defer and suspense

* Added queue column to the concurrency environments table

* Some comments added for clarity

* Fixed bad log message

* Sidemenu: move lower and rename to “Concurrency limits”

* Only show the environments, not tasks. Renamed to “Concurrency limits”

* v3: fix unfreezable state crashes for runs with multiple waits (#1253)

* support named capture groups

* write crash errors to attempt.error

* make restored pod names unique per checkpoint

* use last eight characters of checkpoint id instead

* add more chaos monkey env vars

* Ignore unfreezable states

* prevent excessive queue config parsing errors

* handle dependency resume edge case

* better entry point logging

* ignore checkpoint cancellation timeouts

* add missing idempotency keys to wait for dep replays

* remove checkpoints between attempts

* fix retry container names on kubernetes

* add changeset

* fix types

* bring back internal duration timers

* Added more logging to TaskRunConcurrencyTracker and some more try/catches

* Call subscriber.messageDequeued in dequeueMessageInSharedQueue

* Added messageReplaced to concurrency tracking (when freezing)

* Added depenenciesToBundle guide to bundle all packages

* Include the old message data when replacing, so we get the projectId etc.

* Fix restored container names

* Fix for schedule page not scrolling

* Added a description panel to the Concurrency admin page

* chore: Update version for release (beta) (#1256)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* Release 3.0.0-beta.53

* Added a note to use batchTrigger() instead of trigger()

* The latest react-window-splitter fixes the ESM issues

* Set sensible defaults for the run page

* Deployments page

* Test page

* Schedules page

* Latest version of react-window-splitter (0.2.5)

* Updated to the latest version: react-window-splitter

* Callout if runs don’t start right away now has some top margin

* Small padding fix

* styled the handle focus state

* Added isStaticAtRest prop to resizable panel

* Updated resizable storybook

* Inline code blocks behave nicer when text wraps

* Added ElectricSQL to docker-compose, available on 3060

* Extracted some logic out of the eventRepository for getting a trace. This will be used on the frontend

* Use the new util

* More restructuring ready to use the trace summary from the frontend

* Using ElectricSQL for the run page data

* Min size for resizable panel on test page

* Don’t load the trace in the RunPresenter anymore

* Fix for the resizable panels on the run page

* Added overflow hidden to the panel group

* min size for the test page left hand panel

* Updated to latest window-splitter version

* Removed unused const

* One fix for client-server mismatch

* Slight improvement in the loading state

* Restructured the page so the loading is better

* Improvement to the loading states

* Improved the loading behaviour with the inspector

* WIP on auth, having problems with it

* Upgrade Remix to 2.9.1 (same as PR #1096)

* Switched structure around again so we only call the useTrace hook from the client

* Added auth to the sync

* Overscan more rows in the tree view

* Fix for TS error

* Remove duplicate import

* Revert "Upgrade Remix to 2.9.1 (same as PR #1096)"

This reverts commit e63ee9e924.

* save cookie only when id is used

* Deployment table now scrolls

* removed imports

* A lot of changes to make the inspector live too… WIP

* More major overhauls to get the synced version of the run page working…

* If a span is completed show that

* Set the debounce much lower for selecting the span view

* Load the details run inspector data on demand

* Delete the SpanPresenter

* Use the async payload because it deals with superjson

* Fixed weird merge conflict

* Share some inspector timeline components

* A couple of layout tweaks

* Improved the run inspector loading states

* Fix for paragaph errors

* Fix for focusing on a span

* Undefined typre for useSyncedShape

* ELECTRIC_ORIGIN env var doesn’t have a default, added to the examples

* Updated @electric-sql/react package to the latest

* Fix the timeline duration stretching

* Added some better error handling for the electric sync

* More logging

* Better error when there are bad responses

* Turn off resizable snapshots, there’s a bug

---------

Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
Co-authored-by: James Ritchie <james@trigger.dev>
Co-authored-by: James Ritchie <james@jamesritchie.co.uk>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-04 17:55:36 +01:00
Eric Allam c6b8a5a64b Manually update package versions to 3.0.0-beta.56 2024-09-02 13:39:50 +01:00
Matt Aitken 55fa386bdd Documentation edits made through Mintlify web editor 2024-08-29 21:55:10 +01:00
Eugen Istoc 7c1339c108 Update upgrading-packages.mdx (#1273)
I believe the name of the executable in the `bin` dir is called `triggerdev` instead of `trigger.dev`
2024-08-28 10:50:52 +01:00
nicktrn 4cfb52ef65 Remove packageManager override from docs package.json 2024-08-28 10:39:34 +01:00
Matt Aitken f7d32b83b1 Free up concurrency when using triggerAndWait. Improved errors on runs (#1272)
* Add an error to the final attempt if there isn’t one

* Improved the checkpointResumer test task

* Using triggerAndWait or batchTriggerAndWait frees up concurrency

Normally it frees up just env and org concurrency. If it’s a recursive task then it will free up the run concurrency too (e.g. a task calling itself).

* Removed the filepath and export name from attempt spans

* Run inspector: only show an error if the run is in a finished state
2024-08-25 20:15:45 +01:00
Matt Aitken 0979a521bd Updated docs to 0.0.0-prerelease-20240825150620 2024-08-25 16:12:55 +01:00
Eric Allam e30beb779f Add support + docs for custom esbuild plugins (#1270)
* Add support + docs for custom esbuild plugins

* Fixed the missing subpath export

* Add support for specifying the prisma client generator

* Add `—env-file` support to dev and deploy
2024-08-25 16:04:26 +01:00
Matt Aitken 99a63d4cd5 When enqueuing clear concurrency for that run (#1271)
* Better logging if a background worker isn’t created

* When enqueuing a message into the queue clear the concurrency
2024-08-25 15:56:40 +01:00
Matt Aitken 6744617879 Added @ splat route 2024-08-23 18:28:28 +01:00
Matt Aitken d62979914f Updated the new build system docs with the prerelease package 2024-08-23 14:26:56 +01:00
Matt Aitken fc02e95f2a Removed another old changeset 2024-08-23 14:20:48 +01:00
Matt Aitken 8688822723 Another fix for changeset with old package (react) 2024-08-23 14:20:36 +01:00
Matt Aitken 0d829f9530 Fixed changesets for packages no longer in the repo 2024-08-23 14:20:00 +01:00
James Ritchie 6496539c01 Usage doesn’t show as a red bar if you’re beyond the plan 2024-08-23 14:04:57 +01:00
Matt Aitken 79a5c36d6e build pavkage: weird whitespace change 2024-08-23 14:04:46 +01:00
Eric Allam 2f0d65a490 Fix build typecheck 2024-08-23 13:58:36 +01:00
Eric Allam 92d98e5499 Build before running typecheck now 2024-08-23 13:46:18 +01:00
Matt Aitken a9810b0fd8 Stop heartbeats when creating andWait checkpoints, fix for concurrency tracker (#1268)
* RESUME messages were missing some data that is needed for the concurrency tracker

* Cancel heartbeats when checkpoints are created for triggerAndWait and batchTriggerAndWait

* Added some logs when canceling heartbeats

* Improved the nested dependencies test task
2024-08-23 13:10:39 +01:00
Eric Allam f9ec66c562 v3: new build system (#1265)
* upgrade @opentelemetry packages to the latest versions

* remove v2 only packages, will be moved to a dedicated repo

* remove more v2 code and run pnpm install

* use the npm yalt package in the webapp

* convert @trigger.dev/core to tshy

* Switch from jest to vitest in @trigger.dev/core

* Fixed core test

* move core-backend code into core subpath export

* convert @trigger.dev/sdk to tshy

* Removed hono

* move core-apps to core/v3/apps, remove core-apps, start converting cli-v3

* Fix up some of the commands

* cli now building and loadable

* using package-json-from-dist to get package version now in core and cli

* dev command WIP

* cleaned up some repetition and structure of the entry point stuff

* bringing back the background worker stuff

* Indexing of the v3 catalog

* getting closer to executing dev runs...

* centralize dev logging using event emitter

* Move indexing to it’s own entry point, simplify code

* dev runs working

* Get instrumentation to work with openai

* debugging achieved internally

* provide worker files as part of the worker creation on the server

* support for cjs and esm javascript

* Fixed timeout

* worker manifest now has the config path

* auto-upgrade config to non-deprecated alternatives

* Adding package preview release

* deployment WIP

* improve the syncEnvVars output and adapt resolveEnvVars

* WIP bun runtime

* WIP bun support

* seed tasks with the machine preset if listed in the config

* deploy run executions WIP, extracted TaskRunProcess into 1 place

* deployed tasks running and executing 🎉

* support for waits and better flushing & process cleanup

* Fixed the heartbeating

* Better warning messages

* Improve and unify the indexing between dev and deploy

* Support for external deps that need node-gyp to build

* build extensions can now install custom packages and run instructions in the image. Also prisma extension now works and also works with multiple schema files

* Add back in the main/types/module to sdk

* dev no longer is Ink/React, grace period for disconnections in dev

* Fix the changeset config

* More changeset fixes

* Remove config packages

* More changeset fixes

* Fixed typescript issues (needed to revert back to zod 3.22.3

* Fix pr_checks workflow

* Remove the prepare script

* Fixed tests and package versions

* Remove cli test script

* Remove packages from tailwind watch paths

* Add repo to public packages

* Just commit the generated files and do the building at dev time

* Try and get pkg.pr.new working

* Try again

* Fix emitDecoratorMetadata importing named export from typescript

* config file backwards compat with export const config

* Fixed issue where import errors weren’t coming through

* p-retry is a prod dep

* typescript needs to be a prod dependency for emitDecoratorMetadata

* Add better debug logging to help track down import-in-the-middle bug

* An external is only considered resolvable if it resolves to the same path as the collected external

* Fix runtime checks to allow >=18.20

* Move extensions to a new build package

* Fixed building packages in dockerfile

* Remove the e2e test from publish workflow for now

* Don’t treat pkg.pr.new versions has needing upgrading

* making sure config handleError works, and discovered path aliases don’t work in config files

* Strip empty string env vars so they accidentally override real values

* Couple of things

* Update version to use preview instead of beta

* Hopefully fix re-attempts with >30s delay

* Match socket emit messages to current latest in main

* Initial guide

* Go back to beta

* Go back to the preview, and update guide to use pr preview tags

* Go back to beta

---------

Co-authored-by: Matt Aitken <matt@mattaitken.com>
2024-08-23 13:10:15 +01:00
James Ritchie 2cd8658190 If an org has been deleted, show a ☠️ icon on the admin page 2024-08-22 13:15:00 +01:00
James Ritchie e5895424db Added a link to a Next.js example in the docs 2024-08-21 15:58:18 +01:00
Matt Aitken a724a6fa01 Added Task index parentId 2024-08-21 15:54:23 +01:00
Matt Aitken ba7bbdbbdb Dashboard query improvements (#1266)
* Remove run count from JobPresenter

* Remove job/integration counts from org/projects query

* Removed v2 counts from ProjectPresenter query

* JobRun index idx_jobrun_organizationId_createdAt

* JobRun index idx_jobrun_versionId

* BatchTaskRunItem index taskRunAttemptId

* BatchTaskRunItem index taskRunId
2024-08-21 15:49:18 +01:00
Matt Aitken 7746302b51 Run failed alerts (#1262)
* Add new ProjectAlertType ‘TASK_RUN’ and then migrate channels to it

* WIP new alerts for run failures

* Consolidate failed run status into taskStatus.ts

* Added the read replica into BaseService, could be useful

* Send task run alerts code

* Revert "Added the read replica into BaseService, could be useful"

This reverts commit cc2348a40254ea3f9e11c0feaf303ecd8794433a.

* Allow adding task run alerts in the UI

* Use task run alert not attempt alert…

* Use the primary
2024-08-21 14:53:48 +01:00
Matt Aitken 6558e5f8eb Fix for permanently frozen tasks when at max concurrency
Heartbeats now free up concurrency for paused/frozen runs, then they don’t get crashed but get acked by the sharedQueueConsumer
2024-08-21 11:37:02 +01:00
Matt Aitken 9f07df8abc Don’t nack in requeueTaskRun if it’s WAITING_TO_RESUME or PAUSED 2024-08-20 19:00:29 +01:00
Matt Aitken 8ad599f90e Try set the checkpointEventId when resuming batches and dependency 2024-08-20 16:09:43 +01:00
Matt Aitken 78dac99d72 Release 3.0.0-beta.55
🚀 Publish Trigger.dev Docker / e2e (push) Failing after 4s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 2s
🚀 Publish Trigger.dev Docker / units (push) Failing after 19s
🚀 Publish Trigger.dev Docker / publish (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-infra (push) Has been skipped
2024-08-20 15:50:14 +01:00
James Ritchie d6786002b2 Full Next.js guide (#1259)
* tasks no longer inside a group in the side menu (and added “cron”)

* Delay using a timezone

* Added React Not Defined error to the troubleshooting page

* Improved the React common problem

* Link to v2 docs

* New Development section and entry in Common Problems

* Concurrently running the terminal

* Fixed the .env weirdness

* Added section on creating PATs for Github actions

* Improved the Machine spec and limits page

* Quick start steps now have nice images

* Added a diagram for the lifecycle functions

* Added note about onFailure

* CRON -> cron/Cron

* WIP adding more steps to the next.js guide

* WIP next.js

* WIP adding tabbed steps for pages/app router

* References to Infisical links to their homepage so it’s clearer

* WIP updating the nextjs guide

* WIP nextjs guide

* More nextjs guide steps

* More copy

* Added rate limit trouble shooting

* Removed old prisma error title

* Added secret key step

* Added a note for logging in using a specified domain if self hosting

* typo

* App router docs copy

* Deploy copy update

* Added a favicon.png to fix a docs build error

* Removed unused snippet

* Server actions now inside a tab

* Server actions + restructured the triggering section

* Added troubleshooting snippet for react event handlers

* Added a new troubleshooting snippet for ESM

* Updated old replaying image to reflect the new UI

* Removed references to reattempting

* Updated replaying from the run page

* Added a bulk replay section

* Updated the writing tasks intro page

* Removed edge runtime code for now

* Added edge runtime – it seems to just work!

* import type
2024-08-20 14:50:39 +01:00
github-actions[bot] 9d529e9f17 chore: Update version for release (beta) (#1264)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-20 14:46:19 +01:00
Matt Aitken d48eb9bac8 Fix: was showing deleted team member dev environments (#1261)
* Env vars: don’t show deleted dev environments

* Concurrency limits: don’t show deleted dev environments

* Create/edit schedule: don’t show deleted dev environments
2024-08-20 14:03:24 +01:00
Matt Aitken 0591db5f2b Fix for short runs inside (batch)triggerAndWait (#1263)
* Test for checkpoints

* Make sourceTaskAttemptId optional on resumeBatchRun

* Removed all completions/executions logic from the shared queue consumer

* Removed the sourceTaskAttemptId from ResumeBatchRunService

* Revert "Removed all completions/executions logic from the shared queue consumer"

This reverts commit d35398d50463c81a1975bb5d5bcfca66a24b8ede.

* WIP on triggerAndWait…

* Fixed triggerAndWait continuing when a checkpoint completes

* Remove the ResumeAttempt code that fails attempts (was protecting against infinite restores)

* Removed messageBody.data.completedAttemptIds.length === 0 commented out code

* Don’t ack if there’s no batchRun

* Added the marqs?.replaceMessage back in but NOT when there’s no checkpoint. More logging

This is a fix for when some attempts fail

* Improvement to the test task that now randomly fails attempts

* When a checkpoint happens, only continue the attempt if it’s in the correct state

* Changeset for rollback in branch

* Set keepRunAlive to false when the dependent task isn’t finished

* Changeset manual version (to get inline with the hotfix branch)

* Changeset: Fixes for continuing after waits

* Latest lockfile (after manual changeset version)
2024-08-20 13:57:08 +01:00
nicktrn a64064965d Prevent runs that fail fast from never sending a heartbeat 2024-08-19 13:17:33 +01:00
Matt Aitken 41ec450bee Revert "Remove the RequeueTaskRunService"
This reverts commit 3421f5ec0b.
2024-08-15 20:43:47 +01:00
Matt Aitken 9a9bdc20c9 Revert "Disable upserting task queues when triggering a task"
This reverts commit 6652edf7a7.
2024-08-15 20:42:50 +01:00
Matt Aitken 6652edf7a7 Disable upserting task queues when triggering a task 2024-08-15 19:34:22 +01:00
Matt Aitken 369bc38560 Instead of upserting taskQueues, do a findFirst then create/update 2024-08-15 18:48:50 +01:00
Matt Aitken 27f7132509 Don’t do the auto-increment inside a transaction, this means triggerTask isn’t inside one 2024-08-15 17:44:10 +01:00
Matt Aitken b6fc68aaae Don’t use Graphile for invocation usage reporting 2024-08-15 17:22:52 +01:00
Matt Aitken 3421f5ec0b Remove the RequeueTaskRunService 2024-08-15 17:20:40 +01:00
Matt Aitken 15097a3a67 Use the replica for the v2 run and task presenters 2024-08-15 15:34:57 +01:00
Matt Aitken 1b7bb90852 Only show the first 1000 task on the v2 run page 2024-08-15 15:26:12 +01:00
Matt Aitken 7e68c02636 Don’t reload v2 run page if more than 100 tasks 2024-08-15 15:25:59 +01:00
nicktrn 01e01376f8 Fix attempt retry mechanics for new workers 2024-08-15 12:13:13 +01:00
James Ritchie db9163e775 Added a note to use batchTrigger() instead of trigger() 2024-08-14 13:25:08 +01:00
nicktrn e9bdd0c7e5 Release 3.0.0-beta.53 2024-08-14 11:40:38 +01:00
github-actions[bot] 7cb977ff2c chore: Update version for release (beta) (#1256)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-14 11:39:44 +01:00
James Ritchie 2940b07430 Added a description panel to the Concurrency admin page 2024-08-14 11:33:15 +01:00
James Ritchie c46e3c97b5 Fix for schedule page not scrolling 2024-08-14 11:23:16 +01:00
nicktrn 16cb1c46ac Fix restored container names 2024-08-13 18:10:09 +01:00
Matt Aitken 2b0d68a0b6 Include the old message data when replacing, so we get the projectId etc. 2024-08-13 17:09:29 +01:00
James Ritchie 2088fa392d Added depenenciesToBundle guide to bundle all packages 2024-08-13 17:06:42 +01:00
Matt Aitken f1ffc2e6c8 Added messageReplaced to concurrency tracking (when freezing) 2024-08-13 16:36:39 +01:00
Matt Aitken c0b9394099 Call subscriber.messageDequeued in dequeueMessageInSharedQueue 2024-08-13 15:31:25 +01:00
Matt Aitken 937b61913d Added more logging to TaskRunConcurrencyTracker and some more try/catches 2024-08-13 14:54:50 +01:00
nicktrn 5cf90da722 v3: fix unfreezable state crashes for runs with multiple waits (#1253)
* support named capture groups

* write crash errors to attempt.error

* make restored pod names unique per checkpoint

* use last eight characters of checkpoint id instead

* add more chaos monkey env vars

* Ignore unfreezable states

* prevent excessive queue config parsing errors

* handle dependency resume edge case

* better entry point logging

* ignore checkpoint cancellation timeouts

* add missing idempotency keys to wait for dep replays

* remove checkpoints between attempts

* fix retry container names on kubernetes

* add changeset

* fix types

* bring back internal duration timers
2024-08-13 11:44:40 +01:00
Matt Aitken da6ce3c8d5 Concurrency page and more accurate tracking (#1252)
* Initial TaskRunConcurrencyTracker implementation

* MARQS calls a subscriber to events

* When enqueuing add the extra required metadata

* Track concurrency per environment for tasks too

* Admin page for global concurrency

* Use the new concurrency tracker on the tasks page

* Useful performance test task

* getAllTaskIdentifiers()

* New page for concurrency

* BackgroundWorkerTask index for quick lookup of task identifiers

* Added a way to get concurrency for environments

* Added upgrade/request more concurrency button

* Queued task column working

* Use defer and suspense

* Added queue column to the concurrency environments table

* Some comments added for clarity

* Fixed bad log message

* Sidemenu: move lower and rename to “Concurrency limits”

* Only show the environments, not tasks. Renamed to “Concurrency limits”
2024-08-13 11:43:46 +01:00
James Ritchie 07054f13d1 Fixed v2 usage meter displaying when on paid plan (#1255)
* Fixed v2 usage meter displaying when on paid plan

* Show the free usage panel only for v3 projects
2024-08-13 11:14:28 +01:00
nicktrn c50a94b962 Increase indexing memory limit 2024-08-12 17:05:25 +01:00
Matt Aitken bb38261947 FinalizeRunService (#1250)
* WIP notes on each location where we’ll use finalize

* Initial FinalizeTaskRunService

* ExpireEnqueuedRunService uses FinalizeTaskRunService

* FailedTaskRunService uses FinalizeTaskRunService

* Allow passing in an include when finalizing the run

* CrashTaskRunService using FinalizeTaskRunService

* Remove comments

* Status is optional

* CancelAttemptService using FinalizeTaskRunService

* Import tidy

* CancelTaskRunService using FinalizeTaskRunService

* Import tidying

* CompleteAttemptService system failure switched to FinalizeTaskRunService

* Added more logging to Finalizing

* CompleteAttemptStatus COMPLETED_SUCCESSFULLY

* CompletedAttempt “SYSTEM_FAILURE”

* CompletedService final pair

* Use satisfies so we can derive types from the groups

* Only allow final states to be used with this service

* BaseService tx support, minor improvements
2024-08-08 15:32:06 +01:00
James Ritchie 0d4e3e70c1 Lots of small docs improvements (#1251)
* tasks no longer inside a group in the side menu (and added “cron”)

* Delay using a timezone

* Added React Not Defined error to the troubleshooting page

* Improved the React common problem

* Link to v2 docs

* New Development section and entry in Common Problems

* Concurrently running the terminal

* Fixed the .env weirdness

* Added section on creating PATs for Github actions

* Improved the Machine spec and limits page

* Quick start steps now have nice images

* Added a diagram for the lifecycle functions

* Added note about onFailure

* CRON -> cron/Cron

* Updated the cli-dev steps for the concurrently package

* Fixed capital letter

* Updated diagram text

* Removed dead page
2024-08-08 15:03:05 +01:00
nicktrn bf1482fdb9 Save crash errors on the attempt 2024-08-08 11:42:46 +01:00
Matt Aitken e2d3358271 Added error from the run attempt with nice display (#1249) 2024-08-07 15:50:45 +01:00
James Ritchie 6270abaab8 Remove v2 docs pages (#1246)
* Make the tooltip text color grey so it’s readable again the primary color

* Setup and styling for the Guides section

* Better Guides icon

* Improved the prerequisites

* WIP Nextjs guide and new folder structures

* Copy updates

* Useful next steps is now a snippet

* Better icons for the next steps section

* Renamed the “prerequisites” snippet

* Node.js guide

* Added a “Creating a project” guide

* New snippet for prerequisites

* New Remix guide

* Added Remix to the side bar

* Tweaked icons for creating a project page

* Added the hello world step to the onboarding steps

* Better “useful next steps” snippet card links

* Moved prerequisites

* Fixing links to images

* Moved v2 migration page to guides

* Removed dead link

* Attempt fix for redirect

* Fixed redirects

* Getting started section includes link to roadmap

* Removed icon from side menu

* Deleted all v2 pages (excluding updating mint.json)

* Removed v2 pages, redirects and versions from mint.json

* Deleted v2 snippets

* Deleted un-used pages

* Set of more useful coming soon snippets

* All snippets use the updated format

* updated folder “v3/“ with “/pages”

* Moved all main docs files to the route and updated the redirect

* Fixed URLs in the mdx pages to the new route path

* URL goes to the proper pricing page

* Better delayed runs image

* Attempt fix for self hosting page not redirecting
2024-08-07 11:13:45 +01:00
Matt Aitken 6c71ddbe67 TaskRun completedAt index 2024-08-06 17:31:12 +01:00
James Ritchie fafda21e3b V2 end of life messaging, paid plan badge, and project menu improvements (#1245)
* Updated the v2 project message to include end of life

* Removed the v3 badge. Showing a v2 badge instead

* Removed the v3 badge variant

* Show a badge in the side menu if you’re paying

* Popover menu section header and button state colours now reflects the design UI

* Added a link to the blog in the end-of-life message

* Removed missing variant from badge
2024-08-06 10:50:23 +01:00
nicktrn dc2d68107f Revert "Ignore unfreezable states"
This reverts commit 7d9cec8652.
2024-08-06 10:11:28 +01:00
Matt Aitken b65e7f7a7a Show a different message if your logs are deleted but you’re within your retention period.
This happens if you’ve upgraded but the logs were already deleted.
2024-08-06 10:10:40 +01:00
nicktrn 7d9cec8652 Ignore unfreezable states 2024-08-06 08:56:06 +01:00
Matt Aitken a591e1118f Improved the experience when you have no run logs (#1244)
* Remove the old log truncation

* Added TaskRUn logsDeletedAt column

* Accurate timestamps for the run inspector

* EnsureProperty type when you want to make a single property not nullable

* No logs and upgrade messages working

* Button can be autofocused

* Replay dialog code editor is autofocused

* Fix for wrapping of span duration
2024-08-05 18:06:23 +01:00
nicktrn 4d2ea86d6c Use accurate datetime for run timeline 2024-08-05 15:16:43 +01:00
Eric Allam 366ae5ca7d Create a shared queue span every 10 seconds 2024-08-02 17:31:52 +01:00
Eric Allam 157973a720 Force recording shared queue consumer spans 2024-08-02 17:30:27 +01:00
Eric Allam 1353d66ca1 Additional logging to help track down possible issue with MarQS selection algo 2024-08-02 12:27:50 +01:00
Matt Aitken 1c5ee5d94c Added TaskRun completedAt column (#1243)
* Added TaskRun completedAt column

* When finalising a run set the completedAt date

* A note to discuss whether we need to set the completedAt to null

* Remove the note in the sharedQueueConsumer
2024-08-02 10:31:38 +01:00
1850 changed files with 43898 additions and 353067 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"@trigger.dev/shopify": patch
---
improved error messages when a shopify webhook fails to register
-1
View File
@@ -1,5 +1,4 @@
---
"@trigger.dev/core-apps": patch
"@trigger.dev/core": patch
---
-1
View File
@@ -1,6 +1,5 @@
---
"@trigger.dev/sdk": patch
"@trigger.dev/core-apps": patch
"trigger.dev": patch
"@trigger.dev/core": patch
---
+2 -2
View File
@@ -16,11 +16,11 @@
"webapp",
"emails",
"proxy",
"yalt",
"@trigger.dev/database",
"coordinator",
"docker-provider",
"kubernetes-provider"
"kubernetes-provider",
"@trigger.dev/otlp-importer"
],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Fixed empty env vars overriding in dev runs
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/sdk": patch
---
Fixes for continuing after waits
+7
View File
@@ -0,0 +1,7 @@
---
"@trigger.dev/sdk": patch
"trigger.dev": patch
"@trigger.dev/core": patch
---
New Build System
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/core": patch
---
Removed the folder/filepath from Attempt spans
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/build": patch
---
Fix issue with emitDecoratorMetadata and tsconfigs with extends
-5
View File
@@ -1,5 +0,0 @@
---
"@trigger.dev/react": patch
---
Fix for shared queryKey between useRunDetails and useRunStatuses
+11
View File
@@ -0,0 +1,11 @@
---
"trigger.dev": patch
"@trigger.dev/core": patch
---
Fix issues that could result in unreezable state run crashes. Details:
- Never checkpoint between attempts
- Some messages and socket data now include attempt numbers
- Remove attempt completion replays
- Additional prod entry point logging
- Fail runs that receive deprecated (pre-lazy attempt) execute messages
-1
View File
@@ -1,5 +1,4 @@
---
"@trigger.dev/core-apps": patch
"trigger.dev": patch
"@trigger.dev/core": patch
---
-5
View File
@@ -1,5 +0,0 @@
---
"@trigger.dev/nestjs": patch
---
fix: [nestjs integration] fastify HTTP adapter detection now works correctly for response headers
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/sdk": patch
---
Rollback to try and fix some dependent attempt issues
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Fix entry point paths on windows
-1
View File
@@ -1,5 +1,4 @@
---
"@trigger.dev/core-apps": patch
"trigger.dev": patch
"@trigger.dev/core": patch
---
+7
View File
@@ -0,0 +1,7 @@
---
"@trigger.dev/sdk": patch
"trigger.dev": patch
"@trigger.dev/core": patch
---
Support self-hosters pushing to a custom registry when running deploy
+4 -32
View File
@@ -8,40 +8,12 @@
"proxy": "0.0.11",
"webapp": "1.0.0",
"yalt": "0.0.1",
"@trigger.dev/airtable": "2.3.18",
"@trigger.dev/github": "2.3.18",
"@trigger.dev/linear": "2.3.18",
"@trigger.dev/openai": "2.3.18",
"@trigger.dev/plain": "2.3.18",
"@trigger.dev/replicate": "2.3.18",
"@trigger.dev/resend": "2.3.18",
"@trigger.dev/sendgrid": "2.3.18",
"@trigger.dev/shopify": "2.3.18",
"@trigger.dev/slack": "2.3.18",
"@trigger.dev/stripe": "2.3.18",
"@trigger.dev/supabase": "2.3.18",
"@trigger.dev/typeform": "2.3.18",
"@trigger.dev/astro": "2.3.18",
"@trigger.dev/cli": "2.3.18",
"trigger.dev": "2.3.18",
"@trigger.dev/core": "2.3.18",
"@trigger.dev/core-apps": "0.0.0",
"@trigger.dev/core-backend": "2.3.18",
"@trigger.dev/database": "0.0.1",
"emails": "1.0.0",
"@trigger.dev/eslint-plugin": "2.3.18",
"@trigger.dev/express": "2.3.18",
"@trigger.dev/hono": "2.3.18",
"@trigger.dev/integration-kit": "2.3.18",
"@trigger.dev/nestjs": "2.3.18",
"@trigger.dev/nextjs": "2.3.18",
"@trigger.dev/otlp-importer": "2.3.11",
"@trigger.dev/react": "2.3.18",
"@trigger.dev/remix": "2.3.18",
"@trigger.dev/sveltekit": "2.3.18",
"@trigger.dev/testing": "2.3.18",
"@trigger.dev/sdk": "2.3.18",
"@trigger.dev/yalt": "2.3.18"
"@trigger.dev/sdk": "2.3.18"
},
"changesets": [
"afraid-sheep-joke",
@@ -50,7 +22,6 @@
"beige-pears-explode",
"beige-pens-dance",
"big-tomatoes-deliver",
"blue-pumas-whisper",
"breezy-gorillas-mate",
"brown-boats-bathe",
"brown-spies-burn",
@@ -65,6 +36,7 @@
"curly-monkeys-tell",
"dry-walls-check",
"dull-mangos-press",
"early-impalas-itch",
"eight-pumas-float",
"eleven-paws-join",
"famous-boats-tease",
@@ -79,11 +51,11 @@
"funny-swans-destroy",
"gorgeous-cycles-guess",
"gorgeous-gorillas-compete",
"green-bags-wink",
"green-pens-battle",
"hot-buckets-behave",
"hot-fishes-retire",
"hot-wasps-sin",
"hungry-sloths-promise",
"itchy-chairs-itch",
"khaki-apricots-design",
"khaki-poems-lay",
@@ -91,11 +63,11 @@
"late-icons-lie",
"late-steaks-behave",
"lazy-files-lay",
"lemon-jobs-repair",
"lemon-sloths-hide",
"light-bulldogs-press",
"light-dragons-complain",
"little-crabs-cross",
"long-feet-invent",
"long-fireants-search",
"long-hounds-wave",
"loud-actors-remember",
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/build": patch
---
Add support for prisma typed sql
+6
View File
@@ -0,0 +1,6 @@
---
"@trigger.dev/build": patch
"@trigger.dev/core": patch
---
Added support for custom esbuild plugins
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Fix --project-ref when running deploy
-1
View File
@@ -1,5 +1,4 @@
---
"@trigger.dev/core-apps": patch
"trigger.dev": patch
"@trigger.dev/core": patch
---
-1
View File
@@ -1,5 +1,4 @@
---
"@trigger.dev/otlp-importer": patch
"trigger.dev": patch
---
-1
View File
@@ -2,7 +2,6 @@
"@trigger.dev/sdk": patch
"trigger.dev": patch
"@trigger.dev/core": patch
"@trigger.dev/cli": patch
---
Move to our global system from AsyncLocalStorage for the current task context storage
-1
View File
@@ -1,5 +1,4 @@
---
"@trigger.dev/core-apps": patch
"@trigger.dev/core": patch
---
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Support custom config file names & paths
-1
View File
@@ -1,5 +1,4 @@
---
"@trigger.dev/core-apps": patch
"trigger.dev": patch
---
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Fixed stuck runs when a child run fails with a process exit
-1
View File
@@ -1,7 +1,6 @@
---
"trigger.dev": major
"@trigger.dev/core": major
"@trigger.dev/otlp-importer": major
"@trigger.dev/sdk": major
---
-1
View File
@@ -1,5 +1,4 @@
---
"@trigger.dev/core-apps": patch
"trigger.dev": patch
"@trigger.dev/core": patch
---
+6
View File
@@ -0,0 +1,6 @@
---
"trigger.dev": patch
"@trigger.dev/core": patch
---
Fix for when a log flush times out and the process is checkpointed
+6
View File
@@ -0,0 +1,6 @@
---
"trigger.dev": patch
"@trigger.dev/core": patch
---
Add remote forced externals system, in case we come across another package that cannot be bundled (spurred on by header-generator)
-1
View File
@@ -1,6 +1,5 @@
---
"@trigger.dev/sdk": patch
"@trigger.dev/core-apps": patch
"trigger.dev": patch
"@trigger.dev/core": patch
---
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/sdk": patch
---
Remove msw and retry.interceptFetch
-1
View File
@@ -1,6 +1,5 @@
---
"trigger.dev": patch
"@trigger.dev/cli": patch
---
Add support for tasks located in subdirectories inside trigger dirs
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/core": patch
---
fix node10 moduleResolution in @trigger.dev/core
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/build": patch
---
Added new @trigger.dev/build package that currently has all the build extensions
+6
View File
@@ -0,0 +1,6 @@
---
"trigger.dev": patch
"@trigger.dev/core": patch
---
Only import import-in-the-middle hook if there are instrumented packages
+6
View File
@@ -0,0 +1,6 @@
---
"trigger.dev": patch
"@trigger.dev/core": patch
---
Support for custom conditions
+36
View File
@@ -0,0 +1,36 @@
{
"compilerOptions": {
"target": "es2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"moduleDetection": "force",
"verbatimModuleSyntax": false,
"jsx": "react",
"strict": true,
"alwaysStrict": true,
"strictPropertyInitialization": false,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noFallthroughCasesInSwitch": true,
"resolveJsonModule": true,
"removeComments": false,
"esModuleInterop": true,
"emitDecoratorMetadata": false,
"experimentalDecorators": false,
"downlevelIteration": true,
"isolatedModules": true,
"noUncheckedIndexedAccess": true,
"pretty": true,
"customConditions": ["@triggerdotdev/source"]
}
}
+1
View File
@@ -37,6 +37,7 @@ packages/\*\*/dist
.vercel
.cache
.output
.trigger
apps/\*\*/public/build
cypress/screenshots
+5 -72
View File
@@ -9,12 +9,13 @@ on:
type: string
jobs:
cli-v3:
name: "🧪 CLI v3 tests"
name: "🧪 CLI v3 tests (${{ matrix.os }} - ${{ matrix.package-manager }})"
if: inputs.package == 'cli-v3' || inputs.package == ''
runs-on: buildjet-8vcpu-ubuntu-2204
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [buildjet-8vcpu-ubuntu-2204, windows-latest]
package-manager: ["npm", "pnpm", "yarn"]
steps:
- name: ⬇️ Checkout repo
@@ -46,76 +47,8 @@ jobs:
run: corepack enable
- name: Run E2E Tests
shell: bash
run: |
PM=${{ matrix.package-manager }} pnpm --filter trigger.dev run test:e2e
webapp:
name: "🧪 Webapp tests"
if: inputs.package == 'webapp' || inputs.package == ''
runs-on: buildjet-16vcpu-ubuntu-2204
steps:
- name: 🐳 Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME || vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN || vars.DOCKERHUB_TOKEN }}
LOG=debug PM=${{ matrix.package-manager }} pnpm --filter trigger.dev run test:e2e
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
fetch-depth: 0
submodules: recursive
- name: ⎔ Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8.15.5
- name: ⎔ Setup node
uses: buildjet/setup-node@v3
with:
node-version: 20.11.1
cache: "pnpm"
- name: Install Protoc
uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: 📥 Download deps
run: pnpm install --frozen-lockfile
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: |
# Setup environment variables
cp ./.env.example ./.env
cp ./references/nextjs-test/.env.example ./references/nextjs-test/.env.local
# Build packages
pnpm run build --filter @references/nextjs-test^...
cd apps/webapp && pnpm run build:server
cd ../..
pnpm --filter @trigger.dev/database generate
pnpm --filter @trigger.dev/otlp-importer generate
# Move trigger-cli bin to correct place
pnpm install --frozen-lockfile
# Execute tests
pnpm run docker
pnpm run db:migrate
pnpm run db:seed
pnpm run test:e2e
# Cleanup
pnpm run docker:stop
- name: Upload Playwright report
uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
+38 -12
View File
@@ -2,13 +2,7 @@ name: 🤖 PR Checks
on:
pull_request:
branches:
- main
paths-ignore:
- "**.md"
- "**.mdx"
- ".github/CODEOWNERS"
- ".github/ISSUE_TEMPLATE/**"
types: [opened, synchronize, reopened]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
@@ -27,8 +21,40 @@ jobs:
uses: ./.github/workflows/unit-tests.yml
secrets: inherit
# e2e:
# uses: ./.github/workflows/e2e.yml
# with:
# package: webapp
# secrets: inherit
e2e:
uses: ./.github/workflows/e2e.yml
with:
package: cli-v3
secrets: inherit
preview-release:
name: Preview Release
needs: [typecheck, units, e2e]
if: |
github.repository == 'triggerdotdev/trigger.dev'
runs-on: buildjet-8vcpu-ubuntu-2204
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: ⎔ Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8.15.5
- name: ⎔ Setup node
uses: buildjet/setup-node@v3
with:
node-version: 20.11.1
cache: "pnpm"
- name: 📥 Download deps
run: pnpm install --frozen-lockfile
- name: 🏗️ Build
run: pnpm run build --filter "@trigger.dev/*" --filter "trigger.dev"
- name: ⚡ publish preview release
run: npx pkg-pr-new publish --no-template $(ls -d ./packages/*)
-6
View File
@@ -49,12 +49,6 @@ jobs:
uses: ./.github/workflows/unit-tests.yml
secrets: inherit
e2e:
uses: ./.github/workflows/e2e.yml
with:
package: cli-v3
secrets: inherit
publish:
needs: [typecheck, units]
uses: ./.github/workflows/publish-docker.yml
+3 -5
View File
@@ -38,17 +38,15 @@ jobs:
node-version: 20.11.1
cache: "pnpm"
- name: Install Protoc
uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: 📥 Download deps
run: pnpm install --frozen-lockfile
- name: 📀 Generate Prisma Client
run: pnpm run generate
- name: 🏗️ Build
run: pnpm run build --filter "@trigger.dev/*" --filter "trigger.dev"
- name: 🔎 Type check
run: pnpm run typecheck --filter "@trigger.dev/*" --filter "trigger.dev"
+4 -6
View File
@@ -22,11 +22,6 @@ jobs:
node-version: 20.11.1
cache: "pnpm"
- name: Install Protoc
uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: 📥 Download deps
run: pnpm install --frozen-lockfile
@@ -34,4 +29,7 @@ jobs:
run: pnpm run generate
- name: 🔎 Type check
run: pnpm run typecheck --filter webapp
run: pnpm run typecheck
- name: 🔎 Check exports
run: pnpm run check-exports
-15
View File
@@ -22,21 +22,6 @@ jobs:
node-version: 20.11.1
cache: "pnpm"
- name: Install Protoc
uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: ⎔ Setup Deno
uses: denoland/setup-deno@v1
with:
deno-version: v1.x
- name: ⎔ Setup bun
uses: oven-sh/setup-bun@v1
with:
bun-version: "1.0.15"
- name: 📥 Download deps
run: pnpm install --frozen-lockfile
+5 -2
View File
@@ -12,7 +12,6 @@ coverage
# next.js
.next/
out/
build
dist
packages/**/dist
@@ -54,4 +53,8 @@ apps/**/public/build
/playwright/.cache/
.cosine
.trigger/
.trigger
.tshy*
.yarn
*.tsbuildinfo
/packages/cli-v3/src/package.json
-1
View File
@@ -7,5 +7,4 @@ tailwind.css
**/.react-email/
**/storybook-static/
**/.changeset/
**/build/
**/dist/
+1 -2
View File
@@ -1,9 +1,8 @@
{
"recommendations": [
"astro-build.astro-vscode",
"denoland.vscode-deno"
],
"unwantedRecommendations": [
]
}
}
+37 -6
View File
@@ -23,17 +23,16 @@
{
"type": "node-terminal",
"request": "launch",
"name": "Debug v2 job catalog",
"command": "pnpm run byo-auth",
"envFile": "${workspaceFolder}/references/job-catalog/.env",
"cwd": "${workspaceFolder}/references/job-catalog",
"name": "Debug V3 init CLI",
"command": "pnpm exec triggerdev init",
"cwd": "${workspaceFolder}/references/init-shell",
"sourceMaps": true
},
{
"type": "node-terminal",
"request": "launch",
"name": "Debug V3 Dev CLI",
"command": "pnpm exec triggerdev dev --log-level debug",
"command": "pnpm exec triggerdev dev",
"cwd": "${workspaceFolder}/references/v3-catalog",
"sourceMaps": true
},
@@ -41,7 +40,23 @@
"type": "node-terminal",
"request": "launch",
"name": "Debug V3 Deploy CLI",
"command": "pnpm exec triggerdev deploy",
"command": "pnpm exec triggerdev deploy --self-hosted --load-image",
"cwd": "${workspaceFolder}/references/v3-catalog",
"sourceMaps": true
},
{
"type": "node-terminal",
"request": "launch",
"name": "Debug V3 list-profiles CLI",
"command": "pnpm exec triggerdev list-profiles --log-level debug",
"cwd": "${workspaceFolder}/references/v3-catalog",
"sourceMaps": true
},
{
"type": "node-terminal",
"request": "launch",
"name": "Debug V3 update CLI",
"command": "pnpm exec triggerdev update",
"cwd": "${workspaceFolder}/references/v3-catalog",
"sourceMaps": true
},
@@ -60,6 +75,22 @@
"port": 9229,
"restart": true,
"skipFiles": ["<node_internals>/**"]
},
{
"type": "node-terminal",
"request": "launch",
"name": "Debug CLI e2e tests",
"command": "MOD=otel-telemetry-loader pnpm run test:e2e",
"cwd": "${workspaceFolder}/packages/cli-v3",
"sourceMaps": true
},
{
"type": "node-terminal",
"request": "launch",
"name": "debug v3 hello-world dev",
"command": "pnpm exec triggerdev dev",
"cwd": "${workspaceFolder}/references/hello-world",
"sourceMaps": true
}
]
}
+6 -1
View File
@@ -1,4 +1,9 @@
{
"deno.enablePaths": ["references/deno-reference", "runtime_tests/tests/deno"],
"debug.toolBarLocation": "commandCenter"
"debug.toolBarLocation": "commandCenter",
"typescript.tsdk": "node_modules/typescript/lib",
"search.exclude": {
"**/node_modules/**": true,
"packages/cli-v3/e2e": true
}
}
-1
View File
@@ -17,7 +17,6 @@
"license": "MIT",
"dependencies": {
"@trigger.dev/core": "workspace:*",
"@trigger.dev/core-apps": "workspace:*",
"execa": "^8.0.1",
"nanoid": "^5.0.6",
"prom-client": "^15.1.0",
+7 -3
View File
@@ -12,7 +12,11 @@ export class ChaosMonkey {
private chaosEventRate = 0.2;
private delayInSeconds = 45;
constructor(private enabled = false) {
constructor(
private enabled = false,
private disableErrors = false,
private disableDelays = false
) {
if (this.enabled) {
console.log("🍌 Chaos monkey enabled");
}
@@ -32,8 +36,8 @@ export class ChaosMonkey {
async call({
$,
throwErrors = true,
addDelays = true,
throwErrors = !this.disableErrors,
addDelays = !this.disableDelays,
}: {
$?: Execa$<string>;
throwErrors?: boolean;
+14 -8
View File
@@ -1,6 +1,6 @@
import { ExponentialBackoff } from "@trigger.dev/core-apps/backoff";
import { isExecaChildProcess, testDockerCheckpoint } from "@trigger.dev/core-apps/checkpoints";
import { SimpleLogger } from "@trigger.dev/core-apps/logger";
import { ExponentialBackoff } from "@trigger.dev/core/v3/apps";
import { testDockerCheckpoint, isExecaChildProcess } from "@trigger.dev/core/v3/apps";
import { SimpleLogger } from "@trigger.dev/core/v3/apps";
import { $ } from "execa";
import { nanoid } from "nanoid";
import fs from "node:fs/promises";
@@ -17,6 +17,7 @@ type CheckpointAndPushOptions = {
projectRef: string;
deploymentVersion: string;
shouldHeartbeat?: boolean;
attemptNumber?: number;
};
type CheckpointAndPushResult =
@@ -258,6 +259,7 @@ export class Checkpointer {
leaveRunning = true, // This mirrors kubernetes behaviour more accurately
projectRef,
deploymentVersion,
attemptNumber,
}: CheckpointAndPushOptions): Promise<CheckpointAndPushResult> {
this.#logger.log("Checkpointing with backoff", {
runId,
@@ -297,6 +299,7 @@ export class Checkpointer {
leaveRunning,
projectRef,
deploymentVersion,
attemptNumber,
});
if (result.success) {
@@ -359,6 +362,7 @@ export class Checkpointer {
leaveRunning = true, // This mirrors kubernetes behaviour more accurately
projectRef,
deploymentVersion,
attemptNumber,
}: CheckpointAndPushOptions): Promise<CheckpointAndPushResult> {
await this.init();
@@ -367,6 +371,7 @@ export class Checkpointer {
leaveRunning,
projectRef,
deploymentVersion,
attemptNumber,
};
if (!this.#dockerMode && !this.#canCheckpoint) {
@@ -418,13 +423,14 @@ export class Checkpointer {
this.#logger.log("Checkpointing:", { options });
const containterName = this.#getRunContainerName(runId);
const containterNameWithAttempt = this.#getRunContainerName(runId, attemptNumber);
// Create checkpoint (docker)
if (this.#dockerMode) {
try {
if (this.opts.forceSimulate || !this.#canCheckpoint) {
this.#logger.log("Simulating checkpoint");
this.#logger.debug(await $$`docker pause ${containterName}`);
this.#logger.debug(await $$`docker pause ${containterNameWithAttempt}`);
} else {
if (this.simulateCheckpointFailure) {
if (performance.now() < this.simulateCheckpointFailureSeconds * 1000) {
@@ -435,11 +441,11 @@ export class Checkpointer {
if (leaveRunning) {
this.#logger.debug(
await $$`docker checkpoint create --leave-running ${containterName} ${exportLocation}`
await $$`docker checkpoint create --leave-running ${containterNameWithAttempt} ${exportLocation}`
);
} else {
this.#logger.debug(
await $$`docker checkpoint create ${containterName} ${exportLocation}`
await $$`docker checkpoint create ${containterNameWithAttempt} ${exportLocation}`
);
}
}
@@ -581,7 +587,7 @@ export class Checkpointer {
return this.#failedCheckpoints.has(runId);
}
#getRunContainerName(suffix: string) {
return `task-run-${suffix}`;
#getRunContainerName(suffix: string, attemptNumber?: number) {
return `task-run-${suffix}${attemptNumber && attemptNumber > 1 ? `-att${attemptNumber}` : ""}`;
}
}
+45 -6
View File
@@ -10,8 +10,8 @@ import {
} from "@trigger.dev/core/v3";
import { ZodNamespace } from "@trigger.dev/core/v3/zodNamespace";
import { ZodSocketConnection } from "@trigger.dev/core/v3/zodSocket";
import { HttpReply, getTextBody } from "@trigger.dev/core-apps/http";
import { SimpleLogger } from "@trigger.dev/core-apps/logger";
import { HttpReply, getTextBody } from "@trigger.dev/core/v3/apps";
import { SimpleLogger } from "@trigger.dev/core/v3/apps";
import { ChaosMonkey } from "./chaosMonkey";
import { Checkpointer } from "./checkpointer";
@@ -49,7 +49,11 @@ const PLATFORM_SECRET = process.env.PLATFORM_SECRET || "coordinator-secret";
const SECURE_CONNECTION = ["1", "true"].includes(process.env.SECURE_CONNECTION ?? "false");
const logger = new SimpleLogger(`[${NODE_NAME}]`);
const chaosMonkey = new ChaosMonkey(!!process.env.CHAOS_MONKEY_ENABLED);
const chaosMonkey = new ChaosMonkey(
!!process.env.CHAOS_MONKEY_ENABLED,
!!process.env.CHAOS_MONKEY_DISABLE_ERRORS,
!!process.env.CHAOS_MONKEY_DISABLE_DELAYS
);
class TaskCoordinator {
#httpServer: ReturnType<typeof createServer>;
@@ -290,6 +294,7 @@ class TaskCoordinator {
setSocketDataFromHeader("projectRef", "x-trigger-project-ref");
setSocketDataFromHeader("runId", "x-trigger-run-id");
setSocketDataFromHeader("attemptFriendlyId", "x-trigger-attempt-friendly-id", false);
setSocketDataFromHeader("attemptNumber", "x-trigger-attempt-number", false);
setSocketDataFromHeader("envId", "x-trigger-env-id");
setSocketDataFromHeader("deploymentId", "x-trigger-deployment-id");
setSocketDataFromHeader("deploymentVersion", "x-trigger-deployment-version");
@@ -306,6 +311,10 @@ class TaskCoordinator {
onConnection: async (socket, handler, sender) => {
const logger = new SimpleLogger(`[prod-worker][${socket.id}]`);
const getAttemptNumber = () => {
return socket.data.attemptNumber ? parseInt(socket.data.attemptNumber) : undefined;
};
const crashRun = async (error: { name: string; message: string; stack?: string }) => {
try {
this.#platformSocket?.send("RUN_CRASHED", {
@@ -381,6 +390,10 @@ class TaskCoordinator {
socket.data.attemptFriendlyId = attemptFriendlyId;
};
const updateAttemptNumber = (attemptNumber: string | number) => {
socket.data.attemptNumber = String(attemptNumber);
};
this.#platformSocket?.send("LOG", {
metadata: socket.data,
text: "connected",
@@ -430,6 +443,7 @@ class TaskCoordinator {
});
updateAttemptFriendlyId(executionAck.payload.execution.attempt.id);
updateAttemptNumber(executionAck.payload.execution.attempt.number);
} catch (error) {
logger.error("Error", { error });
@@ -505,11 +519,17 @@ class TaskCoordinator {
updateAttemptFriendlyId(message.attemptFriendlyId);
this.#platformSocket?.send("READY_FOR_RESUME", message);
if (message.version === "v2") {
updateAttemptNumber(message.attemptNumber);
}
this.#platformSocket?.send("READY_FOR_RESUME", { ...message, version: "v1" });
});
// MARK: RUN COMPLETED
socket.on("TASK_RUN_COMPLETED", async ({ completion, execution }, callback) => {
socket.on("TASK_RUN_COMPLETED", async (message, callback) => {
const { completion, execution } = message;
logger.log("completed task", { completionId: completion.id });
// Cancel all in-progress checkpoints (if any)
@@ -518,8 +538,10 @@ class TaskCoordinator {
await chaosMonkey.call({ throwErrors: false });
const completeWithoutCheckpoint = (shouldExit: boolean) => {
const supportsRetryCheckpoints = message.version === "v1";
this.#platformSocket?.send("TASK_RUN_COMPLETED", {
version: "v1",
version: supportsRetryCheckpoints ? "v1" : "v2",
execution,
completion,
});
@@ -546,6 +568,15 @@ class TaskCoordinator {
if (completion.retry.delay < this.#delayThresholdInMs) {
completeWithoutCheckpoint(false);
// Prevents runs that fail fast from never sending a heartbeat
this.#sendRunHeartbeat(socket.data.runId);
return;
}
if (message.version === "v2") {
completeWithoutCheckpoint(true);
return;
}
@@ -681,6 +712,7 @@ class TaskCoordinator {
runId: socket.data.runId,
projectRef: socket.data.projectRef,
deploymentVersion: socket.data.deploymentVersion,
attemptNumber: getAttemptNumber(),
});
if (!checkpoint) {
@@ -752,6 +784,7 @@ class TaskCoordinator {
runId: socket.data.runId,
projectRef: socket.data.projectRef,
deploymentVersion: socket.data.deploymentVersion,
attemptNumber: getAttemptNumber(),
});
if (!checkpoint) {
@@ -821,6 +854,7 @@ class TaskCoordinator {
runId: socket.data.runId,
projectRef: socket.data.projectRef,
deploymentVersion: socket.data.deploymentVersion,
attemptNumber: getAttemptNumber(),
});
if (!checkpoint) {
@@ -905,6 +939,7 @@ class TaskCoordinator {
}
updateAttemptFriendlyId(createAttempt.executionPayload.execution.attempt.id);
updateAttemptNumber(createAttempt.executionPayload.execution.attempt.number);
callback({
success: true,
@@ -924,6 +959,10 @@ class TaskCoordinator {
if (message.attemptFriendlyId) {
updateAttemptFriendlyId(message.attemptFriendlyId);
}
if (message.attemptNumber) {
updateAttemptNumber(message.attemptNumber);
}
});
},
onDisconnect: async (socket, handler, sender, logger) => {
+1 -3
View File
@@ -11,9 +11,7 @@
"skipLibCheck": true,
"paths": {
"@trigger.dev/core/v3": ["../../packages/core/src/v3"],
"@trigger.dev/core/v3/*": ["../../packages/core/src/v3/*"],
"@trigger.dev/core-apps": ["../../packages/core-apps/src"],
"@trigger.dev/core-apps/*": ["../../packages/core-apps/src/*"]
"@trigger.dev/core/v3/*": ["../../packages/core/src/v3/*"]
}
}
}
-1
View File
@@ -17,7 +17,6 @@
"license": "MIT",
"dependencies": {
"@trigger.dev/core": "workspace:*",
"@trigger.dev/core-apps": "workspace:*",
"execa": "^8.0.1"
},
"devDependencies": {
+10 -9
View File
@@ -5,15 +5,16 @@ import {
TaskOperationsCreateOptions,
TaskOperationsIndexOptions,
TaskOperationsRestoreOptions,
} from "@trigger.dev/core-apps/provider";
import { SimpleLogger } from "@trigger.dev/core-apps/logger";
import { isExecaChildProcess, testDockerCheckpoint } from "@trigger.dev/core-apps/checkpoints";
} from "@trigger.dev/core/v3/apps";
import { SimpleLogger } from "@trigger.dev/core/v3/apps";
import { isExecaChildProcess, testDockerCheckpoint } from "@trigger.dev/core/v3/apps";
import { setTimeout } from "node:timers/promises";
import { PostStartCauses, PreStopCauses } from "@trigger.dev/core/v3";
const MACHINE_NAME = process.env.MACHINE_NAME || "local";
const COORDINATOR_PORT = process.env.COORDINATOR_PORT || 8020;
const COORDINATOR_HOST = process.env.COORDINATOR_HOST || "127.0.0.1";
const DOCKER_NETWORK = process.env.DOCKER_NETWORK || "host";
const OTEL_EXPORTER_OTLP_ENDPOINT =
process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "http://0.0.0.0:4318";
@@ -90,7 +91,7 @@ class DockerTaskOperations implements TaskOperations {
logger.debug(
await execa("docker", [
"run",
"--network=host",
`--network=${DOCKER_NETWORK}`,
"--rm",
`--env=INDEX_TASKS=true`,
`--env=TRIGGER_SECRET_KEY=${opts.apiKey}`,
@@ -109,11 +110,11 @@ class DockerTaskOperations implements TaskOperations {
async create(opts: TaskOperationsCreateOptions) {
await this.init();
const containerName = this.#getRunContainerName(opts.runId);
const containerName = this.#getRunContainerName(opts.runId, opts.nextAttemptNumber);
const runArgs = [
"run",
"--network=host",
`--network=${DOCKER_NETWORK}`,
"--detach",
`--env=TRIGGER_ENV_ID=${opts.envId}`,
`--env=TRIGGER_RUN_ID=${opts.runId}`,
@@ -150,7 +151,7 @@ class DockerTaskOperations implements TaskOperations {
async restore(opts: TaskOperationsRestoreOptions) {
await this.init();
const containerName = this.#getRunContainerName(opts.runId);
const containerName = this.#getRunContainerName(opts.runId, opts.attemptNumber);
if (!this.#canCheckpoint || this.opts.forceSimulate) {
logger.log("Simulating restore");
@@ -195,8 +196,8 @@ class DockerTaskOperations implements TaskOperations {
return `task-index-${suffix}`;
}
#getRunContainerName(suffix: string) {
return `task-run-${suffix}`;
#getRunContainerName(suffix: string, attemptNumber?: number) {
return `task-run-${suffix}${attemptNumber && attemptNumber > 1 ? `-att${attemptNumber}` : ""}`;
}
async #sendPostStart(containerName: string): Promise<void> {
+2 -4
View File
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es2016",
"target": "es2018",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
@@ -9,9 +9,7 @@
"skipLibCheck": true,
"paths": {
"@trigger.dev/core/v3": ["../../packages/core/src/v3"],
"@trigger.dev/core/v3/*": ["../../packages/core/src/v3/*"],
"@trigger.dev/core-apps": ["../../packages/core-apps/src"],
"@trigger.dev/core-apps/*": ["../../packages/core-apps/src/*"]
"@trigger.dev/core/v3/*": ["../../packages/core/src/v3/*"]
}
}
}
-1
View File
@@ -18,7 +18,6 @@
"dependencies": {
"@kubernetes/client-node": "^0.20.0",
"@trigger.dev/core": "workspace:*",
"@trigger.dev/core-apps": "workspace:*",
"p-queue": "^8.0.1"
},
"devDependencies": {
+36 -31
View File
@@ -6,15 +6,14 @@ import {
TaskOperationsIndexOptions,
TaskOperationsPrePullDeploymentOptions,
TaskOperationsRestoreOptions,
} from "@trigger.dev/core-apps/provider";
import { SimpleLogger } from "@trigger.dev/core-apps/logger";
} from "@trigger.dev/core/v3/apps";
import { SimpleLogger } from "@trigger.dev/core/v3/apps";
import {
MachinePreset,
PostStartCauses,
PreStopCauses,
EnvironmentType,
} from "@trigger.dev/core/v3";
import { randomUUID } from "crypto";
import { TaskMonitor } from "./taskMonitor";
import { PodCleaner } from "./podCleaner";
import { UptimeHeartbeat } from "./uptimeHeartbeat";
@@ -41,7 +40,7 @@ type Namespace = {
};
};
type ComputeResources = {
type ResourceQuantities = {
[K in "cpu" | "memory" | "ephemeral-storage"]?: string;
};
@@ -102,7 +101,7 @@ class KubernetesTaskOperations implements TaskOperations {
resources: {
limits: {
cpu: "1",
memory: "1G",
memory: "2G",
"ephemeral-storage": "2Gi",
},
},
@@ -139,10 +138,12 @@ class KubernetesTaskOperations implements TaskOperations {
}
async create(opts: TaskOperationsCreateOptions) {
const containerName = this.#getRunContainerName(opts.runId, opts.nextAttemptNumber);
await this.#createPod(
{
metadata: {
name: this.#getRunContainerName(opts.runId),
name: containerName,
namespace: this.#namespace.metadata.name,
labels: {
...this.#getSharedLabels(opts),
@@ -157,22 +158,14 @@ class KubernetesTaskOperations implements TaskOperations {
terminationGracePeriodSeconds: 60 * 60,
containers: [
{
name: this.#getRunContainerName(opts.runId),
name: containerName,
image: opts.image,
ports: [
{
containerPort: 8000,
},
],
resources: {
requests: {
...this.#defaultResourceRequests,
},
limits: {
...this.#defaultResourceLimits,
...this.#getResourcesFromMachineConfig(opts.machine),
},
},
resources: this.#getResourcesForMachine(opts.machine),
lifecycle: {
preStop: {
exec: {
@@ -211,7 +204,7 @@ class KubernetesTaskOperations implements TaskOperations {
await this.#createPod(
{
metadata: {
name: `${this.#getRunContainerName(opts.runId)}-${randomUUID().slice(0, 8)}`,
name: `${this.#getRunContainerName(opts.runId)}-${opts.checkpointId.slice(-8)}`,
namespace: this.#namespace.metadata.name,
labels: {
...this.#getSharedLabels(opts),
@@ -263,15 +256,7 @@ class KubernetesTaskOperations implements TaskOperations {
containerPort: 8000,
},
],
resources: {
requests: {
...this.#defaultResourceRequests,
},
limits: {
...this.#defaultResourceLimits,
...this.#getResourcesFromMachineConfig(opts.machine),
},
},
resources: this.#getResourcesForMachine(opts.machine),
lifecycle: {
postStart: {
exec: {
@@ -412,13 +397,13 @@ class KubernetesTaskOperations implements TaskOperations {
};
}
get #defaultResourceRequests(): ComputeResources {
get #defaultResourceRequests(): ResourceQuantities {
return {
"ephemeral-storage": "2Gi",
};
}
get #defaultResourceLimits(): ComputeResources {
get #defaultResourceLimits(): ResourceQuantities {
return {
"ephemeral-storage": "10Gi",
};
@@ -484,13 +469,33 @@ class KubernetesTaskOperations implements TaskOperations {
};
}
#getResourcesFromMachineConfig(preset: MachinePreset): ComputeResources {
#getResourceRequestsForMachine(preset: MachinePreset): ResourceQuantities {
return {
cpu: `${preset.cpu * 0.75}`,
memory: `${preset.memory}G`,
};
}
#getResourceLimitsForMachine(preset: MachinePreset): ResourceQuantities {
return {
cpu: `${preset.cpu}`,
memory: `${preset.memory}G`,
};
}
#getResourcesForMachine(preset: MachinePreset): k8s.V1ResourceRequirements {
return {
requests: {
...this.#defaultResourceRequests,
...this.#getResourceRequestsForMachine(preset),
},
limits: {
...this.#defaultResourceLimits,
...this.#getResourceLimitsForMachine(preset),
},
};
}
#getLifecycleCommand<THookType extends "postStart" | "preStop">(
type: THookType,
cause: THookType extends "postStart" ? PostStartCauses : PreStopCauses
@@ -514,8 +519,8 @@ class KubernetesTaskOperations implements TaskOperations {
return `task-index-${suffix}`;
}
#getRunContainerName(suffix: string) {
return `task-run-${suffix}`;
#getRunContainerName(suffix: string, attemptNumber?: number) {
return `task-run-${suffix}${attemptNumber && attemptNumber > 1 ? `-att${attemptNumber}` : ""}`;
}
#getPrePullContainerName(suffix: string) {
+1 -1
View File
@@ -1,5 +1,5 @@
import * as k8s from "@kubernetes/client-node";
import { SimpleLogger } from "@trigger.dev/core-apps/logger";
import { SimpleLogger } from "@trigger.dev/core/v3/apps";
type PodCleanerOptions = {
runtimeEnv: "local" | "kubernetes";
+2 -2
View File
@@ -1,6 +1,6 @@
import * as k8s from "@kubernetes/client-node";
import { SimpleLogger } from "@trigger.dev/core-apps/logger";
import { EXIT_CODE_ALREADY_HANDLED, EXIT_CODE_CHILD_NONZERO } from "@trigger.dev/core-apps/process";
import { SimpleLogger } from "@trigger.dev/core/v3/apps";
import { EXIT_CODE_ALREADY_HANDLED, EXIT_CODE_CHILD_NONZERO } from "@trigger.dev/core/v3/apps";
import { setTimeout } from "timers/promises";
import PQueue from "p-queue";
import type { Prettify } from "@trigger.dev/core/v3";
@@ -1,5 +1,5 @@
import * as k8s from "@kubernetes/client-node";
import { SimpleLogger } from "@trigger.dev/core-apps/logger";
import { SimpleLogger } from "@trigger.dev/core/v3/apps";
type UptimeHeartbeatOptions = {
runtimeEnv: "local" | "kubernetes";
+1 -3
View File
@@ -9,9 +9,7 @@
"skipLibCheck": true,
"paths": {
"@trigger.dev/core/v3": ["../../packages/core/src/v3"],
"@trigger.dev/core/v3/*": ["../../packages/core/src/v3/*"],
"@trigger.dev/core-apps": ["../../packages/core-apps/src"],
"@trigger.dev/core-apps/*": ["../../packages/core-apps/src/*"]
"@trigger.dev/core/v3/*": ["../../packages/core/src/v3/*"]
}
}
}
+1 -1
View File
@@ -18,4 +18,4 @@
"zod": "3.22.3",
"zod-error": "1.5.0"
}
}
}
@@ -1,20 +1,16 @@
import * as Property from "~/components/primitives/PropertyTable";
import { ShieldCheckIcon } from "@heroicons/react/20/solid";
import * as Property from "~/components/primitives/PropertyTable";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "~/components/primitives/Tooltip";
import {
useIsImpersonating,
useOptionalOrganization,
useOrganization,
} from "~/hooks/useOrganizations";
import { useOptionalProject, useProject } from "~/hooks/useProject";
import { useIsImpersonating, useOptionalOrganization } from "~/hooks/useOrganizations";
import { useOptionalProject } from "~/hooks/useProject";
import { useHasAdminAccess, useUser } from "~/hooks/useUser";
export function AdminDebugTooltip({ children }: { children: React.ReactNode }) {
export function AdminDebugTooltip({ children }: { children?: React.ReactNode }) {
const hasAdminAccess = useHasAdminAccess();
const isImpersonating = useIsImpersonating();
@@ -60,6 +60,23 @@ export function UsageBar({
/>
</motion.div>
)}
<motion.div
initial={{ width: usagePercentage / startFactor + "%" }}
animate={{ width: usagePercentage + "%" }}
transition={{ duration: 1.5, type: "spring" }}
style={{ width: `${usagePercentage}%` }}
className={cn(
"absolute h-3 rounded-l-sm",
tierLimit && current > tierLimit ? "bg-green-700" : "bg-green-600"
)}
>
<Legend
text="Used:"
value={formatCurrency(current, false)}
position="topRow1"
percentage={usagePercentage}
/>
</motion.div>
{tierLimit !== undefined && (
<motion.div
initial={{ width: tierRunLimitPercentage / startFactor + "%" }}
@@ -92,23 +109,7 @@ export function UsageBar({
/>
</motion.div>
)}
<motion.div
initial={{ width: usagePercentage / startFactor + "%" }}
animate={{ width: usagePercentage + "%" }}
transition={{ duration: 1.5, type: "spring" }}
style={{ width: `${usagePercentage}%` }}
className={cn(
"absolute h-3 rounded-l-sm",
tierLimit && current > tierLimit ? "bg-rose-600" : "bg-green-600"
)}
>
<Legend
text="Used:"
value={formatCurrency(current, false)}
position="topRow1"
percentage={usagePercentage}
/>
</motion.div>
<motion.div
initial={{ width: usageCappedToLimitPercentage / startFactor + "%" }}
animate={{ width: usageCappedToLimitPercentage + "%" }}
@@ -9,6 +9,7 @@ import {
CursorArrowRaysIcon,
IdentificationIcon,
KeyIcon,
RectangleStackIcon,
ServerStackIcon,
ShieldCheckIcon,
SignalIcon,
@@ -19,9 +20,9 @@ import { DiscordIcon, SlackIcon } from "@trigger.dev/companyicons";
import { Fragment, useEffect, useRef, useState } from "react";
import { TaskIcon } from "~/assets/icons/TaskIcon";
import { useFeatures } from "~/hooks/useFeatures";
import { MatchedOrganization } from "~/hooks/useOrganizations";
import { MatchedProject } from "~/hooks/useProject";
import { User } from "~/models/user.server";
import { type MatchedOrganization } from "~/hooks/useOrganizations";
import { type MatchedProject } from "~/hooks/useProject";
import { type User } from "~/models/user.server";
import { useCurrentPlan } from "~/routes/_app.orgs.$organizationSlug/route";
import { cn } from "~/utils/cn";
import {
@@ -46,6 +47,7 @@ import {
projectTriggersPath,
v3ApiKeysPath,
v3BillingPath,
v3ConcurrencyPath,
v3DeploymentsPath,
v3EnvironmentVariablesPath,
v3ProjectAlertsPath,
@@ -63,8 +65,7 @@ import { StepContentContainer } from "../StepContentContainer";
import { UserProfilePhoto } from "../UserProfilePhoto";
import { FreePlanUsage } from "../billing/v2/FreePlanUsage";
import { Badge } from "../primitives/Badge";
import { Button } from "../primitives/Buttons";
import { Callout } from "../primitives/Callout";
import { Button, LinkButton } from "../primitives/Buttons";
import { ClipboardField } from "../primitives/ClipboardField";
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "../primitives/Dialog";
import { Icon } from "../primitives/Icon";
@@ -80,19 +81,10 @@ import {
import { StepNumber } from "../primitives/StepNumber";
import { TextLink } from "../primitives/TextLink";
import { SideMenuHeader } from "./SideMenuHeader";
import { MenuCount, SideMenuItem } from "./SideMenuItem";
import { SideMenuItem } from "./SideMenuItem";
type SideMenuUser = Pick<User, "email" | "admin"> & { isImpersonating: boolean };
type SideMenuProject = Pick<
MatchedProject,
| "id"
| "name"
| "slug"
| "hasInactiveExternalTriggers"
| "jobCount"
| "httpEndpointCount"
| "version"
>;
type SideMenuProject = Pick<MatchedProject, "id" | "name" | "slug" | "version">;
type SideMenuProps = {
user: SideMenuUser;
@@ -107,6 +99,9 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
const currentPlan = useCurrentPlan();
const { isManagedCloud } = useFeatures();
const isV3Project = project.version === "V3";
const isFreeV3User = currentPlan?.v3Subscription?.isPaying === false;
useEffect(() => {
const handleScroll = () => {
if (borderRef.current) {
@@ -164,7 +159,6 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
icon="integration"
to={organizationIntegrationsPath(organization)}
data-action="integrations"
hasWarning={organization.hasUnconfiguredIntegrations}
/>
)}
<SideMenuItem
@@ -195,6 +189,11 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
to={v3BillingPath(organization)}
iconColor="text-blue-600"
data-action="billing"
badge={
currentPlan?.v3Subscription?.isPaying
? currentPlan?.v3Subscription?.plan?.title
: undefined
}
/>
</>
)}
@@ -218,14 +217,25 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
</div>
<div className="m-2">
{project.version === "V2" && (
<Callout variant={"info"}>
<Paragraph variant="small">
This is a v2 project.{" "}
<TextLink href="https://trigger.dev/docs/v3/upgrading-from-v2">
Upgrade to v3
<div className="flex flex-col gap-3 rounded border border-success/50 bg-success/10 p-3">
<Paragraph variant="small/bright">
This is a v2 project. V2 will be deprecated on January 31, 2025.{" "}
<TextLink
className="text-text-bright underline decoration-text-dimmed underline-offset-2 transition hover:text-text-bright hover:decoration-text-bright"
to="https://trigger.dev/blog/v2-end-of-life-announcement"
>
Learn more
</TextLink>
.
</Paragraph>
</Callout>
<LinkButton
variant="primary/medium"
to="https://trigger.dev/docs/v3/upgrading-from-v2"
fullWidth
>
Upgrade to v3
</LinkButton>
</div>
)}
</div>
<div className="flex flex-col gap-1 border-t border-grid-bright p-1">
@@ -319,7 +329,7 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
</Button>
}
/>
{currentPlan?.v3Subscription?.isPaying === false && (
{isV3Project && isFreeV3User && (
<FreePlanUsage
to={v3BillingPath(organization)}
percentage={currentPlan.v3Usage.usagePercentage}
@@ -374,10 +384,10 @@ function ProjectSelector({
title={
<div className="flex w-full items-center justify-between text-text-bright">
<span className="grow truncate text-left">{p.name}</span>
{p.version === "V2" ? (
<MenuCount count={p.jobCount} />
) : (
<Badge variant="v3">v3</Badge>
{p.version === "V2" && (
<Badge variant="small" className="normal-case">
v2
</Badge>
)}
</div>
}
@@ -396,7 +406,7 @@ function ProjectSelector({
</div>
</Fragment>
))}
<div className="border-t border-charcoal-800 p-1">
<div className="border-t border-charcoal-700 p-1">
<PopoverMenuItem to={newOrganizationPath()} title="New Organization" icon="plus" />
</div>
</PopoverContent>
@@ -486,7 +496,6 @@ function V2ProjectSideMenu({
name="Jobs"
icon="job"
iconColor="text-indigo-500"
count={project.jobCount}
to={projectPath(organization, project)}
data-action="jobs"
/>
@@ -502,7 +511,6 @@ function V2ProjectSideMenu({
iconColor="text-amber-500"
to={projectTriggersPath(organization, project)}
data-action="triggers"
hasWarning={project.hasInactiveExternalTriggers}
/>
<SideMenuItem
name="Events"
@@ -514,7 +522,6 @@ function V2ProjectSideMenu({
name="HTTP endpoints"
icon="http-endpoint"
iconColor="text-pink-500"
count={project.httpEndpointCount}
to={projectHttpEndpointsPath(organization, project)}
data-action="httpendpoints"
/>
@@ -589,6 +596,7 @@ function V3ProjectSideMenu({
to={v3EnvironmentVariablesPath(organization, project)}
data-action="environment variables"
/>
<SideMenuItem
name="Deployments"
icon={ServerStackIcon}
@@ -605,6 +613,13 @@ function V3ProjectSideMenu({
data-action="alerts"
/>
)}
<SideMenuItem
name="Concurrency limits"
icon={RectangleStackIcon}
iconColor="text-indigo-500"
to={v3ConcurrencyPath(organization, project)}
data-action="concurrency"
/>
<SideMenuItem
name="Project settings"
icon="settings"
@@ -1,8 +1,8 @@
import { AnchorHTMLAttributes } from "react";
import { type AnchorHTMLAttributes } from "react";
import { usePathName } from "~/hooks/usePathName";
import { cn } from "~/utils/cn";
import { LinkButton } from "../primitives/Buttons";
import { IconNames } from "../primitives/NamedIcon";
import { type IconNames } from "../primitives/NamedIcon";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip";
import { Icon } from "../primitives/Icon";
import { IconExclamationCircle } from "@tabler/icons-react";
@@ -13,7 +13,7 @@ export function SideMenuItem({
name,
to,
hasWarning,
count,
badge,
target,
subItem = false,
}: {
@@ -22,7 +22,7 @@ export function SideMenuItem({
name: string;
to: string;
hasWarning?: string | boolean;
count?: number;
badge?: string;
target?: AnchorHTMLAttributes<HTMLAnchorElement>["target"];
subItem?: boolean;
}) {
@@ -47,20 +47,20 @@ export function SideMenuItem({
<div className="flex w-full items-center justify-between">
{name}
<div className="flex items-center gap-1">
{count !== undefined && count > 0 && <MenuCount count={count} />}
{badge !== undefined && <MenuCount count={badge} />}
{typeof hasWarning === "string" ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Icon icon={IconExclamationCircle} className="h-5 w-5 text-rose-500" />
<Icon icon={IconExclamationCircle} className="h-5 w-5 text-error" />
</TooltipTrigger>
<TooltipContent className="flex items-center gap-1 border border-rose-500 bg-rose-500/20 backdrop-blur-xl">
<TooltipContent className="flex items-center gap-1 border border-error bg-error/20 backdrop-blur-xl">
{hasWarning}
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
hasWarning && <Icon icon={IconExclamationCircle} className="h-5 w-5 text-rose-500" />
hasWarning && <Icon icon={IconExclamationCircle} className="h-5 w-5 text-error" />
)}
</div>
</div>
@@ -68,8 +68,10 @@ export function SideMenuItem({
);
}
export function MenuCount({ count }: { count: number | string }) {
function MenuCount({ count }: { count: number | string }) {
return (
<div className="rounded-full bg-charcoal-900 px-2 py-1 text-xxs text-text-dimmed">{count}</div>
<div className="rounded-full bg-charcoal-900 px-2 py-1 text-xxs uppercase tracking-wider text-text-dimmed">
{count}
</div>
);
}
@@ -8,7 +8,6 @@ const variants = {
"grid place-items-center rounded-full px-[0.4rem] h-4 tracking-wider text-xxs bg-background-dimmed text-text-dimmed uppercase whitespace-nowrap",
outline:
"grid place-items-center rounded-sm px-1.5 h-5 tracking-wider text-xxs border border-dimmed text-text-dimmed uppercase whitespace-nowrap",
v3: "grid place-items-center rounded-full px-[0.4rem] h-5 tracking-wider text-xxs bg-charcoal-750 text-primary whitespace-nowrap",
"outline-rounded":
"grid place-items-center rounded-full px-1 h-4 tracking-wider text-xxs border border-blue-500 text-blue-500 uppercase whitespace-nowrap",
};
@@ -245,12 +245,12 @@ export function ButtonContent(props: ButtonContentPropsType) {
type ButtonPropsType = Pick<
JSX.IntrinsicElements["button"],
"type" | "disabled" | "onClick" | "name" | "value" | "form"
"type" | "disabled" | "onClick" | "name" | "value" | "form" | "autoFocus"
> &
React.ComponentProps<typeof ButtonContent>;
export const Button = forwardRef<HTMLButtonElement, ButtonPropsType>(
({ type, disabled, onClick, ...props }, ref) => {
({ type, disabled, autoFocus, onClick, ...props }, ref) => {
const innerRef = useRef<HTMLButtonElement>(null);
useImperativeHandle(ref, () => innerRef.current as HTMLButtonElement);
@@ -276,6 +276,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonPropsType>(
value={props.value}
ref={innerRef}
form={props.form}
autoFocus={autoFocus}
>
<ButtonContent {...props} />
</button>
@@ -4,8 +4,8 @@ import { ChevronDownIcon, EllipsisVerticalIcon } from "@heroicons/react/24/solid
import * as PopoverPrimitive from "@radix-ui/react-popover";
import * as React from "react";
import { cn } from "~/utils/cn";
import { ButtonContentPropsType, LinkButton } from "./Buttons";
import { Paragraph, ParagraphVariant } from "./Paragraph";
import { type ButtonContentPropsType, LinkButton } from "./Buttons";
import { Paragraph, type ParagraphVariant } from "./Paragraph";
const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger;
@@ -41,7 +41,7 @@ function PopoverSectionHeader({
variant?: ParagraphVariant;
}) {
return (
<Paragraph variant={variant} className="bg-charcoal-900 px-2.5 py-1.5">
<Paragraph variant={variant} className="bg-charcoal-750 px-2.5 py-1.5">
{title}
</Paragraph>
);
@@ -71,7 +71,10 @@ function PopoverMenuItem({
fullWidth
textAlignLeft
TrailingIcon={isSelected ? "check" : undefined}
className={isSelected ? "bg-charcoal-750 group-hover:bg-charcoal-800" : undefined}
className={cn(
"group-hover:bg-charcoal-700",
isSelected ? "bg-charcoal-750 group-hover:bg-charcoal-600/50" : undefined
)}
>
{title}
</LinkButton>
@@ -1,10 +1,10 @@
import { Form, useFetcher, useNavigation, useSubmit } from "@remix-run/react";
import { Form, useNavigation, useSubmit } from "@remix-run/react";
import { useCallback, useEffect, useRef } from "react";
import { UseDataFunctionReturn, useTypedFetcher } from "remix-typedjson";
import { JSONEditor } from "~/components/code/JSONEditor";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
import { Button } from "~/components/primitives/Buttons";
import { DialogContent, DialogDescription, DialogHeader } from "~/components/primitives/Dialog";
import { DialogContent, DialogHeader } from "~/components/primitives/Dialog";
import { Header3 } from "~/components/primitives/Headers";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Label } from "~/components/primitives/Label";
@@ -27,7 +27,7 @@ export function ReplayRunDialog({ runFriendlyId, failedRedirect }: ReplayRunDial
function ReplayContent({ runFriendlyId, failedRedirect }: ReplayRunDialogProps) {
const fetcher = useTypedFetcher<typeof loader>();
const isLoading = fetcher.state !== "idle";
const isLoading = fetcher.state === "loading";
useEffect(() => {
fetcher.load(`/resources/taskruns/${runFriendlyId}/replay`);
@@ -98,6 +98,7 @@ function ReplayForm({
<Header3 spacing>Payload</Header3>
<div className="mb-3 max-h-[70vh] overflow-y-auto rounded-sm border border-grid-dimmed bg-charcoal-900 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
<JSONEditor
autoFocus
defaultValue={currentJson.current}
readOnly={false}
basicSetup
@@ -67,23 +67,17 @@ const taskRunStatusDescriptions: Record<TaskRunStatus, string> = {
EXPIRED: "Task has surpassed its ttl and won't be executed",
};
export const QUEUED_STATUSES: TaskRunStatus[] = ["PENDING", "WAITING_FOR_DEPLOY", "DELAYED"];
export const QUEUED_STATUSES = [
"PENDING",
"WAITING_FOR_DEPLOY",
"DELAYED",
] satisfies TaskRunStatus[];
export const RUNNING_STATUSES: TaskRunStatus[] = [
export const RUNNING_STATUSES = [
"EXECUTING",
"RETRYING_AFTER_FAILURE",
"WAITING_TO_RESUME",
];
export const FINISHED_STATUSES: TaskRunStatus[] = [
"COMPLETED_SUCCESSFULLY",
"CANCELED",
"COMPLETED_WITH_ERRORS",
"INTERRUPTED",
"SYSTEM_FAILURE",
"CRASHED",
"EXPIRED",
];
] satisfies TaskRunStatus[];
export function descriptionForTaskRunStatus(status: TaskRunStatus): string {
return taskRunStatusDescriptions[status];
+1
View File
@@ -114,6 +114,7 @@ const EnvironmentSchema = z.object({
DEPOT_TOKEN: z.string().optional(),
DEPOT_PROJECT_ID: z.string().optional(),
DEPOT_ORG_ID: z.string().optional(),
DEPOT_REGION: z.string().default("us-east-1"),
CONTAINER_REGISTRY_ORIGIN: z.string().optional(),
CONTAINER_REGISTRY_USERNAME: z.string().optional(),
CONTAINER_REGISTRY_PASSWORD: z.string().optional(),
+4
View File
@@ -13,3 +13,7 @@ interface Array<T> {
export type Prettify<T> = {
[K in keyof T]: T[K];
} & {};
export type EnsureProperty<T, K extends keyof T> = T & {
[P in K]-?: NonNullable<T[P]>;
};
+32
View File
@@ -1,5 +1,12 @@
import { redirect } from "@remix-run/server-runtime";
import { prisma } from "~/db.server";
import { SearchParams } from "~/routes/admin._index";
import {
clearImpersonationId,
commitImpersonationSession,
setImpersonationId,
} from "~/services/impersonation.server";
import { requireUser } from "~/services/session.server";
const pageSize = 20;
@@ -32,6 +39,7 @@ export async function adminGetUsers(userId: string, { page, search }: SearchPara
select: {
title: true,
slug: true,
deletedAt: true,
},
},
},
@@ -120,6 +128,7 @@ export async function adminGetOrganizations(userId: string, { page, search }: Se
title: true,
v2Enabled: true,
v3Enabled: true,
deletedAt: true,
members: {
select: {
user: {
@@ -217,3 +226,26 @@ export async function setV3Enabled(userId: string, id: string, v3Enabled: boolea
},
});
}
export async function redirectWithImpersonation(request: Request, userId: string, path: string) {
const user = await requireUser(request);
if (!user.admin) {
throw new Error("Unauthorized");
}
const session = await setImpersonationId(userId, request);
return redirect(path, {
headers: { "Set-Cookie": await commitImpersonationSession(session) },
});
}
export async function clearImpersonation(request: Request, path: string) {
const session = await clearImpersonationId(request);
return redirect(path, {
headers: {
"Set-Cookie": await commitImpersonationSession(session),
},
});
}
@@ -116,7 +116,20 @@ export async function disconnectSession(environmentId: string) {
return session;
}
type DisplayableInputEnvironment = Prisma.RuntimeEnvironmentGetPayload<{
export async function findLatestSession(environmentId: string) {
const session = await prisma.runtimeEnvironmentSession.findFirst({
where: {
environmentId,
},
orderBy: {
createdAt: "desc",
},
});
return session;
}
export type DisplayableInputEnvironment = Prisma.RuntimeEnvironmentGetPayload<{
select: {
id: true;
type: true;
+21 -1
View File
@@ -1,5 +1,6 @@
import type { JobRun, Task, TaskAttempt } from "@trigger.dev/database";
import type { JobRun, Task, TaskAttempt, TaskTriggerSource } from "@trigger.dev/database";
import { CachedTask, ServerTask } from "@trigger.dev/core";
import { PrismaClientOrTransaction, sqlDatabaseSchema } from "~/db.server";
export type TaskWithAttempts = Task & {
attempts: TaskAttempt[];
@@ -116,3 +117,22 @@ function prepareTaskForCaching(task: TaskForCaching): CachedTask {
function calculateCachedTaskSize(task: CachedTask): number {
return JSON.stringify(task).length;
}
/**
*
* @param prisma An efficient query to get all task identifiers for a project.
* It has indexes for fast performance.
* It does NOT care about versions, so includes all tasks ever created.
*/
export function getAllTaskIdentifiers(prisma: PrismaClientOrTransaction, projectId: string) {
return prisma.$queryRaw<
{
slug: string;
triggerSource: TaskTriggerSource;
}[]
>`
SELECT DISTINCT(slug), "triggerSource"
FROM ${sqlDatabaseSchema}."BackgroundWorkerTask"
WHERE "projectId" = ${projectId}
ORDER BY slug ASC;`;
}
+3 -5
View File
@@ -318,7 +318,7 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
identifier: K,
payload: z.infer<TMessageCatalog[K]>,
options?: ZodWorkerEnqueueOptions
): Promise<GraphileJob> {
): Promise<GraphileJob | undefined> {
const task = this.#tasks[identifier];
const optionsWithoutTx = removeUndefinedKeys(omit(options ?? {}, ["tx"]));
@@ -439,11 +439,9 @@ export class ZodWorker<TMessageCatalog extends MessageCatalogSchema> {
identifier,
payload,
spec,
error: JSON.stringify(rows.error),
});
throw new Error(
`Failed to add job to queue, zod parsing error: ${JSON.stringify(rows.error)}`
);
return { job: undefined, durationInMs: Math.floor(durationInMs) };
}
const job = rows.data[0];
@@ -14,8 +14,6 @@ import { Job } from "@trigger.dev/database";
import { BasePresenter } from "./v3/basePresenter.server";
export class JobPresenter extends BasePresenter {
public async call({
userId,
jobSlug,
@@ -96,15 +94,6 @@ export class JobPresenter extends BasePresenter {
slug: true,
},
},
_count: {
select: {
runs: {
where: {
isTest: false,
},
},
},
},
},
where: {
slug: jobSlug,
@@ -196,9 +185,6 @@ export class JobPresenter extends BasePresenter {
const projectRootPath = projectPath({ slug: organizationSlug }, { slug: projectSlug });
//we exclude test runs from this count
const hasRealRuns = job._count.runs > 0;
return {
id: job.id,
slug: job.slug,
@@ -214,9 +200,6 @@ export class JobPresenter extends BasePresenter {
? `${projectRootPath}/${alias.version.triggerLink}`
: undefined,
},
noRunsHelp: hasRealRuns
? undefined
: this.#getNoRunsHelp(alias.version.triggerHelp, projectRootPath),
integrations,
hasIntegrationsRequiringAction: integrations.some((i) => i.setupStatus === "MISSING_FIELDS"),
lastRun,
@@ -224,19 +207,4 @@ export class JobPresenter extends BasePresenter {
environments,
};
}
#getNoRunsHelp(data: Prisma.JsonValue, projectPath: string) {
const triggerHelp = TriggerHelpSchema.nullish().parse(data);
if (!triggerHelp) {
return undefined;
}
if (triggerHelp.noRuns) {
triggerHelp.noRuns.link = triggerHelp.noRuns.link
? `${projectPath}/${triggerHelp.noRuns.link}`
: undefined;
return triggerHelp.noRuns;
}
}
}
@@ -186,29 +186,10 @@ export class OrganizationsPresenter {
id: true,
slug: true,
name: true,
_count: {
select: {
jobs: {
where: {
internal: false,
deletedAt: null,
},
},
},
},
version: true,
},
orderBy: { name: "asc" },
},
_count: {
select: {
integrations: {
where: {
setupStatus: "MISSING_FIELDS",
},
},
},
},
},
});
@@ -221,10 +202,8 @@ export class OrganizationsPresenter {
id: project.id,
slug: project.slug,
name: project.name,
jobCount: project._count.jobs,
version: project.version,
})),
hasUnconfiguredIntegrations: org._count.integrations > 0,
runsEnabled: org.runsEnabled,
};
});
@@ -28,22 +28,6 @@ export class ProjectPresenter {
deletedAt: true,
version: true,
externalRef: true,
_count: {
select: {
sources: {
where: {
active: false,
},
},
jobs: {
where: {
internal: false,
deletedAt: null,
},
},
httpEndpoints: true,
},
},
environments: {
select: {
id: true,
@@ -81,9 +65,6 @@ export class ProjectPresenter {
updatedAt: project.updatedAt,
deletedAt: project.deletedAt,
version: project.version,
hasInactiveExternalTriggers: project._count.sources > 0,
jobCount: project._count.jobs,
httpEndpointCount: project._count.httpEndpoints,
environments: sortEnvironments(
project.environments.map((environment) => ({
...displayableEnvironment(environment, userId),
@@ -4,7 +4,7 @@ import {
EventSpecificationSchema,
StyleSchema,
} from "@trigger.dev/core";
import { PrismaClient, prisma } from "~/db.server";
import { $replica, PrismaClient, prisma } from "~/db.server";
import { isRunCompleted, runBasicStatus } from "~/models/jobRun.server";
import { mergeProperties } from "~/utils/mergeProperties.server";
import { taskListToTree } from "~/utils/taskListToTree";
@@ -110,7 +110,7 @@ export class RunPresenter {
}
query({ id, userId }: RunOptions) {
return this.#prismaClient.jobRun.findFirst({
return $replica.jobRun.findFirst({
select: {
id: true,
number: true,
@@ -198,6 +198,7 @@ export class RunPresenter {
orderBy: {
createdAt: "asc",
},
take: 1000,
},
runConnections: {
select: {
@@ -1,5 +1,5 @@
import { RedactSchema, StyleSchema } from "@trigger.dev/core";
import { PrismaClient, prisma } from "~/db.server";
import { $replica, PrismaClient, prisma } from "~/db.server";
import { mergeProperties } from "~/utils/mergeProperties.server";
import { Redactor } from "~/utils/redactor";
@@ -16,7 +16,7 @@ export class TaskDetailsPresenter {
}
public async call({ id, userId }: DetailsProps) {
const task = await this.#prismaClient.task.findFirst({
const task = await $replica.task.findFirst({
select: {
id: true,
displayKey: true,
@@ -1,6 +1,6 @@
import { logger } from "~/services/logger.server";
import { BasePresenter } from "./basePresenter.server";
import { ProjectAlertChannel } from "@trigger.dev/database";
import { type ProjectAlertChannel } from "@trigger.dev/database";
import { decryptSecret } from "~/services/secrets/secretStore.server";
import { env } from "~/env.server";
import {
@@ -1,7 +1,7 @@
import {
ProjectAlertChannel,
ProjectAlertChannelType,
ProjectAlertType,
type ProjectAlertChannel,
type ProjectAlertChannelType,
type ProjectAlertType,
} from "@trigger.dev/database";
import assertNever from "assert-never";
import { z } from "zod";
@@ -12,7 +12,12 @@ import {
} from "~/models/projectAlert.server";
import { decryptSecret } from "~/services/secrets/secretStore.server";
export const ApiAlertType = z.enum(["attempt_failure", "deployment_failure", "deployment_success"]);
export const ApiAlertType = z.enum([
"run_failure",
"attempt_failure",
"deployment_failure",
"deployment_success",
]);
export type ApiAlertType = z.infer<typeof ApiAlertType>;
@@ -72,6 +77,8 @@ export class ApiAlertChannelPresenter {
public static alertTypeToApi(alertType: ProjectAlertType): ApiAlertType {
switch (alertType) {
case "TASK_RUN":
return "run_failure";
case "TASK_RUN_ATTEMPT":
return "attempt_failure";
case "DEPLOYMENT_FAILURE":
@@ -85,6 +92,8 @@ export class ApiAlertChannelPresenter {
public static alertTypeFromApi(alertType: ApiAlertType): ProjectAlertType {
switch (alertType) {
case "run_failure":
return "TASK_RUN";
case "attempt_failure":
return "TASK_RUN_ATTEMPT";
case "deployment_failure":
@@ -0,0 +1,102 @@
import { QUEUED_STATUSES } from "~/components/runs/v3/TaskRunStatus";
import { Prisma, sqlDatabaseSchema } from "~/db.server";
import { type Project } from "~/models/project.server";
import {
displayableEnvironment,
type DisplayableInputEnvironment,
} from "~/models/runtimeEnvironment.server";
import { type User } from "~/models/user.server";
import { getLimit } from "~/services/platform.v3.server";
import { filterOrphanedEnvironments, sortEnvironments } from "~/utils/environmentSort";
import { concurrencyTracker } from "~/v3/services/taskRunConcurrencyTracker.server";
import { BasePresenter } from "./basePresenter.server";
export type Environment = Awaited<
ReturnType<ConcurrencyPresenter["environmentConcurrency"]>
>[number];
export class ConcurrencyPresenter extends BasePresenter {
public async call({ userId, projectSlug }: { userId: User["id"]; projectSlug: Project["slug"] }) {
const project = await this._replica.project.findFirst({
select: {
id: true,
organizationId: true,
environments: {
select: {
id: true,
apiKey: true,
pkApiKey: true,
type: true,
slug: true,
updatedAt: true,
orgMember: {
select: {
user: { select: { id: true, name: true, displayName: true } },
},
},
maximumConcurrencyLimit: true,
},
},
},
where: {
slug: projectSlug,
organization: {
members: {
some: {
userId,
},
},
},
},
});
if (!project) {
throw new Error(`Project not found: ${projectSlug}`);
}
return {
environments: this.environmentConcurrency(
project.id,
userId,
filterOrphanedEnvironments(project.environments)
),
};
}
async environmentConcurrency(
projectId: string,
userId: string,
environments: (DisplayableInputEnvironment & { maximumConcurrencyLimit: number })[]
) {
const environmentConcurrency = await concurrencyTracker.environmentConcurrentRunCounts(
projectId,
environments.map((env) => env.id)
);
const queued = await this._replica.$queryRaw<
{
runtimeEnvironmentId: string;
count: BigInt;
}[]
>`
SELECT
"runtimeEnvironmentId",
COUNT(*)
FROM
${sqlDatabaseSchema}."TaskRun" as tr
WHERE
tr."projectId" = ${projectId}
AND tr."status" = ANY(ARRAY[${Prisma.join(QUEUED_STATUSES)}]::\"TaskRunStatus\"[])
GROUP BY
tr."runtimeEnvironmentId";`;
const sortedEnvironments = sortEnvironments(environments).map((environment) => ({
...displayableEnvironment(environment, userId),
concurrencyLimit: environment.maximumConcurrencyLimit,
concurrency: environmentConcurrency[environment.id] ?? 0,
queued: Number(queued.find((q) => q.runtimeEnvironmentId === environment.id)?.count ?? 0),
}));
return sortedEnvironments;
}
}
@@ -1,16 +1,13 @@
import {
DeploymentErrorData,
ExternalBuildData,
TaskMetadataFailedToParseData,
groupTaskMetadataIssuesByTask,
prepareDeploymentError,
} from "@trigger.dev/core/v3";
import { WorkerDeployment, WorkerDeploymentStatus } from "@trigger.dev/database";
import { z } from "zod";
import { WorkerDeployment } from "@trigger.dev/database";
import { PrismaClient, prisma } from "~/db.server";
import { Organization } from "~/models/organization.server";
import { Project } from "~/models/project.server";
import { User } from "~/models/user.server";
import { safeJsonParse } from "~/utils/json";
import { getUsername } from "~/utils/username";
export type ErrorData = {
@@ -164,75 +161,12 @@ export class DeploymentPresenter {
return;
}
const parsedErrorData = DeploymentErrorData.safeParse(errorData);
const deploymentError = DeploymentErrorData.safeParse(errorData);
if (!parsedErrorData.success) {
if (!deploymentError.success) {
return;
}
if (parsedErrorData.data.name === "TaskMetadataParseError") {
const errorJson = safeJsonParse(parsedErrorData.data.stack);
if (errorJson) {
const parsedError = TaskMetadataFailedToParseData.safeParse(errorJson);
if (parsedError.success) {
return {
name: parsedErrorData.data.name,
message: parsedErrorData.data.message,
stack: createTaskMetadataFailedErrorStack(parsedError.data),
stderr: parsedErrorData.data.stderr,
};
} else {
return {
name: parsedErrorData.data.name,
message: parsedErrorData.data.message,
stderr: parsedErrorData.data.stderr,
};
}
} else {
return {
name: parsedErrorData.data.name,
message: parsedErrorData.data.message,
stderr: parsedErrorData.data.stderr,
};
}
}
return {
name: parsedErrorData.data.name,
message: parsedErrorData.data.message,
stack: parsedErrorData.data.stack,
stderr: parsedErrorData.data.stderr,
};
return prepareDeploymentError(deploymentError.data);
}
}
function createTaskMetadataFailedErrorStack(
data: z.infer<typeof TaskMetadataFailedToParseData>
): string {
const stack = [];
const groupedIssues = groupTaskMetadataIssuesByTask(data.tasks, data.zodIssues);
for (const key in groupedIssues) {
const taskWithIssues = groupedIssues[key];
if (!taskWithIssues) {
continue;
}
stack.push("\n");
stack.push(` ${taskWithIssues.exportName} in ${taskWithIssues.filePath}`);
for (const issue of taskWithIssues.issues) {
if (issue.path) {
stack.push(` x ${issue.path} ${issue.message}`);
} else {
stack.push(` x ${issue.message}`);
}
}
}
return stack.join("\n");
}
@@ -1,6 +1,8 @@
import { RuntimeEnvironmentType } from "@trigger.dev/database";
import { PrismaClient, prisma } from "~/db.server";
import { displayableEnvironment } from "~/models/runtimeEnvironment.server";
import { logger } from "~/services/logger.server";
import { filterOrphanedEnvironments } from "~/utils/environmentSort";
import { getTimezones } from "~/utils/timezones.server";
type EditScheduleOptions = {
@@ -68,9 +70,11 @@ export class EditSchedulePresenter {
},
});
const possibleEnvironments = project.environments.map((environment) => {
return displayableEnvironment(environment, userId);
});
const possibleEnvironments = filterOrphanedEnvironments(project.environments).map(
(environment) => {
return displayableEnvironment(environment, userId);
}
);
return {
possibleTasks: possibleTasks.map((task) => task.slug),
@@ -115,13 +119,16 @@ export class EditSchedulePresenter {
return {
...schedule,
cron: schedule.generatorExpression,
environments: schedule.instances.map((instance) => {
environments: schedule.instances.flatMap((instance) => {
const environment = possibleEnvironments.find((env) => env.id === instance.environmentId);
if (!environment) {
throw new Error(`Environment with id ${instance.environmentId} not found`);
logger.error(
`EditSchedulePresenter: environment with id ${instance.environmentId} not found`
);
return [];
}
return environment;
return [environment];
}),
};
}
@@ -1,7 +1,7 @@
import { PrismaClient, prisma } from "~/db.server";
import { Project } from "~/models/project.server";
import { User } from "~/models/user.server";
import { sortEnvironments } from "~/utils/environmentSort";
import { filterOrphanedEnvironments, sortEnvironments } from "~/utils/environmentSort";
import { EnvironmentVariablesRepository } from "~/v3/environmentVariables/environmentVariablesRepository.server";
type Result = Awaited<ReturnType<EnvironmentVariablesPresenter["call"]>>;
@@ -78,11 +78,11 @@ export class EnvironmentVariablesPresenter {
where: {
project: {
slug: projectSlug,
}
},
},
});
const sortedEnvironments = sortEnvironments(environments);
const sortedEnvironments = sortEnvironments(filterOrphanedEnvironments(environments));
const repository = new EnvironmentVariablesRepository(this.#prismaClient);
const variables = await repository.getProject(project.id);
@@ -104,12 +104,12 @@ export class EnvironmentVariablesPresenter {
}, {} as Record<string, { value: string | undefined; environment: { type: string; id: string } }>),
};
}),
environments: sortedEnvironments.filter(
(e) => e.orgMember?.userId === userId || e.orgMember === null
).map((environment) => ({
id: environment.id,
type: environment.type,
})),
environments: sortedEnvironments
.filter((e) => e.orgMember?.userId === userId || e.orgMember === null)
.map((environment) => ({
id: environment.id,
type: environment.type,
})),
hasStaging: environments.some((environment) => environment.type === "STAGING"),
};
}
@@ -1,10 +1,10 @@
import { Prisma, type TaskRunStatus, type TaskTriggerSource } from "@trigger.dev/database";
import { Prisma, type TaskRunStatus } from "@trigger.dev/database";
import parse from "parse-duration";
import { type Direction } from "~/components/runs/RunStatuses";
import { FINISHED_STATUSES } from "~/components/runs/v3/TaskRunStatus";
import { sqlDatabaseSchema } from "~/db.server";
import { displayableEnvironment } from "~/models/runtimeEnvironment.server";
import { isCancellableRunStatus } from "~/v3/taskStatus";
import { getAllTaskIdentifiers } from "~/models/task.server";
import { isCancellableRunStatus, isFinalRunStatus } from "~/v3/taskStatus";
import { BasePresenter } from "./basePresenter.server";
export type RunListOptions = {
@@ -97,16 +97,7 @@ export class RunListPresenter extends BasePresenter {
});
//get all possible tasks
const possibleTasksAsync = this._replica.$queryRaw<
{
slug: string;
triggerSource: TaskTriggerSource;
}[]
>`
SELECT DISTINCT(slug), "triggerSource"
FROM ${sqlDatabaseSchema}."BackgroundWorkerTask"
WHERE "projectId" = ${project.id}
ORDER BY slug ASC;`;
const possibleTasksAsync = getAllTaskIdentifiers(this._replica, project.id);
//get possible bulk actions
const bulkActionsAsync = this._replica.bulkActionGroup.findMany({
@@ -313,7 +304,7 @@ WHERE
throw new Error(`Environment not found for TaskRun ${run.id}`);
}
const hasFinished = FINISHED_STATUSES.includes(run.status);
const hasFinished = isFinalRunStatus(run.status);
const startedAt = run.startedAt ?? run.lockedAt;
@@ -1,9 +1,9 @@
import { millisecondsToNanoseconds } from "@trigger.dev/core/v3";
import { createTreeFromFlatItems, flattenTree } from "~/components/primitives/TreeView/TreeView";
import { FINISHED_STATUSES } from "~/components/runs/v3/TaskRunStatus";
import { PrismaClient, prisma } from "~/db.server";
import { getUsername } from "~/utils/username";
import { eventRepository } from "~/v3/eventRepository.server";
import { isFinalRunStatus } from "~/v3/taskStatus";
type Result = Awaited<ReturnType<RunPresenter["call"]>>;
export type Run = Result["run"];
@@ -35,6 +35,8 @@ export class RunPresenter {
spanId: true,
friendlyId: true,
status: true,
completedAt: true,
logsDeletedAt: true,
runtimeEnvironment: {
select: {
id: true,
@@ -75,7 +77,9 @@ export class RunPresenter {
traceId: run.traceId,
spanId: run.spanId,
status: run.status,
isFinished: FINISHED_STATUSES.includes(run.status),
isFinished: isFinalRunStatus(run.status),
completedAt: run.completedAt,
logsDeletedAt: run.logsDeletedAt,
environment: {
id: run.runtimeEnvironment.id,
organizationId: run.runtimeEnvironment.organizationId,
@@ -134,7 +138,9 @@ export class RunPresenter {
traceId: run.traceId,
spanId: run.spanId,
status: run.status,
isFinished: FINISHED_STATUSES.includes(run.status),
isFinished: isFinalRunStatus(run.status),
completedAt: run.completedAt,
logsDeletedAt: run.logsDeletedAt,
environment: {
id: run.runtimeEnvironment.id,
organizationId: run.runtimeEnvironment.organizationId,
@@ -1,9 +1,9 @@
import { Context, MachinePresetName, prettyPrintPacket } from "@trigger.dev/core/v3";
import { FINISHED_STATUSES, RUNNING_STATUSES } from "~/components/runs/v3/TaskRunStatus";
import { MachinePresetName, prettyPrintPacket, TaskRunError } from "@trigger.dev/core/v3";
import { RUNNING_STATUSES } from "~/components/runs/v3/TaskRunStatus";
import { eventRepository } from "~/v3/eventRepository.server";
import { BasePresenter } from "./basePresenter.server";
import { machineDefinition } from "@trigger.dev/platform/v3";
import { machinePresetFromName } from "~/v3/machinePresets.server";
import { FINAL_ATTEMPT_STATUSES, isFinalRunStatus } from "~/v3/taskStatus";
import { BasePresenter } from "./basePresenter.server";
type Result = Awaited<ReturnType<SpanPresenter["call"]>>;
export type Span = NonNullable<NonNullable<Result>["span"]>;
@@ -57,6 +57,7 @@ export class SpanPresenter extends BasePresenter {
async getRun(spanId: string) {
const run = await this._replica.taskRun.findFirst({
select: {
id: true,
traceId: true,
//metadata
number: true,
@@ -81,6 +82,8 @@ export class SpanPresenter extends BasePresenter {
createdAt: true,
updatedAt: true,
queuedAt: true,
completedAt: true,
logsDeletedAt: true,
//idempotency
idempotencyKey: true,
//delayed
@@ -111,17 +114,6 @@ export class SpanPresenter extends BasePresenter {
payload: true,
payloadType: true,
maxAttempts: true,
//finished attempt
attempts: {
select: {
output: true,
outputType: true,
error: true,
},
where: {
status: "COMPLETED",
},
},
project: {
include: {
organization: true,
@@ -143,9 +135,27 @@ export class SpanPresenter extends BasePresenter {
return;
}
const finishedAttempt = run.attempts.at(0);
const isFinished = isFinalRunStatus(run.status);
const finishedAttempt = isFinished
? await this._replica.taskRunAttempt.findFirst({
select: {
output: true,
outputType: true,
error: true,
},
where: {
status: { in: FINAL_ATTEMPT_STATUSES },
taskRunId: run.id,
},
orderBy: {
createdAt: "desc",
},
})
: null;
const output =
finishedAttempt === undefined
finishedAttempt === null
? undefined
: finishedAttempt.outputType === "application/store"
? `/resources/packets/${run.runtimeEnvironment.id}/${finishedAttempt.output}`
@@ -160,6 +170,19 @@ export class SpanPresenter extends BasePresenter {
? await prettyPrintPacket(run.payload, run.payloadType ?? undefined)
: undefined;
let error: TaskRunError | undefined = undefined;
if (finishedAttempt?.error) {
const result = TaskRunError.safeParse(finishedAttempt.error);
if (result.success) {
error = result.data;
} else {
error = {
type: "CUSTOM_ERROR",
raw: JSON.stringify(finishedAttempt.error),
};
}
}
const span = await eventRepository.getSpan(spanId, run.traceId);
const context = {
@@ -213,6 +236,8 @@ export class SpanPresenter extends BasePresenter {
updatedAt: run.updatedAt,
delayUntil: run.delayUntil,
expiredAt: run.expiredAt,
completedAt: run.completedAt,
logsDeletedAt: run.logsDeletedAt,
ttl: run.ttl,
taskIdentifier: run.taskIdentifier,
version: run.lockedToVersion?.version,
@@ -237,14 +262,14 @@ export class SpanPresenter extends BasePresenter {
costInCents: run.costInCents,
totalCostInCents: run.costInCents + run.baseCostInCents,
usageDurationMs: run.usageDurationMs,
isFinished: FINISHED_STATUSES.includes(run.status),
isFinished,
isRunning: RUNNING_STATUSES.includes(run.status),
payload,
payloadType: run.payloadType,
output,
outputType: finishedAttempt?.outputType ?? "application/json",
error,
links: span?.links,
events: span?.events,
context: JSON.stringify(context, null, 2),
};
}
@@ -1,9 +1,9 @@
import { Prisma } from "@trigger.dev/database";
import type {
RuntimeEnvironmentType,
TaskTriggerSource,
TaskRunStatus as TaskRunStatusType,
} from "@trigger.dev/database";
import { Prisma } from "@trigger.dev/database";
import { QUEUED_STATUSES, RUNNING_STATUSES } from "~/components/runs/v3/TaskRunStatus";
import { sqlDatabaseSchema } from "~/db.server";
import type { Organization } from "~/models/organization.server";
@@ -20,6 +20,7 @@ import { logger } from "~/services/logger.server";
import { BasePresenter } from "./basePresenter.server";
import { TaskRunStatus } from "~/database-types";
import { CURRENT_DEPLOYMENT_LABEL } from "~/consts";
import { concurrencyTracker } from "~/v3/services/taskRunConcurrencyTracker.server";
export type Task = {
slug: string;
@@ -114,7 +115,7 @@ export class TaskListPresenter extends BasePresenter {
JOIN ${sqlDatabaseSchema}."BackgroundWorkerTask" tasks ON tasks."workerId" = workers.id
ORDER BY slug ASC;`;
//group by the task identifier (task.slug). Add the latestRun and add all the environments.
//group by the task identifier (task.slug).
const outputTasks = tasks.reduce((acc, task) => {
const environment = project.environments.find((env) => env.id === task.runtimeEnvironmentId);
if (!environment) {
@@ -251,51 +252,40 @@ export class TaskListPresenter extends BasePresenter {
return {};
}
const statuses = await this._replica.$queryRaw<
const concurrencies = await concurrencyTracker.taskConcurrentRunCounts(projectId, tasks);
const queued = await this._replica.$queryRaw<
{
taskIdentifier: string;
status: TaskRunStatusType;
count: BigInt;
}[]
>`
SELECT
tr."taskIdentifier",
tr."status",
tr."taskIdentifier",
COUNT(*)
FROM
${sqlDatabaseSchema}."TaskRun" as tr
WHERE
tr."taskIdentifier" IN (${Prisma.join(tasks)})
AND tr."projectId" = ${projectId}
AND tr."status" IN ('PENDING', 'WAITING_FOR_DEPLOY', 'EXECUTING', 'RETRYING_AFTER_FAILURE', 'WAITING_TO_RESUME')
AND tr."status" = ANY(ARRAY[${Prisma.join(QUEUED_STATUSES)}]::\"TaskRunStatus\"[])
GROUP BY
tr."taskIdentifier",
tr."status"
tr."taskIdentifier"
ORDER BY
tr."taskIdentifier" ASC,
tr."status" ASC;`;
tr."taskIdentifier" ASC`;
return statuses.reduce((acc, a) => {
let existingTask = acc[a.taskIdentifier];
//create an object combining the queued and concurrency counts
const result: Record<string, { queued: number; running: number }> = {};
for (const task of tasks) {
const concurrency = concurrencies[task] ?? 0;
const queuedCount = queued.find((q) => q.taskIdentifier === task)?.count ?? 0;
if (!existingTask) {
existingTask = {
queued: 0,
running: 0,
};
acc[a.taskIdentifier] = existingTask;
}
if (QUEUED_STATUSES.includes(a.status)) {
existingTask.queued += Number(a.count);
}
if (RUNNING_STATUSES.includes(a.status)) {
existingTask.running += Number(a.count);
}
return acc;
}, {} as Record<string, { queued: number; running: number }>);
result[task] = {
queued: Number(queuedCount),
running: concurrency,
};
}
return result;
}
async #getAverageDurations(tasks: string[], projectId: string) {
@@ -105,18 +105,18 @@ export class UsagePresenter extends BasePresenter {
//usage by task
const tasks = this._replica.$queryRaw<TaskUsageItem[]>`
SELECT
tr."taskIdentifier",
COUNT(*) AS "runCount",
AVG(tr."usageDurationMs") AS "averageDuration",
SUM(tr."usageDurationMs") AS "totalDuration",
AVG(tr."costInCents") / 100.0 AS "averageCost",
SUM(tr."costInCents") / 100.0 AS "totalCost",
SUM(tr."baseCostInCents") / 100.0 AS "totalBaseCost"
tr."taskIdentifier",
COUNT(*) AS "runCount",
AVG(tr."usageDurationMs") AS "averageDuration",
SUM(tr."usageDurationMs") AS "totalDuration",
AVG(tr."costInCents") / 100.0 AS "averageCost",
SUM(tr."costInCents") / 100.0 AS "totalCost",
SUM(tr."baseCostInCents") / 100.0 AS "totalBaseCost"
FROM
${sqlDatabaseSchema}."TaskRun" tr
JOIN ${sqlDatabaseSchema}."Project" pr ON pr.id = tr."projectId"
JOIN ${sqlDatabaseSchema}."Organization" org ON org.id = pr."organizationId"
JOIN ${sqlDatabaseSchema}."RuntimeEnvironment" env ON env."projectId" = pr.id
JOIN ${sqlDatabaseSchema}."RuntimeEnvironment" env ON env."id" = tr."runtimeEnvironmentId"
WHERE
env.type <> 'DEVELOPMENT'
AND tr."createdAt" > ${startOfMonth}
@@ -132,7 +132,7 @@ export class UsagePresenter extends BasePresenter {
averageDuration: Number(item.averageDuration),
averageCost: Number(item.averageCost) + env.CENTS_PER_RUN / 100,
totalDuration: Number(item.totalDuration),
totalCost: Number(item.totalCost + item.totalBaseCost),
totalCost: Number(item.totalCost) + Number(item.totalBaseCost),
}))
.sort((a, b) => b.totalCost - a.totalCost);
});
+6
View File
@@ -0,0 +1,6 @@
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { clearImpersonation } from "~/models/admin.server";
export async function loader({ request, params }: LoaderFunctionArgs) {
return clearImpersonation(request, "/admin");
}
@@ -0,0 +1,60 @@
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { redirect } from "remix-typedjson";
import { $replica } from "~/db.server";
import { clearImpersonation, redirectWithImpersonation } from "~/models/admin.server";
import { logger } from "~/services/logger.server";
import { requireUser } from "~/services/session.server";
export async function loader({ request, params }: LoaderFunctionArgs) {
const user = await requireUser(request);
if (!user.admin) {
return redirect("/");
}
const path = params["*"];
const organizationSlug = params.organizationSlug;
logger.debug("Impersonating user", { path, organizationSlug });
if (!organizationSlug) {
logger.debug("Exiting impersonation mode");
return clearImpersonation(request, "/admin");
}
const org = await $replica.organization.findFirst({
where: {
slug: organizationSlug,
deletedAt: null,
},
select: {
members: {
select: {
user: {
select: {
id: true,
confirmedBasicDetails: true,
},
},
},
},
},
});
if (!org) {
logger.debug("Organization not found", { organizationSlug });
return clearImpersonation(request, "/admin");
}
const firstValidMember = org.members.find((m) => m.user.confirmedBasicDetails);
if (!firstValidMember) {
logger.debug("No valid members found", { organizationSlug });
return clearImpersonation(request, "/admin");
}
return redirectWithImpersonation(
request,
firstValidMember.user.id,
`/orgs/${organizationSlug}/${path}`
);
}

Some files were not shown because too many files have changed in this diff Show More