Browse Source

Merge pull request #512 from mikecao/dev

v1.14.0
master v1.14.0
Mike Cao 6 months ago
committed by GitHub
parent
commit
405766d829
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 76 additions and 54 deletions
  1. +1
    -1
      components/common/Calendar.js
  2. +1
    -2
      components/common/DateFilter.js
  3. +7
    -4
      components/common/NoData.module.css
  4. +1
    -1
      components/layout/GridLayout.module.css
  5. +4
    -4
      components/metrics/BarChart.js
  6. +4
    -3
      components/metrics/DataTable.module.css
  7. +1
    -1
      components/metrics/EventsTable.js
  8. +0
    -2
      components/metrics/MetricsTable.js
  9. +1
    -0
      components/metrics/MetricsTable.module.css
  10. +14
    -7
      components/metrics/RealtimeLog.js
  11. +4
    -0
      components/metrics/RealtimeLog.module.css
  12. +0
    -1
      components/metrics/ReferrersTable.js
  13. +1
    -1
      lib/constants.js
  14. +16
    -0
      lib/date.js
  15. +0
    -14
      lib/lang.js
  16. +1
    -1
      lib/queries.js
  17. +3
    -2
      package.json
  18. +16
    -10
      pages/api/website/[id]/metrics.js
  19. +1
    -0
      styles/index.css

+ 1
- 1
components/common/Calendar.js View File

@ -18,7 +18,7 @@ import {
} from 'date-fns';
import Button from './Button';
import useLocale from 'hooks/useLocale';
import { dateFormat } from 'lib/lang';
import { dateFormat } from 'lib/date';
import { chunk } from 'lib/array';
import Chevron from 'assets/chevron-down.svg';
import Cross from 'assets/times.svg';


+ 1
- 2
components/common/DateFilter.js View File

@ -6,8 +6,7 @@ import Modal from './Modal';
import DropDown from './DropDown';
import DatePickerForm from 'components/forms/DatePickerForm';
import useLocale from 'hooks/useLocale';
import { getDateRange } from 'lib/date';
import { dateFormat } from 'lib/lang';
import { getDateRange, dateFormat } from 'lib/date';
import Calendar from 'assets/calendar-alt.svg';
import Icon from './Icon';


+ 7
- 4
components/common/NoData.module.css View File

@ -1,8 +1,11 @@
.container {
color: var(--gray500);
font-size: var(--font-size-normal);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
position: relative;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
width: 100%;
height: 100%;
}

+ 1
- 1
components/layout/GridLayout.module.css View File

@ -35,6 +35,6 @@
.row > .col {
border-top: 1px solid var(--gray300);
border-left: 0;
padding: 0;
padding: 20px 0;
}
}

+ 4
- 4
components/metrics/BarChart.js View File

@ -3,7 +3,7 @@ import classNames from 'classnames';
import ChartJS from 'chart.js';
import Legend from 'components/metrics/Legend';
import { formatLongNumber } from 'lib/format';
import { dateFormat, timeFormat } from 'lib/lang';
import { dateFormat } from 'lib/date';
import useLocale from 'hooks/useLocale';
import useTheme from 'hooks/useTheme';
import { DEFAUL_CHART_HEIGHT, DEFAULT_ANIMATION_DURATION, THEME_COLORS } from 'lib/constants';
@ -46,7 +46,7 @@ export default function BarChart({
case 'minute':
return index % 2 === 0 ? dateFormat(d, 'H:mm', locale) : '';
case 'hour':
return timeFormat(d, locale);
return dateFormat(d, 'p', locale);
case 'day':
if (records > 31) {
if (w <= 500) {
@ -93,9 +93,9 @@ export default function BarChart({
function getTooltipFormat(unit) {
switch (unit) {
case 'hour':
return 'EEE ha — MMM d yyyy';
return 'EEE p — PPP';
default:
return 'EEE MMMM d yyyy';
return 'PPPP';
}
}


+ 4
- 3
components/metrics/DataTable.module.css View File

@ -1,14 +1,15 @@
.table {
position: relative;
height: 100%;
font-size: var(--font-size-small);
display: flex;
flex-direction: column;
flex: 1;
display: grid;
grid-template-rows: fit-content(100%) auto;
overflow: hidden;
}
.body {
position: relative;
height: 100%;
overflow: auto;
}


+ 1
- 1
components/metrics/EventsTable.js View File

@ -19,7 +19,7 @@ export default function EventsTable({ websiteId, ...props }) {
function handleDataLoad(data) {
setEventTypes([...new Set(data.map(({ x }) => x.split('\t')[0]))]);
props.onDataLoad(data);
props.onDataLoad?.(data);
}
return (


+ 0
- 2
components/metrics/MetricsTable.js View File

@ -17,7 +17,6 @@ import styles from './MetricsTable.module.css';
export default function MetricsTable({
websiteId,
websiteDomain,
type,
className,
dataFilter,
@ -42,7 +41,6 @@ export default function MetricsTable({
type,
start_at: +startDate,
end_at: +endDate,
domain: websiteDomain,
url,
},
onDataLoad,


+ 1
- 0
components/metrics/MetricsTable.module.css View File

@ -1,6 +1,7 @@
.container {
position: relative;
min-height: 430px;
height: 100%;
font-size: var(--font-size-small);
display: flex;
flex-direction: column;


+ 14
- 7
components/metrics/RealtimeLog.js View File

@ -2,11 +2,11 @@ import React, { useMemo, useState } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { FixedSizeList } from 'react-window';
import firstBy from 'thenby';
import { format } from 'date-fns';
import Icon from 'components/common/Icon';
import Tag from 'components/common/Tag';
import Dot from 'components/common/Dot';
import FilterButtons from 'components/common/FilterButtons';
import NoData from 'components/common/NoData';
import { devices } from 'components/messages';
import useLocale from 'hooks/useLocale';
import useCountryNames from 'hooks/useCountryNames';
@ -15,8 +15,8 @@ import Bolt from 'assets/bolt.svg';
import Visitor from 'assets/visitor.svg';
import Eye from 'assets/eye.svg';
import { stringToColor } from 'lib/format';
import { dateFormat } from 'lib/date';
import styles from './RealtimeLog.module.css';
import NoData from '../common/NoData';
const TYPE_ALL = 0;
const TYPE_PAGEVIEW = 1;
@ -129,7 +129,12 @@ export default function RealtimeLog({ data, websites, websiteId }) {
id="message.log.visitor"
defaultMessage="Visitor from {country} using {browser} on {os} {device}"
values={{
country: <b>{countryNames[country]}</b>,
country: (
<b>
{countryNames[country] ||
intl.formatMessage({ id: 'label.unknown', defaultMessage: 'Unknown' })}
</b>
),
browser: <b>{BROWSERS[browser]}</b>,
os: <b>{os}</b>,
device: <b>{intl.formatMessage(devices[device])?.toLowerCase()}</b>,
@ -140,7 +145,7 @@ export default function RealtimeLog({ data, websites, websiteId }) {
}
function getTime({ created_at }) {
return format(new Date(created_at), 'h:mm:ss');
return dateFormat(new Date(created_at), 'pp', locale);
}
function getColor(row) {
@ -176,9 +181,11 @@ export default function RealtimeLog({ data, websites, websiteId }) {
</div>
<div className={styles.body}>
{logs?.length === 0 && <NoData />}
<FixedSizeList height={400} itemCount={logs.length} itemSize={40}>
{Row}
</FixedSizeList>
{logs?.length > 0 && (
<FixedSizeList height={400} itemCount={logs.length} itemSize={40}>
{Row}
</FixedSizeList>
)}
</div>
</div>
);


+ 4
- 0
components/metrics/RealtimeLog.module.css View File

@ -1,6 +1,9 @@
.table {
font-size: var(--font-size-xsmall);
overflow: hidden;
height: 100%;
display: grid;
grid-template-rows: fit-content(100%) fit-content(100%) auto;
}
.header {
@ -21,6 +24,7 @@
.body {
overflow: auto;
height: 100%;
}
.icon {


+ 0
- 1
components/metrics/ReferrersTable.js View File

@ -42,7 +42,6 @@ export default function ReferrersTable({ websiteId, websiteDomain, showFilters,
type="referrer"
metric={<FormattedMessage id="metrics.views" defaultMessage="Views" />}
websiteId={websiteId}
websiteDomain={websiteDomain}
dataFilter={refFilter}
filterOptions={{
domain: websiteDomain,


+ 1
- 1
lib/constants.js View File

@ -80,7 +80,7 @@ export const POSTGRESQL_DATE_FORMATS = {
year: 'YYYY-01-01',
};
export const DOMAIN_REGEX = /^localhost(:\d{1,5})?|((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/;
export const DOMAIN_REGEX = /^(localhost(:[1-9]\d{0,4})?|((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63})$/;
export const DESKTOP_SCREEN_WIDTH = 1920;
export const LAPTOP_SCREEN_WIDTH = 1024;


+ 16
- 0
lib/date.js View File

@ -23,7 +23,10 @@ import {
differenceInCalendarDays,
differenceInCalendarMonths,
differenceInCalendarYears,
format,
} from 'date-fns';
import { enUS } from 'date-fns/locale';
import { dateLocales } from 'lib/lang';
export function getTimezone() {
return moment.tz.guess();
@ -150,3 +153,16 @@ export function getDateLength(startDate, endDate, unit) {
const [diff] = dateFuncs[unit];
return diff(endDate, startDate) + 1;
}
export const customFormats = {
'en-US': {
p: 'ha',
pp: 'h:mm:ss',
},
};
export function dateFormat(date, str, locale = 'en-US') {
return format(date, customFormats?.[locale]?.[str] || str, {
locale: dateLocales[locale] || enUS,
});
}

+ 0
- 14
lib/lang.js View File

@ -1,4 +1,3 @@
import { format } from 'date-fns';
import {
cs,
da,
@ -118,11 +117,6 @@ export const dateLocales = {
'it-IT': it,
};
const timeFormats = {
// https://date-fns.org/v2.17.0/docs/format
'en-US': 'ha',
};
export const menuOptions = [
{ label: '中文', value: 'zh-CN', display: 'cn' },
{ label: '中文(繁體)', value: 'zh-TW', display: 'tw' },
@ -153,11 +147,3 @@ export const menuOptions = [
{ label: 'Türkçe', value: 'tr-TR', display: 'tr' },
{ label: 'українська', value: 'uk-UA', display: 'uk' },
];
export function dateFormat(date, str, locale) {
return format(date, str, { locale: dateLocales[locale] || enUS });
}
export function timeFormat(date, locale = 'en-US') {
return format(date, timeFormats[locale] || 'p', { locale: dateLocales[locale] });
}

+ 1
- 1
lib/queries.js View File

@ -428,7 +428,7 @@ export function getPageviewMetrics(website_id, start_at, end_at, field, table, f
if (domain) {
domainFilter = `and referrer not like $${params.length + 1} and referrer not like '/%'`;
params.push(`%${domain}%`);
params.push(`%://${domain}/%`);
}
if (url) {


+ 3
- 2
package.json View File

@ -1,10 +1,10 @@
{
"name": "umami",
"version": "1.13.0",
"version": "1.14.0",
"description": "A simple, fast, website analytics alternative to Google Analytics. ",
"author": "Mike Cao <mike@mikecao.com>",
"license": "MIT",
"homepage": "https://github.com/mikecao/umami",
"homepage": "https://umami.is",
"repository": {
"type": "git",
"url": "https://github.com/mikecao/umami.git"
@ -77,6 +77,7 @@
"moment-timezone": "^0.5.32",
"next": "^10.0.7",
"prompts": "2.4.0",
"prop-types": "^15.7.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-intl": "^5.12.3",


+ 16
- 10
pages/api/website/[id]/metrics.js View File

@ -1,6 +1,5 @@
import { getPageviewMetrics, getSessionMetrics } from 'lib/queries';
import { ok, badRequest, methodNotAllowed, unauthorized } from 'lib/response';
import { DOMAIN_REGEX } from 'lib/constants';
import { getPageviewMetrics, getSessionMetrics, getWebsiteById } from 'lib/queries';
import { ok, methodNotAllowed, unauthorized, badRequest } from 'lib/response';
import { allowQuery } from 'lib/auth';
const sessionColumns = ['browser', 'os', 'device', 'country'];
@ -31,11 +30,7 @@ export default async (req, res) => {
return unauthorized(res);
}
const { id, type, start_at, end_at, domain, url } = req.query;
if (domain && !DOMAIN_REGEX.test(domain)) {
return badRequest(res);
}
const { id, type, start_at, end_at, url } = req.query;
const websiteId = +id;
const startDate = new Date(+start_at);
@ -47,7 +42,18 @@ export default async (req, res) => {
return ok(res, data);
}
if (type === 'event' || pageviewColumns.includes(type)) {
if (pageviewColumns.includes(type) || type === 'event') {
let domain;
if (type === 'referrer') {
const website = getWebsiteById(websiteId);
if (!website) {
return badRequest(res);
}
domain = website.domain;
}
const data = await getPageviewMetrics(
websiteId,
startDate,
@ -55,7 +61,7 @@ export default async (req, res) => {
getColumn(type),
getTable(type),
{
domain: type !== 'event' && domain,
domain,
url: type !== 'url' && url,
},
);


+ 1
- 0
styles/index.css View File

@ -14,6 +14,7 @@ body {
font-size: var(--font-size-normal);
color: var(--gray900);
background: var(--gray75);
overflow-y: overlay;
}
.zh-CN {


Loading…
Cancel
Save