1- import { GitMeta } from "@trigger.dev/core/v3" ;
1+ import { GitMeta , } from "@trigger.dev/core/v3" ;
2+ import { type RuntimeEnvironmentType } from "@trigger.dev/database" ;
23import { type z } from "zod" ;
34import { type Prisma , type PrismaClient , prisma } from "~/db.server" ;
45import { type Project } from "~/models/project.server" ;
56import { type User } from "~/models/user.server" ;
67import { type BranchesOptions } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.branches/route" ;
78import { getCurrentPlan , getPlans } from "~/services/platform.v3.server" ;
89import { checkBranchLimit } from "~/services/upsertBranch.server" ;
10+ import { devPresence } from "./DevPresence.server" ;
11+ import { sortEnvironments } from "~/utils/environmentSort" ;
912
1013type Result = Awaited < ReturnType < BranchesPresenter [ "call" ] > > ;
1114export type Branch = Result [ "branches" ] [ number ] ;
@@ -58,12 +61,14 @@ export class BranchesPresenter {
5861 public async call ( {
5962 userId,
6063 projectSlug,
64+ env,
6165 showArchived = false ,
6266 search,
6367 page = 1 ,
6468 } : {
6569 userId : User [ "id" ] ;
6670 projectSlug : Project [ "slug" ] ;
71+ env : "preview" | "development" ;
6772 } & Options ) {
6873 const project = await this . #prismaClient. project . findFirst ( {
6974 select : {
@@ -86,12 +91,16 @@ export class BranchesPresenter {
8691 throw new Error ( "Project not found" ) ;
8792 }
8893
94+ // TODO audit mishmash of preview/developement preview/dev stg/dev PREVIEW/DEVELOPMENT
95+ const envType = env === "preview" ? "PREVIEW" : "DEVELOPMENT" ;
96+
8997 const branchableEnvironment = await this . #prismaClient. runtimeEnvironment . findFirst ( {
9098 select : {
9199 id : true ,
92100 } ,
93101 where : {
94102 projectId : project . id ,
103+ type : envType ,
95104 isBranchableEnvironment : true ,
96105 } ,
97106 } ) ;
@@ -119,23 +128,29 @@ export class BranchesPresenter {
119128 } ;
120129 }
121130
131+ // The "default" DEV branch has no branchName, so searching 'default' wouldn't
132+ // display it... hacky way around that just always show it.
133+ const branchNameWhere = envType === "DEVELOPMENT" ?
134+ search
135+ ? { OR : [ { contains : search , mode : "insensitive" as const } , { is : null } ] }
136+ : { } :
137+ search
138+ ? { contains : search , mode : "insensitive" as const }
139+ : { not : null } ;
140+ const orgMemberWhere = envType === "DEVELOPMENT" ? { orgMember : { userId } } : { } ;
141+
142+
122143 const visibleCount = await this . #prismaClient. runtimeEnvironment . count ( {
123144 where : {
124145 projectId : project . id ,
125- branchName : search
126- ? {
127- contains : search ,
128- mode : "insensitive" ,
129- }
130- : {
131- not : null ,
132- } ,
146+ type : envType ,
147+ branchName : branchNameWhere ,
148+ ...orgMemberWhere ,
133149 ...( showArchived ? { } : { archivedAt : null } ) ,
134150 } ,
135151 } ) ;
136152
137- // Limits
138- const limits = await checkBranchLimit ( this . #prismaClient, project . organizationId , project . id ) ;
153+ const limits = await checkBranchLimit ( { prisma : this . #prismaClient, organizationId : project . organizationId , projectId : project . id , userId, env } ) ;
139154
140155 const [ currentPlan , plans ] = await Promise . all ( [
141156 getCurrentPlan ( project . organizationId ) ,
@@ -161,14 +176,9 @@ export class BranchesPresenter {
161176 } ,
162177 where : {
163178 projectId : project . id ,
164- branchName : search
165- ? {
166- contains : search ,
167- mode : "insensitive" ,
168- }
169- : {
170- not : null ,
171- } ,
179+ type : envType ,
180+ branchName : branchNameWhere ,
181+ ...orgMemberWhere ,
172182 ...( showArchived ? { } : { archivedAt : null } ) ,
173183 } ,
174184 orderBy : {
@@ -178,35 +188,34 @@ export class BranchesPresenter {
178188 take : BRANCHES_PER_PAGE ,
179189 } ) ;
180190
191+ const totalBranchesWhere = envType === "DEVELOPMENT" ? { } : { not : null } ;
181192 const totalBranches = await this . #prismaClient. runtimeEnvironment . count ( {
182193 where : {
183194 projectId : project . id ,
184- branchName : {
185- not : null ,
186- } ,
195+ type : envType ,
196+ branchName : totalBranchesWhere ,
197+ ... orgMemberWhere ,
187198 } ,
188199 } ) ;
189200
201+
202+ const branchesFiltered = branches
203+ . filter ( ( branch ) => envType === "DEVELOPMENT" || branch . branchName !== null )
204+ . map ( ( branch ) => ( {
205+ ...branch ,
206+ git : processGitMetadata ( branch . git ) ,
207+ branchName : branch . branchName ?? "default" ,
208+ } ) ) ;
209+
210+ const branchesWithActivity = await hydrateEnvsWithActivity ( userId , project . id , branchesFiltered ) ;
211+ const branchesSorted = sortEnvironments ( branchesWithActivity ) ;
212+
190213 return {
191214 branchableEnvironment,
192215 currentPage : page ,
193216 totalPages : Math . ceil ( visibleCount / BRANCHES_PER_PAGE ) ,
194217 hasBranches : totalBranches > 0 ,
195- branches : branches . flatMap ( ( branch ) => {
196- if ( branch . branchName === null ) {
197- return [ ] ;
198- }
199-
200- const git = processGitMetadata ( branch . git ) ;
201-
202- return [
203- {
204- ...branch ,
205- branchName : branch . branchName ,
206- git,
207- } as const ,
208- ] ;
209- } ) ,
218+ branches : branchesSorted ,
210219 hasFilters,
211220 limits,
212221 canPurchaseBranches,
@@ -218,6 +227,23 @@ export class BranchesPresenter {
218227 }
219228}
220229
230+ export async function hydrateEnvsWithActivity < T extends { type : RuntimeEnvironmentType ; id : string } >
231+ ( userId : string , projectId : string , environments : T [ ] ) : Promise < Array < T & { lastActivity : Date | undefined ; isConnected : boolean | undefined } > > {
232+ const recentDevBranchIds = await devPresence . getRecentBranchIds ( userId , projectId ) ;
233+
234+ return Promise . all ( environments . map ( async ( env ) => {
235+ if ( env . type !== "DEVELOPMENT" ) {
236+ return { ...env , lastActivity : undefined , isConnected : undefined } ;
237+ }
238+
239+ const devHit = recentDevBranchIds . get ( env . id ) ;
240+ const lastActivity = devHit === undefined ? undefined : devHit ;
241+ // TODO change dev-presence to a different data structure to avoid N calls?
242+ const isConnected = devHit === undefined ? undefined : await devPresence . isConnected ( env . id ) ;
243+ return { ...env , lastActivity, isConnected } ;
244+ } ) ) ;
245+ }
246+
221247export function processGitMetadata ( data : Prisma . JsonValue ) : GitMetaLinks | null {
222248 if ( ! data ) return null ;
223249
0 commit comments