Updated fetch hook API.

This commit is contained in:
Mike Cao 2020-10-09 12:39:03 -07:00
commit 69b317386a
21 changed files with 112 additions and 76 deletions

7
components/common/Tag.js Normal file
View File

@ -0,0 +1,7 @@
import React from 'react';
import classNames from 'classnames';
import styles from './Tag.module.css';
export default function Tag({ className, children }) {
return <span className={classNames(styles.tag, className)}>{children}</span>;
}

View File

@ -1,4 +1,4 @@
.type {
.tag {
font-size: var(--font-size-small);
padding: 2px 4px;
border: 1px solid var(--gray300);

View File

@ -5,7 +5,10 @@ import useFetch from 'hooks/useFetch';
import styles from './ActiveUsers.module.css';
export default function ActiveUsers({ websiteId, token, className }) {
const { data } = useFetch(`/api/website/${websiteId}/active`, { token }, { interval: 60000 });
const { data } = useFetch(`/api/website/${websiteId}/active`, {
params: { token },
interval: 60000,
});
const count = useMemo(() => {
return data?.[0]?.x || 0;
}, [data]);

View File

@ -17,14 +17,16 @@ export default function EventsChart({ websiteId, token }) {
const { data } = useFetch(
`/api/website/${websiteId}/events`,
{
start_at: +startDate,
end_at: +endDate,
unit,
tz: timezone,
url: query.url,
token,
params: {
start_at: +startDate,
end_at: +endDate,
unit,
tz: timezone,
url: query.url,
token,
},
},
{ update: [modified] },
[modified],
);
const datasets = useMemo(() => {
if (!data) return [];

View File

@ -1,7 +1,7 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import MetricsTable from './MetricsTable';
import styles from './EventsTable.module.css';
import Tag from 'components/common/Tag';
export default function EventsTable({ websiteId, token, limit, onDataLoad }) {
return (
@ -22,7 +22,7 @@ const Label = ({ value }) => {
const [event, label] = value.split(':');
return (
<>
<span className={styles.type}>{event}</span>
<Tag>{event}</Tag>
{label}
</>
);

View File

@ -18,17 +18,19 @@ export default function MetricsBar({ websiteId, token, className }) {
query: { url },
} = usePageQuery();
console.log({ modified });
const { data, error, loading } = useFetch(
`/api/website/${websiteId}/stats`,
{
start_at: +startDate,
end_at: +endDate,
url,
token,
},
{
update: [modified],
params: {
start_at: +startDate,
end_at: +endDate,
url,
token,
},
},
[modified],
);
const formatFunc = format ? formatLongNumber : formatNumber;

View File

@ -40,14 +40,18 @@ export default function MetricsTable({
const { data, loading, error } = useFetch(
`/api/website/${websiteId}/metrics`,
{
type,
start_at: +startDate,
end_at: +endDate,
domain: websiteDomain,
url,
token,
params: {
type,
start_at: +startDate,
end_at: +endDate,
domain: websiteDomain,
url,
token,
},
onDataLoad,
delay: 300,
},
{ onDataLoad, delay: 300, update: [modified] },
[modified],
);
const [format, setFormat] = useState(true);
const formatFunc = format ? formatLongNumber : formatNumber;

View File

@ -4,7 +4,7 @@ import tinycolor from 'tinycolor2';
import CheckVisible from 'components/helpers/CheckVisible';
import BarChart from './BarChart';
import useTheme from 'hooks/useTheme';
import { THEME_COLORS } from 'lib/constants';
import { THEME_COLORS, DEFAULT_ANIMATION_DURATION } from 'lib/constants';
export default function PageviewsChart({
websiteId,
@ -13,7 +13,7 @@ export default function PageviewsChart({
records,
className,
loading,
animationDuration = 300,
animationDuration = DEFAULT_ANIMATION_DURATION,
}) {
const intl = useIntl();
const [theme] = useTheme();

View File

@ -2,6 +2,7 @@ import React, { useMemo, useRef } from 'react';
import { format, parseISO, startOfMinute, subMinutes, isBefore } from 'date-fns';
import PageviewsChart from './PageviewsChart';
import { getDateArray } from 'lib/date';
import { DEFAULT_ANIMATION_DURATION } from 'lib/constants';
function mapData(data) {
let last = 0;
@ -44,7 +45,7 @@ export default function RealtimeChart({ data, unit, ...props }) {
prevEndDate.current = endDate;
return 0;
}
return 300;
return DEFAULT_ANIMATION_DURATION;
}, [data]);
return (

View File

@ -5,6 +5,7 @@ import firstBy from 'thenby';
import { format } from 'date-fns';
import Icon from 'components/common/Icon';
import Table, { TableRow } from 'components/common/Table';
import Tag from 'components/common/Tag';
import useLocale from 'hooks/useLocale';
import useCountryNames from 'hooks/useCountryNames';
import { BROWSERS } from 'lib/constants';
@ -92,7 +93,7 @@ export default function RealtimeLog({ data, websites }) {
if (event_type) {
return (
<div>
<span className={styles.event}>{event_type}</span> {event_value}
<Tag>{event_type}</Tag> {event_value}
</div>
);
}

View File

@ -12,14 +12,6 @@
overflow: auto;
}
.event {
font-size: var(--font-size-small);
padding: 2px 4px;
border: 1px solid var(--gray300);
border-radius: 4px;
margin-right: 10px;
}
.icon {
align-self: center;
margin-right: 20px;

View File

@ -35,14 +35,17 @@ export default function WebsiteChart({
const { data, loading, error } = useFetch(
`/api/website/${websiteId}/pageviews`,
{
start_at: +startDate,
end_at: +endDate,
unit,
tz: timezone,
url,
token,
params: {
start_at: +startDate,
end_at: +endDate,
unit,
tz: timezone,
url,
token,
},
onDataLoad,
},
{ onDataLoad, update: [modified] },
[modified],
);
const chartData = useMemo(() => {

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useMemo } from 'react';
import { FormattedMessage } from 'react-intl';
import { subMinutes, startOfMinute } from 'date-fns';
import Page from 'components/layout/Page';
@ -18,19 +18,34 @@ function mergeData(state, data, time) {
.filter(({ created_at }) => new Date(created_at).getTime() >= time);
}
function filterWebsite(data, id) {
return data.filter(({ website_id }) => website_id === id);
}
export default function RealtimeDashboard() {
const [data, setData] = useState();
const [website, setWebsite] = useState();
const { data: init, loading } = useFetch('/api/realtime', { type: 'init' });
const { data: updates } = useFetch(
'/api/realtime',
{ type: 'update', start_at: data?.timestamp },
{
disabled: !init?.token || !data,
interval: REALTIME_INTERVAL,
headers: { 'x-umami-token': init?.token },
},
);
const { data: init, loading } = useFetch('/api/realtime', { params: { type: 'init' } });
const { data: updates } = useFetch('/api/realtime', {
params: { type: 'update', start_at: data?.timestamp },
disabled: !init?.token || !data,
interval: REALTIME_INTERVAL,
headers: { 'x-umami-token': init?.token },
});
const realtimeData = useMemo(() => {
if (website) {
const { website_id } = website;
const { pageviews, sessions, events, ...props } = data;
return {
pageviews: filterWebsite(pageviews, website_id),
sessions: filterWebsite(sessions, website_id),
events: filterWebsite(events, website_id),
...props,
};
}
return data;
}, [data, website]);
useEffect(() => {
if (init && !data) {
@ -70,10 +85,15 @@ export default function RealtimeDashboard() {
</div>
<DropDown value={selectedValue} options={options} onChange={handleSelect} />
</PageHeader>
<RealtimeChart websiteId={website?.website_id} data={data} unit="minute" records={30} />
<RealtimeChart
websiteId={website?.website_id}
data={realtimeData}
unit="minute"
records={REALTIME_RANGE}
/>
<div className="row">
<div className="col-12">
<RealtimeLog data={data} websites={websites} />
<RealtimeLog data={realtimeData} websites={websites} />
</div>
</div>
</Page>

View File

@ -31,7 +31,7 @@ const views = {
};
export default function WebsiteDetails({ websiteId, token }) {
const { data } = useFetch(`/api/website/${websiteId}`, { token });
const { data } = useFetch(`/api/website/${websiteId}`, { params: { token } });
const [chartLoaded, setChartLoaded] = useState(false);
const [countryData, setCountryData] = useState();
const [eventsData, setEventsData] = useState();

View File

@ -9,7 +9,7 @@ import Arrow from 'assets/arrow-right.svg';
import styles from './WebsiteList.module.css';
export default function WebsiteList({ userId }) {
const { data } = useFetch('/api/websites', { user_id: userId });
const { data } = useFetch('/api/websites', { params: { user_id: userId } });
if (!data) {
return null;

View File

@ -25,7 +25,7 @@ export default function AccountSettings() {
const [deleteAccount, setDeleteAccount] = useState();
const [saved, setSaved] = useState(0);
const [message, setMessage] = useState();
const { data } = useFetch(`/api/accounts`, {}, { update: [saved] });
const { data } = useFetch(`/api/accounts`, {}, [saved]);
const Checkmark = ({ is_admin }) => (is_admin ? <Icon icon={<Check />} size="medium" /> : null);

View File

@ -29,7 +29,7 @@ export default function WebsiteSettings() {
const [showUrl, setShowUrl] = useState();
const [saved, setSaved] = useState(0);
const [message, setMessage] = useState();
const { data } = useFetch(`/api/websites`, {}, { update: [saved] });
const { data } = useFetch(`/api/websites`, {}, [saved]);
const Buttons = row => (
<ButtonLayout align="right">

View File

@ -4,14 +4,14 @@ import { get } from 'lib/web';
import { updateQuery } from 'redux/actions/queries';
import { useRouter } from 'next/router';
export default function useFetch(url, params = {}, options = {}) {
export default function useFetch(url, options = {}, update = []) {
const dispatch = useDispatch();
const [data, setData] = useState();
const [status, setStatus] = useState();
const [error, setError] = useState();
const [loading, setLoadiing] = useState(false);
const { basePath } = useRouter();
const { update = [], onDataLoad = () => {}, disabled, headers, interval, delay = 0 } = options;
const { params, disabled, headers, interval, delay = 0, onDataLoad } = options;
async function loadData() {
try {
@ -30,7 +30,7 @@ export default function useFetch(url, params = {}, options = {}) {
}
setStatus(status);
onDataLoad(data);
onDataLoad?.(data);
} catch (e) {
console.error(e);
setError(e);
@ -41,17 +41,17 @@ export default function useFetch(url, params = {}, options = {}) {
useEffect(() => {
if (url && !disabled) {
if (!data) {
setTimeout(() => loadData(), delay);
}
const id = interval ? setInterval(() => loadData(), interval) : null;
return () => {
clearInterval(id);
};
setTimeout(() => loadData(), delay);
}
}, [data, url, disabled, ...update]);
}, [url, disabled, ...update]);
useEffect(() => {
const id = interval ? setInterval(() => loadData(), interval) : null;
return () => {
clearInterval(id);
};
}, [interval, params]);
return { data, status, error, loading };
}

View File

@ -51,6 +51,7 @@ export const EVENT_COLORS = [
'#ffec16',
];
export const DEFAULT_ANIMATION_DURATION = 300;
export const DEFAULT_DATE_RANGE = '24hour';
export const POSTGRESQL = 'postgresql';

View File

@ -1,6 +1,6 @@
{
"name": "umami",
"version": "0.82.0",
"version": "0.88.0",
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
"author": "Mike Cao <mike@mikecao.com>",
"license": "MIT",

View File

@ -8,7 +8,7 @@ export default function SharePage() {
const router = useRouter();
const { id } = router.query;
const shareId = id?.[0];
const { data } = useFetch(shareId ? `/api/share/${shareId}` : null);
const { data } = useFetch(`/api/share/${shareId}`, { disabled: !shareId });
if (!data) {
return null;