mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-11-08 23:25:51 +01:00
feat: versions filter by semver range (#4555)
* fix: improve dark mode styles * fix background color * feat: ui improvements * add tests
This commit is contained in:
parent
a99a4bb1b3
commit
ba53d1edc8
6
.changeset/eighty-lobsters-study.md
Normal file
6
.changeset/eighty-lobsters-study.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
'@verdaccio/ui-theme': patch
|
||||
'@verdaccio/ui-components': patch
|
||||
---
|
||||
|
||||
feat: versions filter by semver range
|
@ -55,6 +55,7 @@
|
||||
"version-history": "Version History",
|
||||
"not-available": "Not available",
|
||||
"deprecated": "Deprecated",
|
||||
"search.placeholder": "Search for version by semver range, e.g. ^1.0.0",
|
||||
"hide-deprecated": "All deprecated versions are hidden by global configuration"
|
||||
},
|
||||
"package": {
|
||||
|
@ -7,4 +7,4 @@
|
||||
"filename": "index.html",
|
||||
"base": "http://localhost:9000/"
|
||||
}
|
||||
</script>
|
||||
</script>
|
@ -35,7 +35,7 @@ module.exports = Object.assign({}, config, {
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 70,
|
||||
functions: 76,
|
||||
functions: 75,
|
||||
lines: 80,
|
||||
statements: 81,
|
||||
},
|
||||
|
@ -53,6 +53,7 @@
|
||||
"react-redux": "8.1.3",
|
||||
"react-router": "5.3.4",
|
||||
"react-router-dom": "5.3.4",
|
||||
"semver": "7.6.0",
|
||||
"react-virtualized": "9.22.5",
|
||||
"redux": "4.2.1",
|
||||
"validator": "13.11.0"
|
||||
|
@ -36,7 +36,7 @@ const themeModes = {
|
||||
},
|
||||
dark: {
|
||||
...colors,
|
||||
primary: '#24394e',
|
||||
primary: '#ffffff',
|
||||
secondary: '#424242',
|
||||
background: '#1A202C',
|
||||
},
|
||||
|
@ -23,7 +23,7 @@ const EngineItem: FC<EngineItemProps> = ({ title, element, engineText }) => (
|
||||
<Grid item={true} xs={6}>
|
||||
<List subheader={<StyledText variant={'subtitle1'}>{title}</StyledText>}>
|
||||
<EngineListItem>
|
||||
<Avatar sx={{ bgcolor: '#FFF' }}>{element}</Avatar>
|
||||
<Avatar sx={{ bgcolor: 'transparent' }}>{element}</Avatar>
|
||||
<Typography variant="subtitle2">{engineText}</Typography>
|
||||
</EngineListItem>
|
||||
</List>
|
||||
|
@ -23,6 +23,7 @@ const InstallListItemText = styled(ListItemText)({
|
||||
|
||||
const PackageMangerAvatar = styled(Avatar)({
|
||||
borderRadius: '0px',
|
||||
backgroundColor: 'transparent',
|
||||
padding: 0,
|
||||
});
|
||||
|
||||
|
@ -40,9 +40,7 @@ const RepositoryListItemText = styled(ListItemText)({
|
||||
const RepositoryAvatar = styled(Avatar)({
|
||||
borderRadius: '0px',
|
||||
padding: '0',
|
||||
img: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
backgroundColor: 'transparent',
|
||||
});
|
||||
|
||||
const Repository: React.FC<{ packageMeta: any }> = ({ packageMeta }) => {
|
||||
|
@ -44,7 +44,7 @@ const VersionsHistoryList: React.FC<Props> = ({ versions, packageName, time }) =
|
||||
<Link to={`/-/web/detail/${packageName}/v/${version}`} variant="caption">
|
||||
<ListItemText disableTypography={false} primary={version}></ListItemText>
|
||||
</Link>
|
||||
{typeof versions[version].deprecated === 'string' ? (
|
||||
{typeof versions[version]?.deprecated === 'string' ? (
|
||||
<Chip
|
||||
color="warning"
|
||||
data-testid="deprecated-badge"
|
||||
@ -55,7 +55,7 @@ const VersionsHistoryList: React.FC<Props> = ({ versions, packageName, time }) =
|
||||
/>
|
||||
) : null}
|
||||
<Spacer />
|
||||
<ListItemText title={utils.formatDate(time[version])}>
|
||||
<ListItemText data-testid={`version-list-text`} title={utils.formatDate(time[version])}>
|
||||
{time[version]
|
||||
? utils.formatDateDistance(time[version])
|
||||
: t('versions.not-available')}
|
||||
|
@ -2,24 +2,31 @@
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import { cleanup, render, screen } from '../../test/test-react-testing-library';
|
||||
import { fireEvent, render, screen } from '../../test/test-react-testing-library';
|
||||
import Versions, { Props } from './Versions';
|
||||
import data from './__partials__/data.json';
|
||||
import dataDeprecated from './__partials__/deprecated-versions.json';
|
||||
|
||||
const ComponentToBeRendered: React.FC<Props> = (props) => (
|
||||
const VersionsComponent: React.FC<Props> = (props) => (
|
||||
<MemoryRouter>
|
||||
<Versions {...props} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
jest.mock('lodash/debounce', () =>
|
||||
jest.fn((fn) => {
|
||||
fn.cancel = jest.fn();
|
||||
return fn;
|
||||
})
|
||||
);
|
||||
|
||||
describe('<Version /> component', () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('should render versions', () => {
|
||||
const { getByText } = render(<ComponentToBeRendered packageMeta={data} packageName={'foo'} />);
|
||||
const { getByText } = render(<VersionsComponent packageMeta={data} packageName={'foo'} />);
|
||||
|
||||
expect(getByText('versions.version-history')).toBeTruthy();
|
||||
expect(getByText('versions.current-tags')).toBeTruthy();
|
||||
@ -31,17 +38,26 @@ describe('<Version /> component', () => {
|
||||
expect(screen.queryByTestId('deprecated-badge')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should not render versions', () => {
|
||||
const { queryByText } = render(<ComponentToBeRendered packageMeta={{}} packageName={'foo'} />);
|
||||
test('should filter by version', () => {
|
||||
render(<VersionsComponent packageMeta={data} packageName={'foo'} />);
|
||||
expect(screen.getByText('versions.version-history')).toBeTruthy();
|
||||
expect(screen.getByText('versions.current-tags')).toBeTruthy();
|
||||
expect(screen.queryAllByTestId('version-list-text')).toHaveLength(65);
|
||||
fireEvent.change(screen.getByRole('textbox'), { target: { value: '2.3.0' } });
|
||||
expect(screen.queryAllByTestId('version-list-text')).toHaveLength(1);
|
||||
});
|
||||
|
||||
expect(queryByText('versions.version-history')).toBeFalsy();
|
||||
expect(queryByText('versions.current-tags')).toBeFalsy();
|
||||
test('should not render versions', () => {
|
||||
render(<VersionsComponent packageMeta={{}} packageName={'foo'} />);
|
||||
|
||||
expect(screen.queryByText('versions.version-history')).toBeFalsy();
|
||||
expect(screen.queryByText('versions.current-tags')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('should render versions deprecated settings', () => {
|
||||
window.__VERDACCIO_BASENAME_UI_OPTIONS.hideDeprecatedVersions = true;
|
||||
const { getByText } = render(
|
||||
<ComponentToBeRendered packageMeta={dataDeprecated} packageName={'foo'} />
|
||||
<VersionsComponent packageMeta={dataDeprecated} packageName={'foo'} />
|
||||
);
|
||||
expect(getByText('versions.hide-deprecated')).toBeTruthy();
|
||||
|
||||
|
@ -1,8 +1,11 @@
|
||||
import Alert from '@mui/material/Alert';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { useTheme } from '@mui/styles';
|
||||
import React from 'react';
|
||||
import debounce from 'lodash/debounce';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import semver from 'semver';
|
||||
|
||||
import { useConfig } from '../../providers';
|
||||
import VersionsHistoryList from './HistoryList';
|
||||
@ -14,16 +17,33 @@ const Versions: React.FC<Props> = ({ packageMeta, packageName }) => {
|
||||
const { t } = useTranslation();
|
||||
const { configOptions } = useConfig();
|
||||
const theme = useTheme();
|
||||
|
||||
if (!packageMeta) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { versions = {}, time = {}, ['dist-tags']: distTags = {} } = packageMeta;
|
||||
|
||||
const hasDistTags = distTags && Object.keys(distTags).length > 0 && packageName;
|
||||
const hasVersionHistory = versions && Object.keys(versions).length > 0 && packageName;
|
||||
const [packageVersions, setPackageVersions] = useState(versions);
|
||||
if (!packageMeta || Object.keys(packageMeta).length === 0) {
|
||||
return null;
|
||||
}
|
||||
const hideDeprecatedVersions = configOptions.hideDeprecatedVersions;
|
||||
const hasDistTags = distTags && Object.keys(distTags).length > 0 && packageName;
|
||||
const hasVersionHistory =
|
||||
packageVersions && Object.keys(packageVersions).length > 0 && packageName;
|
||||
|
||||
const filterVersions = (textSearch) => {
|
||||
const filteredVersions = Object.keys(versions).reduce((acc, version) => {
|
||||
if (textSearch !== '') {
|
||||
if (typeof versions[version] !== 'undefined') {
|
||||
if (semver.satisfies(version, textSearch, { includePrerelease: true, loose: true })) {
|
||||
acc[version] = versions[version];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
acc[version] = versions[version];
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
setPackageVersions(filteredVersions);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -33,20 +53,28 @@ const Versions: React.FC<Props> = ({ packageMeta, packageName }) => {
|
||||
<VersionsTagList packageName={packageName} tags={distTags} time={time} />
|
||||
</>
|
||||
) : null}
|
||||
<>
|
||||
<Typography variant="subtitle1">{t('versions.version-history')}</Typography>
|
||||
<TextField
|
||||
helperText={t('versions.search.placeholder')}
|
||||
onChange={debounce((e) => {
|
||||
filterVersions(e.target.value);
|
||||
}, 200)}
|
||||
size="small"
|
||||
variant="standard"
|
||||
/>
|
||||
</>
|
||||
{hasVersionHistory ? (
|
||||
<>
|
||||
<Typography variant="subtitle1">{t('versions.version-history')}</Typography>
|
||||
<>
|
||||
{hideDeprecatedVersions && (
|
||||
<Alert
|
||||
severity="info"
|
||||
sx={{ marginTop: theme.spacing(1), marginBottom: theme.spacing(1) }}
|
||||
>
|
||||
{t('versions.hide-deprecated')}
|
||||
</Alert>
|
||||
)}
|
||||
<VersionsHistoryList packageName={packageName} time={time} versions={versions} />
|
||||
</>
|
||||
{hideDeprecatedVersions === true && (
|
||||
<Alert
|
||||
severity="info"
|
||||
sx={{ marginTop: theme.spacing(1), marginBottom: theme.spacing(1) }}
|
||||
>
|
||||
{t('versions.hide-deprecated')}
|
||||
</Alert>
|
||||
)}
|
||||
<VersionsHistoryList packageName={packageName} time={time} versions={packageVersions} />
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
|
@ -14,13 +14,7 @@ interface Props {
|
||||
const DetailContainerTabs: React.FC<Props> = ({ tabPosition, onChange }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Tabs
|
||||
color={'primary'}
|
||||
indicatorColor={'primary'}
|
||||
onChange={onChange}
|
||||
value={tabPosition}
|
||||
variant={'fullWidth'}
|
||||
>
|
||||
<Tabs onChange={onChange} value={tabPosition} variant={'fullWidth'}>
|
||||
<Tab data-testid={'readme-tab'} id={'readme-tab'} label={t('tab.readme')} />
|
||||
<Tab data-testid={'dependencies-tab'} id={'dependencies-tab'} label={t('tab.dependencies')} />
|
||||
<Tab data-testid={'versions-tab'} id={'versions-tab'} label={t('tab.versions')} />
|
||||
|
@ -160,7 +160,6 @@ exports[`DetailContainer renders correctly 1`] = `
|
||||
>
|
||||
<div
|
||||
class="MuiTabs-root emotion-1 emotion-2"
|
||||
color="primary"
|
||||
>
|
||||
<div
|
||||
class="MuiTabs-scroller MuiTabs-fixed emotion-3"
|
||||
|
33
pnpm-lock.yaml
generated
33
pnpm-lock.yaml
generated
@ -426,24 +426,6 @@ importers:
|
||||
specifier: 9.9.2
|
||||
version: 9.9.2
|
||||
|
||||
e2e/cli/e2e-pnpm6:
|
||||
dependencies:
|
||||
'@verdaccio/test-cli-commons':
|
||||
specifier: workspace:1.1.0
|
||||
version: link:../cli-commons
|
||||
pnpm:
|
||||
specifier: ^6.35.1
|
||||
version: 6.35.1
|
||||
|
||||
e2e/cli/e2e-pnpm7:
|
||||
dependencies:
|
||||
'@verdaccio/test-cli-commons':
|
||||
specifier: workspace:1.1.0
|
||||
version: link:../cli-commons
|
||||
pnpm:
|
||||
specifier: ^7.27.1
|
||||
version: 7.32.0
|
||||
|
||||
e2e/cli/e2e-pnpm8:
|
||||
dependencies:
|
||||
'@verdaccio/test-cli-commons':
|
||||
@ -1950,6 +1932,9 @@ importers:
|
||||
redux:
|
||||
specifier: 4.2.1
|
||||
version: 4.2.1
|
||||
semver:
|
||||
specifier: 7.6.0
|
||||
version: 7.6.0
|
||||
validator:
|
||||
specifier: 13.11.0
|
||||
version: 13.11.0
|
||||
@ -24003,18 +23988,6 @@ packages:
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
/pnpm@6.35.1:
|
||||
resolution: {integrity: sha512-Pt5JEjV2JSORAqw/XtjfF4k34PBQFpqV3kUMFYSlyrWJsio5Hr3lqy2oJkpprMrVbOyBJsxXfZDQOlvmytIrgA==}
|
||||
engines: {node: '>=12.17'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/pnpm@7.32.0:
|
||||
resolution: {integrity: sha512-XkLEMinrF4046cWGvvam7dsCKeRdJ9i2SeDiKNodoZEPmJp1KrzQe1qYC5Vs/v9qBXJqyI0vLzjoMHjXgphP6g==}
|
||||
engines: {node: '>=14.6'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/pnpm@8.15.5:
|
||||
resolution: {integrity: sha512-sFGjLH5pWDO4SSbTspuMylclS1ifBknYmcbp0O22cLkex+KkNFm65zdZu1zmGcMmbxFr+THOItHvF1mn5Fqpbw==}
|
||||
engines: {node: '>=16.14'}
|
||||
|
Loading…
Reference in New Issue
Block a user