Projects STRLCPY gradejs Commits 7e35d74e
🤬
  • feat(redesign): initial storage and page logic rework

  • Loading...
  • zardak committed 2 years ago
    7e35d74e
    1 parent ef59b379
  • ■ ■ ■ ■
    cli/local_start.sh
    skipped 43 lines
    44 44   
    45 45  echo "Starting queue puller script"
    46 46  AWS_REGION=test AWS_ACCESS_KEY_ID=secret AWS_SECRET_ACCESS_KEY=secret \
    47  - ts-node --swc cli/localSqsPuller.ts 2>&1 &
     47 + ./node_modules/.bin/ts-node --swc cli/localSqsPuller.ts 2>&1 &
    48 48  PULLER_PID=$!
    49 49   
    50 50  echo "Starting public api package"
    skipped 69 lines
  • ■ ■ ■ ■ ■ ■
    packages/public-api/src/website/service.ts
    skipped 101 lines
    102 102   
    103 103   await webPageScanRepo.save(scanEntity);
    104 104   
    105  - await Promise.all([
    106  - syncPackageUsageByHostname(scanEntity, em),
    107  - syncScansWithVulnerabilities(scanEntity, em),
    108  - ]);
     105 + if (scanEntity.status === 'processed') {
     106 + await Promise.all([
     107 + syncPackageUsageByHostname(scanEntity, em),
     108 + syncScansWithVulnerabilities(scanEntity, em),
     109 + ]);
     110 + }
    109 111   
    110 112   return scanEntity;
    111 113   });
    skipped 12 lines
  • ■ ■ ■ ■
    packages/web/src/components/App.tsx
    skipped 38 lines
    39 39   </Helmet>
    40 40   <Routes>
    41 41   <Route index element={<HomePage />} />
    42  - <Route path='/w/:hostname' element={<WebsiteResultsPage />} />
     42 + <Route path='/w/*' element={<WebsiteResultsPage />} />
    43 43   <Route path='*' element={<Navigate replace to='/' />} />
    44 44   </Routes>
    45 45   </>
    skipped 3 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/containers/SearchBarContainer.tsx
     1 +import React, { useCallback, useEffect, useState } from 'react';
     2 +import { useScanResult } from '../../store/hooks/useScanResult';
     3 +import SearchBar from '../ui/SearchBar/SearchBar';
     4 +import { useNavigate } from 'react-router-dom';
     5 + 
     6 +type Props = {
     7 + size?: 'default' | 'large';
     8 + placeholder?: string;
     9 +};
     10 + 
     11 +// TODO: Dedupe logic at Home component
     12 +export default function SearchBarContainer({ size = 'default', placeholder }: Props) {
     13 + const [inputValue, setInputValue] = useState('');
     14 + const [submittedValue, setSubmittedValue] = useState<string | undefined>(undefined);
     15 + 
     16 + const submitHandler = useCallback(() => {
     17 + setSubmittedValue(inputValue);
     18 + }, [inputValue]);
     19 + 
     20 + const navigate = useNavigate();
     21 + const { displayUrl, scanResult } = useScanResult(submittedValue);
     22 + 
     23 + useEffect(() => {
     24 + if (scanResult && displayUrl && !scanResult.isLoading) {
     25 + navigate(`/w/${displayUrl}`);
     26 + }
     27 + }, [scanResult, displayUrl]);
     28 + 
     29 + return (
     30 + <SearchBar
     31 + size={size}
     32 + placeholder={placeholder}
     33 + value={inputValue}
     34 + onChange={setInputValue}
     35 + onSubmit={submitHandler}
     36 + />
     37 + );
     38 +}
     39 + 
  • ■ ■ ■ ■ ■
    packages/web/src/components/layouts/Error/Error.tsx
    skipped 8 lines
    9 9  import PackagesBySourceCardList from '../../ui/CardList/PackagesBySourceCardList';
    10 10  import PopularPackageCardList from '../../ui/CardList/PopularPackageCardList';
    11 11  import { packagesBySourceListData, popularPackageListData } from '../../../mocks/CardListsMocks';
     12 +import SearchBarContainer from '../../containers/SearchBarContainer';
    12 13   
    13 14  export type Props = {
    14 15   host: string;
    skipped 21 lines
    36 37   </div>
    37 38   
    38 39   <div className={styles.searchWrapper}>
    39  - <SearchBar size='large' placeholder={actionTitle} />
     40 + <SearchBarContainer size='large' placeholder={actionTitle} />
    40 41   </div>
    41 42   </section>
    42 43   
    skipped 16 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/pages/Home.tsx
    skipped 5 lines
    6 6  export function HomePage() {
    7 7   const navigate = useNavigate();
    8 8   const [requestedScanUrl, setRequestedScanUrl] = useState<string | undefined>(undefined);
    9  - const { normalizedUrl, scanResult } = useScanResult(requestedScanUrl);
     9 + const { displayUrl, scanResult } = useScanResult(requestedScanUrl);
    10 10   
    11 11   useEffect(() => {
    12  - if (scanResult && normalizedUrl && !scanResult.isLoading) {
    13  - navigate(`/w/${normalizedUrl}`);
     12 + if (scanResult && displayUrl && !scanResult.isLoading) {
     13 + navigate(`/w/${displayUrl}`);
    14 14   }
    15  - }, [scanResult, normalizedUrl]);
     15 + }, [scanResult, displayUrl]);
    16 16   
    17 17   const handleScanRequest = useCallback(async (address: string) => {
    18 18   setRequestedScanUrl(address);
    19 19   }, []);
    20 20   
    21  - if (normalizedUrl && scanResult?.error) {
    22  - return <Error host={normalizedUrl} />;
     21 + if (displayUrl && scanResult?.error) {
     22 + return <Error host={displayUrl} />;
    23 23   }
    24 24   
    25 25   return <Home onSubmit={handleScanRequest} loading={scanResult?.isLoading} />;
    skipped 2 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/pages/WebsiteResults.tsx
    skipped 8 lines
    9 9  import { applyScanFilters } from '../../store/slices/scans';
    10 10   
    11 11  export function WebsiteResultsPage() {
    12  - const { hostname } = useParams();
     12 + const { '*': scanUrl } = useParams();
    13 13   const dispatch = useAppDispatch();
    14 14   
    15  - const { normalizedUrl, scanResult } = useScanResult(hostname, true);
     15 + const { displayUrl, normalizedUrl, scanResult } = useScanResult(scanUrl, true);
    16 16   
    17 17   const setFilters = useMemo(() => {
    18 18   if (!normalizedUrl) {
    skipped 20 lines
    39 39   message='An unexpected error occurred. Try visiting us later.'
    40 40   action='Would you like to try another URL or report an issue?'
    41 41   actionTitle='Try another URL'
    42  - host={normalizedUrl ?? ''}
     42 + host={displayUrl ?? ''}
    43 43   />
    44 44   );
    45 45   }
    skipped 6 lines
    52 52   message='The entered website appears to be protected by a third-party service, such as DDoS prevention, password protection or geolocation restrictions.'
    53 53   action='Would you like to try another URL or report an issue?'
    54 54   actionTitle='Try another URL'
    55  - host={normalizedUrl ?? ''}
     55 + host={displayUrl ?? ''}
    56 56   />
    57 57   );
    58 58   }
    skipped 6 lines
    65 65   message='It looks like the entered website is not built with Webpack.'
    66 66   action='Would you like to try another URL or report an issue?'
    67 67   actionTitle='Try another URL'
    68  - host={normalizedUrl ?? ''}
     68 + host={displayUrl ?? ''}
    69 69   />
    70 70   );
    71 71   }
    72 72   
    73  - const title = `List of NPM packages that are used on ${hostname} - GradeJS`;
     73 + const title = `List of NPM packages that are used on ${displayUrl} - GradeJS`;
    74 74   const description =
    75  - `GradeJS has discovered ${packagesStats.total} NPM packages used on ${hostname}` +
     75 + `GradeJS has discovered ${packagesStats.total} NPM packages used on ${displayUrl}` +
    76 76   (packagesStats.vulnerable > 0 ? `, ${packagesStats.vulnerable} are vulnerable` : '') +
    77 77   (packagesStats.outdated > 0 ? `, ${packagesStats.outdated} are outdated` : '');
    78 78   
    skipped 6 lines
    85 85   <meta property='og:description' content={description} />
    86 86   </Helmet>
    87 87   <Website
    88  - isLoading={isLoading}
    89  - isPending={isPending}
     88 + isLoading={isLoading || isPending}
     89 + isPending={isLoading || isPending}
    90 90   packages={packagesFiltered ?? []}
    91  - host={normalizedUrl ?? ''}
     91 + host={displayUrl ?? ''}
    92 92   vulnerabilities={scanResult?.scan?.scanResult?.vulnerabilities ?? {}}
    93 93   onFiltersApply={setFilters}
    94 94   />
    skipped 4 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/Header/Header.tsx
    skipped 2 lines
    3 3  import clsx from 'clsx';
    4 4  import Container from '../Container/Container';
    5 5  import { Icon } from '../Icon/Icon';
    6  -import SearchBar from '../SearchBar/SearchBar';
     6 +import SearchBarContainer from '../../containers/SearchBarContainer';
     7 +import { Link } from 'react-router-dom';
    7 8   
    8 9  export type Props = {
    9 10   variant?: 'default' | 'light';
    skipped 12 lines
    22 23   <header className={clsx(styles.header, styles[variant], className)}>
    23 24   <Container>
    24 25   <div className={clsx(styles.headerInner, showSearch && styles.showSearch)}>
    25  - {/* TODO: add Link from react router */}
    26  - <a href='/' className={styles.logo}>
     26 + <Link to='/' className={styles.logo}>
    27 27   <Icon
    28 28   kind='logo'
    29 29   width={129}
    30 30   height={25}
    31 31   color={variant === 'light' ? 'white' : '#212121'}
    32 32   />
    33  - </a>
     33 + </Link>
    34 34   
    35 35   {showSearch && (
    36 36   <div className={styles.searchWrapper}>
    37  - <SearchBar />
     37 + <SearchBarContainer />
    38 38   </div>
    39 39   )}
    40 40   
    skipped 7 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/components/ui/SearchBar/SearchBar.tsx
    1  -import React, { useState } from 'react';
     1 +import React, { useCallback } from 'react';
    2 2  import styles from './SearchBar.module.scss';
    3 3  import { Icon } from '../Icon/Icon';
    4 4  import clsx from 'clsx';
    5 5   
    6 6  type Props = {
    7  - value?: string;
    8 7   size?: 'default' | 'large';
    9 8   placeholder?: string;
     9 + value: string;
     10 + onChange: (value: string) => void;
     11 + onSubmit: () => void;
    10 12  };
    11 13   
    12  -// TODO: connect search to redux and get/update with it
    13 14  export default function SearchBar({
    14  - value = 'pinterest.com',
    15 15   size = 'default',
    16 16   placeholder = 'Start analyzing...',
     17 + value,
     18 + onChange,
     19 + onSubmit,
    17 20  }: Props) {
    18  - const [inputText, setInputText] = useState<string>(value);
     21 + const changeHandler = useCallback(
     22 + (e: React.ChangeEvent<HTMLInputElement>) => {
     23 + onChange(e.target.value);
     24 + },
     25 + [onChange]
     26 + );
    19 27   
    20  - const changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
    21  - setInputText(e.target.value);
    22  - };
    23  - 
    24  - const clearHandler = () => {
    25  - setInputText('');
    26  - };
     28 + const keyPressHandler = useCallback(
     29 + (e: React.KeyboardEvent<HTMLInputElement>) => {
     30 + console.log(e);
     31 + if (e.key === 'Enter') {
     32 + onSubmit();
     33 + }
     34 + },
     35 + [onSubmit]
     36 + );
    27 37   
    28 38   return (
    29 39   <div className={clsx(styles.searchBar, styles[size])}>
    30 40   <input
    31 41   type='text'
    32 42   className={styles.input}
    33  - value={inputText}
     43 + placeholder={placeholder}
     44 + value={value}
    34 45   onChange={changeHandler}
    35  - placeholder={placeholder}
     46 + onKeyPress={keyPressHandler}
    36 47   />
    37  - {inputText ? (
    38  - <button type='button' className={styles.clear} onClick={clearHandler}>
    39  - {size === 'large' ? (
    40  - <Icon kind='cross' width={32} height={32} color='#8E8AA0' />
    41  - ) : (
    42  - <Icon kind='cross' width={24} height={24} color='#8E8AA0' />
    43  - )}
    44  - </button>
    45  - ) : (
    46  - <button type='submit' className={styles.submit}>
     48 + {!!value && (
     49 + <button type='submit' className={styles.submit} onClick={onSubmit}>
    47 50   {size === 'large' ? (
    48 51   <Icon kind='arrow' width={17} height={30} stroke='#8E8AA0' />
    49 52   ) : (
    skipped 8 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/store/hooks/useScanResult.ts
    skipped 3 lines
    4 4  import { makeSelectScanResultByUrl } from '../selectors/websiteResults';
    5 5   
    6 6  export const useScanResult = (scanUrl: string | undefined, pollWhilePending = false) => {
    7  - const { normalizedUrl, fullUrl } = useMemo(() => {
     7 + const { displayUrl, normalizedUrl } = useMemo(() => {
    8 8   if (!scanUrl) {
    9 9   return {};
    10 10   }
    skipped 5 lines
    16 16   : scanUrl;
    17 17   
    18 18   const parsedUrl = new URL(prefixedScanUrl);
     19 + const shortenedUrl = `${parsedUrl.hostname}${parsedUrl.pathname}`;
    19 20   return {
    20  - normalizedUrl: `${parsedUrl.hostname}${parsedUrl.pathname}`,
    21  - fullUrl: prefixedScanUrl,
     21 + displayUrl: shortenedUrl.endsWith('/') ? shortenedUrl.slice(0, -1) : shortenedUrl,
     22 + normalizedUrl: prefixedScanUrl,
    22 23   };
    23 24   } catch (_) {
    24 25   return {};
    skipped 6 lines
    31 32   const scanResult = useAppSelector((state) => scanResultSelector(state, normalizedUrl));
    32 33   
    33 34   const requestScan = useCallback(async (requestedScanUrl) => {
    34  - return dispatch(requestWebPageScan(requestedScanUrl));
     35 + return dispatch(requestWebPageScan({ scanUrl: requestedScanUrl }));
    35 36   }, []);
    36 37   
    37 38   // Initial request if entity wasn't loaded
    38 39   useEffect(() => {
    39  - if (fullUrl && !scanResult?.scan && !scanResult?.isLoading && !scanResult?.error) {
    40  - requestScan(fullUrl);
     40 + if (normalizedUrl && !scanResult?.scan && !scanResult?.isLoading && !scanResult?.error) {
     41 + requestScan(normalizedUrl);
    41 42   }
    42  - }, [fullUrl, scanResult]);
     43 + }, [normalizedUrl, scanResult]);
    43 44   
    44 45   // Poll while scan is pending
    45 46   useEffect(() => {
    skipped 5 lines
    51 52   !scanResult?.error
    52 53   ) {
    53 54   timeoutId = window.setTimeout(() => {
    54  - requestScan(fullUrl);
     55 + requestScan(normalizedUrl);
    55 56   }, 1000);
    56 57   }
    57 58   
    58 59   return () => clearTimeout(timeoutId);
    59  - }, [pollWhilePending, fullUrl, scanResult]);
     60 + }, [pollWhilePending, normalizedUrl, scanResult]);
    60 61   
    61  - return { normalizedUrl, scanResult, requestScan };
     62 + return { normalizedUrl, displayUrl, scanResult, requestScan };
    62 63  };
    63 64   
  • ■ ■ ■ ■ ■ ■
    packages/web/src/store/selectors/websiteResults.ts
    skipped 2 lines
    3 3  import { createSelector } from '@reduxjs/toolkit';
    4 4  import { GithubAdvisorySeverity } from '@gradejs-public/shared';
    5 5  import { RootState } from '../';
    6  -import { FiltersState } from '../../components/layouts/Filters/Filters';
     6 +import { DefaultFiltersAndSorters, FiltersState } from '../../components/layouts/Filters/Filters';
    7 7  import { SeverityWeightMap } from '../../components/ui/Vulnerability/Vulnerability';
    8 8  import type { ClientApi } from '../../services/apiClient';
    9 9   
    skipped 82 lines
    92 92   isFailed: !!scanResult?.error,
    93 93   isPending: scanResult?.scan?.status === 'pending',
    94 94   isProtected: scanResult?.scan?.status === 'protected',
    95  - isInvalid: scanResult?.scan?.scanResult?.identifiedPackages.length === 0,
     95 + isInvalid:
     96 + scanResult?.scan?.status === 'failed' ||
     97 + scanResult?.scan?.scanResult?.identifiedPackages.length === 0,
    96 98   })),
    97 99   packagesStats: createSelector([makeSelectScanResultByUrl()], (scanResult) => {
    98 100   const packages = scanResult?.scan?.scanResult?.identifiedPackages ?? [];
    skipped 12 lines
    111 113   const packages = scanResult?.scan?.scanResult?.identifiedPackages ?? [];
    112 114   const vulnerabilities = scanResult?.scan?.scanResult?.vulnerabilities ?? {};
    113 115   
    114  - const filters = scanResult?.filters;
    115  - 
    116  - if (!filters) {
    117  - return [];
    118  - }
     116 + const filters = scanResult?.filters ?? DefaultFiltersAndSorters;
    119 117   
    120 118   return filterModes[filters.filter](
    121 119   sortingModes[filters.sort](packages, vulnerabilities),
    skipped 8 lines
  • ■ ■ ■ ■ ■ ■
    packages/web/src/store/slices/scans.ts
    skipped 15 lines
    16 16   
    17 17  const requestWebPageScan = createAsyncThunk(
    18 18   'scans/requestWebPageScan',
    19  - async ({ normalizedUrl }: { normalizedUrl: string }) => {
    20  - return client.mutation('requestWebPageScan', normalizedUrl);
     19 + async ({ scanUrl }: { scanUrl: string }) => {
     20 + return client.mutation('requestWebPageScan', scanUrl);
    21 21   }
    22 22  );
    23 23   
    skipped 19 lines
    43 43   extraReducers: (builder) => {
    44 44   builder
    45 45   .addCase(requestWebPageScan.pending, (state, action) => {
    46  - const { normalizedUrl } = action.meta.arg;
     46 + const { scanUrl } = action.meta.arg;
    47 47   
    48  - const previousScanState = state[normalizedUrl] ?? null;
     48 + const previousScanState = state[scanUrl] ?? null;
    49 49   
    50  - state[normalizedUrl] = {
     50 + state[scanUrl] = {
    51 51   ...previousScanState,
    52 52   isLoading: true,
    53 53   error: undefined,
    skipped 1 lines
    55 55   };
    56 56   })
    57 57   .addCase(requestWebPageScan.rejected, (state, action) => {
    58  - const { normalizedUrl } = action.meta.arg;
     58 + const { scanUrl } = action.meta.arg;
    59 59   
    60  - const previousScanState = state[normalizedUrl] ?? null;
     60 + const previousScanState = state[scanUrl] ?? null;
    61 61   
    62  - state[normalizedUrl] = {
     62 + state[scanUrl] = {
    63 63   ...previousScanState,
    64 64   isLoading: false,
    65 65   error: action.error,
    skipped 1 lines
    67 67   };
    68 68   })
    69 69   .addCase(requestWebPageScan.fulfilled, (state, action) => {
    70  - const { normalizedUrl } = action.meta.arg;
     70 + const { scanUrl } = action.meta.arg;
    71 71   
    72  - const previousScanState = state[normalizedUrl] ?? null;
     72 + const previousScanState = state[scanUrl] ?? null;
    73 73   
    74  - state[normalizedUrl] = {
     74 + state[scanUrl] = {
    75 75   ...previousScanState,
    76 76   isLoading: false,
    77 77   error: undefined,
    skipped 10 lines
Please wait...
Page is in error, reload to recover