mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2026-03-11 18:13:47 +00:00
Added new visits settings
This commit is contained in:
@@ -3,6 +3,7 @@ import { dissoc, mergeDeepRight } from 'ramda';
|
|||||||
import { buildReducer } from '../../utils/helpers/redux';
|
import { buildReducer } from '../../utils/helpers/redux';
|
||||||
import { RecursivePartial } from '../../utils/utils';
|
import { RecursivePartial } from '../../utils/utils';
|
||||||
import { Theme } from '../../utils/theme';
|
import { Theme } from '../../utils/theme';
|
||||||
|
import { DateInterval } from '../../utils/dates/types';
|
||||||
|
|
||||||
export const SET_SETTINGS = 'shlink/realTimeUpdates/SET_SETTINGS';
|
export const SET_SETTINGS = 'shlink/realTimeUpdates/SET_SETTINGS';
|
||||||
|
|
||||||
@@ -24,10 +25,15 @@ export interface UiSettings {
|
|||||||
theme: Theme;
|
theme: Theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VisitsSettings {
|
||||||
|
defaultInterval: DateInterval;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
realTimeUpdates: RealTimeUpdatesSettings;
|
realTimeUpdates: RealTimeUpdatesSettings;
|
||||||
shortUrlCreation?: ShortUrlCreationSettings;
|
shortUrlCreation?: ShortUrlCreationSettings;
|
||||||
ui?: UiSettings;
|
ui?: UiSettings;
|
||||||
|
visits?: VisitsSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: Settings = {
|
const initialState: Settings = {
|
||||||
@@ -40,6 +46,9 @@ const initialState: Settings = {
|
|||||||
ui: {
|
ui: {
|
||||||
theme: 'light',
|
theme: 'light',
|
||||||
},
|
},
|
||||||
|
visits: {
|
||||||
|
defaultInterval: 'last30Days',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
type SettingsAction = Action & Settings;
|
type SettingsAction = Action & Settings;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { RouteComponentProps } from 'react-router';
|
|||||||
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||||
import { ShlinkVisitsParams } from '../api/types';
|
import { ShlinkVisitsParams } from '../api/types';
|
||||||
import { Topics } from '../mercure/helpers/Topics';
|
import { Topics } from '../mercure/helpers/Topics';
|
||||||
|
import { Settings } from '../settings/reducers/settings';
|
||||||
import VisitsStats from './VisitsStats';
|
import VisitsStats from './VisitsStats';
|
||||||
import { OrphanVisitsHeader } from './OrphanVisitsHeader';
|
import { OrphanVisitsHeader } from './OrphanVisitsHeader';
|
||||||
import { VisitsInfo } from './types';
|
import { VisitsInfo } from './types';
|
||||||
@@ -10,6 +11,7 @@ export interface OrphanVisitsProps extends RouteComponentProps {
|
|||||||
getOrphanVisits: (params: ShlinkVisitsParams) => void;
|
getOrphanVisits: (params: ShlinkVisitsParams) => void;
|
||||||
orphanVisits: VisitsInfo;
|
orphanVisits: VisitsInfo;
|
||||||
cancelGetOrphanVisits: () => void;
|
cancelGetOrphanVisits: () => void;
|
||||||
|
settings: Settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OrphanVisits = boundToMercureHub(({
|
export const OrphanVisits = boundToMercureHub(({
|
||||||
@@ -18,12 +20,14 @@ export const OrphanVisits = boundToMercureHub(({
|
|||||||
getOrphanVisits,
|
getOrphanVisits,
|
||||||
orphanVisits,
|
orphanVisits,
|
||||||
cancelGetOrphanVisits,
|
cancelGetOrphanVisits,
|
||||||
|
settings,
|
||||||
}: OrphanVisitsProps) => (
|
}: OrphanVisitsProps) => (
|
||||||
<VisitsStats
|
<VisitsStats
|
||||||
getVisits={getOrphanVisits}
|
getVisits={getOrphanVisits}
|
||||||
cancelGetVisits={cancelGetOrphanVisits}
|
cancelGetVisits={cancelGetOrphanVisits}
|
||||||
visitsInfo={orphanVisits}
|
visitsInfo={orphanVisits}
|
||||||
baseUrl={url}
|
baseUrl={url}
|
||||||
|
settings={settings}
|
||||||
>
|
>
|
||||||
<OrphanVisitsHeader orphanVisits={orphanVisits} goBack={goBack} />
|
<OrphanVisitsHeader orphanVisits={orphanVisits} goBack={goBack} />
|
||||||
</VisitsStats>
|
</VisitsStats>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { ShlinkVisitsParams } from '../api/types';
|
|||||||
import { parseQuery } from '../utils/helpers/query';
|
import { parseQuery } from '../utils/helpers/query';
|
||||||
import { Topics } from '../mercure/helpers/Topics';
|
import { Topics } from '../mercure/helpers/Topics';
|
||||||
import { ShortUrlDetail } from '../short-urls/reducers/shortUrlDetail';
|
import { ShortUrlDetail } from '../short-urls/reducers/shortUrlDetail';
|
||||||
|
import { Settings } from '../settings/reducers/settings';
|
||||||
import { ShortUrlVisits as ShortUrlVisitsState } from './reducers/shortUrlVisits';
|
import { ShortUrlVisits as ShortUrlVisitsState } from './reducers/shortUrlVisits';
|
||||||
import ShortUrlVisitsHeader from './ShortUrlVisitsHeader';
|
import ShortUrlVisitsHeader from './ShortUrlVisitsHeader';
|
||||||
import VisitsStats from './VisitsStats';
|
import VisitsStats from './VisitsStats';
|
||||||
@@ -15,6 +16,7 @@ export interface ShortUrlVisitsProps extends RouteComponentProps<{ shortCode: st
|
|||||||
getShortUrlDetail: Function;
|
getShortUrlDetail: Function;
|
||||||
shortUrlDetail: ShortUrlDetail;
|
shortUrlDetail: ShortUrlDetail;
|
||||||
cancelGetShortUrlVisits: () => void;
|
cancelGetShortUrlVisits: () => void;
|
||||||
|
settings: Settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShortUrlVisits = boundToMercureHub(({
|
const ShortUrlVisits = boundToMercureHub(({
|
||||||
@@ -26,6 +28,7 @@ const ShortUrlVisits = boundToMercureHub(({
|
|||||||
getShortUrlVisits,
|
getShortUrlVisits,
|
||||||
getShortUrlDetail,
|
getShortUrlDetail,
|
||||||
cancelGetShortUrlVisits,
|
cancelGetShortUrlVisits,
|
||||||
|
settings,
|
||||||
}: ShortUrlVisitsProps) => {
|
}: ShortUrlVisitsProps) => {
|
||||||
const { shortCode } = params;
|
const { shortCode } = params;
|
||||||
const { domain } = parseQuery<{ domain?: string }>(search);
|
const { domain } = parseQuery<{ domain?: string }>(search);
|
||||||
@@ -42,6 +45,7 @@ const ShortUrlVisits = boundToMercureHub(({
|
|||||||
visitsInfo={shortUrlVisits}
|
visitsInfo={shortUrlVisits}
|
||||||
baseUrl={url}
|
baseUrl={url}
|
||||||
domain={domain}
|
domain={domain}
|
||||||
|
settings={settings}
|
||||||
>
|
>
|
||||||
<ShortUrlVisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} goBack={goBack} />
|
<ShortUrlVisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} goBack={goBack} />
|
||||||
</VisitsStats>
|
</VisitsStats>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
|||||||
import ColorGenerator from '../utils/services/ColorGenerator';
|
import ColorGenerator from '../utils/services/ColorGenerator';
|
||||||
import { ShlinkVisitsParams } from '../api/types';
|
import { ShlinkVisitsParams } from '../api/types';
|
||||||
import { Topics } from '../mercure/helpers/Topics';
|
import { Topics } from '../mercure/helpers/Topics';
|
||||||
|
import { Settings } from '../settings/reducers/settings';
|
||||||
import { TagVisits as TagVisitsState } from './reducers/tagVisits';
|
import { TagVisits as TagVisitsState } from './reducers/tagVisits';
|
||||||
import TagVisitsHeader from './TagVisitsHeader';
|
import TagVisitsHeader from './TagVisitsHeader';
|
||||||
import VisitsStats from './VisitsStats';
|
import VisitsStats from './VisitsStats';
|
||||||
@@ -11,6 +12,7 @@ export interface TagVisitsProps extends RouteComponentProps<{ tag: string }> {
|
|||||||
getTagVisits: (tag: string, query: any) => void;
|
getTagVisits: (tag: string, query: any) => void;
|
||||||
tagVisits: TagVisitsState;
|
tagVisits: TagVisitsState;
|
||||||
cancelGetTagVisits: () => void;
|
cancelGetTagVisits: () => void;
|
||||||
|
settings: Settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TagVisits = (colorGenerator: ColorGenerator) => boundToMercureHub(({
|
const TagVisits = (colorGenerator: ColorGenerator) => boundToMercureHub(({
|
||||||
@@ -19,12 +21,19 @@ const TagVisits = (colorGenerator: ColorGenerator) => boundToMercureHub(({
|
|||||||
getTagVisits,
|
getTagVisits,
|
||||||
tagVisits,
|
tagVisits,
|
||||||
cancelGetTagVisits,
|
cancelGetTagVisits,
|
||||||
|
settings,
|
||||||
}: TagVisitsProps) => {
|
}: TagVisitsProps) => {
|
||||||
const { tag } = params;
|
const { tag } = params;
|
||||||
const loadVisits = (params: ShlinkVisitsParams) => getTagVisits(tag, params);
|
const loadVisits = (params: ShlinkVisitsParams) => getTagVisits(tag, params);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VisitsStats getVisits={loadVisits} cancelGetVisits={cancelGetTagVisits} visitsInfo={tagVisits} baseUrl={url}>
|
<VisitsStats
|
||||||
|
getVisits={loadVisits}
|
||||||
|
cancelGetVisits={cancelGetTagVisits}
|
||||||
|
visitsInfo={tagVisits}
|
||||||
|
baseUrl={url}
|
||||||
|
settings={settings}
|
||||||
|
>
|
||||||
<TagVisitsHeader tagVisits={tagVisits} goBack={goBack} colorGenerator={colorGenerator} />
|
<TagVisitsHeader tagVisits={tagVisits} goBack={goBack} colorGenerator={colorGenerator} />
|
||||||
</VisitsStats>
|
</VisitsStats>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { ShlinkVisitsParams } from '../api/types';
|
|||||||
import { DateInterval, DateRange, intervalToDateRange } from '../utils/dates/types';
|
import { DateInterval, DateRange, intervalToDateRange } from '../utils/dates/types';
|
||||||
import { Result } from '../utils/Result';
|
import { Result } from '../utils/Result';
|
||||||
import { ShlinkApiError } from '../api/ShlinkApiError';
|
import { ShlinkApiError } from '../api/ShlinkApiError';
|
||||||
|
import { Settings } from '../settings/reducers/settings';
|
||||||
import SortableBarGraph from './helpers/SortableBarGraph';
|
import SortableBarGraph from './helpers/SortableBarGraph';
|
||||||
import GraphCard from './helpers/GraphCard';
|
import GraphCard from './helpers/GraphCard';
|
||||||
import LineChartCard from './helpers/LineChartCard';
|
import LineChartCard from './helpers/LineChartCard';
|
||||||
@@ -25,6 +26,7 @@ import './VisitsStats.scss';
|
|||||||
export interface VisitsStatsProps {
|
export interface VisitsStatsProps {
|
||||||
getVisits: (params: Partial<ShlinkVisitsParams>) => void;
|
getVisits: (params: Partial<ShlinkVisitsParams>) => void;
|
||||||
visitsInfo: VisitsInfo;
|
visitsInfo: VisitsInfo;
|
||||||
|
settings: Settings;
|
||||||
cancelGetVisits: () => void;
|
cancelGetVisits: () => void;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
domain?: string;
|
domain?: string;
|
||||||
@@ -59,7 +61,6 @@ const highlightedVisitsToStats = (
|
|||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
let selectedBar: string | undefined;
|
let selectedBar: string | undefined;
|
||||||
const initialInterval: DateInterval = 'last30Days';
|
|
||||||
|
|
||||||
const VisitsNavLink: FC<VisitsNavLinkProps & { to: string }> = ({ subPath, title, icon, to }) => (
|
const VisitsNavLink: FC<VisitsNavLinkProps & { to: string }> = ({ subPath, title, icon, to }) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
@@ -74,7 +75,10 @@ const VisitsNavLink: FC<VisitsNavLinkProps & { to: string }> = ({ subPath, title
|
|||||||
</NavLink>
|
</NavLink>
|
||||||
);
|
);
|
||||||
|
|
||||||
const VisitsStats: FC<VisitsStatsProps> = ({ children, visitsInfo, getVisits, cancelGetVisits, baseUrl, domain }) => {
|
const VisitsStats: FC<VisitsStatsProps> = (
|
||||||
|
{ children, visitsInfo, getVisits, cancelGetVisits, baseUrl, domain, settings },
|
||||||
|
) => {
|
||||||
|
const initialInterval: DateInterval = settings.visits?.defaultInterval ?? 'last30Days';
|
||||||
const [ dateRange, setDateRange ] = useState<DateRange>(intervalToDateRange(initialInterval));
|
const [ dateRange, setDateRange ] = useState<DateRange>(intervalToDateRange(initialInterval));
|
||||||
const [ highlightedVisits, setHighlightedVisits ] = useState<NormalizedVisit[]>([]);
|
const [ highlightedVisits, setHighlightedVisits ] = useState<NormalizedVisit[]>([]);
|
||||||
const [ highlightedLabel, setHighlightedLabel ] = useState<string | undefined>();
|
const [ highlightedLabel, setHighlightedLabel ] = useState<string | undefined>();
|
||||||
|
|||||||
@@ -18,19 +18,19 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||||||
|
|
||||||
bottle.serviceFactory('ShortUrlVisits', () => ShortUrlVisits);
|
bottle.serviceFactory('ShortUrlVisits', () => ShortUrlVisits);
|
||||||
bottle.decorator('ShortUrlVisits', connect(
|
bottle.decorator('ShortUrlVisits', connect(
|
||||||
[ 'shortUrlVisits', 'shortUrlDetail', 'mercureInfo' ],
|
[ 'shortUrlVisits', 'shortUrlDetail', 'mercureInfo', 'settings' ],
|
||||||
[ 'getShortUrlVisits', 'getShortUrlDetail', 'cancelGetShortUrlVisits', 'createNewVisits', 'loadMercureInfo' ],
|
[ 'getShortUrlVisits', 'getShortUrlDetail', 'cancelGetShortUrlVisits', 'createNewVisits', 'loadMercureInfo' ],
|
||||||
));
|
));
|
||||||
|
|
||||||
bottle.serviceFactory('TagVisits', TagVisits, 'ColorGenerator');
|
bottle.serviceFactory('TagVisits', TagVisits, 'ColorGenerator');
|
||||||
bottle.decorator('TagVisits', connect(
|
bottle.decorator('TagVisits', connect(
|
||||||
[ 'tagVisits', 'mercureInfo' ],
|
[ 'tagVisits', 'mercureInfo', 'settings' ],
|
||||||
[ 'getTagVisits', 'cancelGetTagVisits', 'createNewVisits', 'loadMercureInfo' ],
|
[ 'getTagVisits', 'cancelGetTagVisits', 'createNewVisits', 'loadMercureInfo' ],
|
||||||
));
|
));
|
||||||
|
|
||||||
bottle.serviceFactory('OrphanVisits', () => OrphanVisits);
|
bottle.serviceFactory('OrphanVisits', () => OrphanVisits);
|
||||||
bottle.decorator('OrphanVisits', connect(
|
bottle.decorator('OrphanVisits', connect(
|
||||||
[ 'orphanVisits', 'mercureInfo' ],
|
[ 'orphanVisits', 'mercureInfo', 'settings' ],
|
||||||
[ 'getOrphanVisits', 'cancelGetOrphanVisits', 'createNewVisits', 'loadMercureInfo' ],
|
[ 'getOrphanVisits', 'cancelGetOrphanVisits', 'createNewVisits', 'loadMercureInfo' ],
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ describe('settingsReducer', () => {
|
|||||||
const realTimeUpdates = { enabled: true };
|
const realTimeUpdates = { enabled: true };
|
||||||
const shortUrlCreation = { validateUrls: false };
|
const shortUrlCreation = { validateUrls: false };
|
||||||
const ui = { theme: 'light' };
|
const ui = { theme: 'light' };
|
||||||
const settings = { realTimeUpdates, shortUrlCreation, ui };
|
const visits = { defaultInterval: 'last30Days' };
|
||||||
|
const settings = { realTimeUpdates, shortUrlCreation, ui, visits };
|
||||||
|
|
||||||
describe('reducer', () => {
|
describe('reducer', () => {
|
||||||
it('returns realTimeUpdates when action is SET_SETTINGS', () => {
|
it('returns realTimeUpdates when action is SET_SETTINGS', () => {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Visit, VisitsInfo } from '../../src/visits/types';
|
|||||||
import LineChartCard from '../../src/visits/helpers/LineChartCard';
|
import LineChartCard from '../../src/visits/helpers/LineChartCard';
|
||||||
import VisitsTable from '../../src/visits/VisitsTable';
|
import VisitsTable from '../../src/visits/VisitsTable';
|
||||||
import { Result } from '../../src/utils/Result';
|
import { Result } from '../../src/utils/Result';
|
||||||
|
import { Settings } from '../../src/settings/reducers/settings';
|
||||||
|
|
||||||
describe('<VisitStats />', () => {
|
describe('<VisitStats />', () => {
|
||||||
const visits = [ Mock.all<Visit>(), Mock.all<Visit>(), Mock.all<Visit>() ];
|
const visits = [ Mock.all<Visit>(), Mock.all<Visit>(), Mock.all<Visit>() ];
|
||||||
@@ -23,6 +24,7 @@ describe('<VisitStats />', () => {
|
|||||||
visitsInfo={Mock.of<VisitsInfo>(visitsInfo)}
|
visitsInfo={Mock.of<VisitsInfo>(visitsInfo)}
|
||||||
cancelGetVisits={() => {}}
|
cancelGetVisits={() => {}}
|
||||||
baseUrl={''}
|
baseUrl={''}
|
||||||
|
settings={Mock.all<Settings>()}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user