diff --git a/db/clickhouse/migrations/04_add_tag.sql b/db/clickhouse/migrations/04_add_tag.sql index cfe0de20..7ffc4995 100644 --- a/db/clickhouse/migrations/04_add_tag.sql +++ b/db/clickhouse/migrations/04_add_tag.sql @@ -1,6 +1,6 @@ -- add tag column ALTER TABLE umami.website_event ADD COLUMN "tag" String AFTER "event_name"; -ALTER TABLE umami.website_event_stats_hourly ADD COLUMN "tag" String AFTER "max_time"; +ALTER TABLE umami.website_event_stats_hourly ADD COLUMN "tag" SimpleAggregateFunction(groupArrayArray, Array(String)) AFTER "max_time"; -- update materialized view DROP TABLE umami.website_event_stats_hourly_mv; @@ -58,7 +58,7 @@ FROM (SELECT sumIf(1, event_type = 1) views, min(created_at) min_time, max(created_at) max_time, - tag, + arrayFilter(x -> x != '', groupArray(tag)) tag, toStartOfHour(created_at) timestamp FROM umami.website_event GROUP BY website_id, @@ -74,5 +74,4 @@ GROUP BY website_id, subdivision1, city, event_type, - tag, timestamp); \ No newline at end of file diff --git a/src/app/(main)/websites/[websiteId]/WebsiteExpandedView.tsx b/src/app/(main)/websites/[websiteId]/WebsiteExpandedView.tsx index 86a7717f..95e718b4 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteExpandedView.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteExpandedView.tsx @@ -1,20 +1,21 @@ -import { Icons, Icon, Text, Dropdown, Item } from 'react-basics'; +import LinkButton from 'components/common/LinkButton'; +import { useLocale, useMessages, useNavigation } from 'components/hooks'; +import SideNav from 'components/layout/SideNav'; import BrowsersTable from 'components/metrics/BrowsersTable'; -import CountriesTable from 'components/metrics/CountriesTable'; -import RegionsTable from 'components/metrics/RegionsTable'; import CitiesTable from 'components/metrics/CitiesTable'; +import CountriesTable from 'components/metrics/CountriesTable'; import DevicesTable from 'components/metrics/DevicesTable'; +import EventsTable from 'components/metrics/EventsTable'; +import HostsTable from 'components/metrics/HostsTable'; import LanguagesTable from 'components/metrics/LanguagesTable'; import OSTable from 'components/metrics/OSTable'; import PagesTable from 'components/metrics/PagesTable'; import QueryParametersTable from 'components/metrics/QueryParametersTable'; import ReferrersTable from 'components/metrics/ReferrersTable'; -import HostsTable from 'components/metrics/HostsTable'; +import RegionsTable from 'components/metrics/RegionsTable'; import ScreenTable from 'components/metrics/ScreenTable'; -import EventsTable from 'components/metrics/EventsTable'; -import SideNav from 'components/layout/SideNav'; -import { useNavigation, useMessages, useLocale } from 'components/hooks'; -import LinkButton from 'components/common/LinkButton'; +import TagsTable from 'components/metrics/TagsTable'; +import { Dropdown, Icon, Icons, Item, Text } from 'react-basics'; import styles from './WebsiteExpandedView.module.css'; const views = { @@ -34,6 +35,7 @@ const views = { language: LanguagesTable, event: EventsTable, query: QueryParametersTable, + tag: TagsTable, }; export default function WebsiteExpandedView({ @@ -117,6 +119,11 @@ export default function WebsiteExpandedView({ label: formatMessage(labels.hosts), url: renderUrl({ view: 'host' }), }, + { + key: 'tag', + label: formatMessage(labels.tags), + url: renderUrl({ view: 'tag' }), + }, ]; const DetailsComponent = views[view] || (() => null); diff --git a/src/app/(main)/websites/[websiteId]/compare/WebsiteCompareTables.tsx b/src/app/(main)/websites/[websiteId]/compare/WebsiteCompareTables.tsx index 1b21103d..af5a06d4 100644 --- a/src/app/(main)/websites/[websiteId]/compare/WebsiteCompareTables.tsx +++ b/src/app/(main)/websites/[websiteId]/compare/WebsiteCompareTables.tsx @@ -1,24 +1,25 @@ -import { useState } from 'react'; -import SideNav from 'components/layout/SideNav'; import { useDateRange, useMessages, useNavigation } from 'components/hooks'; -import PagesTable from 'components/metrics/PagesTable'; -import ReferrersTable from 'components/metrics/ReferrersTable'; -import BrowsersTable from 'components/metrics/BrowsersTable'; -import OSTable from 'components/metrics/OSTable'; -import DevicesTable from 'components/metrics/DevicesTable'; -import ScreenTable from 'components/metrics/ScreenTable'; -import CountriesTable from 'components/metrics/CountriesTable'; -import RegionsTable from 'components/metrics/RegionsTable'; -import CitiesTable from 'components/metrics/CitiesTable'; -import LanguagesTable from 'components/metrics/LanguagesTable'; -import EventsTable from 'components/metrics/EventsTable'; -import QueryParametersTable from 'components/metrics/QueryParametersTable'; import { Grid, GridRow } from 'components/layout/Grid'; +import SideNav from 'components/layout/SideNav'; +import BrowsersTable from 'components/metrics/BrowsersTable'; +import ChangeLabel from 'components/metrics/ChangeLabel'; +import CitiesTable from 'components/metrics/CitiesTable'; +import CountriesTable from 'components/metrics/CountriesTable'; +import DevicesTable from 'components/metrics/DevicesTable'; +import EventsTable from 'components/metrics/EventsTable'; +import LanguagesTable from 'components/metrics/LanguagesTable'; import MetricsTable from 'components/metrics/MetricsTable'; -import useStore from 'store/websites'; +import OSTable from 'components/metrics/OSTable'; +import PagesTable from 'components/metrics/PagesTable'; +import QueryParametersTable from 'components/metrics/QueryParametersTable'; +import ReferrersTable from 'components/metrics/ReferrersTable'; +import RegionsTable from 'components/metrics/RegionsTable'; +import ScreenTable from 'components/metrics/ScreenTable'; +import TagsTable from 'components/metrics/TagsTable'; import { getCompareDate } from 'lib/date'; import { formatNumber } from 'lib/format'; -import ChangeLabel from 'components/metrics/ChangeLabel'; +import { useState } from 'react'; +import useStore from 'store/websites'; import styles from './WebsiteCompareTables.module.css'; const views = { @@ -35,6 +36,7 @@ const views = { language: LanguagesTable, event: EventsTable, query: QueryParametersTable, + tag: TagsTable, }; export function WebsiteCompareTables({ websiteId }: { websiteId: string }) { @@ -109,6 +111,16 @@ export function WebsiteCompareTables({ websiteId }: { websiteId: string }) { label: formatMessage(labels.queryParameters), url: renderUrl({ view: 'query' }), }, + { + key: 'host', + label: formatMessage(labels.hosts), + url: renderUrl({ view: 'host' }), + }, + { + key: 'tag', + label: formatMessage(labels.tags), + url: renderUrl({ view: 'tag' }), + }, ]; const renderChange = ({ x, y }) => { diff --git a/src/components/hooks/useFields.ts b/src/components/hooks/useFields.ts index e6fc54b3..859ca1ce 100644 --- a/src/components/hooks/useFields.ts +++ b/src/components/hooks/useFields.ts @@ -15,6 +15,7 @@ export function useFields() { { name: 'region', type: 'string', label: formatMessage(labels.region) }, { name: 'city', type: 'string', label: formatMessage(labels.city) }, { name: 'host', type: 'string', label: formatMessage(labels.host) }, + { name: 'tag', type: 'string', label: formatMessage(labels.tag) }, ]; return { fields }; diff --git a/src/components/hooks/useFilterParams.ts b/src/components/hooks/useFilterParams.ts index 525f3492..55deed14 100644 --- a/src/components/hooks/useFilterParams.ts +++ b/src/components/hooks/useFilterParams.ts @@ -7,7 +7,21 @@ export function useFilterParams(websiteId: string) { const { startDate, endDate, unit } = dateRange; const { timezone, toUtc } = useTimezone(); const { - query: { url, referrer, title, query, host, os, browser, device, country, region, city, event }, + query: { + url, + referrer, + title, + query, + host, + os, + browser, + device, + country, + region, + city, + event, + tag, + }, } = useNavigation(); return { @@ -27,5 +41,6 @@ export function useFilterParams(websiteId: string) { region, city, event, + tag, }; } diff --git a/src/components/messages.ts b/src/components/messages.ts index 1d2cc9b2..688dd11d 100644 --- a/src/components/messages.ts +++ b/src/components/messages.ts @@ -98,6 +98,7 @@ export const labels = defineMessages({ devices: { id: 'label.devices', defaultMessage: 'Devices' }, countries: { id: 'label.countries', defaultMessage: 'Countries' }, languages: { id: 'label.languages', defaultMessage: 'Languages' }, + tags: { id: 'label.tags', defaultMessage: 'Tags' }, count: { id: 'label.count', defaultMessage: 'Count' }, average: { id: 'label.average', defaultMessage: 'Average' }, sum: { id: 'label.sum', defaultMessage: 'Sum' }, @@ -220,6 +221,7 @@ export const labels = defineMessages({ browser: { id: 'label.browser', defaultMessage: 'Browser' }, device: { id: 'label.device', defaultMessage: 'Device' }, pageTitle: { id: 'label.pageTitle', defaultMessage: 'Page title' }, + tag: { id: 'label.tag', defaultMessage: 'Tag' }, day: { id: 'label.day', defaultMessage: 'Day' }, date: { id: 'label.date', defaultMessage: 'Date' }, pageOf: { id: 'label.page-of', defaultMessage: 'Page {current} of {total}' }, diff --git a/src/components/metrics/HostsTable.tsx b/src/components/metrics/HostsTable.tsx index d3a0f3bf..45147eac 100644 --- a/src/components/metrics/HostsTable.tsx +++ b/src/components/metrics/HostsTable.tsx @@ -25,7 +25,7 @@ export function HostsTable(props: MetricsTableProps) { {...props} title={formatMessage(labels.hosts)} type="host" - metric={formatMessage(labels.views)} + metric={formatMessage(labels.visitors)} renderLabel={renderLink} /> diff --git a/src/components/metrics/TagsTable.tsx b/src/components/metrics/TagsTable.tsx new file mode 100644 index 00000000..a1130bb4 --- /dev/null +++ b/src/components/metrics/TagsTable.tsx @@ -0,0 +1,30 @@ +import MetricsTable, { MetricsTableProps } from './MetricsTable'; +import FilterLink from 'components/common/FilterLink'; +import { useMessages } from 'components/hooks'; +import { Flexbox } from 'react-basics'; + +export function TagsTable(props: MetricsTableProps) { + const { formatMessage, labels } = useMessages(); + + const renderLink = ({ x: tag }) => { + return ( + + + + ); + }; + + return ( + <> + + + ); +} + +export default TagsTable; diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 5d3a9776..7f8acf88 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -33,7 +33,7 @@ export const FILTER_REFERRERS = 'filter-referrers'; export const FILTER_PAGES = 'filter-pages'; export const UNIT_TYPES = ['year', 'month', 'hour', 'day', 'minute']; -export const EVENT_COLUMNS = ['url', 'entry', 'exit', 'referrer', 'title', 'query', 'event']; +export const EVENT_COLUMNS = ['url', 'entry', 'exit', 'referrer', 'title', 'query', 'event', 'tag']; export const SESSION_COLUMNS = [ 'browser', @@ -63,6 +63,7 @@ export const FILTER_COLUMNS = { city: 'city', language: 'language', event: 'event_name', + tag: 'tag', }; export const COLLECTION_TYPE = { diff --git a/src/lib/types.ts b/src/lib/types.ts index 7fabbfc8..615882ef 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -179,6 +179,7 @@ export interface QueryFilters { language?: string; event?: string; search?: string; + tag?: string; } export interface QueryOptions { diff --git a/src/pages/api/websites/[websiteId]/events/series.ts b/src/pages/api/websites/[websiteId]/events/series.ts index 08cade12..6d67a264 100644 --- a/src/pages/api/websites/[websiteId]/events/series.ts +++ b/src/pages/api/websites/[websiteId]/events/series.ts @@ -24,6 +24,7 @@ export interface WebsiteEventsRequestQuery { country?: string; region: string; city?: string; + tag?: string; } const schema = { @@ -43,6 +44,7 @@ const schema = { country: yup.string(), region: yup.string(), city: yup.string(), + tag: yup.string(), }), }; diff --git a/src/pages/api/websites/[websiteId]/metrics.ts b/src/pages/api/websites/[websiteId]/metrics.ts index 3dac217b..1996a61a 100644 --- a/src/pages/api/websites/[websiteId]/metrics.ts +++ b/src/pages/api/websites/[websiteId]/metrics.ts @@ -29,6 +29,7 @@ export interface WebsiteMetricsRequestQuery { limit?: number; offset?: number; search?: string; + tag?: string; } const schema = { @@ -53,6 +54,7 @@ const schema = { limit: yup.number(), offset: yup.number(), search: yup.string(), + tag: yup.string(), }), }; diff --git a/src/pages/api/websites/[websiteId]/pageviews.ts b/src/pages/api/websites/[websiteId]/pageviews.ts index badb8a47..c3b6b797 100644 --- a/src/pages/api/websites/[websiteId]/pageviews.ts +++ b/src/pages/api/websites/[websiteId]/pageviews.ts @@ -25,6 +25,7 @@ export interface WebsitePageviewRequestQuery { country?: string; region: string; city?: string; + tag?: string; compare?: string; } @@ -45,6 +46,7 @@ const schema = { country: yup.string(), region: yup.string(), city: yup.string(), + tag: yup.string(), compare: yup.string(), }), }; diff --git a/src/pages/api/websites/[websiteId]/sessions/stats.ts b/src/pages/api/websites/[websiteId]/sessions/stats.ts index a522bd6b..fe92ce6f 100644 --- a/src/pages/api/websites/[websiteId]/sessions/stats.ts +++ b/src/pages/api/websites/[websiteId]/sessions/stats.ts @@ -23,6 +23,7 @@ export interface WebsiteSessionStatsRequestQuery { country?: string; region?: string; city?: string; + tag?: string; } const schema = { @@ -42,6 +43,7 @@ const schema = { country: yup.string(), region: yup.string(), city: yup.string(), + tag: yup.string(), }), }; diff --git a/src/pages/api/websites/[websiteId]/stats.ts b/src/pages/api/websites/[websiteId]/stats.ts index 9ca84c74..dfc9198d 100644 --- a/src/pages/api/websites/[websiteId]/stats.ts +++ b/src/pages/api/websites/[websiteId]/stats.ts @@ -24,6 +24,7 @@ export interface WebsiteStatsRequestQuery { country?: string; region?: string; city?: string; + tag?: string; compare?: string; } @@ -44,6 +45,7 @@ const schema = { country: yup.string(), region: yup.string(), city: yup.string(), + tag: yup.string(), compare: yup.string(), }), };