From aaec3d9f7ae2bfc8e94e84f35f39610fc578cda0 Mon Sep 17 00:00:00 2001 From: Max Virgil Date: Mon, 13 Jan 2025 10:41:49 -0800 Subject: [PATCH] Use real upload call for "sourcemaps upload" with error handling (#69) * Use real upload call for "sourcemaps upload" with error handling Tested locally, with custom headers (not in PR) --- src/sourcemaps/index.ts | 36 +++++++++++++++++++++++++++--------- src/utils/httpUtils.ts | 2 +- test/utils/httpUtils.test.ts | 6 +++--- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/sourcemaps/index.ts b/src/sourcemaps/index.ts index 9befa60..df39c04 100644 --- a/src/sourcemaps/index.ts +++ b/src/sourcemaps/index.ts @@ -25,7 +25,8 @@ import { computeSourceMapId } from './computeSourceMapId'; import { injectFile } from './injectFile'; import { Logger } from '../utils/logger'; import { Spinner } from '../utils/spinner'; -import { mockUploadFile } from '../utils/httpUtils'; +import { uploadFile } from '../utils/httpUtils'; +import { AxiosError } from 'axios'; export type SourceMapInjectOptions = { directory: string; @@ -141,22 +142,22 @@ export async function runSourcemapUpload(options: SourceMapUploadOptions, ctx: S let success = 0; let failed = 0; - logger.info('Upload URL: %s', `https://api.${realm}.signalfx.com/v1/sourcemap/id/{id}`); + logger.info('Upload URL: %s', getSourceMapUploadUrl(realm, '{id}')); logger.info('Found %s source maps to upload', jsMapFilePaths.length); spinner.start(''); for (let i = 0; i < jsMapFilePaths.length; i++) { const filesRemaining = jsMapFilePaths.length - i; const path = jsMapFilePaths[i]; const sourceMapId = await computeSourceMapId(path, { directory }); - const url = `https://api.${realm}.signalfx.com/v1/sourcemap/id/${sourceMapId}`; + const url = getSourceMapUploadUrl(realm, sourceMapId); const file = { filePath: path, fieldName: 'file' }; + const parameters = Object.fromEntries([ ['appName', appName], ['appVersion', appVersion], - ['sourceMapId', sourceMapId], // eslint-disable-next-line @typescript-eslint/no-unused-vars ].filter(([_, value]) => typeof value !== 'undefined')); @@ -167,7 +168,7 @@ export async function runSourcemapUpload(options: SourceMapUploadOptions, ctx: S // upload a single file try { - await mockUploadFile({ + await uploadFile({ url, file, onProgress: ({ loaded, total }) => { @@ -176,10 +177,22 @@ export async function runSourcemapUpload(options: SourceMapUploadOptions, ctx: S parameters }); success++; - // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { - logger.error('Upload failed for %s', path); failed++; + spinner.stop(); + + const ae = e as AxiosError; + if (ae.response) { + logger.error(ae.response.status, ae.response.statusText); + logger.error(ae.response.data); + } else if (ae.request) { + logger.error(`Response from ${url} was not received`); + logger.error(ae.cause); + } else { + logger.error(`Request to ${url} could not be sent`); + logger.error(e); + } + logger.error('Upload failed for %s', path); } } spinner.stop(); @@ -187,15 +200,20 @@ export async function runSourcemapUpload(options: SourceMapUploadOptions, ctx: S /* * Print summary of results */ - logger.info(`${success} source maps were uploaded successfully`); + logger.info(`${success} source map(s) were uploaded successfully`); if (failed > 0) { - logger.info(`${failed} source maps could not be uploaded`); + logger.info(`${failed} source map(s) could not be uploaded`); } if (jsMapFilePaths.length === 0) { logger.warn(`No source map files were found. Verify that ${directory} is the correct directory for your source map files.`); } } +function getSourceMapUploadUrl(realm: string, idPathParam: string): string { + const API_BASE_URL = process.env.O11Y_API_BASE_URL || `https://api.${realm}.signalfx.com`; + return `${API_BASE_URL}/v1/sourcemaps/id/${idPathParam}`; +} + function throwDirectoryReadErrorDuringInject(err: unknown, directory: string): never { throwAsUserFriendlyErrnoException( err, diff --git a/src/utils/httpUtils.ts b/src/utils/httpUtils.ts index 6916fe1..7a6ccf6 100644 --- a/src/utils/httpUtils.ts +++ b/src/utils/httpUtils.ts @@ -53,7 +53,7 @@ export const uploadFile = async ({ url, file, parameters, onProgress }: UploadOp const fileSizeInBytes = fs.statSync(file.filePath).size; - await axios.post(url, formData, { + await axios.put(url, formData, { headers: { ...formData.getHeaders(), }, diff --git a/test/utils/httpUtils.test.ts b/test/utils/httpUtils.test.ts index 06c2f7a..bf288a3 100644 --- a/test/utils/httpUtils.test.ts +++ b/test/utils/httpUtils.test.ts @@ -35,7 +35,7 @@ afterEach(() => { describe('uploadFile', () => { test('should upload a file and report progress', async () => { - jest.spyOn(axios, 'post').mockResolvedValue({ + jest.spyOn(axios, 'put').mockResolvedValue({ data: { success: true } }); @@ -65,7 +65,7 @@ describe('uploadFile', () => { }); test('should throw axios errors during upload', async () => { - jest.spyOn(axios, 'post').mockRejectedValue(new Error('Axios error during upload')); + jest.spyOn(axios, 'put').mockRejectedValue(new Error('Axios error during upload')); await expect(uploadFile({ url: 'http://splunko11ycloud.com/upload', @@ -85,7 +85,7 @@ describe('uploadFile', () => { }); it('should upload a file without progress reporting when onProgress is not provided', async () => { - jest.spyOn(axios, 'post').mockResolvedValue({ + jest.spyOn(axios, 'put').mockResolvedValue({ data: { success: true } });