mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-22 17:10:26 +03:00
Merge pull request #1044 from acelaya-forks/feature/swc-0.5
Update to latest shlink-web-component
This commit is contained in:
commit
b12bb6cdc5
5 changed files with 135 additions and 96 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -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
105
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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‘s effect might depend on Shlink server‘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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 }));
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue