Skip to content

Commit 43404f1

Browse files
authored
internal: (studio) pass the getProjectOptions function (#32573)
1 parent 9837082 commit 43404f1

File tree

8 files changed

+252
-153
lines changed

8 files changed

+252
-153
lines changed

packages/server/lib/cloud/api/axios_middleware/transform_error.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,15 @@ export const transformError = (err: AxiosError | Error & { error?: any, statusCo
1313
{ data: err.error, status: err.statusCode }
1414

1515
if (isObject(data)) {
16-
const body = JSON.stringify(data, null, 2)
16+
let body: string | null = null
1717

18-
err.message = [status, body].join('\n\n')
18+
try {
19+
body = JSON.stringify(data, null, 2)
20+
} catch (e) {
21+
// do nothing
22+
}
23+
24+
err.message = body ? [status, body].join('\n\n') : `${status}`
1925
}
2026

2127
err.isApiError = true

packages/server/lib/cloud/studio/StudioLifecycleManager.ts

Lines changed: 62 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { CloudRequest } from '../api/cloud_request'
1111
import { isRetryableError } from '../network/is_retryable_error'
1212
import { asyncRetry } from '../../util/async_retry'
1313
import { postStudioSession } from '../api/studio/post_studio_session'
14-
import type { StudioStatus } from '@packages/types'
14+
import type { StudioServerOptions, StudioStatus } from '@packages/types'
1515
import path from 'path'
1616
import os from 'os'
1717
import { ensureStudioBundle } from './ensure_studio_bundle'
@@ -39,7 +39,6 @@ export class StudioLifecycleManager {
3939
private currentStudioHash?: string
4040

4141
private initializationParams?: {
42-
projectId?: string
4342
cloudDataSource: CloudDataSource
4443
cfg: Cfg
4544
debugData: any
@@ -55,20 +54,17 @@ export class StudioLifecycleManager {
5554
/**
5655
* Initialize the studio manager and possibly set up protocol.
5756
* Also registers this instance in the data context.
58-
* @param projectId The project ID
5957
* @param cloudDataSource The cloud data source
6058
* @param cfg The project configuration
6159
* @param debugData Debug data for the configuration
6260
* @param ctx Data context to register this instance with
6361
*/
6462
initializeStudioManager ({
65-
projectId,
6663
cloudDataSource,
6764
cfg,
6865
debugData,
6966
ctx,
7067
}: {
71-
projectId?: string
7268
cloudDataSource: CloudDataSource
7369
cfg: Cfg
7470
debugData: any
@@ -77,7 +73,7 @@ export class StudioLifecycleManager {
7773
debug('Initializing studio manager')
7874

7975
// Store initialization parameters for retry
80-
this.initializationParams = { projectId, cloudDataSource, cfg, debugData, ctx }
76+
this.initializationParams = { cloudDataSource, cfg, debugData, ctx }
8177

8278
// Register this instance in the data context
8379
ctx.update((data) => {
@@ -88,48 +84,64 @@ export class StudioLifecycleManager {
8884

8985
this.updateStatus('INITIALIZING')
9086

87+
const getProjectOptions = async () => {
88+
const [user, config] = await Promise.all([
89+
ctx.actions.auth.authApi.getUser(),
90+
ctx.project.getConfig(),
91+
])
92+
93+
return {
94+
user,
95+
projectSlug: config.projectId || undefined,
96+
}
97+
}
98+
9199
const studioManagerPromise = this.createStudioManager({
92-
projectId,
93100
cloudDataSource,
94101
cfg,
95102
debugData,
103+
getProjectOptions,
96104
}).catch(async (error) => {
97105
debug('Error during studio manager setup: %o', error)
98106

99-
const { cloudUrl, cloudHeaders } = await getCloudMetadata(cloudDataSource)
100-
101-
reportStudioError({
102-
cloudApi: {
103-
cloudUrl,
104-
cloudHeaders,
105-
CloudRequest,
106-
isRetryableError,
107-
asyncRetry,
108-
},
109-
studioHash: projectId,
110-
projectSlug: cfg.projectId,
111-
error,
112-
studioMethod: 'initializeStudioManager',
113-
studioMethodArgs: [],
114-
})
107+
try {
108+
const { cloudUrl, cloudHeaders } = await getCloudMetadata(cloudDataSource)
109+
110+
reportStudioError({
111+
cloudApi: {
112+
cloudUrl,
113+
cloudHeaders,
114+
CloudRequest,
115+
isRetryableError,
116+
asyncRetry,
117+
},
118+
studioHash: this.currentStudioHash,
119+
projectSlug: (await getProjectOptions()).projectSlug,
120+
error,
121+
studioMethod: 'initializeStudioManager',
122+
studioMethodArgs: [],
123+
})
115124

116-
this.updateStatus('IN_ERROR')
125+
this.updateStatus('IN_ERROR')
117126

118-
telemetryManager.mark(BUNDLE_LIFECYCLE_MARK_NAMES.BUNDLE_LIFECYCLE_END)
119-
reportTelemetry(BUNDLE_LIFECYCLE_TELEMETRY_GROUP_NAMES.COMPLETE_BUNDLE_LIFECYCLE, {
120-
success: false,
121-
})
127+
telemetryManager.mark(BUNDLE_LIFECYCLE_MARK_NAMES.BUNDLE_LIFECYCLE_END)
128+
reportTelemetry(BUNDLE_LIFECYCLE_TELEMETRY_GROUP_NAMES.COMPLETE_BUNDLE_LIFECYCLE, {
129+
success: false,
130+
})
131+
} catch (error) {
132+
debug('Error reporting studio error: %o', error)
133+
}
122134

123135
return null
124136
})
125137

126138
this.studioManagerPromise = studioManagerPromise
127139

128140
this.setupWatcher({
129-
projectId,
130141
cloudDataSource,
131142
cfg,
132143
debugData,
144+
getProjectOptions,
133145
})
134146
}
135147

@@ -158,30 +170,32 @@ export class StudioLifecycleManager {
158170
}
159171

160172
private async createStudioManager ({
161-
projectId,
162173
cloudDataSource,
163174
cfg,
164175
debugData,
176+
getProjectOptions,
165177
}: {
166-
projectId?: string
167178
cloudDataSource: CloudDataSource
168179
cfg: Cfg
169180
debugData: any
181+
getProjectOptions: StudioServerOptions['getProjectOptions']
170182
}): Promise<StudioManager> {
171183
let studioPath: string
172184
let studioHash: string
173185
let manifest: Record<string, string>
174186

187+
const currentProjectOptions = await getProjectOptions()
188+
175189
initializeTelemetryReporter({
176-
projectSlug: projectId,
190+
projectSlug: currentProjectOptions.projectSlug,
177191
cloudDataSource,
178192
})
179193

180194
telemetryManager.mark(BUNDLE_LIFECYCLE_MARK_NAMES.BUNDLE_LIFECYCLE_START)
181195

182196
telemetryManager.mark(BUNDLE_LIFECYCLE_MARK_NAMES.POST_STUDIO_SESSION_START)
183197
const studioSession = await postStudioSession({
184-
projectId,
198+
projectId: currentProjectOptions.projectSlug,
185199
})
186200

187201
telemetryManager.mark(BUNDLE_LIFECYCLE_MARK_NAMES.POST_STUDIO_SESSION_END)
@@ -192,22 +206,27 @@ export class StudioLifecycleManager {
192206
studioHash = studioSession.studioUrl.split('/').pop()?.split('.')[0] ?? ''
193207
studioPath = path.join(os.tmpdir(), 'cypress', 'studio', studioHash)
194208

209+
debug('Setting current studio hash: %s', studioHash)
195210
// Store the current studio hash so that we can clear the cache entry when retrying
196211
this.currentStudioHash = studioHash
197212

198213
let hashLoadingPromise = StudioLifecycleManager.hashLoadingMap.get(studioHash)
199214

200215
if (!hashLoadingPromise) {
216+
debug('Ensuring studio bundle for hash: %s', studioHash)
217+
201218
hashLoadingPromise = ensureStudioBundle({
202219
studioUrl: studioSession.studioUrl,
203220
studioPath,
204-
projectId,
221+
projectId: currentProjectOptions.projectSlug,
205222
})
206223

207224
StudioLifecycleManager.hashLoadingMap.set(studioHash, hashLoadingPromise)
208225
}
209226

210227
manifest = await hashLoadingPromise
228+
229+
debug('Manifest: %o', manifest)
211230
} else {
212231
studioPath = process.env.CYPRESS_LOCAL_STUDIO_PATH
213232
studioHash = 'local'
@@ -226,10 +245,14 @@ export class StudioLifecycleManager {
226245
const actualHash = crypto.createHash('sha256').update(script).digest('hex')
227246

228247
if (!expectedHash) {
248+
debug('Expected hash %s for studio server script not found in manifest: %o', expectedHash, manifest)
249+
229250
throw new Error('Expected hash for studio server script not found in manifest')
230251
}
231252

232253
if (actualHash !== expectedHash) {
254+
debug('Invalid hash for studio server script: %s !== %s', actualHash, expectedHash)
255+
233256
throw new Error('Invalid hash for studio server script')
234257
}
235258
}
@@ -244,16 +267,15 @@ export class StudioLifecycleManager {
244267
script,
245268
studioPath,
246269
studioHash,
247-
projectSlug: projectId,
248270
cloudApi: {
249271
cloudUrl,
250272
cloudHeaders,
251273
CloudRequest,
252274
isRetryableError,
253275
asyncRetry,
254276
},
255-
shouldEnableStudio: this.cloudStudioRequested,
256277
manifest,
278+
getProjectOptions,
257279
})
258280

259281
telemetryManager.mark(BUNDLE_LIFECYCLE_MARK_NAMES.STUDIO_MANAGER_SETUP_END)
@@ -270,7 +292,7 @@ export class StudioLifecycleManager {
270292
telemetryManager.mark(BUNDLE_LIFECYCLE_MARK_NAMES.STUDIO_PROTOCOL_PREPARE_START)
271293
await protocolManager.prepareProtocol(script, {
272294
runId: 'studio',
273-
projectId: cfg.projectId,
295+
projectId: currentProjectOptions.projectSlug,
274296
testingType: cfg.testingType,
275297
cloudApi: {
276298
url: routes.apiUrl,
@@ -320,15 +342,15 @@ export class StudioLifecycleManager {
320342
}
321343

322344
private setupWatcher ({
323-
projectId,
324345
cloudDataSource,
325346
cfg,
326347
debugData,
348+
getProjectOptions,
327349
}: {
328-
projectId?: string
329350
cloudDataSource: CloudDataSource
330351
cfg: Cfg
331352
debugData: any
353+
getProjectOptions: StudioServerOptions['getProjectOptions']
332354
}) {
333355
// Don't setup a watcher if the studio bundle is NOT local
334356
if (!process.env.CYPRESS_LOCAL_STUDIO_PATH) {
@@ -348,10 +370,10 @@ export class StudioLifecycleManager {
348370
await this.studioManager?.destroy()
349371
this.studioManager = undefined
350372
this.studioManagerPromise = this.createStudioManager({
351-
projectId,
352373
cloudDataSource,
353374
cfg,
354375
debugData,
376+
getProjectOptions,
355377
}).then((studioManager) => {
356378
// eslint-disable-next-line no-console
357379
console.log('Studio manager reloaded')

packages/server/lib/cloud/studio/studio.ts

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { StudioManagerShape, StudioStatus, StudioServerDefaultShape, StudioServerShape, ProtocolManagerShape, StudioCloudApi, StudioAIInitializeOptions, StudioEvent, StudioAddSocketListenersOptions } from '@packages/types'
1+
import type { StudioManagerShape, StudioStatus, StudioServerDefaultShape, StudioServerShape, ProtocolManagerShape, StudioCloudApi, StudioAIInitializeOptions, StudioEvent, StudioAddSocketListenersOptions, StudioServerOptions } from '@packages/types'
22
import type { Router } from 'express'
33
import Debug from 'debug'
44
import { requireScript } from '../require_script'
@@ -13,10 +13,9 @@ interface SetupOptions {
1313
script: string
1414
studioPath: string
1515
studioHash?: string
16-
projectSlug?: string
1716
cloudApi: StudioCloudApi
18-
shouldEnableStudio: boolean
1917
manifest: Record<string, string>
18+
getProjectOptions: StudioServerOptions['getProjectOptions']
2019
}
2120

2221
const debug = Debug('cypress:server:studio')
@@ -27,30 +26,12 @@ export class StudioManager implements StudioManagerShape {
2726
private _studioServer: StudioServerShape | undefined
2827
private _studioElectron: StudioElectron | undefined
2928

30-
static createInErrorManager ({ cloudApi, studioHash, projectSlug, error, studioMethod, studioMethodArgs }: ReportStudioErrorOptions): StudioManager {
31-
const manager = new StudioManager()
32-
33-
manager.status = 'IN_ERROR'
34-
35-
reportStudioError({
36-
cloudApi,
37-
studioHash,
38-
projectSlug,
39-
error,
40-
studioMethod,
41-
studioMethodArgs,
42-
})
43-
44-
return manager
45-
}
46-
47-
async setup ({ script, studioPath, studioHash, projectSlug, cloudApi, shouldEnableStudio, manifest }: SetupOptions): Promise<void> {
29+
async setup ({ script, studioPath, studioHash, cloudApi, manifest, getProjectOptions }: SetupOptions): Promise<void> {
4830
const { createStudioServer } = requireScript<StudioServer>(script).default
4931

5032
this._studioServer = await createStudioServer({
5133
studioHash,
5234
studioPath,
53-
projectSlug,
5435
cloudApi,
5536
betterSqlite3Path: path.dirname(require.resolve('better-sqlite3/package.json')),
5637
manifest,
@@ -65,9 +46,10 @@ export class StudioManager implements StudioManagerShape {
6546

6647
return actualHash === expectedHash
6748
},
49+
getProjectOptions,
6850
})
6951

70-
this.status = shouldEnableStudio ? 'ENABLED' : 'INITIALIZED'
52+
this.status = 'ENABLED'
7153
}
7254

7355
initializeRoutes (router: Router): void {
@@ -79,10 +61,8 @@ export class StudioManager implements StudioManagerShape {
7961
async captureStudioEvent (event: StudioEvent): Promise<void> {
8062
if (this._studioServer) {
8163
// this request is not essential - we don't want studio to error out if a telemetry request fails
82-
return (await this.invokeAsync('captureStudioEvent', { isEssential: false }, event))
64+
await this.invokeAsync('captureStudioEvent', { isEssential: false }, event)
8365
}
84-
85-
return Promise.resolve()
8666
}
8767

8868
addSocketListeners (options: StudioAddSocketListenersOptions): void {

0 commit comments

Comments
 (0)