Merge pull request #1044 from acelaya-forks/feature/swc-0.5

Update to latest shlink-web-component
This commit is contained in:
Alejandro Celaya 2024-01-29 17:57:37 +01:00 committed by GitHub
commit b12bb6cdc5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 135 additions and 96 deletions

View file

@ -4,21 +4,31 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).
## [Unreleased]
## [4.0.0] - 2024-01-29
### Added
* *Nothing*
* [shlink-web-component #7](https://github.com/shlinkio/shlink-web-component/issues/7) Allow comparing visits for multiple short URLs, tags or domains.
When in the tags, domains or short URLs tables, you can now pick up to 5 items to compare their visits. Once selected, you are taken to a section displaying a comparative line chart, which supports all regular visits filtering capabilities.
* [shlink-web-component #9](https://github.com/shlinkio/shlink-web-component/issues/9) Allow comparing visits with the previous period.
* [shlink-web-component #12](https://github.com/shlinkio/shlink-web-component/issues/12) and [#13](https://github.com/shlinkio/shlink-web-component/issues/13) Add new "Visits options" section for arbitrary visit stats options. Add section to delete short URL and orphan visits there.
This section is only visible if short URL visits deletion or orphan visits deletion are supported by connected Shlink server.
* [shlink-web-component #10](https://github.com/shlinkio/shlink-web-component/issues/10) Improve general accessibility: Add accessibility tests, fix accessibility issues and enable accessibility linting rules.
### Changed
* [#338](https://github.com/shlinkio/shlink-web-client/issues/338) Extract `@shlinkio/shlink-web-component` and `@shlinkio/shlink-frontend-kit` as external libs.
* [#978](https://github.com/shlinkio/shlink-web-client/issues/978) Use system preferred theme as default theme.
* Use API client from `@shlinkio/shlink-js-sdk` to consume Shlink servers.
* [#902](https://github.com/shlinkio/shlink-web-client/pull/902) Docker image is no longer running as root. As a side effect, exposed port is `8080`, not `80` anymore.
* [shlink-web-component #117](https://github.com/shlinkio/shlink-web-component/issues/117) Migrate charts from Chart.JS to Recharts.
### Deprecated
* *Nothing*
### Removed
* *Nothing*
* Drop support for Shlink older than v3.0.0
### Fixed
* [#910](https://github.com/shlinkio/shlink-web-client/issues/910) Fix warnings related with missing `act` in tests and refs in `AppUpdateBanner`.

105
package-lock.json generated
View file

@ -18,7 +18,7 @@
"@shlinkio/data-manipulation": "^1.0.3",
"@shlinkio/shlink-frontend-kit": "^0.4.2",
"@shlinkio/shlink-js-sdk": "^0.2.2",
"@shlinkio/shlink-web-component": "^0.4.1",
"@shlinkio/shlink-web-component": "^0.5.0",
"bootstrap": "5.2.3",
"bottlejs": "^2.0.1",
"clsx": "^2.1.0",
@ -2980,26 +2980,26 @@
"integrity": "sha512-gY9EiaULbEwmrTsnXk0MQUG/3bOvhxHQNOU35psFX2NbB8OzdfoE1iiT/Sez3awmmxz+/p8d6aULBt/ywxukIQ=="
},
"node_modules/@shlinkio/shlink-web-component": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@shlinkio/shlink-web-component/-/shlink-web-component-0.4.1.tgz",
"integrity": "sha512-jzzrbe6ufzF6X1JTqZAjLr5eAtfhJDseglqXVdQ+UXLNYvvGcxrp5LlTxsnf9LQUgLkvZ1qfz5Knfk143B1PtA==",
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@shlinkio/shlink-web-component/-/shlink-web-component-0.5.0.tgz",
"integrity": "sha512-YuZ7VSJtGpHUVUoO1Zvdmn0P5msbxh8xqiJVyvKx9F/x5uvodRIHuUypR3n/A69DfvYt9SUagGZAz3BtG1UB8g==",
"dependencies": {
"@json2csv/plainjs": "^7.0.4",
"@shlinkio/data-manipulation": "^1.0.2",
"@json2csv/plainjs": "^7.0.5",
"@shlinkio/data-manipulation": "^1.0.3",
"bottlejs": "^2.0.1",
"bowser": "^2.11.0",
"clsx": "^2.0.0",
"clsx": "^2.1.0",
"compare-versions": "^6.1.0",
"date-fns": "^2.30.0",
"date-fns": "^3.3.1",
"event-source-polyfill": "^1.0.31",
"leaflet": "^1.9.4",
"react-copy-to-clipboard": "^5.1.0",
"react-datepicker": "^4.24.0",
"react-datepicker": "^4.25.0",
"react-external-link": "^2.2.0",
"react-leaflet": "^4.2.1",
"react-swipeable": "^7.0.1",
"react-tag-autocomplete": "^7.1.0",
"recharts": "^2.10.3"
"recharts": "^2.10.4"
},
"peerDependencies": {
"@fortawesome/fontawesome-svg-core": "^6.4.2",
@ -3008,12 +3008,12 @@
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@reduxjs/toolkit": "^2.0.1",
"@shlinkio/shlink-frontend-kit": "^0.4.0",
"@shlinkio/shlink-js-sdk": "^0.2.0",
"@shlinkio/shlink-frontend-kit": "^0.4.2",
"@shlinkio/shlink-js-sdk": "^0.2.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^9.0.1",
"react-router-dom": "^6.14.2",
"react-router-dom": "^6.20.1",
"reactstrap": "^9.2.0"
},
"peerDependenciesMeta": {
@ -3022,21 +3022,6 @@
}
}
},
"node_modules/@shlinkio/shlink-web-component/node_modules/date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
"dependencies": {
"@babel/runtime": "^7.21.0"
},
"engines": {
"node": ">=0.11"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/date-fns"
}
},
"node_modules/@shlinkio/stylelint-config-css-coding-standard": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@shlinkio/stylelint-config-css-coding-standard/-/stylelint-config-css-coding-standard-1.1.1.tgz",
@ -8713,9 +8698,9 @@
}
},
"node_modules/react-datepicker": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.24.0.tgz",
"integrity": "sha512-2QUC2pP+x4v3Jp06gnFllxKsJR0yoT/K6y86ItxEsveTXUpsx+NBkChWXjU0JsGx/PL8EQnsxN0wHl4zdA1m/g==",
"version": "4.25.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.25.0.tgz",
"integrity": "sha512-zB7CSi44SJ0sqo8hUQ3BF1saE/knn7u25qEMTO1CQGofY1VAKahO8k9drZtp0cfW1DMfoYLR3uSY1/uMvbEzbg==",
"dependencies": {
"@popperjs/core": "^2.11.8",
"classnames": "^2.2.6",
@ -9031,9 +9016,9 @@
}
},
"node_modules/recharts": {
"version": "2.10.3",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.10.3.tgz",
"integrity": "sha512-G4J96fKTZdfFQd6aQnZjo2nVNdXhp+uuLb00+cBTGLo85pChvm1+E67K3wBOHDE/77spcYb2Cy9gYWVqiZvQCg==",
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.11.0.tgz",
"integrity": "sha512-5s+u1m5Hwxb2nh0LABkE3TS/lFqFHyWl7FnPbQhHobbQQia4ih1t3o3+ikPYr31Ns+kYe4FASIthKeKi/YYvMg==",
"dependencies": {
"clsx": "^2.0.0",
"eventemitter3": "^4.0.1",
@ -10432,9 +10417,9 @@
}
},
"node_modules/victory-vendor": {
"version": "36.7.0",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.7.0.tgz",
"integrity": "sha512-nqYuTkLSdTTeACyXcCLbL7rl0y6jpzLPtTNGOtSnajdR+xxMxBdjMxDjfNJNlhR+ZU8vbXz+QejntcbY7h9/ZA==",
"version": "36.8.2",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.8.2.tgz",
"integrity": "sha512-NfSQi7ISCdBbDpn3b6rg+8RpFZmWIM9mcks48BbogHE2F6h1XKdA34oiCKP5hP1OGvTotDRzsexiJKzrK4Exuw==",
"dependencies": {
"@types/d3-array": "^3.0.3",
"@types/d3-ease": "^3.0.0",
@ -13134,36 +13119,26 @@
"integrity": "sha512-gY9EiaULbEwmrTsnXk0MQUG/3bOvhxHQNOU35psFX2NbB8OzdfoE1iiT/Sez3awmmxz+/p8d6aULBt/ywxukIQ=="
},
"@shlinkio/shlink-web-component": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@shlinkio/shlink-web-component/-/shlink-web-component-0.4.1.tgz",
"integrity": "sha512-jzzrbe6ufzF6X1JTqZAjLr5eAtfhJDseglqXVdQ+UXLNYvvGcxrp5LlTxsnf9LQUgLkvZ1qfz5Knfk143B1PtA==",
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@shlinkio/shlink-web-component/-/shlink-web-component-0.5.0.tgz",
"integrity": "sha512-YuZ7VSJtGpHUVUoO1Zvdmn0P5msbxh8xqiJVyvKx9F/x5uvodRIHuUypR3n/A69DfvYt9SUagGZAz3BtG1UB8g==",
"requires": {
"@json2csv/plainjs": "^7.0.4",
"@shlinkio/data-manipulation": "^1.0.2",
"@json2csv/plainjs": "^7.0.5",
"@shlinkio/data-manipulation": "^1.0.3",
"bottlejs": "^2.0.1",
"bowser": "^2.11.0",
"clsx": "^2.0.0",
"clsx": "^2.1.0",
"compare-versions": "^6.1.0",
"date-fns": "^2.30.0",
"date-fns": "^3.3.1",
"event-source-polyfill": "^1.0.31",
"leaflet": "^1.9.4",
"react-copy-to-clipboard": "^5.1.0",
"react-datepicker": "^4.24.0",
"react-datepicker": "^4.25.0",
"react-external-link": "^2.2.0",
"react-leaflet": "^4.2.1",
"react-swipeable": "^7.0.1",
"react-tag-autocomplete": "^7.1.0",
"recharts": "^2.10.3"
},
"dependencies": {
"date-fns": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
"requires": {
"@babel/runtime": "^7.21.0"
}
}
"recharts": "^2.10.4"
}
},
"@shlinkio/stylelint-config-css-coding-standard": {
@ -17015,9 +16990,9 @@
}
},
"react-datepicker": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.24.0.tgz",
"integrity": "sha512-2QUC2pP+x4v3Jp06gnFllxKsJR0yoT/K6y86ItxEsveTXUpsx+NBkChWXjU0JsGx/PL8EQnsxN0wHl4zdA1m/g==",
"version": "4.25.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.25.0.tgz",
"integrity": "sha512-zB7CSi44SJ0sqo8hUQ3BF1saE/knn7u25qEMTO1CQGofY1VAKahO8k9drZtp0cfW1DMfoYLR3uSY1/uMvbEzbg==",
"requires": {
"@popperjs/core": "^2.11.8",
"classnames": "^2.2.6",
@ -17221,9 +17196,9 @@
}
},
"recharts": {
"version": "2.10.3",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.10.3.tgz",
"integrity": "sha512-G4J96fKTZdfFQd6aQnZjo2nVNdXhp+uuLb00+cBTGLo85pChvm1+E67K3wBOHDE/77spcYb2Cy9gYWVqiZvQCg==",
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.11.0.tgz",
"integrity": "sha512-5s+u1m5Hwxb2nh0LABkE3TS/lFqFHyWl7FnPbQhHobbQQia4ih1t3o3+ikPYr31Ns+kYe4FASIthKeKi/YYvMg==",
"requires": {
"clsx": "^2.0.0",
"eventemitter3": "^4.0.1",
@ -18209,9 +18184,9 @@
}
},
"victory-vendor": {
"version": "36.7.0",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.7.0.tgz",
"integrity": "sha512-nqYuTkLSdTTeACyXcCLbL7rl0y6jpzLPtTNGOtSnajdR+xxMxBdjMxDjfNJNlhR+ZU8vbXz+QejntcbY7h9/ZA==",
"version": "36.8.2",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.8.2.tgz",
"integrity": "sha512-NfSQi7ISCdBbDpn3b6rg+8RpFZmWIM9mcks48BbogHE2F6h1XKdA34oiCKP5hP1OGvTotDRzsexiJKzrK4Exuw==",
"requires": {
"@types/d3-array": "^3.0.3",
"@types/d3-ease": "^3.0.0",

View file

@ -35,7 +35,7 @@
"@shlinkio/data-manipulation": "^1.0.3",
"@shlinkio/shlink-frontend-kit": "^0.4.2",
"@shlinkio/shlink-js-sdk": "^0.2.2",
"@shlinkio/shlink-web-component": "^0.4.1",
"@shlinkio/shlink-web-component": "^0.5.0",
"bootstrap": "5.2.3",
"bottlejs": "^2.0.1",
"clsx": "^2.1.0",

View file

@ -1,30 +1,50 @@
import { LabeledFormGroup, SimpleCard, ToggleSwitch } from '@shlinkio/shlink-frontend-kit';
import type { Settings, VisitsSettings as VisitsSettingsConfig } from '@shlinkio/shlink-web-component';
import type { FC } from 'react';
import { useCallback } from 'react';
import { FormGroup } from 'reactstrap';
import type { DateInterval } from '../utils/dates/DateIntervalSelector';
import { DateIntervalSelector } from '../utils/dates/DateIntervalSelector';
import { FormText } from '../utils/forms/FormText';
interface VisitsProps {
type VisitsProps = {
settings: Settings;
setVisitsSettings: (settings: VisitsSettingsConfig) => void;
}
};
const currentDefaultInterval = (settings: Settings): DateInterval => settings.visits?.defaultInterval ?? 'last30Days';
export const VisitsSettings: FC<VisitsProps> = ({ settings, setVisitsSettings }) => (
export const VisitsSettings: FC<VisitsProps> = ({ settings, setVisitsSettings }) => {
const updateSettings = useCallback(
({ defaultInterval, ...rest }: Partial<VisitsSettingsConfig>) => setVisitsSettings(
{ defaultInterval: defaultInterval ?? currentDefaultInterval(settings), ...rest },
),
[setVisitsSettings, settings],
);
return (
<SimpleCard title="Visits" className="h-100">
<FormGroup>
<ToggleSwitch
checked={!!settings.visits?.excludeBots}
onChange={(excludeBots) => setVisitsSettings(
{ defaultInterval: currentDefaultInterval(settings), excludeBots },
)}
onChange={(excludeBots) => updateSettings({ excludeBots })}
>
Exclude bots wherever possible (this option&lsquo;s effect might depend on Shlink server&lsquo;s version).
<FormText>
The visits coming from potential bots will be <b>{settings.visits?.excludeBots ? 'excluded' : 'included'}</b>.
The visits coming from potential bots will
be <b>{settings.visits?.excludeBots ? 'excluded' : 'included'}</b>.
</FormText>
</ToggleSwitch>
</FormGroup>
<FormGroup>
<ToggleSwitch
checked={!!settings.visits?.loadPrevInterval}
onChange={(loadPrevInterval) => updateSettings({ loadPrevInterval })}
>
Compare visits with previous period.
<FormText>
When loading visits, previous period <b>{settings.visits?.loadPrevInterval ? 'will' : 'won\'t'}</b> be
loaded by default.
</FormText>
</ToggleSwitch>
</FormGroup>
@ -32,8 +52,9 @@ export const VisitsSettings: FC<VisitsProps> = ({ settings, setVisitsSettings })
<DateIntervalSelector
allText="All visits"
active={currentDefaultInterval(settings)}
onChange={(defaultInterval) => setVisitsSettings({ defaultInterval })}
onChange={(defaultInterval) => updateSettings({ defaultInterval })}
/>
</LabeledFormGroup>
</SimpleCard>
);
};

View file

@ -19,6 +19,7 @@ describe('<VisitsSettings />', () => {
expect(screen.getByRole('heading')).toHaveTextContent('Visits');
expect(screen.getByText('Default interval to load on visits sections:')).toBeInTheDocument();
expect(screen.getByText(/^Exclude bots wherever possible/)).toBeInTheDocument();
expect(screen.getByText('Compare visits with previous period.')).toBeInTheDocument();
});
it.each([
@ -93,4 +94,36 @@ describe('<VisitsSettings />', () => {
await user.click(screen.getByText(/^Exclude bots wherever possible/));
expect(setVisitsSettings).toHaveBeenCalledWith(expect.objectContaining({ excludeBots: true }));
});
it.each([
[
fromPartial<Settings>({}),
/When loading visits, previous period won't be loaded by default.$/,
/When loading visits, previous period will be loaded by default.$/,
],
[
fromPartial<Settings>({ visits: { loadPrevInterval: false } }),
/When loading visits, previous period won't be loaded by default.$/,
/When loading visits, previous period will be loaded by default.$/,
],
[
fromPartial<Settings>({ visits: { loadPrevInterval: true } }),
/When loading visits, previous period will be loaded by default.$/,
/When loading visits, previous period won't be loaded by default.$/,
],
])('displays expected helper text for prev interval control', (settings, expectedText, notExpectedText) => {
setUp(settings);
const visitsComponent = screen.getByText('Compare visits with previous period.');
expect(visitsComponent).toHaveTextContent(expectedText);
expect(visitsComponent).not.toHaveTextContent(notExpectedText);
});
it('invokes setVisitsSettings when loading prev visits is toggled', async () => {
const { user } = setUp();
await user.click(screen.getByText('Compare visits with previous period.'));
expect(setVisitsSettings).toHaveBeenCalledWith(expect.objectContaining({ loadPrevInterval: true }));
});
});