diff --git a/apps/start/src/components/grafana-grid.tsx b/apps/start/src/components/grafana-grid.tsx new file mode 100644 index 00000000..4b4232cc --- /dev/null +++ b/apps/start/src/components/grafana-grid.tsx @@ -0,0 +1,95 @@ +import type { IServiceReport } from '@openpanel/db'; +import { useMemo } from 'react'; +import { Responsive, WidthProvider } from 'react-grid-layout'; + +const ResponsiveGridLayout = WidthProvider(Responsive); + +export type Layout = ReactGridLayout.Layout; + +export const useReportLayouts = ( + reports: NonNullable[], +): ReactGridLayout.Layouts => { + return useMemo(() => { + const baseLayout = reports.map((report, index) => ({ + i: report.id, + x: report.layout?.x ?? (index % 2) * 6, + y: report.layout?.y ?? Math.floor(index / 2) * 4, + w: report.layout?.w ?? 6, + h: report.layout?.h ?? 4, + minW: 3, + minH: 3, + })); + + return { + lg: baseLayout, + md: baseLayout, + sm: baseLayout.map((item) => ({ ...item, w: Math.min(item.w, 6) })), + xs: baseLayout.map((item) => ({ ...item, w: 4, x: 0 })), + xxs: baseLayout.map((item) => ({ ...item, w: 2, x: 0 })), + }; + }, [reports]); +}; + +export function GrafanaGrid({ + layouts, + children, + transitions, + onLayoutChange, + onDragStop, + onResizeStop, + isDraggable, + isResizable, +}: { + children: React.ReactNode; + transitions?: boolean; +} & Pick< + ReactGridLayout.ResponsiveProps, + | 'layouts' + | 'onLayoutChange' + | 'onDragStop' + | 'onResizeStop' + | 'isDraggable' + | 'isResizable' +>) { + return ( + <> + +
+ + {children} + +
+ + ); +} diff --git a/apps/start/src/components/login-navbar.tsx b/apps/start/src/components/login-navbar.tsx index e8cb4770..1c703abb 100644 --- a/apps/start/src/components/login-navbar.tsx +++ b/apps/start/src/components/login-navbar.tsx @@ -33,7 +33,7 @@ export function LoginNavbar({ className }: { className?: string }) {
  • - + Posthog alternative
  • diff --git a/apps/start/src/components/overview/overview-top-geo.tsx b/apps/start/src/components/overview/overview-top-geo.tsx index 1ce23552..ab8a8db5 100644 --- a/apps/start/src/components/overview/overview-top-geo.tsx +++ b/apps/start/src/components/overview/overview-top-geo.tsx @@ -211,7 +211,6 @@ export default function OverviewTopGeo({ projectId }: OverviewTopGeoProps) { void; }[]; }>; - report: IChartProps & { id?: string }; + report: IChartInput & { id?: string }; isLazyLoading: boolean; isEditMode: boolean; shareId?: string; - shareType?: 'dashboard' | 'report'; + reportId?: string; }; type ReportChartContextProviderProps = ReportChartContextType & { @@ -42,8 +41,6 @@ type ReportChartContextProviderProps = ReportChartContextType & { export type ReportChartProps = Partial & { report: IChartInput; lazy?: boolean; - shareId?: string; - shareType?: 'dashboard' | 'report'; }; const context = createContext(null); @@ -58,20 +55,6 @@ export const useReportChartContext = () => { return ctx; }; -export const useSelectReportChartContext = ( - selector: (ctx: ReportChartContextType) => T, -) => { - const ctx = useReportChartContext(); - const [state, setState] = useState(selector(ctx)); - useEffect(() => { - const newState = selector(ctx); - if (!isEqual(newState, state)) { - setState(newState); - } - }, [ctx]); - return state; -}; - export const ReportChartProvider = ({ children, ...propsToContext diff --git a/apps/start/src/components/report-chart/conversion/index.tsx b/apps/start/src/components/report-chart/conversion/index.tsx index fdf94a2d..a594b5cb 100644 --- a/apps/start/src/components/report-chart/conversion/index.tsx +++ b/apps/start/src/components/report-chart/conversion/index.tsx @@ -12,33 +12,27 @@ import { Chart } from './chart'; import { Summary } from './summary'; export function ReportConversionChart() { - const { isLazyLoading, report, shareId, shareType } = useReportChartContext(); + const { isLazyLoading, report, shareId } = useReportChartContext(); const trpc = useTRPC(); const { range, startDate, endDate, interval } = useOverviewOptions(); console.log(report.limit); const res = useQuery( - shareId && shareType && 'id' in report && report.id - ? trpc.chart.conversionByReport.queryOptions( - { - reportId: report.id, - shareId, - shareType, - range: range ?? undefined, - startDate: startDate ?? undefined, - endDate: endDate ?? undefined, - interval: interval ?? undefined, - }, - { - placeholderData: keepPreviousData, - staleTime: 1000 * 60 * 1, - enabled: !isLazyLoading, - }, - ) - : trpc.chart.conversion.queryOptions(report, { - placeholderData: keepPreviousData, - staleTime: 1000 * 60 * 1, - enabled: !isLazyLoading, - }), + trpc.chart.conversion.queryOptions( + { + ...report, + shareId, + reportId: 'id' in report ? report.id : undefined, + range: range ?? report.range, + startDate: startDate ?? report.startDate, + endDate: endDate ?? report.endDate, + interval: interval ?? report.interval, + }, + { + placeholderData: keepPreviousData, + staleTime: 1000 * 60 * 1, + enabled: !isLazyLoading, + }, + ), ); if ( diff --git a/apps/start/src/components/report-chart/funnel/index.tsx b/apps/start/src/components/report-chart/funnel/index.tsx index e7633239..5b519085 100644 --- a/apps/start/src/components/report-chart/funnel/index.tsx +++ b/apps/start/src/components/report-chart/funnel/index.tsx @@ -25,50 +25,35 @@ export function ReportFunnelChart() { endDate, previous, breakdowns, + interval, }, isLazyLoading, shareId, - shareType, } = useReportChartContext(); const { range: overviewRange, startDate: overviewStartDate, endDate: overviewEndDate, interval: overviewInterval } = useOverviewOptions(); const trpc = useTRPC(); + const input: IChartInput = { + series, + range: overviewRange ?? range, + projectId, + interval: overviewInterval ?? interval ?? 'day', + chartType: 'funnel', + breakdowns, + funnelWindow, + funnelGroup, + previous, + metric: 'sum', + startDate: overviewStartDate ?? startDate, + endDate: overviewEndDate ?? endDate, + limit: 20, + shareId, + reportId: id, + }; const res = useQuery( - shareId && shareType && id - ? trpc.chart.funnelByReport.queryOptions( - { - reportId: id, - shareId, - shareType, - range: overviewRange ?? undefined, - startDate: overviewStartDate ?? undefined, - endDate: overviewEndDate ?? undefined, - interval: overviewInterval ?? undefined, - }, - { - enabled: !isLazyLoading && series.length > 0, - }, - ) - : (() => { - const input: IChartInput = { - series, - range, - projectId, - interval: 'day', - chartType: 'funnel', - breakdowns, - funnelWindow, - funnelGroup, - previous, - metric: 'sum', - startDate, - endDate, - limit: 20, - }; - return trpc.chart.funnel.queryOptions(input, { - enabled: !isLazyLoading && input.series.length > 0, - }); - })(), + trpc.chart.funnel.queryOptions(input, { + enabled: !isLazyLoading && input.series.length > 0, + }), ); if (isLazyLoading || res.isLoading) { diff --git a/apps/start/src/components/report-chart/histogram/index.tsx b/apps/start/src/components/report-chart/histogram/index.tsx index d8092833..e3c2b384 100644 --- a/apps/start/src/components/report-chart/histogram/index.tsx +++ b/apps/start/src/components/report-chart/histogram/index.tsx @@ -10,33 +10,27 @@ import { useReportChartContext } from '../context'; import { Chart } from './chart'; export function ReportHistogramChart() { - const { isLazyLoading, report, shareId, shareType } = useReportChartContext(); + const { isLazyLoading, report, shareId } = useReportChartContext(); const trpc = useTRPC(); const { range, startDate, endDate, interval } = useOverviewOptions(); const res = useQuery( - shareId && shareType && 'id' in report && report.id - ? trpc.chart.chartByReport.queryOptions( - { - reportId: report.id, - shareId, - shareType, - range: range ?? undefined, - startDate: startDate ?? undefined, - endDate: endDate ?? undefined, - interval: interval ?? undefined, - }, - { - placeholderData: keepPreviousData, - staleTime: 1000 * 60 * 1, - enabled: !isLazyLoading, - }, - ) - : trpc.chart.chart.queryOptions(report, { - placeholderData: keepPreviousData, - staleTime: 1000 * 60 * 1, - enabled: !isLazyLoading, - }), + trpc.chart.chart.queryOptions( + { + ...report, + shareId, + reportId: 'id' in report ? report.id : undefined, + range: range ?? report.range, + startDate: startDate ?? report.startDate, + endDate: endDate ?? report.endDate, + interval: interval ?? report.interval, + }, + { + placeholderData: keepPreviousData, + staleTime: 1000 * 60 * 1, + enabled: !isLazyLoading, + }, + ), ); if ( diff --git a/apps/start/src/components/report-chart/line/index.tsx b/apps/start/src/components/report-chart/line/index.tsx index 111e8458..5b2c90a1 100644 --- a/apps/start/src/components/report-chart/line/index.tsx +++ b/apps/start/src/components/report-chart/line/index.tsx @@ -11,33 +11,27 @@ import { useReportChartContext } from '../context'; import { Chart } from './chart'; export function ReportLineChart() { - const { isLazyLoading, report, shareId, shareType } = useReportChartContext(); + const { isLazyLoading, report, shareId } = useReportChartContext(); const trpc = useTRPC(); const { range, startDate, endDate, interval } = useOverviewOptions(); const res = useQuery( - shareId && shareType && 'id' in report && report.id - ? trpc.chart.chartByReport.queryOptions( - { - reportId: report.id, - shareId, - shareType, - range: range ?? undefined, - startDate: startDate ?? undefined, - endDate: endDate ?? undefined, - interval: interval ?? undefined, - }, - { - placeholderData: keepPreviousData, - staleTime: 1000 * 60 * 1, - enabled: !isLazyLoading, - }, - ) - : trpc.chart.chart.queryOptions(report, { - placeholderData: keepPreviousData, - staleTime: 1000 * 60 * 1, - enabled: !isLazyLoading, - }), + trpc.chart.chart.queryOptions( + { + ...report, + shareId, + reportId: 'id' in report ? report.id : undefined, + range: range ?? report.range, + startDate: startDate ?? report.startDate, + endDate: endDate ?? report.endDate, + interval: interval ?? report.interval, + }, + { + placeholderData: keepPreviousData, + staleTime: 1000 * 60 * 1, + enabled: !isLazyLoading, + }, + ), ); if ( diff --git a/apps/start/src/components/report-chart/map/index.tsx b/apps/start/src/components/report-chart/map/index.tsx index 7989c07b..8dd256f7 100644 --- a/apps/start/src/components/report-chart/map/index.tsx +++ b/apps/start/src/components/report-chart/map/index.tsx @@ -10,33 +10,27 @@ import { useReportChartContext } from '../context'; import { Chart } from './chart'; export function ReportMapChart() { - const { isLazyLoading, report, shareId, shareType } = useReportChartContext(); + const { isLazyLoading, report, shareId } = useReportChartContext(); const trpc = useTRPC(); const { range, startDate, endDate, interval } = useOverviewOptions(); const res = useQuery( - shareId && shareType && 'id' in report && report.id - ? trpc.chart.chartByReport.queryOptions( - { - reportId: report.id, - shareId, - shareType, - range: range ?? undefined, - startDate: startDate ?? undefined, - endDate: endDate ?? undefined, - interval: interval ?? undefined, - }, - { - placeholderData: keepPreviousData, - staleTime: 1000 * 60 * 1, - enabled: !isLazyLoading, - }, - ) - : trpc.chart.chart.queryOptions(report, { - placeholderData: keepPreviousData, - staleTime: 1000 * 60 * 1, - enabled: !isLazyLoading, - }), + trpc.chart.chart.queryOptions( + { + ...report, + shareId, + reportId: 'id' in report ? report.id : undefined, + range: range ?? report.range, + startDate: startDate ?? report.startDate, + endDate: endDate ?? report.endDate, + interval: interval ?? report.interval, + }, + { + placeholderData: keepPreviousData, + staleTime: 1000 * 60 * 1, + enabled: !isLazyLoading, + }, + ), ); if ( diff --git a/apps/start/src/components/report-chart/metric/index.tsx b/apps/start/src/components/report-chart/metric/index.tsx index c0b9153f..83447a7b 100644 --- a/apps/start/src/components/report-chart/metric/index.tsx +++ b/apps/start/src/components/report-chart/metric/index.tsx @@ -9,33 +9,27 @@ import { useReportChartContext } from '../context'; import { Chart } from './chart'; export function ReportMetricChart() { - const { isLazyLoading, report, shareId, shareType } = useReportChartContext(); + const { isLazyLoading, report, shareId } = useReportChartContext(); const trpc = useTRPC(); const { range, startDate, endDate, interval } = useOverviewOptions(); const res = useQuery( - shareId && shareType && 'id' in report && report.id - ? trpc.chart.chartByReport.queryOptions( - { - reportId: report.id, - shareId, - shareType, - range: range ?? undefined, - startDate: startDate ?? undefined, - endDate: endDate ?? undefined, - interval: interval ?? undefined, - }, - { - placeholderData: keepPreviousData, - staleTime: 1000 * 60 * 1, - enabled: !isLazyLoading, - }, - ) - : trpc.chart.chart.queryOptions(report, { - placeholderData: keepPreviousData, - staleTime: 1000 * 60 * 1, - enabled: !isLazyLoading, - }), + trpc.chart.chart.queryOptions( + { + ...report, + shareId, + reportId: 'id' in report ? report.id : undefined, + range: range ?? report.range, + startDate: startDate ?? report.startDate, + endDate: endDate ?? report.endDate, + interval: interval ?? report.interval, + }, + { + placeholderData: keepPreviousData, + staleTime: 1000 * 60 * 1, + enabled: !isLazyLoading, + }, + ), ); if ( diff --git a/apps/start/src/components/report-chart/metric/metric-card.tsx b/apps/start/src/components/report-chart/metric/metric-card.tsx index 7383889f..334091bf 100644 --- a/apps/start/src/components/report-chart/metric/metric-card.tsx +++ b/apps/start/src/components/report-chart/metric/metric-card.tsx @@ -54,10 +54,7 @@ export function MetricCard({ metric, unit, }: MetricCardProps) { - const { - report: { previousIndicatorInverted }, - isEditMode, - } = useReportChartContext(); + const { isEditMode } = useReportChartContext(); const number = useNumber(); const renderValue = (value: number | undefined, unitClassName?: string) => { @@ -80,7 +77,7 @@ export function MetricCard({ const previous = serie.metrics.previous?.[metric]; const graphColors = getDiffIndicator( - previousIndicatorInverted, + false, previous?.state, '#6ee7b7', // green '#fda4af', // red diff --git a/apps/start/src/components/report-chart/pie/index.tsx b/apps/start/src/components/report-chart/pie/index.tsx index dc58942b..7420ac6d 100644 --- a/apps/start/src/components/report-chart/pie/index.tsx +++ b/apps/start/src/components/report-chart/pie/index.tsx @@ -10,33 +10,27 @@ import { useReportChartContext } from '../context'; import { Chart } from './chart'; export function ReportPieChart() { - const { isLazyLoading, report, shareId, shareType } = useReportChartContext(); + const { isLazyLoading, report, shareId } = useReportChartContext(); const trpc = useTRPC(); const { range, startDate, endDate, interval } = useOverviewOptions(); const res = useQuery( - shareId && shareType && 'id' in report && report.id - ? trpc.chart.aggregateByReport.queryOptions( - { - reportId: report.id, - shareId, - shareType, - range: range ?? undefined, - startDate: startDate ?? undefined, - endDate: endDate ?? undefined, - interval: interval ?? undefined, - }, - { - placeholderData: keepPreviousData, - staleTime: 1000 * 60 * 1, - enabled: !isLazyLoading, - }, - ) - : trpc.chart.aggregate.queryOptions(report, { - placeholderData: keepPreviousData, - staleTime: 1000 * 60 * 1, - enabled: !isLazyLoading, - }), + trpc.chart.aggregate.queryOptions( + { + ...report, + shareId, + reportId: 'id' in report ? report.id : undefined, + range: range ?? report.range, + startDate: startDate ?? report.startDate, + endDate: endDate ?? report.endDate, + interval: interval ?? report.interval, + }, + { + placeholderData: keepPreviousData, + staleTime: 1000 * 60 * 1, + enabled: !isLazyLoading, + }, + ), ); if ( diff --git a/apps/start/src/components/report-chart/retention/index.tsx b/apps/start/src/components/report-chart/retention/index.tsx index 550b0326..e204a4b5 100644 --- a/apps/start/src/components/report-chart/retention/index.tsx +++ b/apps/start/src/components/report-chart/retention/index.tsx @@ -24,7 +24,6 @@ export function ReportRetentionChart() { }, isLazyLoading, shareId, - shareType, } = useReportChartContext(); const { range: overviewRange, startDate: overviewStartDate, endDate: overviewEndDate, interval: overviewInterval } = useOverviewOptions(); const eventSeries = series.filter((item) => item.type === 'event'); @@ -34,40 +33,25 @@ export function ReportRetentionChart() { firstEvent.length > 0 && secondEvent.length > 0 && !isLazyLoading; const trpc = useTRPC(); const res = useQuery( - shareId && shareType && id - ? trpc.chart.cohortByReport.queryOptions( - { - reportId: id, - shareId, - shareType, - range: overviewRange ?? undefined, - startDate: overviewStartDate ?? undefined, - endDate: overviewEndDate ?? undefined, - interval: overviewInterval ?? undefined, - }, - { - placeholderData: keepPreviousData, - staleTime: 1000 * 60 * 1, - enabled: isEnabled, - }, - ) - : trpc.chart.cohort.queryOptions( - { - firstEvent, - secondEvent, - projectId, - range, - startDate, - endDate, - criteria, - interval, - }, - { - placeholderData: keepPreviousData, - staleTime: 1000 * 60 * 1, - enabled: isEnabled, - }, - ), + trpc.chart.cohort.queryOptions( + { + firstEvent, + secondEvent, + projectId, + range: overviewRange ?? range, + startDate: overviewStartDate ?? startDate, + endDate: overviewEndDate ?? endDate, + criteria, + interval: overviewInterval ?? interval, + shareId, + reportId: id, + }, + { + placeholderData: keepPreviousData, + staleTime: 1000 * 60 * 1, + enabled: isEnabled, + }, + ), ); if (!isEnabled) { diff --git a/apps/start/src/components/report/report-item.tsx b/apps/start/src/components/report/report-item.tsx new file mode 100644 index 00000000..f87fb7f1 --- /dev/null +++ b/apps/start/src/components/report/report-item.tsx @@ -0,0 +1,258 @@ +import { ReportChart } from '@/components/report-chart'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { cn } from '@/utils/cn'; +import { CopyIcon, MoreHorizontal, Trash } from 'lucide-react'; + +import { timeWindows } from '@openpanel/constants'; + +import { useRouter } from '@tanstack/react-router'; + +export function ReportItemSkeleton() { + return ( +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + ); +} + +export function ReportItem({ + report, + organizationId, + projectId, + range, + startDate, + endDate, + interval, + onDelete, + onDuplicate, +}: { + report: any; + organizationId: string; + projectId: string; + range: any; + startDate: any; + endDate: any; + interval: any; + onDelete: (reportId: string) => void; + onDuplicate: (reportId: string) => void; +}) { + const router = useRouter(); + const chartRange = report.range; + + return ( +
    +
    +
    { + if (event.metaKey) { + window.open( + `/${organizationId}/${projectId}/reports/${report.id}`, + '_blank', + ); + return; + } + router.navigate({ + to: '/$organizationId/$projectId/reports/$reportId', + params: { + organizationId, + projectId, + reportId: report.id, + }, + }); + }} + onKeyUp={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + router.navigate({ + to: '/$organizationId/$projectId/reports/$reportId', + params: { + organizationId, + projectId, + reportId: report.id, + }, + }); + } + }} + role="button" + tabIndex={0} + > +
    {report.name}
    + {chartRange !== null && ( +
    + + {timeWindows[chartRange as keyof typeof timeWindows]?.label} + + {startDate && endDate ? ( + Custom dates + ) : ( + range !== null && + chartRange !== range && ( + + {timeWindows[range as keyof typeof timeWindows]?.label} + + ) + )} +
    + )} +
    +
    +
    + + + + + + + + +
    + + + + + + { + event.stopPropagation(); + onDuplicate(report.id); + }} + > + + Duplicate + + + { + event.stopPropagation(); + onDelete(report.id); + }} + > + + Delete + + + + +
    +
    +
    + +
    +
    + ); +} + +export function ReportItemReadOnly({ + report, + shareId, + range, + startDate, + endDate, + interval, +}: { + report: any; + shareId: string; + range: any; + startDate: any; + endDate: any; + interval: any; +}) { + const chartRange = report.range; + + return ( +
    +
    +
    +
    {report.name}
    + {chartRange !== null && ( +
    + + {timeWindows[chartRange as keyof typeof timeWindows]?.label} + + {startDate && endDate ? ( + Custom dates + ) : ( + range !== null && + chartRange !== range && ( + + {timeWindows[range as keyof typeof timeWindows]?.label} + + ) + )} +
    + )} +
    +
    +
    + +
    +
    + ); +} diff --git a/apps/start/src/components/report/sidebar/ReportSettings.tsx b/apps/start/src/components/report/sidebar/ReportSettings.tsx index 71107152..6ce8f08a 100644 --- a/apps/start/src/components/report/sidebar/ReportSettings.tsx +++ b/apps/start/src/components/report/sidebar/ReportSettings.tsx @@ -67,7 +67,7 @@ export function ReportSettings() { return (

    Settings

    -
    +
    {fields.includes('previous') && (