From 72f9a8d2c25cd53527414460ba243eb5e17807f3 Mon Sep 17 00:00:00 2001 From: Vladyslava <71496479+Vladyslava95@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:30:20 +0200 Subject: [PATCH 01/10] Update auth.test.ts --- tests/api/auth.test.ts | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/tests/api/auth.test.ts b/tests/api/auth.test.ts index fb762204..28a3db6b 100644 --- a/tests/api/auth.test.ts +++ b/tests/api/auth.test.ts @@ -1,13 +1,25 @@ -import { test, expect } from '@playwright/test'; +import { test } from './fixtures/base'; +import { expect } from '@playwright/test'; +import { randomUUID } from 'node:crypto'; +// TO DO: investigate how run test with auth and without it -test.fixme('should return success response if the Autorization header is right', async () => { - // const response = await fetchListOfReports(env.API_TOKEN as string); - // expect(response.status).toBe(200); - // expect(await response.text()).not.toBe('Unauthorized'); -}); +// test ('should return success response if the Autorization header is right', async ({request}) => { +// const token = process.env.API_TOKEN; +// const response = await request.get('/api/result/list', { +// headers: { +// Authorization: `${token}`, +// }, +// }); +// expect(response.status()).toBe(200); +// expect(await response.text()).not.toBe('Unauthorized'); +// }); -test.fixme('should return unauthorised if the Autorization header is wrong', async () => { - // const response = await fetchListOfReports(randomUUID()); - // expect(response.status).toBe(401); - // expect(await response.text()).toBe('Unauthorized'); -}); +// test ('should return unauthorised if the Autorization header is wrong', async ({request}) => { +// const response = await request.get('/api/report/list', { +// headers: { +// Authorization: `Bearer ${randomUUID()}`, +// }, +// }); +// expect(response.status()).toBe(401); +// expect(await response.text()).toBe('Unauthorized'); +// }); From ad82f7c6ebf7f5ea11897a0f3a1221013def9e41 Mon Sep 17 00:00:00 2001 From: Shelex <11396724+Shelex@users.noreply.github.com> Date: Sat, 8 Nov 2025 16:59:33 +0200 Subject: [PATCH 02/10] fix: info request is triggered twice --- app/components/aside.tsx | 5 ++--- app/components/page-layout.tsx | 23 +++++++++++++---------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/app/components/aside.tsx b/app/components/aside.tsx index d2a7309f..56bee02a 100644 --- a/app/components/aside.tsx +++ b/app/components/aside.tsx @@ -27,13 +27,12 @@ export const Aside: React.FC = () => { const pathname = usePathname(); const session = useSession(); const { authRequired } = useAuthConfig(); + const isAuthenticated = authRequired === false || session.status === 'authenticated'; const { data: serverInfo } = useQuery('/api/info', { - dependencies: [], + enabled: isAuthenticated, }); - const isAuthenticated = authRequired === false || session.status === 'authenticated'; - return ( diff --git a/app/components/page-layout.tsx b/app/components/page-layout.tsx index 6b621161..4978e7db 100644 --- a/app/components/page-layout.tsx +++ b/app/components/page-layout.tsx @@ -14,12 +14,20 @@ interface PageLayoutProps { render: (props: { info: ServerDataInfo; onUpdate: () => void }) => React.ReactNode; } -export default function PageLayout({ render }: PageLayoutProps) { +export default function PageLayout({ render }: Readonly) { const { data: session, status } = useSession(); const authIsLoading = status === 'loading'; const { authRequired } = useAuthConfig(); + const isAuthenticated = authRequired === false || status === 'authenticated'; - const { data: info, error, refetch, isLoading: isInfoLoading } = useQuery('/api/info'); + const { + data: info, + error, + refetch, + isLoading: isInfoLoading, + } = useQuery('/api/info', { + enabled: isAuthenticated, + }); const [refreshId, setRefreshId] = useState(uuidv4()); useEffect(() => { @@ -34,17 +42,12 @@ export default function PageLayout({ render }: PageLayoutProps) { }, [authIsLoading, session, authRequired]); useLayoutEffect(() => { - // Skip session check if auth is not required - if (authRequired === false) { - refetch(); - + // skip refetch is not authorized + if (authRequired && (authIsLoading || !session)) { return; } - if (authIsLoading || !session) { - return; - } - refetch(); + refetch({ cancelRefetch: false }); }, [refreshId, session, authRequired]); if (authIsLoading || isInfoLoading) { From bbdc48948444a59f063ba509fd3a94366e898042 Mon Sep 17 00:00:00 2001 From: Shelex <11396724+Shelex@users.noreply.github.com> Date: Sat, 8 Nov 2025 17:03:20 +0200 Subject: [PATCH 03/10] fix: icon colors --- app/api/report/trend/route.ts | 4 +--- app/components/header-links.tsx | 11 ++++++++++- app/components/icons.tsx | 23 ++++++++++++++++++++--- app/components/trend-chart.tsx | 9 +++++++-- app/styles/globals.css | 4 ++-- readme.md | 2 +- 6 files changed, 41 insertions(+), 12 deletions(-) diff --git a/app/api/report/trend/route.ts b/app/api/report/trend/route.ts index 1bda6a47..ea367aa5 100644 --- a/app/api/report/trend/route.ts +++ b/app/api/report/trend/route.ts @@ -7,9 +7,7 @@ export const dynamic = 'force-dynamic'; // defaults to auto export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); const project = searchParams.get('project') ?? ''; - const { reports } = await service.getReports({ project }); - - const latestReports = reports.slice(0, 20); + const { reports: latestReports } = await service.getReports({ project, pagination: { offset: 0, limit: 20 } }); return Response.json(latestReports); } diff --git a/app/components/header-links.tsx b/app/components/header-links.tsx index 382a1cd0..3386e636 100644 --- a/app/components/header-links.tsx +++ b/app/components/header-links.tsx @@ -1,6 +1,14 @@ import { Link } from '@heroui/link'; -import { GithubIcon, DiscordIcon, TelegramIcon, LinkIcon, BitbucketIcon, CyborgTestIcon } from '@/app/components/icons'; +import { + GithubIcon, + DiscordIcon, + TelegramIcon, + LinkIcon, + BitbucketIcon, + CyborgTestIcon, + SlackIcon, +} from '@/app/components/icons'; import { SiteWhiteLabelConfig } from '@/app/types'; interface HeaderLinksProps { @@ -17,6 +25,7 @@ export const HeaderLinks: React.FC = ({ config, withTitle = fa { name: 'github', Icon: GithubIcon, title: 'GitHub' }, { name: 'cyborgTest', Icon: CyborgTestIcon, title: 'Cyborg Test' }, { name: 'bitbucket', Icon: BitbucketIcon, title: 'Bitbucket' }, + { name: 'slack', Icon: SlackIcon, title: 'Slack' }, ]; const socialLinks = Object.entries(links).map(([name, href]) => { diff --git a/app/components/icons.tsx b/app/components/icons.tsx index fe8af021..825768f5 100644 --- a/app/components/icons.tsx +++ b/app/components/icons.tsx @@ -26,6 +26,23 @@ export const GithubIcon: FC = ({ size = 40, width, height, ...prop ); }; +export const SlackIcon: FC = ({ size = 40, width, height, ...props }) => ( + + + + + + +); + export const BitbucketIcon: FC = ({ size = 40, width, height, ...props }) => { return ( = ({ size = 40, width, height, ...prop export const TrendIcon: FC = ({ size = 40, width, height, ...props }) => { return ( = ({ size = 24, width, height, ...pr return ( ) { flaky: getPercentage(r.stats.flaky, r.stats.total), flakyCount: r.stats.flaky, total: r.stats.total, - reportUrl: r.reportUrl, + reportUrl: `/report/${r.reportID}`, })); return ( {reportHistory.length <= 1 ? ( - Not enough data for trend chart +
+
+ +
+
) : ( Date: Sat, 8 Nov 2025 17:09:45 +0200 Subject: [PATCH 04/10] chore: use listV2 endpoint for s3 --- app/lib/service/index.ts | 15 +-------------- app/lib/storage/fs.ts | 3 ++- app/lib/storage/s3.ts | 24 +++++------------------- app/lib/time.ts | 7 +++++++ 4 files changed, 15 insertions(+), 34 deletions(-) diff --git a/app/lib/service/index.ts b/app/lib/service/index.ts index 8a922a3d..17cb1b2a 100644 --- a/app/lib/service/index.ts +++ b/app/lib/service/index.ts @@ -24,6 +24,7 @@ import { defaultConfig } from '@/app/lib/config'; import { env } from '@/app/config/env'; import { type S3 } from '@/app/lib/storage/s3'; import { isValidPlaywrightVersion } from '@/app/lib/pw'; +import { getTimestamp } from '@/app/lib/time'; class Service { private static instance: Service; @@ -79,13 +80,6 @@ class Service { }); } - const getTimestamp = (date?: Date | string) => { - if (!date) return 0; - if (typeof date === 'string') return new Date(date).getTime(); - - return date.getTime(); - }; - reports.sort((a, b) => getTimestamp(b.createdAt) - getTimestamp(a.createdAt)); const currentReports = handlePagination(reports, input?.pagination); @@ -215,13 +209,6 @@ class Service { return await storage.readResults(input); } - const getTimestamp = (date?: Date | string) => { - if (!date) return 0; - if (typeof date === 'string') return new Date(date).getTime(); - - return date.getTime(); - }; - cached.sort((a, b) => getTimestamp(b.createdAt) - getTimestamp(a.createdAt)); let filtered = input?.project diff --git a/app/lib/storage/fs.ts b/app/lib/storage/fs.ts index 051f9c13..4ea1c10f 100644 --- a/app/lib/storage/fs.ts +++ b/app/lib/storage/fs.ts @@ -77,8 +77,9 @@ export async function readFile(targetPath: string, contentType: string | null) { async function getResultsCount() { const files = await fs.readdir(RESULTS_FOLDER); + const zipFilesCount = files.filter((file) => file.endsWith('.zip')); - return Math.round(files.length / 2); + return zipFilesCount.length; } export async function readResults(input?: ReadResultsInput) { diff --git a/app/lib/storage/s3.ts b/app/lib/storage/s3.ts index e69c76f3..858cf02b 100644 --- a/app/lib/storage/s3.ts +++ b/app/lib/storage/s3.ts @@ -1,5 +1,5 @@ -import { randomUUID, type UUID } from 'crypto'; -import fs from 'fs/promises'; +import { randomUUID, type UUID } from 'node:crypto'; +import fs from 'node:fs/promises'; import path from 'node:path'; import { PassThrough, Readable } from 'node:stream'; @@ -42,6 +42,7 @@ import { withError } from '@/app/lib/withError'; import { env } from '@/app/config/env'; import { SiteWhiteLabelConfig } from '@/app/types'; import { defaultConfig, isConfigValid } from '@/app/lib/config'; +import { getTimestamp } from '@/app/lib/time'; const createClient = () => { const endPoint = env.S3_ENDPOINT; @@ -183,7 +184,7 @@ export class S3 implements Storage { let resultCount = 0; let indexCount = 0; let totalSize = 0; - const stream = this.client.listObjects(this.bucket, folderPath, true); + const stream = this.client.listObjectsV2(this.bucket, folderPath, true); return new Promise((resolve, reject) => { stream.on('data', (obj) => { @@ -287,14 +288,6 @@ export class S3 implements Storage { total: 0, }; } - - const getTimestamp = (date?: Date | string) => { - if (!date) return 0; - if (typeof date === 'string') return new Date(date).getTime(); - - return date.getTime(); - }; - jsonFiles.sort((a, b) => getTimestamp(b.lastModified) - getTimestamp(a.lastModified)); // check if we can apply pagination early @@ -423,13 +416,6 @@ export class S3 implements Storage { }); reportsStream.on('end', async () => { - const getTimestamp = (date?: Date | string) => { - if (!date) return 0; - if (typeof date === 'string') return new Date(date).getTime(); - - return date.getTime(); - }; - reports.sort((a, b) => getTimestamp(b.createdAt) - getTimestamp(a.createdAt)); const currentReports = handlePagination(reports, input?.pagination); @@ -691,7 +677,7 @@ export class S3 implements Storage { console.error(`[s3] failed to create temporary folder: ${mkdirTempError.message}`); } - const resultsStream = this.client.listObjects(this.bucket, RESULTS_BUCKET, true); + const resultsStream = this.client.listObjectsV2(this.bucket, RESULTS_BUCKET, true); console.log(`[s3] start processing...`); for await (const result of resultsStream) { diff --git a/app/lib/time.ts b/app/lib/time.ts index 175e9be8..fb5c5c68 100644 --- a/app/lib/time.ts +++ b/app/lib/time.ts @@ -18,3 +18,10 @@ export const parseMilliseconds = (ms: number) => { return `${leftMs}ms`; }; + +export const getTimestamp = (date?: Date | string) => { + if (!date) return 0; + if (typeof date === 'string') return new Date(date).getTime(); + + return date.getTime(); +}; From af4b653766ceb739051e606f0ce2b2e8bab9a904 Mon Sep 17 00:00:00 2001 From: Shelex <11396724+Shelex@users.noreply.github.com> Date: Sat, 8 Nov 2025 17:13:54 +0200 Subject: [PATCH 05/10] feat: add pass rate for reports, report details, fix metadata display as object --- app/components/inline-stats-circle.tsx | 34 +++++++++++++++++++++ app/components/report-details/file-list.tsx | 8 +++-- app/components/reports-table.tsx | 21 ++++++++++--- 3 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 app/components/inline-stats-circle.tsx diff --git a/app/components/inline-stats-circle.tsx b/app/components/inline-stats-circle.tsx new file mode 100644 index 00000000..f524e516 --- /dev/null +++ b/app/components/inline-stats-circle.tsx @@ -0,0 +1,34 @@ +'use client'; + +import { FC } from 'react'; +import { CircularProgress } from '@heroui/react'; + +import { type ReportStats } from '@/app/lib/parser/types'; + +type ReportFiltersProps = { + stats: ReportStats; +}; + +const InlineStatsCircle: FC = ({ stats }) => { + if (!stats.total) return null; + + const passedPercentage = (stats.expected / (stats.total - stats.skipped)) * 100; + + return ( + + ); +}; + +export default InlineStatsCircle; diff --git a/app/components/report-details/file-list.tsx b/app/components/report-details/file-list.tsx index 91631b40..74f0c03f 100644 --- a/app/components/report-details/file-list.tsx +++ b/app/components/report-details/file-list.tsx @@ -1,11 +1,12 @@ 'use client'; import { FC, useEffect, useState } from 'react'; -import { Accordion, AccordionItem, Spinner } from '@heroui/react'; +import { Accordion, AccordionItem, Alert, Spinner } from '@heroui/react'; import { toast } from 'sonner'; import { subtitle } from '../primitives'; import { StatChart } from '../stat-chart'; +import InlineStatsCircle from '../inline-stats-circle'; import FileSuitesTree from './suite-tree'; import ReportFilters from './tests-filters'; @@ -49,13 +50,14 @@ const FileList: FC = ({ report }) => { {!filteredTests?.files?.length ? ( -

No files found

+ ) : ( - + {(filteredTests?.files ?? []).map((file) => ( } title={

{file.fileName} diff --git a/app/components/reports-table.tsx b/app/components/reports-table.tsx index 5b8d5f3b..36f01f70 100644 --- a/app/components/reports-table.tsx +++ b/app/components/reports-table.tsx @@ -21,6 +21,7 @@ import { toast } from 'sonner'; import { withBase } from '../lib/url'; import TablePaginationOptions from './table-pagination-options'; +import InlineStatsCircle from './inline-stats-circle'; import { withQueryParams } from '@/app/lib/network'; import { defaultProjectName } from '@/app/lib/constants'; @@ -33,6 +34,7 @@ import { ReadReportsHistory, ReportHistory } from '@/app/lib/storage'; const columns = [ { name: 'Title', uid: 'title' }, { name: 'Project', uid: 'project' }, + { name: 'Pass Rate', uid: 'passRate' }, { name: 'Created at', uid: 'createdAt' }, { name: 'Size', uid: 'size' }, { name: '', uid: 'actions' }, @@ -45,6 +47,7 @@ const coreFields = [ 'createdAt', 'size', 'sizeBytes', + 'options', 'reportUrl', 'metadata', 'startTime', @@ -53,6 +56,7 @@ const coreFields = [ 'projectNames', 'stats', 'errors', + 'playwrightVersion', ]; const getMetadataItems = (item: ReportHistory) => { @@ -74,6 +78,12 @@ const getMetadataItems = (item: ReportHistory) => { metadata.push({ key: 'branch', value: itemWithMetadata.branch, icon: }); } + if (itemWithMetadata.playwrightVersion) { + metadata.push({ key: 'playwright', value: itemWithMetadata.playwrightVersion }); + } + + metadata.push({ key: 'workers', value: itemWithMetadata.metadata?.actualWorkers }); + // Add any other metadata fields Object.entries(itemWithMetadata).forEach(([key, value]) => { if (!coreFields.includes(key) && !['environment', 'workingDir', 'branch'].includes(key)) { @@ -195,7 +205,7 @@ export default function ReportsTable({ onChange }: Readonly) > {(item) => ( - +

{/* Main title and link */} @@ -224,12 +234,13 @@ export default function ReportsTable({ onChange }: Readonly)
- {item.project} - + {item.project} + {} + - {item.size} - + {item.size} +
diff --git a/app/settings/components/ServerCache.tsx b/app/settings/components/ServerCache.tsx new file mode 100644 index 00000000..4c00b036 --- /dev/null +++ b/app/settings/components/ServerCache.tsx @@ -0,0 +1,46 @@ +'use client'; + +import { Button } from '@heroui/react'; +import { toast } from 'sonner'; +import { useQueryClient } from '@tanstack/react-query'; + +import useMutation from '@/app/hooks/useMutation'; +import { invalidateCache } from '@/app/lib/query-cache'; + +interface ServerCacheProps { + isEnabled?: boolean; +} + +export default function ServerCache({ isEnabled }: ServerCacheProps) { + const queryClient = useQueryClient(); + const { + mutate: cacheRefresh, + isPending, + error, + } = useMutation('/api/cache/refresh', { + method: 'POST', + onSuccess: () => { + invalidateCache(queryClient, { queryKeys: ['/api'] }); + toast.success(`cache refreshed successfully`); + }, + }); + + return ( +
+

{isEnabled ? 'Enabled' : 'Disabled'}

+ {isEnabled && ( + + )} + {error && toast.error(error.message)} +
+ ); +} diff --git a/package-lock.json b/package-lock.json index cf4001a5..2ef098e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@react-aria/visually-hidden": "^3.8.19", "@tanstack/react-query": "^5.59.15", "@tanstack/react-query-devtools": "^5.59.15", + "better-sqlite3": "^12.4.1", "busboy": "^1.6.0", "clsx": "^2.1.1", "croner": "^9.0.0", @@ -44,6 +45,7 @@ "devDependencies": { "@cyborgtests/reporter-playwright-reports-server": "^2.3.3", "@playwright/test": "^1.55.0", + "@types/better-sqlite3": "^7.6.13", "@types/busboy": "^1.5.4", "@types/jest": "^29.5.12", "@types/jsonwebtoken": "^9.0.7", @@ -8517,6 +8519,16 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/busboy": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.4.tgz", @@ -9744,6 +9756,26 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/baseline-browser-mapping": { "version": "2.8.21", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.21.tgz", @@ -9754,6 +9786,20 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/better-sqlite3": { + "version": "12.4.1", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.4.1.tgz", + "integrity": "sha512-3yVdyZhklTiNrtg+4WqHpJpFDd+WHTg2oM7UcR80GqL05AOV0xEJzc6qNvFYoEtE+hRp1n9MpN6/+4yhlGkDXQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -9766,6 +9812,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bowser": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.12.1.tgz", @@ -9837,6 +9917,30 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -10023,6 +10127,12 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -10498,6 +10608,21 @@ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", "license": "MIT" }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dedent": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", @@ -10513,6 +10638,15 @@ } } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -10721,6 +10855,15 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/envalid": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/envalid/-/envalid-8.1.0.tgz", @@ -11825,6 +11968,15 @@ "node": ">= 0.8.0" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/expect": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", @@ -11939,6 +12091,12 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -12072,6 +12230,12 @@ } } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -12268,6 +12432,12 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, "node_modules/glob": { "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", @@ -12492,6 +12662,26 @@ "node": ">=10.17.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -12573,6 +12763,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, "node_modules/input-otp": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.1.tgz", @@ -14312,6 +14508,18 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -14331,7 +14539,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -14346,6 +14553,12 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, "node_modules/motion-dom": { "version": "11.18.1", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", @@ -14396,6 +14609,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, "node_modules/napi-postinstall": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", @@ -14544,6 +14763,18 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-abi": { + "version": "3.85.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -14734,7 +14965,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -15281,6 +15511,32 @@ "preact": ">=10" } }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -15385,6 +15641,16 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -15432,6 +15698,30 @@ ], "license": "MIT" }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -16152,6 +16442,51 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/simple-swizzle": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", @@ -16709,6 +17044,48 @@ "node": ">=14.0.0" } }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -16983,6 +17360,18 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -17559,7 +17948,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { diff --git a/package.json b/package.json index 848364e4..40949f5f 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@react-aria/visually-hidden": "^3.8.19", "@tanstack/react-query": "^5.59.15", "@tanstack/react-query-devtools": "^5.59.15", + "better-sqlite3": "^12.4.1", "busboy": "^1.6.0", "clsx": "^2.1.1", "croner": "^9.0.0", @@ -49,6 +50,7 @@ "devDependencies": { "@cyborgtests/reporter-playwright-reports-server": "^2.3.3", "@playwright/test": "^1.55.0", + "@types/better-sqlite3": "^7.6.13", "@types/busboy": "^1.5.4", "@types/jest": "^29.5.12", "@types/jsonwebtoken": "^9.0.7", diff --git a/readme.md b/readme.md index a42060ff..30bdc58d 100644 --- a/readme.md +++ b/readme.md @@ -84,17 +84,18 @@ The Playwright Reports Server provides APIs for managing and generating reports The app is configured with environment variables, so it could be specified as `.env` file as well, however there are no mandatory options. -| Name | Description | Default | -| ---------------------- | ------------------------------------------------------------------------------------------------ | ------- | -| `API_TOKEN` | API token for [Authorization](#authorization) | | -| `AUTH_SECRET` | Secret to encrypt JWT | | -| `UI_AUTH_EXPIRE_HOURS` | Duration of auth session | `"2"` | -| `USE_SERVER_CACHE` | Use server side indexed cache for backend queries, improves UX, reduces impact on a backend / s3 | `false` | -| `DATA_STORAGE` | Where to store data, check for additional configuration [Storage Options](#storage-options) | `"fs"` | -| `JIRA_BASE_URL` | Jira instance URL (e.g., https://your-domain.atlassian.net) | | -| `JIRA_EMAIL` | Jira account email address | | -| `JIRA_API_TOKEN` | Jira API token for authentication | | -| `JIRA_PROJECT_KEY` | Default Jira project key for ticket creation | | +| Name | Description | Default | +| --------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------- | +| `API_TOKEN` | API token for [Authorization](#authorization) | | +| `AUTH_SECRET` | Secret to encrypt JWT | | +| `UI_AUTH_EXPIRE_HOURS` | Duration of auth session | `"2"` | +| `USE_SERVER_CACHE` | Use sqlite3 for storing metadata for results and reports, config caching - improves UX, reduces impact on a s3/fs | `false` | +| `SERVER_CACHE_REFRESH_CRON` | | | +| `DATA_STORAGE` | Where to store data, check for additional configuration [Storage Options](#storage-options) | `"fs"` | +| `JIRA_BASE_URL` | Jira instance URL (e.g., https://your-domain.atlassian.net) | | +| `JIRA_EMAIL` | Jira account email address | | +| `JIRA_API_TOKEN` | Jira API token for authentication | | +| `JIRA_PROJECT_KEY` | Default Jira project key for ticket creation | | ## API Routes From 2475d4bfbdf1feea930a420eb6587b2e082a76f5 Mon Sep 17 00:00:00 2001 From: Shelex <11396724+Shelex@users.noreply.github.com> Date: Wed, 19 Nov 2025 20:05:17 +0200 Subject: [PATCH 09/10] fix: download only zip for report generation --- app/lib/storage/s3.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/storage/s3.ts b/app/lib/storage/s3.ts index bae16d5a..39bcb9ab 100644 --- a/app/lib/storage/s3.ts +++ b/app/lib/storage/s3.ts @@ -895,7 +895,7 @@ export class S3 implements Storage { const id = fileName.replace(path.extname(fileName), ''); - if (resultsIds.includes(id)) { + if (resultsIds.includes(id) && path.extname(fileName) === '.zip') { console.log(`[s3] file id is in target results, downloading...`); const localFilePath = path.join(tempFolder, fileName); From 20051891e14568878c4b3772f1969b109b9e8ab3 Mon Sep 17 00:00:00 2001 From: Shelex <11396724+Shelex@users.noreply.github.com> Date: Wed, 19 Nov 2025 23:33:08 +0200 Subject: [PATCH 10/10] chore: test upload --- .github/workflows/test_upload.yml | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/test_upload.yml diff --git a/.github/workflows/test_upload.yml b/.github/workflows/test_upload.yml new file mode 100644 index 00000000..4d7e0896 --- /dev/null +++ b/.github/workflows/test_upload.yml @@ -0,0 +1,35 @@ +name: Test Upload + +on: + workflow_dispatch: + inputs: + url: + description: 'Server URL' + required: true + type: string + apitoken: + description: 'API Token' + required: true + type: string + size_mb: + description: 'File size in megabytes' + required: true + type: number + default: 1 + +jobs: + upload-test: + runs-on: ubuntu-latest + steps: + - name: Generate test file + run: | + # Create a file of specified size in MB + dd if=/dev/zero of=examplefile.zip bs=${{inputs.size_mb}}M + ls -lh examplefile.zip + + - name: Upload file to server + run: | + curl -X POST \ + -H "Authorization: ${{ inputs.apitoken }}" \ + -F "file=@examplefile.zip" \ + "${{ inputs.url }}/api/result/upload"