mirror of
https://github.com/umami-software/umami.git
synced 2026-02-14 17:45:38 +01:00
Compare commits
4 commits
f9442001e4
...
05f9a67727
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05f9a67727 | ||
|
|
dfaebcd1b7 | ||
|
|
8d5e8b072d | ||
|
|
7800fce2ec |
10 changed files with 325 additions and 295 deletions
20
package.json
20
package.json
|
|
@ -76,13 +76,13 @@
|
||||||
"@dicebear/core": "^9.2.3",
|
"@dicebear/core": "^9.2.3",
|
||||||
"@fontsource/inter": "^4.5.15",
|
"@fontsource/inter": "^4.5.15",
|
||||||
"@hello-pangea/dnd": "^17.0.0",
|
"@hello-pangea/dnd": "^17.0.0",
|
||||||
"@prisma/adapter-pg": "^6.14.0",
|
"@prisma/adapter-pg": "^6.15.0",
|
||||||
"@prisma/client": "^6.14.0",
|
"@prisma/client": "^6.15.0",
|
||||||
"@prisma/extension-read-replicas": "^0.4.1",
|
"@prisma/extension-read-replicas": "^0.4.1",
|
||||||
"@react-spring/web": "^9.7.3",
|
"@react-spring/web": "^9.7.3",
|
||||||
"@svgr/cli": "^8.1.0",
|
"@svgr/cli": "^8.1.0",
|
||||||
"@tanstack/react-query": "^5.85.5",
|
"@tanstack/react-query": "^5.85.5",
|
||||||
"@umami/react-zen": "^0.168.0",
|
"@umami/react-zen": "^0.169.0",
|
||||||
"@umami/redis-client": "^0.27.0",
|
"@umami/redis-client": "^0.27.0",
|
||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"chalk": "^5.6.0",
|
"chalk": "^5.6.0",
|
||||||
|
|
@ -112,12 +112,12 @@
|
||||||
"lucide-react": "^0.540.0",
|
"lucide-react": "^0.540.0",
|
||||||
"maxmind": "^4.3.28",
|
"maxmind": "^4.3.28",
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"next": "15.5.0",
|
"next": "15.5.2",
|
||||||
"node-fetch": "^3.2.8",
|
"node-fetch": "^3.2.8",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"papaparse": "^5.5.3",
|
"papaparse": "^5.5.3",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"prisma": "6.14.0",
|
"prisma": "6.15.0",
|
||||||
"pure-rand": "^7.0.1",
|
"pure-rand": "^7.0.1",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
|
@ -144,12 +144,12 @@
|
||||||
"@rollup/plugin-replace": "^5.0.2",
|
"@rollup/plugin-replace": "^5.0.2",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/node": "^22.16.5",
|
"@types/node": "^22.18.0",
|
||||||
"@types/react": "^19.1.11",
|
"@types/react": "^19.1.12",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.8",
|
||||||
"@types/react-window": "^1.8.8",
|
"@types/react-window": "^1.8.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.40.0",
|
"@typescript-eslint/eslint-plugin": "^8.41.0",
|
||||||
"@typescript-eslint/parser": "^8.40.0",
|
"@typescript-eslint/parser": "^8.41.0",
|
||||||
"babel-plugin-react-compiler": "19.1.0-rc.2",
|
"babel-plugin-react-compiler": "19.1.0-rc.2",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cypress": "^13.6.6",
|
"cypress": "^13.6.6",
|
||||||
|
|
|
||||||
539
pnpm-lock.yaml
generated
539
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -15,7 +15,10 @@ export function CohortAddButton({ websiteId }: { websiteId: string }) {
|
||||||
<Text>{formatMessage(labels.cohort)}</Text>
|
<Text>{formatMessage(labels.cohort)}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
<Modal>
|
<Modal>
|
||||||
<Dialog title={formatMessage(labels.cohort)} style={{ width: 800, minHeight: 300 }}>
|
<Dialog
|
||||||
|
title={formatMessage(labels.cohort)}
|
||||||
|
style={{ width: 800, minHeight: 300, maxHeight: '90vh' }}
|
||||||
|
>
|
||||||
{({ close }) => {
|
{({ close }) => {
|
||||||
return <CohortEditForm websiteId={websiteId} onClose={close} />;
|
return <CohortEditForm websiteId={websiteId} onClose={close} />;
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { Edit } from '@/components/icons';
|
||||||
import { Dialog } from '@umami/react-zen';
|
import { Dialog } from '@umami/react-zen';
|
||||||
import { CohortEditForm } from '@/app/(main)/websites/[websiteId]/cohorts/CohortEditForm';
|
import { CohortEditForm } from '@/app/(main)/websites/[websiteId]/cohorts/CohortEditForm';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
|
import { Filter } from '@/lib/types';
|
||||||
|
|
||||||
export function CohortEditButton({
|
export function CohortEditButton({
|
||||||
cohortId,
|
cohortId,
|
||||||
|
|
@ -11,13 +12,16 @@ export function CohortEditButton({
|
||||||
}: {
|
}: {
|
||||||
cohortId: string;
|
cohortId: string;
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
filters: any[];
|
filters: Filter[];
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionButton title={formatMessage(labels.edit)} icon={<Edit />}>
|
<ActionButton title={formatMessage(labels.edit)} icon={<Edit />}>
|
||||||
<Dialog title={formatMessage(labels.cohort)} style={{ width: 800, minHeight: 300 }}>
|
<Dialog
|
||||||
|
title={formatMessage(labels.cohort)}
|
||||||
|
style={{ width: 800, minHeight: 300, maxHeight: '90vh' }}
|
||||||
|
>
|
||||||
{({ close }) => {
|
{({ close }) => {
|
||||||
return (
|
return (
|
||||||
<CohortEditForm
|
<CohortEditForm
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,10 @@ export function SegmentAddButton({ websiteId }: { websiteId: string }) {
|
||||||
<Text>{formatMessage(labels.segment)}</Text>
|
<Text>{formatMessage(labels.segment)}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
<Modal>
|
<Modal>
|
||||||
<Dialog title={formatMessage(labels.segment)} style={{ width: 800, minHeight: 300 }}>
|
<Dialog
|
||||||
|
title={formatMessage(labels.segment)}
|
||||||
|
style={{ width: 800, minHeight: 300, maxHeight: '90vh' }}
|
||||||
|
>
|
||||||
{({ close }) => {
|
{({ close }) => {
|
||||||
return <SegmentEditForm websiteId={websiteId} onClose={close} />;
|
return <SegmentEditForm websiteId={websiteId} onClose={close} />;
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { Edit } from '@/components/icons';
|
||||||
import { Dialog } from '@umami/react-zen';
|
import { Dialog } from '@umami/react-zen';
|
||||||
import { SegmentEditForm } from '@/app/(main)/websites/[websiteId]/segments/SegmentEditForm';
|
import { SegmentEditForm } from '@/app/(main)/websites/[websiteId]/segments/SegmentEditForm';
|
||||||
import { useMessages } from '@/components/hooks';
|
import { useMessages } from '@/components/hooks';
|
||||||
|
import { Filter } from '@/lib/types';
|
||||||
|
|
||||||
export function SegmentEditButton({
|
export function SegmentEditButton({
|
||||||
segmentId,
|
segmentId,
|
||||||
|
|
@ -11,13 +12,16 @@ export function SegmentEditButton({
|
||||||
}: {
|
}: {
|
||||||
segmentId: string;
|
segmentId: string;
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
filters?: any[];
|
filters?: Filter[];
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionButton title={formatMessage(labels.edit)} icon={<Edit />}>
|
<ActionButton title={formatMessage(labels.edit)} icon={<Edit />}>
|
||||||
<Dialog title={formatMessage(labels.segment)} style={{ width: 800, minHeight: 300 }}>
|
<Dialog
|
||||||
|
title={formatMessage(labels.segment)}
|
||||||
|
style={{ width: 800, minHeight: 300, maxHeight: '90vh' }}
|
||||||
|
>
|
||||||
{({ close }) => {
|
{({ close }) => {
|
||||||
return (
|
return (
|
||||||
<SegmentEditForm
|
<SegmentEditForm
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import { useNavigation } from './useNavigation';
|
||||||
|
|
||||||
export interface UseDateRangeOptions {
|
export interface UseDateRangeOptions {
|
||||||
ignoreOffset?: boolean;
|
ignoreOffset?: boolean;
|
||||||
useQueryParameter?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDateRange(websiteId?: string, options: UseDateRangeOptions = {}) {
|
export function useDateRange(websiteId?: string, options: UseDateRangeOptions = {}) {
|
||||||
|
|
@ -21,11 +20,7 @@ export function useDateRange(websiteId?: string, options: UseDateRangeOptions =
|
||||||
} = useNavigation();
|
} = useNavigation();
|
||||||
const websiteConfig = useWebsites(state => state[websiteId]?.dateRange);
|
const websiteConfig = useWebsites(state => state[websiteId]?.dateRange);
|
||||||
const globalConfig = useApp(state => state.dateRangeValue);
|
const globalConfig = useApp(state => state.dateRangeValue);
|
||||||
const dateValue =
|
const dateValue = websiteConfig?.value || date || globalConfig || DEFAULT_DATE_RANGE_VALUE;
|
||||||
(options.useQueryParameter ? date : false) ||
|
|
||||||
websiteConfig?.value ||
|
|
||||||
globalConfig ||
|
|
||||||
DEFAULT_DATE_RANGE_VALUE;
|
|
||||||
|
|
||||||
const dateRange = useMemo(() => {
|
const dateRange = useMemo(() => {
|
||||||
const dateRangeObject = parseDateRange(dateValue, locale);
|
const dateRangeObject = parseDateRange(dateValue, locale);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Select, SelectProps, ListItem, Text } from '@umami/react-zen';
|
import { Select, SelectProps, ListItem, Text } from '@umami/react-zen';
|
||||||
import { useUserWebsitesQuery, useWebsiteQuery, useNavigation } from '@/components/hooks';
|
import {
|
||||||
|
useUserWebsitesQuery,
|
||||||
|
useWebsiteQuery,
|
||||||
|
useNavigation,
|
||||||
|
useMessages,
|
||||||
|
} from '@/components/hooks';
|
||||||
|
import { Empty } from '@/components/common/Empty';
|
||||||
|
|
||||||
export function WebsiteSelect({
|
export function WebsiteSelect({
|
||||||
websiteId,
|
websiteId,
|
||||||
|
|
@ -10,6 +16,7 @@ export function WebsiteSelect({
|
||||||
websiteId?: string;
|
websiteId?: string;
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
} & SelectProps) {
|
} & SelectProps) {
|
||||||
|
const { formatMessage, messages } = useMessages();
|
||||||
const { router, renderUrl } = useNavigation();
|
const { router, renderUrl } = useNavigation();
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const { data: website } = useWebsiteQuery(websiteId);
|
const { data: website } = useWebsiteQuery(websiteId);
|
||||||
|
|
@ -23,6 +30,10 @@ export function WebsiteSelect({
|
||||||
setSearch(value);
|
setSearch(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenChange = () => {
|
||||||
|
setSearch('');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
{...props}
|
{...props}
|
||||||
|
|
@ -35,6 +46,10 @@ export function WebsiteSelect({
|
||||||
searchValue={search}
|
searchValue={search}
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
onChange={handleSelect}
|
onChange={handleSelect}
|
||||||
|
onOpenChange={handleOpenChange}
|
||||||
|
listProps={{
|
||||||
|
renderEmptyState: () => <Empty message={formatMessage(messages.noResultsFound)} />,
|
||||||
|
}}
|
||||||
renderValue={() => (
|
renderValue={() => (
|
||||||
<Text truncate weight="bold" style={{ maxWidth: 160, lineHeight: 1 }}>
|
<Text truncate weight="bold" style={{ maxWidth: 160, lineHeight: 1 }}>
|
||||||
{website?.name}
|
{website?.name}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { PrismaClient } from '@/generated/prisma/client';
|
||||||
import { PrismaPg } from '@prisma/adapter-pg';
|
import { PrismaPg } from '@prisma/adapter-pg';
|
||||||
import { readReplicas } from '@prisma/extension-read-replicas';
|
import { readReplicas } from '@prisma/extension-read-replicas';
|
||||||
import { SESSION_COLUMNS, OPERATORS, DEFAULT_PAGE_SIZE, FILTER_COLUMNS } from './constants';
|
import { SESSION_COLUMNS, OPERATORS, DEFAULT_PAGE_SIZE, FILTER_COLUMNS } from './constants';
|
||||||
import { QueryOptions, QueryFilters } from './types';
|
import { QueryOptions, QueryFilters, Operator } from './types';
|
||||||
import { filtersObjectToArray } from './params';
|
import { filtersObjectToArray } from './params';
|
||||||
|
|
||||||
const log = debug('umami:prisma');
|
const log = debug('umami:prisma');
|
||||||
|
|
@ -143,7 +143,7 @@ function getQueryParams(filters: Record<string, any>) {
|
||||||
return {
|
return {
|
||||||
...filters,
|
...filters,
|
||||||
...filtersObjectToArray(filters).reduce((obj, { name, operator, value }) => {
|
...filtersObjectToArray(filters).reduce((obj, { name, operator, value }) => {
|
||||||
obj[name] = [OPERATORS.contains, OPERATORS.doesNotContain].includes(operator)
|
obj[name] = ([OPERATORS.contains, OPERATORS.doesNotContain] as Operator[]).includes(operator)
|
||||||
? `%${value}%`
|
? `%${value}%`
|
||||||
: value;
|
: value;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { UseQueryOptions } from '@tanstack/react-query';
|
import { UseQueryOptions } from '@tanstack/react-query';
|
||||||
import { DATA_TYPE, PERMISSIONS, ROLES } from './constants';
|
import { DATA_TYPE, PERMISSIONS, ROLES, OPERATORS } from './constants';
|
||||||
import { TIME_UNIT } from './date';
|
import { TIME_UNIT } from './date';
|
||||||
|
|
||||||
export type ObjectValues<T> = T[keyof T];
|
export type ObjectValues<T> = T[keyof T];
|
||||||
|
|
@ -10,6 +10,7 @@ export type TimeUnit = ObjectValues<typeof TIME_UNIT>;
|
||||||
export type Permission = ObjectValues<typeof PERMISSIONS>;
|
export type Permission = ObjectValues<typeof PERMISSIONS>;
|
||||||
export type Role = ObjectValues<typeof ROLES>;
|
export type Role = ObjectValues<typeof ROLES>;
|
||||||
export type DynamicDataType = ObjectValues<typeof DATA_TYPE>;
|
export type DynamicDataType = ObjectValues<typeof DATA_TYPE>;
|
||||||
|
export type Operator = (typeof OPERATORS)[keyof typeof OPERATORS];
|
||||||
|
|
||||||
export interface Auth {
|
export interface Auth {
|
||||||
user?: {
|
user?: {
|
||||||
|
|
@ -26,10 +27,10 @@ export interface Auth {
|
||||||
|
|
||||||
export interface Filter {
|
export interface Filter {
|
||||||
name: string;
|
name: string;
|
||||||
operator: string;
|
operator: Operator;
|
||||||
value: string;
|
value: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
columns?: string;
|
column?: string;
|
||||||
prefix?: string;
|
prefix?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue