diff --git a/__tests__/integration/api.spec.ts b/__tests__/integration/api.spec.ts deleted file mode 100644 index 5d3d989..0000000 --- a/__tests__/integration/api.spec.ts +++ /dev/null @@ -1,150 +0,0 @@ -/* eslint-disable no-unused-expressions */ -// eslint-disable-next-line spaced-comment -/// -import JSZip from 'jszip'; - -/** - * repo link: git, zip - * git: commit id, branch, tag - * subdirectory: undefined, existing, non-existing - */ -describe('Test valid fetch', () => { - beforeEach(() => { - cy.viewport(1920, 1080); - cy.visit('http://localhost:3000'); - }); - - it('zip file', () => { - const expected = ['test', 'main']; - const url = - 'https://code.quarkus.io/d?e=io.quarkus%3Aquarkus-resteasy&e=io.quarkus%3Aquarkus-micrometer&e=io.quarkus%3Aquarkus-smallrye-health&e=io.quarkus%3Aquarkus-openshift&cn=devfile'; - checkForValidZipFetch(url, 'src', expected); - }); - - it('git file', () => { - const expected = ['src', 'README.adoc', 'pom.xml']; - checkForValidZipFetch( - 'https://api.github.com/repos/wildfly/quickstart/zipball/', - 'microprofile-health', - expected, - ); - }); - - it('git repo with tag', () => { - const expected = ['src', 'README.adoc', 'pom.xml']; - checkForValidZipFetch( - 'https://api.github.com/repos/wildfly/quickstart/zipball/23.0.2.Final', - 'microprofile-health', - expected, - ); - }); - - it('git repo with commit ID', () => { - const expected = ['src', 'README.adoc', 'pom.xml']; - checkForValidZipFetch( - 'https://api.github.com/repos/wildfly/quickstart/zipball/66661cf477f74f60c12de40b9d84ce13576e6f44', - 'microprofile-opentracing', - expected, - ); - }); -}); - -describe('Test invalid fetch', () => { - beforeEach(() => { - cy.viewport(1920, 1080); - cy.visit('http://localhost:3000'); - }); - - it('zip file with non-existing subdirectory', () => { - const data = { - url: 'https://code.quarkus.io/d?e=io.quarkus%3Aquarkus-resteasy&e=io.quarkus%3Aquarkus-micrometer&e=io.quarkus%3Aquarkus-smallrye-health&e=io.quarkus%3Aquarkus-openshift&cn=devfile', - subdirectory: 'non-existing', - }; - cy.request({ - url: '/api/download-subdirectory', - failOnStatusCode: false, - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data), - }).should(async (response) => { - expect(response.status).to.eq(404); - }); - }); - - it('zip file with non-existing link', () => { - const data = { - url: 'https://non-existing.com/', - subdirectory: 'non-existing', - }; - cy.request({ - url: '/api/download-subdirectory', - failOnStatusCode: false, - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data), - }).should(async (response) => { - expect(response.status).to.eq(500); - }); - }); -}); - -/** - * Compares two arrays for equality - * @param a - array to compare - * @param b - array to compare - * @returns boolean of whether arrays a and b are equal - */ -function areArraysEqual(a: T, b: T): boolean { - return ( - Array.isArray(a) && - Array.isArray(b) && - a.length === b.length && - a.every((val, index) => val === b[index]) - ); -} - -/** - * Checks that fetch for extracting the subdirectory returns the right zip by - * * checking status of response (should be 200) - * * checking files and folders at first level of zip (should equal @param expectedRootFileAndFolders-) - * @param url - url to fetch zip from - * @param subdirectory - subdirectory to extract from zip - * @param expectedRootFileAndFolders - expected files and folders at first level of zip - */ -function checkForValidZipFetch( - url: string, - subdirectory: string, - expectedRootFileAndFolders: string[], -): void { - const data = { - url, - subdirectory, - }; - const expected = expectedRootFileAndFolders; - expected.sort((a, b) => a.localeCompare(b)); - - cy.request({ - url: '/api/download-subdirectory', - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data), - }).should(async (response) => { - expect(response.status).to.eq(200); - - const array = await response.body; - const zip = await JSZip.loadAsync(array, { base64: true }); - - const zipFiles = new Array(); - Object.keys(zip.files).forEach((fileName) => { - const firstLevelDirectory = fileName.split('/')[0]; - if (!zipFiles.includes(firstLevelDirectory)) { - zipFiles.push(firstLevelDirectory); - } - }); - zipFiles.sort((a, b) => a.localeCompare(b)); - - expect(areArraysEqual(zipFiles, expected)).to.be.true; - }); -} - -export {}; diff --git a/pages/api/download-subdirectory.ts b/pages/api/download-subdirectory.ts deleted file mode 100644 index f5f53ae..0000000 --- a/pages/api/download-subdirectory.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { NextApiRequest, NextApiResponse } from 'next'; -import JSZip from 'jszip'; -/** - * request handler for subdirectory download - * @param req - request body - * @param res - response body - */ -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -): Promise { - try { - const data = req.body; - const response = await fetch(data.url); - const array = await response.arrayBuffer(); - - const zip = await JSZip.loadAsync(array, {}); - - const notFound = Object.keys(zip.files).every((fileName) => { - const firstLevelDirectory = fileName.split('/')[1]; - return firstLevelDirectory !== data.subdirectory; - }); - if (notFound) { - throw TypeError('subdirectory does not exist'); - } - - const rootName = Object.keys(zip.files)[0]; - const subdirectoryZip = zip.folder(rootName)?.folder(data.subdirectory); - - if (!subdirectoryZip) { - throw Error('subdirectory zip is null'); - } - const base64Send = await subdirectoryZip.generateAsync({ type: 'base64' }); - - res.status(200); - res.send(base64Send); - res.end(); - } catch (e) { - const error = e as Error; - if (error.toString().includes('subdirectory does not exist')) { - res.status(404); - } else { - res.status(500); - } - res.json({ error: error.toString() }); - res.end(); - } -} diff --git a/src/components/DevfilePage/DevfilePageProjects/DevfilePageProjects.tsx b/src/components/DevfilePage/DevfilePageProjects/DevfilePageProjects.tsx index 9b6e764..ffc06bb 100644 --- a/src/components/DevfilePage/DevfilePageProjects/DevfilePageProjects.tsx +++ b/src/components/DevfilePage/DevfilePageProjects/DevfilePageProjects.tsx @@ -1,7 +1,7 @@ import styles from './DevfilePageProjects.module.css'; import type { Project, DefaultProps, Devfile } from 'custom-types'; import { DevfilePageProjectDisplay } from '@src/components'; -import { download, UnsupportedLinkError, getUserRegion } from '@src/util/client'; +import { getUserRegion } from '@src/util/client'; import { Alert, AlertActionLink, @@ -72,11 +72,11 @@ export const DevfilePageProjects: React.FC = ({ const router = useRouter(); - async function triggerDownload(project: Project): Promise { + async function downloadStarterProject(devfile: Devfile, project: Project): Promise { setDownloading(true); try { - await download(project); + window.open(`${devfile.registryLink}/starter-projects/${project.name}`); if (analytics) { const region = getUserRegion(router.locale); @@ -96,22 +96,13 @@ export const DevfilePageProjects: React.FC = ({ ); } } catch (error) { - if (error instanceof UnsupportedLinkError) { - setErrorAlert({ - name: 'Unsupported Link', - error: error.toString(), - message: error.message, - alertType: 'warning', - }); - } else { - setErrorAlert({ - name: 'Download Error', - error: (error as Error).toString(), - message: - 'Internal error has occurred during download. Please try again or report as issue. \n', - alertType: 'danger', - }); - } + setErrorAlert({ + name: 'Download Error', + error: (error as Error).toString(), + message: + 'Internal error has occurred during download. Please try again or report as issue. \n', + alertType: 'danger', + }); if (analytics) { const region = getUserRegion(router.locale); @@ -179,7 +170,7 @@ export const DevfilePageProjects: React.FC = ({ data-testid="download-button" className={styles.button} isLoading={downloading} - onClick={(): Promise => triggerDownload(selectedProject)} + onClick={(): Promise => downloadStarterProject(devfile, selectedProject)} variant="tertiary" > Download diff --git a/src/util/client/downloadHelperFuncs.ts b/src/util/client/downloadHelperFuncs.ts deleted file mode 100644 index 266f3a4..0000000 --- a/src/util/client/downloadHelperFuncs.ts +++ /dev/null @@ -1,120 +0,0 @@ -import type { Git, Project } from 'custom-types'; -import { apiWrapper } from '@src/util/client'; -import JSZip from 'jszip'; -import { saveAs } from 'file-saver'; - -/** - * Error for unsupported links - */ -export class UnsupportedLinkError extends Error { - /** - * Error constructor with message 'Unsupported link: {@link link}' - * - * @param link - unsupported link - */ - public constructor(link: string) { - super(`Attempted download with unsupported git repo link at ${link}`); - this.name = 'UnsupportedLinkError '; - Object.setPrototypeOf(this, UnsupportedLinkError.prototype); - } -} - -/** - * Download subdirectory from root folder - * - * @param url - zip url - * @param subdirectory - name of subdirectory to extract from zip - * - * @throws {@link Error} - thrown if error in download - */ -export async function downloadSubdirectory(url: string, subdirectory: string): Promise { - const data = { - url, - subdirectory, - }; - const res = await fetch(apiWrapper('/api/download-subdirectory'), { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data), - }); - - const status = res.status; - if (status !== 200) { - const errorJson = await res.json(); - throw new Error(errorJson.error); - } - - const base64string = await res.text(); - const zip = await JSZip.loadAsync(base64string, { base64: true }); - try { - const blob = await zip.generateAsync({ type: 'blob' }); - saveAs(blob, `${subdirectory}.zip`); - } catch (error) { - throw new Error((error as Error).message); - } -} - -/** - * Extract zip url from project git source - * - * @param git - git source of project, assumes github url of root directory of project - * - * @throws {@link TypeError} - thrown if git remotes isn't configured properly - * @throws {@link UnsupportedLinkException} - thrown if git repo is supported on an unsupported site - */ -export function getURLForGit(git: Git): string { - let url = ''; - const keys = Object.keys(git.remotes); - - if (keys.length === 1) { - url = git.remotes[keys[0]]; - } else if (git.checkoutFrom && git.checkoutFrom.remote) { - // should always be true if keys.length!=1 - url = git.remotes[git.checkoutFrom.remote]; - } else { - throw new TypeError('Invalid git remotes'); - } - - if (url.match(/[.]git$/)) { - url = url.slice(0, url.length - 4); - } - - if (url.match(/github[.]com/)) { - url = `${url.replace('github.com', 'api.github.com/repos')}/zipball/`; // remove '.git' from link and convert to api zip link - } else { - throw new UnsupportedLinkError(url); - } - - if (git.checkoutFrom && git.checkoutFrom.revision) { - url += git.checkoutFrom.revision; - } - return url; -} - -/** - * Download project as a zip file as specified version and subdirectory - * - * @param project - project to download - * - * @throws {@link TypeError} - thrown if git remotes isn't configured properly or if no url locations are found - * @throws {@link UnsupportedLinkError} - thrown if git repo is supported on an unsupported site - * @throws {@link Error} - thrown if git repo is supported on an unsupported site - */ -export async function download(project: Project): Promise { - let url: string; - if (project.git) { - // for git - url = getURLForGit(project.git); - } else if (project.zip && project.zip.location) { - // for zip - url = project.zip.location; - } else { - throw new TypeError('Invalid project has no zip/git url'); - } - - if (project.subDir) { - await downloadSubdirectory(url, project.subDir); - } else { - window.open(url); - } -} diff --git a/src/util/client/index.ts b/src/util/client/index.ts index 8791d7d..6b051b8 100644 --- a/src/util/client/index.ts +++ b/src/util/client/index.ts @@ -3,7 +3,6 @@ * Sort in alphabetical order */ export * from './apiWrapper'; -export * from './downloadHelperFuncs'; export * from './filterElemFuncs'; export * from './getCSSStyles'; export * from './getUserRegion';