Merge branch 'dev' into patch-1

This commit is contained in:
Mike Cao 2022-03-28 22:51:13 -07:00 committed by GitHub
commit 6c6a313782
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
144 changed files with 149 additions and 134 deletions

View File

@ -4,7 +4,7 @@
"es2020": true,
"node": true
},
"extends": ["eslint:recommended", "plugin:react/recommended", "prettier", "next"],
"extends": ["eslint:recommended", "plugin:prettier/recommended", "next"],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
@ -12,7 +12,6 @@
"ecmaVersion": 11,
"sourceType": "module"
},
"plugins": ["react"],
"rules": {
"react/display-name": "off",
"react/react-in-jsx-scope": "off",

View File

@ -102,6 +102,13 @@ npm install
npm run build
```
To update the Docker image, simply pull the new images and rebuild:
```bash
docker-compose pull
docker-compose up --force-recreate
```
## License
MIT

View File

@ -30,7 +30,7 @@ export default function MetricsTable({
const {
resolve,
router,
query: { url },
query: { url, referrer },
} = usePageQuery();
const { data, loading, error } = useFetch(
@ -41,12 +41,13 @@ export default function MetricsTable({
start_at: +startDate,
end_at: +endDate,
url,
referrer,
},
onDataLoad,
delay: DEFAULT_ANIMATION_DURATION,
headers: { [TOKEN_HEADER]: shareToken?.token },
},
[modified],
[modified, url, referrer],
);
const filteredData = useMemo(() => {

View File

@ -19,7 +19,7 @@ export default function ReferrersTable({ websiteId, websiteDomain, showFilters,
const [filter, setFilter] = useState(FILTER_COMBINED);
const {
resolve,
query: { ref: currentRef },
query: { referrer: currentRef },
} = usePageQuery();
const buttons = [
@ -37,7 +37,7 @@ export default function ReferrersTable({ websiteId, websiteDomain, showFilters,
const renderLink = ({ w: link, x: label }) => {
return (
<div className={styles.row}>
<Link href={resolve({ ref: label })} replace={true}>
<Link href={resolve({ referrer: label })} replace={true}>
<a
className={classNames(styles.label, {
[styles.inactive]: currentRef && label !== currentRef,

View File

@ -33,7 +33,7 @@ export default function WebsiteChart({
const {
router,
resolve,
query: { url, ref },
query: { url, referrer },
} = usePageQuery();
const { get } = useApi();
@ -46,12 +46,12 @@ export default function WebsiteChart({
unit,
tz: timezone,
url,
ref,
referrer,
},
onDataLoad,
headers: { [TOKEN_HEADER]: shareToken?.token },
},
[modified, url, ref],
[modified, url, referrer],
);
const chartData = useMemo(() => {
@ -88,7 +88,7 @@ export default function WebsiteChart({
stickyClassName={styles.sticky}
enabled={stickyHeader}
>
<FilterTags params={{ url, ref }} onClick={handleCloseFilter} />
<FilterTags params={{ url, referrer }} onClick={handleCloseFilter} />
<div className="col-12 col-lg-9">
<MetricsBar websiteId={websiteId} />
</div>

View File

@ -1,7 +1,7 @@
import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import { get } from 'lib/web';
import enUS from 'public/country/en-US.json';
import enUS from 'public/intl/country/en-US.json';
const countryNames = {
'en-US': enUS,
@ -12,7 +12,7 @@ export default function useCountryNames(locale) {
const { basePath } = useRouter();
async function loadData(locale) {
const { ok, data } = await get(`${basePath}/country/${locale}.json`);
const { ok, data } = await get(`${basePath}/intl/country/${locale}.json`);
if (ok) {
countryNames[locale] = data;

View File

@ -1,7 +1,7 @@
import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import { get } from 'lib/web';
import enUS from 'public/language/en-US.json';
import enUS from 'public/intl/language/en-US.json';
const languageNames = {
'en-US': enUS,
@ -12,7 +12,7 @@ export default function useLanguageNames(locale) {
const { basePath } = useRouter();
async function loadData(locale) {
const { ok, data } = await get(`${basePath}/language/${locale}.json`);
const { ok, data } = await get(`${basePath}/intl/language/${locale}.json`);
if (ok) {
languageNames[locale] = data;

View File

@ -5,7 +5,7 @@ import { LOCALE_CONFIG } from 'lib/constants';
import { getDateLocale, getTextDirection } from 'lib/lang';
import useStore, { setLocale } from 'store/app';
import useForceUpdate from 'hooks/useForceUpdate';
import enUS from 'public/messages/en-US.json';
import enUS from 'public/intl/messages/en-US.json';
const messages = {
'en-US': enUS,
@ -21,7 +21,7 @@ export default function useLocale() {
const dateLocale = getDateLocale(locale);
async function loadMessages(locale) {
const { ok, data } = await get(`${basePath}/messages/${locale}.json`);
const { ok, data } = await get(`${basePath}/intl/messages/${locale}.json`);
if (ok) {
messages[locale] = data;

View File

@ -28,7 +28,7 @@
"label.enable-share-url": "Activer le partage d'URL",
"label.invalid": "Invalide",
"label.invalid-domain": "Domaine invalide",
"label.language": "Language",
"label.language": "Langage",
"label.last-days": "{x} derniers jours",
"label.last-hours": "{x} dernières heures",
"label.logged-in-as": "Connecté en tant que {username}",
@ -51,7 +51,7 @@
"label.settings": "Paramètres",
"label.share-url": "Partager l'URL",
"label.single-day": "Journée",
"label.theme": "Theme",
"label.theme": "Thème",
"label.this-month": "Ce mois ci",
"label.this-week": "Cette semaine",
"label.this-year": "Cette année",

View File

@ -5,7 +5,7 @@
"label.administrator": "Administrator",
"label.all": "Wszystkie",
"label.all-events": "Wszystkie wydarzenia",
"label.all-time": "All time",
"label.all-time": "Cały czas",
"label.all-websites": "Wszystkie witryny",
"label.back": "Powrót",
"label.cancel": "Anuluj",
@ -14,7 +14,7 @@
"label.copy-to-clipboard": "Skopiuj do schowka",
"label.current-password": "Aktualne hasło",
"label.custom-range": "Zakres niestandardowy",
"label.dashboard": "Dashboard",
"label.dashboard": "Panel",
"label.date-range": "Zakres dat",
"label.default-date-range": "Domyślny zakres dat",
"label.delete": "Usuń",
@ -28,7 +28,7 @@
"label.enable-share-url": "Włącz udostępnianie adresu URL",
"label.invalid": "Nieprawidłowy",
"label.invalid-domain": "Nieprawidłowa witryna",
"label.language": "Language",
"label.language": "Język",
"label.last-days": "Ostatnie {x} dni",
"label.last-hours": "Ostatnie {x} godzin",
"label.logged-in-as": "Zalogowano jako {username}",
@ -37,7 +37,7 @@
"label.more": "Więcej",
"label.name": "Nazwa",
"label.new-password": "Nowe hasło",
"label.owner": "Owner",
"label.owner": "Właściciel",
"label.password": "Hasło",
"label.passwords-dont-match": "Hasła się nie zgadzają",
"label.profile": "Profil",
@ -46,12 +46,12 @@
"label.refresh": "Odśwież",
"label.required": "Wymagany",
"label.reset": "Zresetuj",
"label.reset-website": "Reset statistics",
"label.reset-website": "Zresetuj statystyki",
"label.save": "Zapisz",
"label.settings": "Ustawienia",
"label.share-url": "Udostępnij adres URL",
"label.single-day": "W tym dniu",
"label.theme": "Theme",
"label.theme": "Motyw",
"label.this-month": "W tym miesiącu",
"label.this-week": "W tym tygodniu",
"label.this-year": "W tym roku",
@ -64,7 +64,7 @@
"label.websites": "Witryny",
"message.active-users": "{x} aktualnie {x, plural, one {odwiedzający} other {odwiedzających}}",
"message.confirm-delete": "Czy na pewno chcesz usunąć {target}?",
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.confirm-reset": "Czy na pewno chcesz zresetować statystyki {target}?",
"message.copied": "Skopiowano!",
"message.delete-warning": "Wszystkie powiązane dane również zostaną usunięte.",
"message.failure": "Coś poszło nie tak.",
@ -78,10 +78,10 @@
"message.no-websites-configured": "Nie masz skonfigurowanych żadnych witryn internetowych.",
"message.page-not-found": "Strona nie znaleziona.",
"message.powered-by": "Obsługiwane przez {name}",
"message.reset-warning": "All statistics for this website will be deleted, but your tracking code will remain intact.",
"message.reset-warning": "Wszystkie statystyki tej witryny zostaną usunięte, ale kod śledzenia pozostanie nienaruszony.",
"message.save-success": "Zapisano pomyślnie.",
"message.share-url": "To jest publicznie udostępniany adres URL dla {target}.",
"message.toggle-charts": "Toggle charts",
"message.toggle-charts": "Przełącz wykresy",
"message.track-stats": "Aby śledzić statystyki dla {target}, umieść poniższy kod w sekcji {head} swojej witryny.",
"message.type-delete": "Wpisz {delete} w polu poniżej, aby potwierdzić.",
"message.type-reset": "Wpisz {reset} w polu poniżej, aby potwierdzić.",
@ -99,7 +99,7 @@
"metrics.filter.combined": "Połączone",
"metrics.filter.domain-only": "Tylko domena",
"metrics.filter.raw": "Surowe dane",
"metrics.languages": "Languages",
"metrics.languages": "Języki",
"metrics.operating-systems": "System operacyjny",
"metrics.page-views": "Wyświetlenia strony",
"metrics.pages": "Strony",

View File

@ -5,7 +5,7 @@
"label.administrator": "管理员",
"label.all": "所有",
"label.all-events": "所有事件",
"label.all-time": "所有时间",
"label.all-time": "所有时间",
"label.all-websites": "全部网站",
"label.back": "返回",
"label.cancel": "取消",
@ -28,7 +28,7 @@
"label.enable-share-url": "启用共享链接",
"label.invalid": "输入无效",
"label.invalid-domain": "无效域名",
"label.language": "Language",
"label.language": "语言",
"label.last-days": "最近 {x} 天",
"label.last-hours": "最近 {x} 小时",
"label.logged-in-as": "登录名: {username}",
@ -64,7 +64,7 @@
"label.websites": "网站",
"message.active-users": "当前在线 {x} 人",
"message.confirm-delete": "你确定要删除 {target} 吗?",
"message.confirm-reset": "Are your sure you want to reset {target}'s statistics?",
"message.confirm-reset": "您确定要重置 {target} 的数据吗?",
"message.copied": "复制成功!",
"message.delete-warning": "所有相关数据将会被删除。",
"message.failure": "出现错误。",
@ -99,7 +99,7 @@
"metrics.filter.combined": "总和",
"metrics.filter.domain-only": "只看域名",
"metrics.filter.raw": "原始",
"metrics.languages": "Languages",
"metrics.languages": "语言",
"metrics.operating-systems": "操作系统",
"metrics.page-views": "页面浏览量",
"metrics.pages": "网页",

View File

@ -1,5 +1,5 @@
import { BROWSERS } from './constants';
import { removeTrailingSlash, removeWWW } from './url';
import { removeTrailingSlash, removeWWW, getDomainName } from './url';
export const urlFilter = (data, { raw }) => {
const isValidUrl = url => {
@ -46,11 +46,14 @@ export const urlFilter = (data, { raw }) => {
};
export const refFilter = (data, { domain, domainOnly, raw }) => {
const regex = new RegExp(`http[s]?://([a-z0-9-]+\\.)*${domain}`);
const domainName = getDomainName(domain);
const regex = new RegExp(`http[s]?://([a-z0-9-]+\\.)*${domainName}`);
const links = {};
const isValidRef = ref => {
return ref !== '' && ref !== null && !ref.startsWith('/') && !ref.startsWith('#');
const isValidRef = referrer => {
return (
referrer !== '' && referrer !== null && !referrer.startsWith('/') && !referrer.startsWith('#')
);
};
const cleanUrl = url => {
@ -71,8 +74,8 @@ export const refFilter = (data, { domain, domainOnly, raw }) => {
if (protocol.startsWith('http')) {
const path = removeTrailingSlash(pathname);
const ref = searchParams.get('ref');
const query = ref ? `?ref=${ref}` : '';
const referrer = searchParams.get('referrer');
const query = referrer ? `?referrer=${referrer}` : '';
return removeTrailingSlash(`${removeWWW(hostname)}${path}`) + query;
}

View File

@ -335,7 +335,7 @@ export async function getEvents(websites, start_at) {
export function getWebsiteStats(website_id, start_at, end_at, filters = {}) {
const params = [website_id, start_at, end_at];
const { url, ref } = filters;
const { url, referrer } = filters;
let urlFilter = '';
let refFilter = '';
@ -344,9 +344,9 @@ export function getWebsiteStats(website_id, start_at, end_at, filters = {}) {
params.push(decodeURIComponent(url));
}
if (ref) {
if (referrer) {
refFilter = `and referrer like $${params.length + 1}`;
params.push(`%${decodeURIComponent(ref)}%`);
params.push(`%${decodeURIComponent(referrer)}%`);
}
return rawQuery(
@ -382,7 +382,7 @@ export function getPageviewStats(
filters = {},
) {
const params = [website_id, start_at, end_at];
const { url, ref } = filters;
const { url, referrer } = filters;
let urlFilter = '';
let refFilter = '';
@ -392,9 +392,9 @@ export function getPageviewStats(
params.push(decodeURIComponent(url));
}
if (ref) {
if (referrer) {
refFilter = `and referrer like $${params.length + 1}`;
params.push(`%${decodeURIComponent(ref)}%`);
params.push(`%${decodeURIComponent(referrer)}%`);
}
return rawQuery(
@ -444,10 +444,11 @@ export function getSessionMetrics(website_id, start_at, end_at, field, filters =
export function getPageviewMetrics(website_id, start_at, end_at, field, table, filters = {}) {
const params = [website_id, start_at, end_at];
const { domain, url } = filters;
const { domain, url, referrer } = filters;
let domainFilter = '';
let urlFilter = '';
let refFilter = '';
if (domain) {
domainFilter = `and referrer not like $${params.length + 1} and referrer not like '/%'`;
@ -459,6 +460,11 @@ export function getPageviewMetrics(website_id, start_at, end_at, field, table, f
params.push(decodeURIComponent(url));
}
if (referrer) {
refFilter = `and referrer like $${params.length + 1}`;
params.push(`%${decodeURIComponent(referrer)}%`);
}
return rawQuery(
`
select ${field} x, count(*) y
@ -467,6 +473,7 @@ export function getPageviewMetrics(website_id, start_at, end_at, field, table, f
and created_at between $2 and $3
${domainFilter}
${urlFilter}
${refFilter}
group by 1
order by 2 desc
`,

View File

@ -6,7 +6,9 @@ export function json(res, data = {}) {
return res.status(200).json(data);
}
export function send(res, data) {
export function send(res, data, type = 'text/plain') {
res.setHeader('Content-Type', type);
return res.status(200).send(data);
}

View File

@ -9,7 +9,8 @@ export async function getSession(req) {
throw new Error('Invalid request');
}
const { website: website_uuid, hostname, screen, language, cache } = payload;
const { website: website_uuid, hostname, screen, language } = payload;
const cache = req.headers['x-umami-cache'];
if (cache) {
const result = await parseToken(cache);

View File

@ -12,7 +12,8 @@
"scripts": {
"dev": "next dev",
"build": "npm-run-all build-tracker build-geo build-db build-app",
"start": "next start",
"start": "npm-run-all init start-app",
"start-app": "next start",
"start-env": "node -r dotenv/config scripts/start-env.js",
"build-app": "next build",
"build-tracker": "rollup -c rollup.tracker.config.js",
@ -25,19 +26,20 @@
"build-mysql-client": "dotenv prisma generate -- --schema=./prisma/schema.mysql.prisma",
"build-postgresql-schema": "dotenv prisma db pull -- --schema=./prisma/schema.postgresql.prisma",
"build-postgresql-client": "dotenv prisma generate -- --schema=./prisma/schema.postgresql.prisma",
"postbuild": "node scripts/postbuild.js",
"copy-db-schema": "node scripts/copy-db-schema.js",
"generate-lang": "npm-run-all extract-lang merge-lang",
"extract-lang": "formatjs extract \"{pages,components}/**/*.js\" --out-file build/messages.json",
"merge-lang": "node scripts/merge-lang.js",
"format-lang": "node scripts/format-lang.js",
"compile-lang": "formatjs compile-folder --ast build public/messages",
"compile-lang": "formatjs compile-folder --ast build public/intl/messages",
"check-lang": "node scripts/check-lang.js",
"download-country-names": "node scripts/download-country-names.js",
"download-language-names": "node scripts/download-language-names.js",
"change-password": "node scripts/change-password.js",
"lint": "next lint --quiet",
"prepare": "husky install"
"prepare": "husky install",
"postbuild": "node scripts/postbuild.js",
"init": "node scripts/prestart.js"
},
"lint-staged": {
"**/*.js": [
@ -105,10 +107,7 @@
"dotenv-cli": "^4.0.0",
"eslint": "^7.32.0",
"eslint-config-next": "^12.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.2.0",
"extract-react-intl-messages": "^4.1.1",
"husky": "^7.0.0",
"lint-staged": "^11.0.0",

View File

@ -30,7 +30,7 @@ export default async (req, res) => {
return unauthorized(res);
}
const { id, type, start_at, end_at, url } = req.query;
const { id, type, start_at, end_at, url, referrer } = req.query;
const websiteId = +id;
const startDate = new Date(+start_at);
@ -75,6 +75,7 @@ export default async (req, res) => {
{
domain,
url: type !== 'url' && url,
referrer,
},
);

View File

@ -11,7 +11,7 @@ export default async (req, res) => {
return unauthorized(res);
}
const { id, start_at, end_at, unit, tz, url, ref } = req.query;
const { id, start_at, end_at, unit, tz, url, referrer } = req.query;
const websiteId = +id;
const startDate = new Date(+start_at);
@ -22,10 +22,10 @@ export default async (req, res) => {
}
const [pageviews, sessions] = await Promise.all([
getPageviewStats(websiteId, startDate, endDate, tz, unit, '*', { url, ref }),
getPageviewStats(websiteId, startDate, endDate, tz, unit, '*', { url, referrer }),
getPageviewStats(websiteId, startDate, endDate, tz, unit, 'distinct session_id', {
url,
ref,
referrer,
}),
]);

View File

@ -8,7 +8,7 @@ export default async (req, res) => {
return unauthorized(res);
}
const { id, start_at, end_at, url, ref } = req.query;
const { id, start_at, end_at, url, referrer } = req.query;
const websiteId = +id;
const startDate = new Date(+start_at);
@ -18,8 +18,11 @@ export default async (req, res) => {
const prevStartDate = new Date(+start_at - distance);
const prevEndDate = new Date(+end_at - distance);
const metrics = await getWebsiteStats(websiteId, startDate, endDate, { url, ref });
const prevPeriod = await getWebsiteStats(websiteId, prevStartDate, prevEndDate, { url, ref });
const metrics = await getWebsiteStats(websiteId, startDate, endDate, { url, referrer });
const prevPeriod = await getWebsiteStats(websiteId, prevStartDate, prevEndDate, {
url,
referrer,
});
const stats = Object.keys(metrics[0]).reduce((obj, key) => {
obj[key] = {

Some files were not shown because too many files have changed in this diff Show More