format:fix
This commit is contained in:
@@ -1,44 +1,44 @@
|
|||||||
import { omit, prop, uniqBy } from "ramda";
|
import { omit, prop, uniqBy } from 'ramda';
|
||||||
|
|
||||||
import { generateProfileId, getTime, toISOString } from "@openpanel/common";
|
import { generateProfileId, getTime, toISOString } from '@openpanel/common';
|
||||||
import type { Event, IServiceCreateEventPayload } from "@openpanel/db";
|
import type { Event, IServiceCreateEventPayload } from '@openpanel/db';
|
||||||
import {
|
import {
|
||||||
createEvent as createClickhouseEvent,
|
createEvent as createClickhouseEvent,
|
||||||
db,
|
db,
|
||||||
formatClickhouseDate,
|
formatClickhouseDate,
|
||||||
getSalts,
|
getSalts,
|
||||||
} from "@openpanel/db";
|
} from '@openpanel/db';
|
||||||
|
|
||||||
import { parseIp } from "../src/utils/parseIp";
|
import { parseIp } from '../src/utils/parseIp';
|
||||||
import { parseUserAgent } from "../src/utils/parseUserAgent";
|
import { parseUserAgent } from '../src/utils/parseUserAgent';
|
||||||
|
|
||||||
const clean = omit([
|
const clean = omit([
|
||||||
"ip",
|
'ip',
|
||||||
"os",
|
'os',
|
||||||
"ua",
|
'ua',
|
||||||
"url",
|
'url',
|
||||||
"hash",
|
'hash',
|
||||||
"host",
|
'host',
|
||||||
"path",
|
'path',
|
||||||
"device",
|
'device',
|
||||||
"screen",
|
'screen',
|
||||||
"hostname",
|
'hostname',
|
||||||
"language",
|
'language',
|
||||||
"referrer",
|
'referrer',
|
||||||
"timezone",
|
'timezone',
|
||||||
]);
|
]);
|
||||||
async function main() {
|
async function main() {
|
||||||
const events = await db.event.findMany({
|
const events = await db.event.findMany({
|
||||||
where: {
|
where: {
|
||||||
project_id: "4e2798cb-e255-4e9d-960d-c9ad095aabd7",
|
project_id: '4e2798cb-e255-4e9d-960d-c9ad095aabd7',
|
||||||
name: "screen_view",
|
name: 'screen_view',
|
||||||
createdAt: {
|
createdAt: {
|
||||||
gte: new Date("2024-01-01"),
|
gte: new Date('2024-01-01'),
|
||||||
lt: new Date("2024-02-04"),
|
lt: new Date('2024-02-04'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
createdAt: "asc",
|
createdAt: 'asc',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ async function main() {
|
|||||||
|
|
||||||
const properties = event.properties as Record<string, any>;
|
const properties = event.properties as Record<string, any>;
|
||||||
|
|
||||||
if (properties.ua?.includes("bot")) {
|
if (properties.ua?.includes('bot')) {
|
||||||
// console.log('IGNORE', event.id);
|
// console.log('IGNORE', event.id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -67,20 +67,20 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Total users", Object.keys(grouped).length);
|
console.log('Total users', Object.keys(grouped).length);
|
||||||
|
|
||||||
let uidx = -1;
|
let uidx = -1;
|
||||||
for (const profile_id of Object.keys(grouped)) {
|
for (const profile_id of Object.keys(grouped)) {
|
||||||
uidx++;
|
uidx++;
|
||||||
console.log(`User index ${uidx}`);
|
console.log(`User index ${uidx}`);
|
||||||
|
|
||||||
const events = uniqBy(prop("createdAt"), grouped[profile_id] || []);
|
const events = uniqBy(prop('createdAt'), grouped[profile_id] || []);
|
||||||
|
|
||||||
if (events) {
|
if (events) {
|
||||||
let lastSessionStart = null;
|
let lastSessionStart = null;
|
||||||
let screenViews = 0;
|
let screenViews = 0;
|
||||||
let totalDuration = 0;
|
let totalDuration = 0;
|
||||||
console.log("new user...");
|
console.log('new user...');
|
||||||
let eidx = -1;
|
let eidx = -1;
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
eidx++;
|
eidx++;
|
||||||
@@ -93,7 +93,7 @@ async function main() {
|
|||||||
const projectId = event.project_id;
|
const projectId = event.project_id;
|
||||||
const path = properties.path!;
|
const path = properties.path!;
|
||||||
const ip = properties.ip!;
|
const ip = properties.ip!;
|
||||||
const origin = "https://openpanel.kiddo.se";
|
const origin = 'https://openpanel.kiddo.se';
|
||||||
const ua = properties.ua!;
|
const ua = properties.ua!;
|
||||||
const uaInfo = parseUserAgent(ua);
|
const uaInfo = parseUserAgent(ua);
|
||||||
const salts = await getSalts();
|
const salts = await getSalts();
|
||||||
@@ -133,8 +133,8 @@ async function main() {
|
|||||||
? nextEvent.createdAt.getTime() - event.createdAt.getTime()
|
? nextEvent.createdAt.getTime() - event.createdAt.getTime()
|
||||||
: 0,
|
: 0,
|
||||||
path,
|
path,
|
||||||
referrer: properties?.referrer?.host ?? "", // TODO
|
referrer: properties?.referrer?.host ?? '', // TODO
|
||||||
referrerName: properties?.referrer?.host ?? "", // TODO
|
referrerName: properties?.referrer?.host ?? '', // TODO
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!prevEventAt) {
|
if (!prevEventAt) {
|
||||||
@@ -173,8 +173,8 @@ async function main() {
|
|||||||
async function createEvent(event: IServiceCreateEventPayload) {
|
async function createEvent(event: IServiceCreateEventPayload) {
|
||||||
console.log(
|
console.log(
|
||||||
`Create ${event.name} - ${event.path} - ${formatClickhouseDate(
|
`Create ${event.name} - ${event.path} - ${formatClickhouseDate(
|
||||||
event.createdAt,
|
event.createdAt
|
||||||
)} - ${event.duration / 1000} sec`,
|
)} - ${event.duration / 1000} sec`
|
||||||
);
|
);
|
||||||
await createClickhouseEvent(event);
|
await createClickhouseEvent(event);
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,7 @@ async function createSessionStart(event: IServiceCreateEventPayload) {
|
|||||||
const session: IServiceCreateEventPayload = {
|
const session: IServiceCreateEventPayload = {
|
||||||
...event,
|
...event,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
name: "session_start",
|
name: 'session_start',
|
||||||
createdAt: toISOString(getTime(event.createdAt) - 100),
|
createdAt: toISOString(getTime(event.createdAt) - 100),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@ async function createSessionEnd(
|
|||||||
options: {
|
options: {
|
||||||
screenViews: number;
|
screenViews: number;
|
||||||
totalDuration: number;
|
totalDuration: number;
|
||||||
},
|
}
|
||||||
) {
|
) {
|
||||||
const properties: Record<string, unknown> = {};
|
const properties: Record<string, unknown> = {};
|
||||||
if (options.screenViews === 1) {
|
if (options.screenViews === 1) {
|
||||||
@@ -213,7 +213,7 @@ async function createSessionEnd(
|
|||||||
...sessionStart.properties,
|
...sessionStart.properties,
|
||||||
},
|
},
|
||||||
duration: options.totalDuration,
|
duration: options.totalDuration,
|
||||||
name: "session_end",
|
name: 'session_end',
|
||||||
createdAt: toISOString(prevEventAt.getTime() + 10),
|
createdAt: toISOString(prevEventAt.getTime() + 10),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { logger, logInfo, noop } from "@/utils/logger";
|
import { logger, logInfo, noop } from '@/utils/logger';
|
||||||
import { getClientIp, parseIp } from "@/utils/parseIp";
|
import { getClientIp, parseIp } from '@/utils/parseIp';
|
||||||
import { getReferrerWithQuery, parseReferrer } from "@/utils/parseReferrer";
|
import { getReferrerWithQuery, parseReferrer } from '@/utils/parseReferrer';
|
||||||
import { isUserAgentSet, parseUserAgent } from "@/utils/parseUserAgent";
|
import { isUserAgentSet, parseUserAgent } from '@/utils/parseUserAgent';
|
||||||
import { isSameDomain, parsePath } from "@/utils/url";
|
import { isSameDomain, parsePath } from '@/utils/url';
|
||||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import { omit } from "ramda";
|
import { omit } from 'ramda';
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import { generateDeviceId, getTime, toISOString } from "@openpanel/common";
|
import { generateDeviceId, getTime, toISOString } from '@openpanel/common';
|
||||||
import type { IServiceCreateEventPayload } from "@openpanel/db";
|
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
||||||
import { createEvent, getEvents, getSalts } from "@openpanel/db";
|
import { createEvent, getEvents, getSalts } from '@openpanel/db';
|
||||||
import type { JobsOptions } from "@openpanel/queue";
|
import type { JobsOptions } from '@openpanel/queue';
|
||||||
import { eventsQueue } from "@openpanel/queue";
|
import { eventsQueue } from '@openpanel/queue';
|
||||||
import { findJobByPrefix } from "@openpanel/queue/src/utils";
|
import { findJobByPrefix } from '@openpanel/queue/src/utils';
|
||||||
import type { PostEventPayload } from "@openpanel/sdk";
|
import type { PostEventPayload } from '@openpanel/sdk';
|
||||||
|
|
||||||
const SESSION_TIMEOUT = 1000 * 60 * 30;
|
const SESSION_TIMEOUT = 1000 * 60 * 30;
|
||||||
const SESSION_END_TIMEOUT = SESSION_TIMEOUT + 1000;
|
const SESSION_END_TIMEOUT = SESSION_TIMEOUT + 1000;
|
||||||
@@ -55,7 +55,7 @@ export async function postEvent(
|
|||||||
request: FastifyRequest<{
|
request: FastifyRequest<{
|
||||||
Body: PostEventPayload;
|
Body: PostEventPayload;
|
||||||
}>,
|
}>,
|
||||||
reply: FastifyReply,
|
reply: FastifyReply
|
||||||
) {
|
) {
|
||||||
const contextLogger = createContextLogger(request);
|
const contextLogger = createContextLogger(request);
|
||||||
let deviceId: string | null = null;
|
let deviceId: string | null = null;
|
||||||
@@ -65,23 +65,23 @@ export async function postEvent(
|
|||||||
// replace thing is just for older sdks when we didn't have `__`
|
// replace thing is just for older sdks when we didn't have `__`
|
||||||
// remove when kiddokitchen app (24.09.02) is not used anymore
|
// remove when kiddokitchen app (24.09.02) is not used anymore
|
||||||
return (
|
return (
|
||||||
((properties[name] || properties[name.replace("__", "")]) as
|
((properties[name] || properties[name.replace('__', '')]) as
|
||||||
| string
|
| string
|
||||||
| null
|
| null
|
||||||
| undefined) ?? undefined
|
| undefined) ?? undefined
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const profileId = body.profileId ?? "";
|
const profileId = body.profileId ?? '';
|
||||||
const createdAt = new Date(body.timestamp);
|
const createdAt = new Date(body.timestamp);
|
||||||
const url = getProperty("__path");
|
const url = getProperty('__path');
|
||||||
const { path, hash, query } = parsePath(url);
|
const { path, hash, query } = parsePath(url);
|
||||||
const referrer = isSameDomain(getProperty("__referrer"), url)
|
const referrer = isSameDomain(getProperty('__referrer'), url)
|
||||||
? null
|
? null
|
||||||
: parseReferrer(getProperty("__referrer"));
|
: parseReferrer(getProperty('__referrer'));
|
||||||
const utmReferrer = getReferrerWithQuery(query);
|
const utmReferrer = getReferrerWithQuery(query);
|
||||||
const ip = getClientIp(request)!;
|
const ip = getClientIp(request)!;
|
||||||
const origin = request.headers.origin!;
|
const origin = request.headers.origin!;
|
||||||
const ua = request.headers["user-agent"]!;
|
const ua = request.headers['user-agent']!;
|
||||||
const uaInfo = parseUserAgent(ua);
|
const uaInfo = parseUserAgent(ua);
|
||||||
const salts = await getSalts();
|
const salts = await getSalts();
|
||||||
const currentDeviceId = generateDeviceId({
|
const currentDeviceId = generateDeviceId({
|
||||||
@@ -101,48 +101,48 @@ export async function postEvent(
|
|||||||
|
|
||||||
if (isServerEvent) {
|
if (isServerEvent) {
|
||||||
const [event] = await withTiming(
|
const [event] = await withTiming(
|
||||||
"Get last event (server-event)",
|
'Get last event (server-event)',
|
||||||
getEvents(
|
getEvents(
|
||||||
`SELECT * FROM events WHERE name = 'screen_view' AND profile_id = '${profileId}' AND project_id = '${projectId}' ORDER BY created_at DESC LIMIT 1`,
|
`SELECT * FROM events WHERE name = 'screen_view' AND profile_id = '${profileId}' AND project_id = '${projectId}' ORDER BY created_at DESC LIMIT 1`
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const payload: Omit<IServiceCreateEventPayload, "id"> = {
|
const payload: Omit<IServiceCreateEventPayload, 'id'> = {
|
||||||
name: body.name,
|
name: body.name,
|
||||||
deviceId: event?.deviceId || "",
|
deviceId: event?.deviceId || '',
|
||||||
sessionId: event?.sessionId || "",
|
sessionId: event?.sessionId || '',
|
||||||
profileId,
|
profileId,
|
||||||
projectId,
|
projectId,
|
||||||
properties: Object.assign(
|
properties: Object.assign(
|
||||||
{},
|
{},
|
||||||
omit(["__path", "__referrer"], properties),
|
omit(['__path', '__referrer'], properties),
|
||||||
{
|
{
|
||||||
hash,
|
hash,
|
||||||
query,
|
query,
|
||||||
},
|
}
|
||||||
),
|
),
|
||||||
createdAt,
|
createdAt,
|
||||||
country: event?.country ?? "",
|
country: event?.country ?? '',
|
||||||
city: event?.city ?? "",
|
city: event?.city ?? '',
|
||||||
region: event?.region ?? "",
|
region: event?.region ?? '',
|
||||||
continent: event?.continent ?? "",
|
continent: event?.continent ?? '',
|
||||||
os: event?.os ?? "",
|
os: event?.os ?? '',
|
||||||
osVersion: event?.osVersion ?? "",
|
osVersion: event?.osVersion ?? '',
|
||||||
browser: event?.browser ?? "",
|
browser: event?.browser ?? '',
|
||||||
browserVersion: event?.browserVersion ?? "",
|
browserVersion: event?.browserVersion ?? '',
|
||||||
device: event?.device ?? "",
|
device: event?.device ?? '',
|
||||||
brand: event?.brand ?? "",
|
brand: event?.brand ?? '',
|
||||||
model: event?.model ?? "",
|
model: event?.model ?? '',
|
||||||
duration: 0,
|
duration: 0,
|
||||||
path: event?.path ?? "",
|
path: event?.path ?? '',
|
||||||
referrer: event?.referrer ?? "",
|
referrer: event?.referrer ?? '',
|
||||||
referrerName: event?.referrerName ?? "",
|
referrerName: event?.referrerName ?? '',
|
||||||
referrerType: event?.referrerType ?? "",
|
referrerType: event?.referrerType ?? '',
|
||||||
profile: undefined,
|
profile: undefined,
|
||||||
meta: undefined,
|
meta: undefined,
|
||||||
}
|
};
|
||||||
|
|
||||||
contextLogger.send("server event is queued", {
|
contextLogger.send('server event is queued', {
|
||||||
ip,
|
ip,
|
||||||
origin,
|
origin,
|
||||||
ua,
|
ua,
|
||||||
@@ -156,27 +156,27 @@ export async function postEvent(
|
|||||||
prevEvent: event,
|
prevEvent: event,
|
||||||
});
|
});
|
||||||
|
|
||||||
eventsQueue.add("event", {
|
eventsQueue.add('event', {
|
||||||
type: "createEvent",
|
type: 'createEvent',
|
||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
return reply.status(200).send("");
|
return reply.status(200).send('');
|
||||||
}
|
}
|
||||||
|
|
||||||
const [geo, sessionEndJobCurrentDeviceId, sessionEndJobPreviousDeviceId] =
|
const [geo, sessionEndJobCurrentDeviceId, sessionEndJobPreviousDeviceId] =
|
||||||
await withTiming(
|
await withTiming(
|
||||||
"Get geo and jobs from queue",
|
'Get geo and jobs from queue',
|
||||||
Promise.all([
|
Promise.all([
|
||||||
parseIp(ip),
|
parseIp(ip),
|
||||||
findJobByPrefix(
|
findJobByPrefix(
|
||||||
eventsQueue,
|
eventsQueue,
|
||||||
`sessionEnd:${projectId}:${currentDeviceId}:`,
|
`sessionEnd:${projectId}:${currentDeviceId}:`
|
||||||
),
|
),
|
||||||
findJobByPrefix(
|
findJobByPrefix(
|
||||||
eventsQueue,
|
eventsQueue,
|
||||||
`sessionEnd:${projectId}:${previousDeviceId}:`,
|
`sessionEnd:${projectId}:${previousDeviceId}:`
|
||||||
),
|
),
|
||||||
]),
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
const createSessionStart =
|
const createSessionStart =
|
||||||
@@ -194,9 +194,9 @@ export async function postEvent(
|
|||||||
deviceId = currentDeviceId;
|
deviceId = currentDeviceId;
|
||||||
// Queue session end
|
// Queue session end
|
||||||
eventsQueue.add(
|
eventsQueue.add(
|
||||||
"event",
|
'event',
|
||||||
{
|
{
|
||||||
type: "createSessionEnd",
|
type: 'createSessionEnd',
|
||||||
payload: {
|
payload: {
|
||||||
deviceId,
|
deviceId,
|
||||||
},
|
},
|
||||||
@@ -204,27 +204,27 @@ export async function postEvent(
|
|||||||
{
|
{
|
||||||
delay: SESSION_END_TIMEOUT,
|
delay: SESSION_END_TIMEOUT,
|
||||||
jobId: `sessionEnd:${projectId}:${deviceId}:${Date.now()}`,
|
jobId: `sessionEnd:${projectId}:${deviceId}:${Date.now()}`,
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [[sessionStartEvent], prevEventJob] = await withTiming(
|
const [[sessionStartEvent], prevEventJob] = await withTiming(
|
||||||
"Get session start event",
|
'Get session start event',
|
||||||
Promise.all([
|
Promise.all([
|
||||||
getEvents(
|
getEvents(
|
||||||
`SELECT * FROM events WHERE name = 'session_start' AND device_id = '${deviceId}' AND project_id = '${projectId}' ORDER BY created_at DESC LIMIT 1`,
|
`SELECT * FROM events WHERE name = 'session_start' AND device_id = '${deviceId}' AND project_id = '${projectId}' ORDER BY created_at DESC LIMIT 1`
|
||||||
),
|
),
|
||||||
findJobByPrefix(eventsQueue, `event:${projectId}:${deviceId}:`),
|
findJobByPrefix(eventsQueue, `event:${projectId}:${deviceId}:`),
|
||||||
]),
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
const payload: Omit<IServiceCreateEventPayload, "id"> = {
|
const payload: Omit<IServiceCreateEventPayload, 'id'> = {
|
||||||
name: body.name,
|
name: body.name,
|
||||||
deviceId,
|
deviceId,
|
||||||
profileId,
|
profileId,
|
||||||
projectId,
|
projectId,
|
||||||
sessionId: createSessionStart ? uuid() : sessionStartEvent?.sessionId ?? "",
|
sessionId: createSessionStart ? uuid() : sessionStartEvent?.sessionId ?? '',
|
||||||
properties: Object.assign({}, omit(["__path", "__referrer"], properties), {
|
properties: Object.assign({}, omit(['__path', '__referrer'], properties), {
|
||||||
hash,
|
hash,
|
||||||
query,
|
query,
|
||||||
}),
|
}),
|
||||||
@@ -243,27 +243,27 @@ export async function postEvent(
|
|||||||
duration: 0,
|
duration: 0,
|
||||||
path: path,
|
path: path,
|
||||||
referrer: referrer?.url,
|
referrer: referrer?.url,
|
||||||
referrerName: referrer?.name ?? utmReferrer?.name ?? "",
|
referrerName: referrer?.name ?? utmReferrer?.name ?? '',
|
||||||
referrerType: referrer?.type ?? utmReferrer?.type ?? "",
|
referrerType: referrer?.type ?? utmReferrer?.type ?? '',
|
||||||
profile: undefined,
|
profile: undefined,
|
||||||
meta: undefined,
|
meta: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const isDelayed = prevEventJob ? await prevEventJob?.isDelayed() : false;
|
const isDelayed = prevEventJob ? await prevEventJob?.isDelayed() : false;
|
||||||
|
|
||||||
if (isDelayed && prevEventJob && prevEventJob.data.type === "createEvent") {
|
if (isDelayed && prevEventJob && prevEventJob.data.type === 'createEvent') {
|
||||||
const prevEvent = prevEventJob.data.payload;
|
const prevEvent = prevEventJob.data.payload;
|
||||||
const duration = getTime(payload.createdAt) - getTime(prevEvent.createdAt);
|
const duration = getTime(payload.createdAt) - getTime(prevEvent.createdAt);
|
||||||
contextLogger.add("prevEvent", prevEvent);
|
contextLogger.add('prevEvent', prevEvent);
|
||||||
|
|
||||||
// Set path from prev screen_view event if current event is not a screen_view
|
// Set path from prev screen_view event if current event is not a screen_view
|
||||||
if (payload.name != "screen_view") {
|
if (payload.name != 'screen_view') {
|
||||||
payload.path = prevEvent.path;
|
payload.path = prevEvent.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.name === "screen_view") {
|
if (payload.name === 'screen_view') {
|
||||||
if (duration < 0) {
|
if (duration < 0) {
|
||||||
contextLogger.send("duration is wrong", {
|
contextLogger.send('duration is wrong', {
|
||||||
payload,
|
payload,
|
||||||
duration,
|
duration,
|
||||||
});
|
});
|
||||||
@@ -271,21 +271,21 @@ export async function postEvent(
|
|||||||
// Skip update duration if it's wrong
|
// Skip update duration if it's wrong
|
||||||
// Seems like request is not in right order
|
// Seems like request is not in right order
|
||||||
await withTiming(
|
await withTiming(
|
||||||
"Update previous job with duration",
|
'Update previous job with duration',
|
||||||
prevEventJob.updateData({
|
prevEventJob.updateData({
|
||||||
type: "createEvent",
|
type: 'createEvent',
|
||||||
payload: {
|
payload: {
|
||||||
...prevEvent,
|
...prevEvent,
|
||||||
duration,
|
duration,
|
||||||
},
|
},
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await withTiming("Promote previous job", prevEventJob.promote());
|
await withTiming('Promote previous job', prevEventJob.promote());
|
||||||
}
|
}
|
||||||
} else if (payload.name !== "screen_view") {
|
} else if (payload.name !== 'screen_view') {
|
||||||
contextLogger.send("no previous job", {
|
contextLogger.send('no previous job', {
|
||||||
prevEventJob,
|
prevEventJob,
|
||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
@@ -294,23 +294,23 @@ export async function postEvent(
|
|||||||
if (createSessionStart) {
|
if (createSessionStart) {
|
||||||
// We do not need to queue session_start
|
// We do not need to queue session_start
|
||||||
await withTiming(
|
await withTiming(
|
||||||
"Create session start event",
|
'Create session start event',
|
||||||
createEvent({
|
createEvent({
|
||||||
...payload,
|
...payload,
|
||||||
name: "session_start",
|
name: 'session_start',
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
createdAt: toISOString(getTime(payload.createdAt) - 100),
|
createdAt: toISOString(getTime(payload.createdAt) - 100),
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: JobsOptions = {};
|
const options: JobsOptions = {};
|
||||||
if (payload.name === "screen_view") {
|
if (payload.name === 'screen_view') {
|
||||||
options.delay = SESSION_TIMEOUT;
|
options.delay = SESSION_TIMEOUT;
|
||||||
options.jobId = `event:${projectId}:${deviceId}:${Date.now()}`;
|
options.jobId = `event:${projectId}:${deviceId}:${Date.now()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
contextLogger.send("event is queued", {
|
contextLogger.send('event is queued', {
|
||||||
ip,
|
ip,
|
||||||
origin,
|
origin,
|
||||||
ua,
|
ua,
|
||||||
@@ -328,14 +328,14 @@ export async function postEvent(
|
|||||||
// Queue current event
|
// Queue current event
|
||||||
eventsQueue
|
eventsQueue
|
||||||
.add(
|
.add(
|
||||||
"event",
|
'event',
|
||||||
{
|
{
|
||||||
type: "createEvent",
|
type: 'createEvent',
|
||||||
payload,
|
payload,
|
||||||
},
|
},
|
||||||
options,
|
options
|
||||||
)
|
)
|
||||||
.catch(noop("Failed to queue event"));
|
.catch(noop('Failed to queue event'));
|
||||||
|
|
||||||
reply.status(202).send(deviceId);
|
reply.status(202).send(deviceId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import type * as WebSocket from "ws";
|
import type * as WebSocket from 'ws';
|
||||||
|
|
||||||
import { getSafeJson } from "@openpanel/common";
|
import { getSafeJson } from '@openpanel/common';
|
||||||
import type { IServiceCreateEventPayload } from "@openpanel/db";
|
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
||||||
import { getEvents, getLiveVisitors } from "@openpanel/db";
|
import { getEvents, getLiveVisitors } from '@openpanel/db';
|
||||||
import { redis, redisPub, redisSub } from "@openpanel/redis";
|
import { redis, redisPub, redisSub } from '@openpanel/redis';
|
||||||
|
|
||||||
export function getLiveEventInfo(key: string) {
|
export function getLiveEventInfo(key: string) {
|
||||||
return key.split(":").slice(2) as [string, string];
|
return key.split(':').slice(2) as [string, string];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function test(
|
export async function test(
|
||||||
@@ -16,20 +16,20 @@ export async function test(
|
|||||||
projectId: string;
|
projectId: string;
|
||||||
};
|
};
|
||||||
}>,
|
}>,
|
||||||
reply: FastifyReply,
|
reply: FastifyReply
|
||||||
) {
|
) {
|
||||||
const [event] = await getEvents(
|
const [event] = await getEvents(
|
||||||
`SELECT * FROM events WHERE project_id = '${req.params.projectId}' AND name = 'screen_view' LIMIT 1`,
|
`SELECT * FROM events WHERE project_id = '${req.params.projectId}' AND name = 'screen_view' LIMIT 1`
|
||||||
);
|
);
|
||||||
if (!event) {
|
if (!event) {
|
||||||
return reply.status(404).send("No event found");
|
return reply.status(404).send('No event found');
|
||||||
}
|
}
|
||||||
redisPub.publish("event", JSON.stringify(event));
|
redisPub.publish('event', JSON.stringify(event));
|
||||||
redis.set(
|
redis.set(
|
||||||
`live:event:${event.projectId}:${Math.random() * 1000}`,
|
`live:event:${event.projectId}:${Math.random() * 1000}`,
|
||||||
"",
|
'',
|
||||||
"EX",
|
'EX',
|
||||||
10,
|
10
|
||||||
);
|
);
|
||||||
reply.status(202).send(event);
|
reply.status(202).send(event);
|
||||||
}
|
}
|
||||||
@@ -42,15 +42,15 @@ export function wsVisitors(
|
|||||||
Params: {
|
Params: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
};
|
};
|
||||||
}>,
|
}>
|
||||||
) {
|
) {
|
||||||
const { params } = req;
|
const { params } = req;
|
||||||
|
|
||||||
redisSub.subscribe("event");
|
redisSub.subscribe('event');
|
||||||
redisSub.psubscribe("__key*:expired");
|
redisSub.psubscribe('__key*:expired');
|
||||||
|
|
||||||
const message = (channel: string, message: string) => {
|
const message = (channel: string, message: string) => {
|
||||||
if (channel === "event") {
|
if (channel === 'event') {
|
||||||
const event = getSafeJson<IServiceCreateEventPayload>(message);
|
const event = getSafeJson<IServiceCreateEventPayload>(message);
|
||||||
if (event?.projectId === params.projectId) {
|
if (event?.projectId === params.projectId) {
|
||||||
getLiveVisitors(params.projectId).then((count) => {
|
getLiveVisitors(params.projectId).then((count) => {
|
||||||
@@ -68,14 +68,14 @@ export function wsVisitors(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
redisSub.on("message", message);
|
redisSub.on('message', message);
|
||||||
redisSub.on("pmessage", pmessage);
|
redisSub.on('pmessage', pmessage);
|
||||||
|
|
||||||
connection.socket.on("close", () => {
|
connection.socket.on('close', () => {
|
||||||
redisSub.unsubscribe("event");
|
redisSub.unsubscribe('event');
|
||||||
redisSub.punsubscribe("__key*:expired");
|
redisSub.punsubscribe('__key*:expired');
|
||||||
redisSub.off("message", message);
|
redisSub.off('message', message);
|
||||||
redisSub.off("pmessage", pmessage);
|
redisSub.off('pmessage', pmessage);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,11 +87,11 @@ export function wsEvents(
|
|||||||
Params: {
|
Params: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
};
|
};
|
||||||
}>,
|
}>
|
||||||
) {
|
) {
|
||||||
const { params } = req;
|
const { params } = req;
|
||||||
|
|
||||||
redisSub.subscribe("event");
|
redisSub.subscribe('event');
|
||||||
|
|
||||||
const message = (channel: string, message: string) => {
|
const message = (channel: string, message: string) => {
|
||||||
const event = getSafeJson<IServiceCreateEventPayload>(message);
|
const event = getSafeJson<IServiceCreateEventPayload>(message);
|
||||||
@@ -100,10 +100,10 @@ export function wsEvents(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
redisSub.on("message", message);
|
redisSub.on('message', message);
|
||||||
|
|
||||||
connection.socket.on("close", () => {
|
connection.socket.on('close', () => {
|
||||||
redisSub.unsubscribe("event");
|
redisSub.unsubscribe('event');
|
||||||
redisSub.off("message", message);
|
redisSub.off('message', message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import icoToPng from "ico-to-png";
|
import icoToPng from 'ico-to-png';
|
||||||
import sharp from "sharp";
|
import sharp from 'sharp';
|
||||||
|
|
||||||
import { createHash } from "@openpanel/common";
|
import { createHash } from '@openpanel/common';
|
||||||
import { redis } from "@openpanel/redis";
|
import { redis } from '@openpanel/redis';
|
||||||
|
|
||||||
interface GetFaviconParams {
|
interface GetFaviconParams {
|
||||||
url: string;
|
url: string;
|
||||||
@@ -12,9 +12,9 @@ interface GetFaviconParams {
|
|||||||
async function getImageBuffer(url: string) {
|
async function getImageBuffer(url: string) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
const contentType = res.headers.get("content-type");
|
const contentType = res.headers.get('content-type');
|
||||||
|
|
||||||
if (!contentType?.includes("image")) {
|
if (!contentType?.includes('image')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ async function getImageBuffer(url: string) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contentType === "image/x-icon" || url.endsWith(".ico")) {
|
if (contentType === 'image/x-icon' || url.endsWith('.ico')) {
|
||||||
const arrayBuffer = await res.arrayBuffer();
|
const arrayBuffer = await res.arrayBuffer();
|
||||||
const buffer = Buffer.from(arrayBuffer);
|
const buffer = Buffer.from(arrayBuffer);
|
||||||
return await icoToPng(buffer, 30);
|
return await icoToPng(buffer, 30);
|
||||||
@@ -30,36 +30,36 @@ async function getImageBuffer(url: string) {
|
|||||||
|
|
||||||
return await sharp(await res.arrayBuffer())
|
return await sharp(await res.arrayBuffer())
|
||||||
.resize(30, 30, {
|
.resize(30, 30, {
|
||||||
fit: "cover",
|
fit: 'cover',
|
||||||
})
|
})
|
||||||
.png()
|
.png()
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Failed to get image from url", url);
|
console.log('Failed to get image from url', url);
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageExtensions = ["svg", "png", "jpg", "jpeg", "gif", "webp", "ico"];
|
const imageExtensions = ['svg', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'ico'];
|
||||||
|
|
||||||
export async function getFavicon(
|
export async function getFavicon(
|
||||||
request: FastifyRequest<{
|
request: FastifyRequest<{
|
||||||
Querystring: GetFaviconParams;
|
Querystring: GetFaviconParams;
|
||||||
}>,
|
}>,
|
||||||
reply: FastifyReply,
|
reply: FastifyReply
|
||||||
) {
|
) {
|
||||||
function sendBuffer(buffer: Buffer, cacheKey?: string) {
|
function sendBuffer(buffer: Buffer, cacheKey?: string) {
|
||||||
if (cacheKey) {
|
if (cacheKey) {
|
||||||
redis.set(`favicon:${cacheKey}`, buffer.toString("base64"));
|
redis.set(`favicon:${cacheKey}`, buffer.toString('base64'));
|
||||||
}
|
}
|
||||||
reply.type("image/png");
|
reply.type('image/png');
|
||||||
console.log("buffer", buffer.byteLength);
|
console.log('buffer', buffer.byteLength);
|
||||||
|
|
||||||
return reply.send(buffer);
|
return reply.send(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!request.query.url) {
|
if (!request.query.url) {
|
||||||
return reply.status(404).send("Not found");
|
return reply.status(404).send('Not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = decodeURIComponent(request.query.url);
|
const url = decodeURIComponent(request.query.url);
|
||||||
@@ -69,7 +69,7 @@ export async function getFavicon(
|
|||||||
const cacheKey = createHash(url, 32);
|
const cacheKey = createHash(url, 32);
|
||||||
const cache = await redis.get(`favicon:${cacheKey}`);
|
const cache = await redis.get(`favicon:${cacheKey}`);
|
||||||
if (cache) {
|
if (cache) {
|
||||||
return sendBuffer(Buffer.from(cache, "base64"));
|
return sendBuffer(Buffer.from(cache, 'base64'));
|
||||||
}
|
}
|
||||||
const buffer = await getImageBuffer(url);
|
const buffer = await getImageBuffer(url);
|
||||||
if (buffer && buffer.byteLength > 0) {
|
if (buffer && buffer.byteLength > 0) {
|
||||||
@@ -80,7 +80,7 @@ export async function getFavicon(
|
|||||||
const { hostname, origin } = new URL(url);
|
const { hostname, origin } = new URL(url);
|
||||||
const cache = await redis.get(`favicon:${hostname}`);
|
const cache = await redis.get(`favicon:${hostname}`);
|
||||||
if (cache) {
|
if (cache) {
|
||||||
return sendBuffer(Buffer.from(cache, "base64"));
|
return sendBuffer(Buffer.from(cache, 'base64'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TRY FAVICON.ICO
|
// TRY FAVICON.ICO
|
||||||
@@ -94,7 +94,7 @@ export async function getFavicon(
|
|||||||
|
|
||||||
function findFavicon(res: string) {
|
function findFavicon(res: string) {
|
||||||
const match = res.match(
|
const match = res.match(
|
||||||
/(\<link(.+?)image\/x-icon(.+?)\>|\<link(.+?)shortcut\sicon(.+?)\>)/,
|
/(\<link(.+?)image\/x-icon(.+?)\>|\<link(.+?)shortcut\sicon(.+?)\>)/
|
||||||
);
|
);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
return null;
|
return null;
|
||||||
@@ -112,16 +112,16 @@ export async function getFavicon(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return reply.status(404).send("Not found");
|
return reply.status(404).send('Not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function clearFavicons(
|
export async function clearFavicons(
|
||||||
request: FastifyRequest,
|
request: FastifyRequest,
|
||||||
reply: FastifyReply,
|
reply: FastifyReply
|
||||||
) {
|
) {
|
||||||
const keys = await redis.keys("favicon:*");
|
const keys = await redis.keys('favicon:*');
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
await redis.del(key);
|
await redis.del(key);
|
||||||
}
|
}
|
||||||
return reply.status(404).send("OK");
|
return reply.status(404).send('OK');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import { getClientIp, parseIp } from "@/utils/parseIp";
|
import { getClientIp, parseIp } from '@/utils/parseIp';
|
||||||
import { isUserAgentSet, parseUserAgent } from "@/utils/parseUserAgent";
|
import { isUserAgentSet, parseUserAgent } from '@/utils/parseUserAgent';
|
||||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
import { assocPath, pathOr } from "ramda";
|
import { assocPath, pathOr } from 'ramda';
|
||||||
|
|
||||||
import { getProfileById, upsertProfile } from "@openpanel/db";
|
import { getProfileById, upsertProfile } from '@openpanel/db';
|
||||||
import type {
|
import type {
|
||||||
IncrementProfilePayload,
|
IncrementProfilePayload,
|
||||||
UpdateProfilePayload,
|
UpdateProfilePayload,
|
||||||
} from "@openpanel/sdk";
|
} from '@openpanel/sdk';
|
||||||
|
|
||||||
export async function updateProfile(
|
export async function updateProfile(
|
||||||
request: FastifyRequest<{
|
request: FastifyRequest<{
|
||||||
Body: UpdateProfilePayload;
|
Body: UpdateProfilePayload;
|
||||||
}>,
|
}>,
|
||||||
reply: FastifyReply,
|
reply: FastifyReply
|
||||||
) {
|
) {
|
||||||
const { profileId, properties, ...rest } = request.body;
|
const { profileId, properties, ...rest } = request.body;
|
||||||
const projectId = request.projectId;
|
const projectId = request.projectId;
|
||||||
const ip = getClientIp(request)!;
|
const ip = getClientIp(request)!;
|
||||||
const ua = request.headers["user-agent"]!;
|
const ua = request.headers['user-agent']!;
|
||||||
const uaInfo = parseUserAgent(ua);
|
const uaInfo = parseUserAgent(ua);
|
||||||
const geo = await parseIp(ip);
|
const geo = await parseIp(ip);
|
||||||
|
|
||||||
@@ -40,29 +40,29 @@ export async function incrementProfileProperty(
|
|||||||
request: FastifyRequest<{
|
request: FastifyRequest<{
|
||||||
Body: IncrementProfilePayload;
|
Body: IncrementProfilePayload;
|
||||||
}>,
|
}>,
|
||||||
reply: FastifyReply,
|
reply: FastifyReply
|
||||||
) {
|
) {
|
||||||
const { profileId, property, value } = request.body;
|
const { profileId, property, value } = request.body;
|
||||||
const projectId = request.projectId;
|
const projectId = request.projectId;
|
||||||
|
|
||||||
const profile = await getProfileById(profileId);
|
const profile = await getProfileById(profileId);
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
return reply.status(404).send("Not found");
|
return reply.status(404).send('Not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsed = parseInt(
|
const parsed = parseInt(
|
||||||
pathOr<string>("0", property.split("."), profile.properties),
|
pathOr<string>('0', property.split('.'), profile.properties),
|
||||||
10,
|
10
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isNaN(parsed)) {
|
if (isNaN(parsed)) {
|
||||||
return reply.status(400).send("Not number");
|
return reply.status(400).send('Not number');
|
||||||
}
|
}
|
||||||
|
|
||||||
profile.properties = assocPath(
|
profile.properties = assocPath(
|
||||||
property.split("."),
|
property.split('.'),
|
||||||
parsed + value,
|
parsed + value,
|
||||||
profile.properties,
|
profile.properties
|
||||||
);
|
);
|
||||||
|
|
||||||
await upsertProfile({
|
await upsertProfile({
|
||||||
@@ -78,29 +78,29 @@ export async function decrementProfileProperty(
|
|||||||
request: FastifyRequest<{
|
request: FastifyRequest<{
|
||||||
Body: IncrementProfilePayload;
|
Body: IncrementProfilePayload;
|
||||||
}>,
|
}>,
|
||||||
reply: FastifyReply,
|
reply: FastifyReply
|
||||||
) {
|
) {
|
||||||
const { profileId, property, value } = request.body;
|
const { profileId, property, value } = request.body;
|
||||||
const projectId = request.projectId;
|
const projectId = request.projectId;
|
||||||
|
|
||||||
const profile = await getProfileById(profileId);
|
const profile = await getProfileById(profileId);
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
return reply.status(404).send("Not found");
|
return reply.status(404).send('Not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsed = parseInt(
|
const parsed = parseInt(
|
||||||
pathOr<string>("0", property.split("."), profile.properties),
|
pathOr<string>('0', property.split('.'), profile.properties),
|
||||||
10,
|
10
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isNaN(parsed)) {
|
if (isNaN(parsed)) {
|
||||||
return reply.status(400).send("Not number");
|
return reply.status(400).send('Not number');
|
||||||
}
|
}
|
||||||
|
|
||||||
profile.properties = assocPath(
|
profile.properties = assocPath(
|
||||||
property.split("."),
|
property.split('.'),
|
||||||
parsed - value,
|
parsed - value,
|
||||||
profile.properties,
|
profile.properties
|
||||||
);
|
);
|
||||||
|
|
||||||
await upsertProfile({
|
await upsertProfile({
|
||||||
|
|||||||
@@ -1,43 +1,43 @@
|
|||||||
import cors from "@fastify/cors";
|
import cors from '@fastify/cors';
|
||||||
import Fastify from "fastify";
|
import Fastify from 'fastify';
|
||||||
|
|
||||||
import { redisPub } from "@openpanel/redis";
|
import { redisPub } from '@openpanel/redis';
|
||||||
|
|
||||||
import eventRouter from "./routes/event.router";
|
import eventRouter from './routes/event.router';
|
||||||
import liveRouter from "./routes/live.router";
|
import liveRouter from './routes/live.router';
|
||||||
import miscRouter from "./routes/misc.router";
|
import miscRouter from './routes/misc.router';
|
||||||
import profileRouter from "./routes/profile.router";
|
import profileRouter from './routes/profile.router';
|
||||||
import { logger, logInfo } from "./utils/logger";
|
import { logger, logInfo } from './utils/logger';
|
||||||
|
|
||||||
declare module "fastify" {
|
declare module 'fastify' {
|
||||||
interface FastifyRequest {
|
interface FastifyRequest {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const port = parseInt(process.env.API_PORT || "3000", 10);
|
const port = parseInt(process.env.API_PORT || '3000', 10);
|
||||||
|
|
||||||
const startServer = async () => {
|
const startServer = async () => {
|
||||||
logInfo("Starting server");
|
logInfo('Starting server');
|
||||||
try {
|
try {
|
||||||
const fastify = Fastify({
|
const fastify = Fastify({
|
||||||
logger: logger,
|
logger: logger,
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.register(cors, {
|
fastify.register(cors, {
|
||||||
origin: "*",
|
origin: '*',
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.decorateRequest("projectId", "");
|
fastify.decorateRequest('projectId', '');
|
||||||
fastify.register(eventRouter, { prefix: "/event" });
|
fastify.register(eventRouter, { prefix: '/event' });
|
||||||
fastify.register(profileRouter, { prefix: "/profile" });
|
fastify.register(profileRouter, { prefix: '/profile' });
|
||||||
fastify.register(liveRouter, { prefix: "/live" });
|
fastify.register(liveRouter, { prefix: '/live' });
|
||||||
fastify.register(miscRouter, { prefix: "/misc" });
|
fastify.register(miscRouter, { prefix: '/misc' });
|
||||||
fastify.setErrorHandler((error, request, reply) => {
|
fastify.setErrorHandler((error, request, reply) => {
|
||||||
fastify.log.error(error);
|
fastify.log.error(error);
|
||||||
});
|
});
|
||||||
fastify.get("/", (request, reply) => {
|
fastify.get('/', (request, reply) => {
|
||||||
reply.send({ name: "openpanel sdk api" });
|
reply.send({ name: 'openpanel sdk api' });
|
||||||
});
|
});
|
||||||
// fastify.get('/health-check', async (request, reply) => {
|
// fastify.get('/health-check', async (request, reply) => {
|
||||||
// try {
|
// try {
|
||||||
@@ -47,8 +47,8 @@ const startServer = async () => {
|
|||||||
// reply.status(500).send()
|
// reply.status(500).send()
|
||||||
// }
|
// }
|
||||||
// })
|
// })
|
||||||
if (process.env.NODE_ENV === "production") {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
for (const signal of ["SIGINT", "SIGTERM"]) {
|
for (const signal of ['SIGINT', 'SIGTERM']) {
|
||||||
process.on(signal, (err) => {
|
process.on(signal, (err) => {
|
||||||
logger.fatal(err, `uncaught exception detected ${signal}`);
|
logger.fatal(err, `uncaught exception detected ${signal}`);
|
||||||
fastify.close().then((err) => {
|
fastify.close().then((err) => {
|
||||||
@@ -59,12 +59,12 @@ const startServer = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await fastify.listen({
|
await fastify.listen({
|
||||||
host: process.env.NODE_ENV === "production" ? "0.0.0.0" : "localhost",
|
host: process.env.NODE_ENV === 'production' ? '0.0.0.0' : 'localhost',
|
||||||
port,
|
port,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Notify when keys expires
|
// Notify when keys expires
|
||||||
redisPub.config("SET", "notify-keyspace-events", "Ex");
|
redisPub.config('SET', 'notify-keyspace-events', 'Ex');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +1,56 @@
|
|||||||
import { isBot } from "@/bots";
|
import { isBot } from '@/bots';
|
||||||
import * as controller from "@/controllers/event.controller";
|
import * as controller from '@/controllers/event.controller';
|
||||||
import { validateSdkRequest } from "@/utils/auth";
|
import { validateSdkRequest } from '@/utils/auth';
|
||||||
import type { FastifyPluginCallback, FastifyRequest } from "fastify";
|
import type { FastifyPluginCallback, FastifyRequest } from 'fastify';
|
||||||
|
|
||||||
import { createBotEvent } from "@openpanel/db";
|
import { createBotEvent } from '@openpanel/db';
|
||||||
import type { PostEventPayload } from "@openpanel/sdk";
|
import type { PostEventPayload } from '@openpanel/sdk';
|
||||||
|
|
||||||
const eventRouter: FastifyPluginCallback = (fastify, opts, done) => {
|
const eventRouter: FastifyPluginCallback = (fastify, opts, done) => {
|
||||||
fastify.addHook(
|
fastify.addHook(
|
||||||
"preHandler",
|
'preHandler',
|
||||||
async (
|
async (
|
||||||
req: FastifyRequest<{
|
req: FastifyRequest<{
|
||||||
Body: PostEventPayload;
|
Body: PostEventPayload;
|
||||||
}>,
|
}>,
|
||||||
reply,
|
reply
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const projectId = await validateSdkRequest(req.headers);
|
const projectId = await validateSdkRequest(req.headers);
|
||||||
req.projectId = projectId;
|
req.projectId = projectId;
|
||||||
|
|
||||||
const bot = req.headers["user-agent"]
|
const bot = req.headers['user-agent']
|
||||||
? isBot(req.headers["user-agent"])
|
? isBot(req.headers['user-agent'])
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (bot) {
|
if (bot) {
|
||||||
const path = (req.body?.properties?.__path ||
|
const path = (req.body?.properties?.__path ||
|
||||||
req.body?.properties?.path) as string | undefined;
|
req.body?.properties?.path) as string | undefined;
|
||||||
req.log.warn({ ...req.headers, bot }, "Bot detected (event)");
|
req.log.warn({ ...req.headers, bot }, 'Bot detected (event)');
|
||||||
await createBotEvent({
|
await createBotEvent({
|
||||||
...bot,
|
...bot,
|
||||||
projectId,
|
projectId,
|
||||||
path: path ?? "",
|
path: path ?? '',
|
||||||
createdAt: new Date(req.body?.timestamp),
|
createdAt: new Date(req.body?.timestamp),
|
||||||
});
|
});
|
||||||
reply.status(202).send("OK");
|
reply.status(202).send('OK');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
req.log.error(e, "Failed to create bot event");
|
req.log.error(e, 'Failed to create bot event');
|
||||||
reply.status(401).send();
|
reply.status(401).send();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
fastify.route({
|
fastify.route({
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
url: "/",
|
url: '/',
|
||||||
handler: controller.postEvent,
|
handler: controller.postEvent,
|
||||||
});
|
});
|
||||||
fastify.route({
|
fastify.route({
|
||||||
method: "GET",
|
method: 'GET',
|
||||||
url: "/",
|
url: '/',
|
||||||
handler: controller.postEvent,
|
handler: controller.postEvent,
|
||||||
});
|
});
|
||||||
done();
|
done();
|
||||||
|
|||||||
@@ -1,41 +1,41 @@
|
|||||||
import { stripTrailingSlash } from "@openpanel/common";
|
import { stripTrailingSlash } from '@openpanel/common';
|
||||||
|
|
||||||
import referrers from "../referrers";
|
import referrers from '../referrers';
|
||||||
|
|
||||||
function getHostname(url: string | undefined) {
|
function getHostname(url: string | undefined) {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return "";
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return new URL(url).hostname;
|
return new URL(url).hostname;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return "";
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseReferrer(url: string | undefined) {
|
export function parseReferrer(url: string | undefined) {
|
||||||
const hostname = getHostname(url);
|
const hostname = getHostname(url);
|
||||||
const match = referrers[hostname] ?? referrers[hostname.replace("www.", "")];
|
const match = referrers[hostname] ?? referrers[hostname.replace('www.', '')];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: match?.name ?? "",
|
name: match?.name ?? '',
|
||||||
type: match?.type ?? "unknown",
|
type: match?.type ?? 'unknown',
|
||||||
url: stripTrailingSlash(url ?? ""),
|
url: stripTrailingSlash(url ?? ''),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getReferrerWithQuery(
|
export function getReferrerWithQuery(
|
||||||
query: Record<string, string> | undefined,
|
query: Record<string, string> | undefined
|
||||||
) {
|
) {
|
||||||
if (!query) {
|
if (!query) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const source = query.utm_source ?? query.ref ?? query.utm_referrer ?? "";
|
const source = query.utm_source ?? query.ref ?? query.utm_referrer ?? '';
|
||||||
|
|
||||||
const match = Object.values(referrers).find(
|
const match = Object.values(referrers).find(
|
||||||
(referrer) => referrer.name.toLowerCase() === source?.toLowerCase(),
|
(referrer) => referrer.name.toLowerCase() === source?.toLowerCase()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
@@ -45,6 +45,6 @@ export function getReferrerWithQuery(
|
|||||||
return {
|
return {
|
||||||
name: match.name,
|
name: match.name,
|
||||||
type: match.type,
|
type: match.type,
|
||||||
url: "",
|
url: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"format": "prettier --write \"**/*.{tsx,mjs,ts,md,json}\"",
|
"format": "prettier --check \"**/*.{tsx,mjs,ts,md,json}\"",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"with-env": "dotenv -e ../../.env -c --"
|
"with-env": "dotenv -e ../../.env -c --"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,14 +13,15 @@ import {
|
|||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
|
import { ChevronRight, MoreHorizontal, PlusIcon, Trash } from 'lucide-react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getDefaultIntervalByDates,
|
getDefaultIntervalByDates,
|
||||||
getDefaultIntervalByRange,
|
getDefaultIntervalByRange,
|
||||||
} from '@openpanel/constants';
|
} from '@openpanel/constants';
|
||||||
import type { getReportsByDashboardId } from '@openpanel/db';
|
import type { getReportsByDashboardId } from '@openpanel/db';
|
||||||
import { ChevronRight, MoreHorizontal, PlusIcon, Trash } from 'lucide-react';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
|
|
||||||
import { OverviewReportRange } from '../../overview-sticky-header';
|
import { OverviewReportRange } from '../../overview-sticky-header';
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||||
import { getExists } from '@/server/pageExists';
|
import { getExists } from '@/server/pageExists';
|
||||||
import { getDashboardById, getReportsByDashboardId } from '@openpanel/db';
|
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
|
|
||||||
|
import { getDashboardById, getReportsByDashboardId } from '@openpanel/db';
|
||||||
|
|
||||||
import { ListReports } from './list-reports';
|
import { ListReports } from './list-reports';
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { ToastAction } from '@/components/ui/toast';
|
import { ToastAction } from '@/components/ui/toast';
|
||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { pushModal } from '@/modals';
|
import { pushModal } from '@/modals';
|
||||||
import type { IServiceDashboards } from '@openpanel/db';
|
|
||||||
import { LayoutPanelTopIcon, Pencil, PlusIcon, Trash } from 'lucide-react';
|
import { LayoutPanelTopIcon, Pencil, PlusIcon, Trash } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import type { IServiceDashboards } from '@openpanel/db';
|
||||||
|
|
||||||
interface ListDashboardsProps {
|
interface ListDashboardsProps {
|
||||||
dashboards: IServiceDashboards;
|
dashboards: IServiceDashboards;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||||
import { getExists } from '@/server/pageExists';
|
import { getExists } from '@/server/pageExists';
|
||||||
|
|
||||||
import { getDashboardsByProjectId } from '@openpanel/db';
|
import { getDashboardsByProjectId } from '@openpanel/db';
|
||||||
|
|
||||||
import { HeaderDashboards } from './header-dashboards';
|
import { HeaderDashboards } from './header-dashboards';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ChartSwitchShortcut } from '@/components/report/chart';
|
import { ChartSwitchShortcut } from '@/components/report/chart';
|
||||||
|
|
||||||
import type { IChartEvent } from '@openpanel/validation';
|
import type { IChartEvent } from '@openpanel/validation';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ import {
|
|||||||
useEventQueryFilters,
|
useEventQueryFilters,
|
||||||
useEventQueryNamesFilter,
|
useEventQueryNamesFilter,
|
||||||
} from '@/hooks/useEventQueryFilters';
|
} from '@/hooks/useEventQueryFilters';
|
||||||
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
|
||||||
import { round } from 'mathjs';
|
import { round } from 'mathjs';
|
||||||
|
|
||||||
|
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
event: IServiceCreateEventPayload;
|
event: IServiceCreateEventPayload;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
|||||||
@@ -12,10 +12,11 @@ import {
|
|||||||
SheetTitle,
|
SheetTitle,
|
||||||
} from '@/components/ui/sheet';
|
} from '@/components/ui/sheet';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EventIconColors,
|
EventIconColors,
|
||||||
EventIconMapper,
|
EventIconMapper,
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
SheetTrigger,
|
SheetTrigger,
|
||||||
} from '@/components/ui/sheet';
|
} from '@/components/ui/sheet';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
import type { EventMeta } from '@openpanel/db';
|
|
||||||
import type { VariantProps } from 'class-variance-authority';
|
import type { VariantProps } from 'class-variance-authority';
|
||||||
import { cva } from 'class-variance-authority';
|
import { cva } from 'class-variance-authority';
|
||||||
import type { LucideIcon } from 'lucide-react';
|
import type { LucideIcon } from 'lucide-react';
|
||||||
@@ -20,6 +19,8 @@ import * as Icons from 'lucide-react';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import type { EventMeta } from '@openpanel/db';
|
||||||
|
|
||||||
const variants = cva('flex items-center justify-center shrink-0 rounded-full', {
|
const variants = cva('flex items-center justify-center shrink-0 rounded-full', {
|
||||||
variants: {
|
variants: {
|
||||||
size: {
|
size: {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
|
|||||||
import { useNumber } from '@/hooks/useNumerFormatter';
|
import { useNumber } from '@/hooks/useNumerFormatter';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
import { getProfileName } from '@/utils/getters';
|
import { getProfileName } from '@/utils/getters';
|
||||||
|
|
||||||
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
||||||
|
|
||||||
import { EventDetails } from './event-details';
|
import { EventDetails } from './event-details';
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { useCursor } from '@/hooks/useCursor';
|
import { useCursor } from '@/hooks/useCursor';
|
||||||
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
|
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
|
||||||
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
|
||||||
import { isSameDay } from 'date-fns';
|
import { isSameDay } from 'date-fns';
|
||||||
import { GanttChartIcon } from 'lucide-react';
|
import { GanttChartIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
||||||
|
|
||||||
import { EventListItem } from './event-list-item';
|
import { EventListItem } from './event-list-item';
|
||||||
import EventListener from './event-listener';
|
import EventListener from './event-listener';
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ import {
|
|||||||
} from '@/components/ui/tooltip';
|
} from '@/components/ui/tooltip';
|
||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import useWebSocket from 'react-use-websocket';
|
import useWebSocket from 'react-use-websocket';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
||||||
|
|
||||||
const AnimatedNumbers = dynamic(() => import('react-animated-numbers'), {
|
const AnimatedNumbers = dynamic(() => import('react-animated-numbers'), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
loading: () => <div>0</div>,
|
loading: () => <div>0</div>,
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import {
|
|||||||
eventQueryNamesFilter,
|
eventQueryNamesFilter,
|
||||||
} from '@/hooks/useEventQueryFilters';
|
} from '@/hooks/useEventQueryFilters';
|
||||||
import { getExists } from '@/server/pageExists';
|
import { getExists } from '@/server/pageExists';
|
||||||
import { getEventList, getEventsCount } from '@openpanel/db';
|
|
||||||
import { parseAsInteger } from 'nuqs';
|
import { parseAsInteger } from 'nuqs';
|
||||||
|
|
||||||
|
import { getEventList, getEventsCount } from '@openpanel/db';
|
||||||
|
|
||||||
import { StickyBelowHeader } from '../layout-sticky-below-header';
|
import { StickyBelowHeader } from '../layout-sticky-below-header';
|
||||||
import { EventChart } from './event-chart';
|
import { EventChart } from './event-chart';
|
||||||
import { EventList } from './event-list';
|
import { EventList } from './event-list';
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
import { Combobox } from '@/components/ui/combobox';
|
import { Combobox } from '@/components/ui/combobox';
|
||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import type { IServiceOrganization } from '@openpanel/db';
|
|
||||||
import { Building } from 'lucide-react';
|
import { Building } from 'lucide-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
import type { IServiceOrganization } from '@openpanel/db';
|
||||||
|
|
||||||
interface LayoutOrganizationSelectorProps {
|
interface LayoutOrganizationSelectorProps {
|
||||||
organizations: IServiceOrganization[];
|
organizations: IServiceOrganization[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
import { Combobox } from '@/components/ui/combobox';
|
import { Combobox } from '@/components/ui/combobox';
|
||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import type { getProjectsByOrganizationSlug } from '@openpanel/db';
|
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
import type { getProjectsByOrganizationSlug } from '@openpanel/db';
|
||||||
|
|
||||||
interface LayoutProjectSelectorProps {
|
interface LayoutProjectSelectorProps {
|
||||||
projects: Awaited<ReturnType<typeof getProjectsByOrganizationSlug>>;
|
projects: Awaited<ReturnType<typeof getProjectsByOrganizationSlug>>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import { useEffect, useState } from 'react';
|
|||||||
import { Logo } from '@/components/Logo';
|
import { Logo } from '@/components/Logo';
|
||||||
import { buttonVariants } from '@/components/ui/button';
|
import { buttonVariants } from '@/components/ui/button';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
import type { IServiceDashboards, IServiceOrganization } from '@openpanel/db';
|
|
||||||
import { Rotate as Hamburger } from 'hamburger-react';
|
import { Rotate as Hamburger } from 'hamburger-react';
|
||||||
import { PlusIcon } from 'lucide-react';
|
import { PlusIcon } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
|
import type { IServiceDashboards, IServiceOrganization } from '@openpanel/db';
|
||||||
|
|
||||||
import LayoutMenu from './layout-menu';
|
import LayoutMenu from './layout-menu';
|
||||||
import LayoutOrganizationSelector from './layout-organization-selector';
|
import LayoutOrganizationSelector from './layout-organization-selector';
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import {
|
|||||||
import { getExists } from '@/server/pageExists';
|
import { getExists } from '@/server/pageExists';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
import { getProfileName } from '@/utils/getters';
|
import { getProfileName } from '@/utils/getters';
|
||||||
|
import { notFound } from 'next/navigation';
|
||||||
|
import { parseAsInteger, parseAsString } from 'nuqs';
|
||||||
|
|
||||||
import type { GetEventListOptions } from '@openpanel/db';
|
import type { GetEventListOptions } from '@openpanel/db';
|
||||||
import {
|
import {
|
||||||
getConversionEventNames,
|
getConversionEventNames,
|
||||||
@@ -20,8 +23,6 @@ import {
|
|||||||
getProfileById,
|
getProfileById,
|
||||||
} from '@openpanel/db';
|
} from '@openpanel/db';
|
||||||
import type { IChartEvent, IChartInput } from '@openpanel/validation';
|
import type { IChartEvent, IChartInput } from '@openpanel/validation';
|
||||||
import { notFound } from 'next/navigation';
|
|
||||||
import { parseAsInteger, parseAsString } from 'nuqs';
|
|
||||||
|
|
||||||
import { EventList } from '../../events/event-list';
|
import { EventList } from '../../events/event-list';
|
||||||
import { StickyBelowHeader } from '../../layout-sticky-below-header';
|
import { StickyBelowHeader } from '../../layout-sticky-below-header';
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { OverviewFiltersButtons } from '@/components/overview/filters/overview-f
|
|||||||
import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer';
|
import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer';
|
||||||
import { eventQueryFiltersParser } from '@/hooks/useEventQueryFilters';
|
import { eventQueryFiltersParser } from '@/hooks/useEventQueryFilters';
|
||||||
import { getExists } from '@/server/pageExists';
|
import { getExists } from '@/server/pageExists';
|
||||||
import { getProfileList, getProfileListCount } from '@openpanel/db';
|
|
||||||
import { parseAsInteger } from 'nuqs';
|
import { parseAsInteger } from 'nuqs';
|
||||||
|
|
||||||
|
import { getProfileList, getProfileListCount } from '@openpanel/db';
|
||||||
|
|
||||||
import { StickyBelowHeader } from '../layout-sticky-below-header';
|
import { StickyBelowHeader } from '../layout-sticky-below-header';
|
||||||
import { ProfileList } from './profile-list';
|
import { ProfileList } from './profile-list';
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { KeyValue, KeyValueSubtle } from '@/components/ui/key-value';
|
|||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
|
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
|
||||||
import { getProfileName } from '@/utils/getters';
|
import { getProfileName } from '@/utils/getters';
|
||||||
|
|
||||||
import type { IServiceProfile } from '@openpanel/db';
|
import type { IServiceProfile } from '@openpanel/db';
|
||||||
|
|
||||||
type ProfileListItemProps = IServiceProfile;
|
type ProfileListItemProps = IServiceProfile;
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import { Pagination } from '@/components/Pagination';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useCursor } from '@/hooks/useCursor';
|
import { useCursor } from '@/hooks/useCursor';
|
||||||
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
|
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
|
||||||
import type { IServiceProfile } from '@openpanel/db';
|
|
||||||
import { UsersIcon } from 'lucide-react';
|
import { UsersIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
import type { IServiceProfile } from '@openpanel/db';
|
||||||
|
|
||||||
import { ProfileListItem } from './profile-list-item';
|
import { ProfileListItem } from './profile-list-item';
|
||||||
|
|
||||||
interface ProfileListProps {
|
interface ProfileListProps {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||||
import { getExists } from '@/server/pageExists';
|
import { getExists } from '@/server/pageExists';
|
||||||
import { getOrganizationBySlug, getReportById } from '@openpanel/db';
|
|
||||||
import { Pencil } from 'lucide-react';
|
import { Pencil } from 'lucide-react';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
|
|
||||||
|
import { getOrganizationBySlug, getReportById } from '@openpanel/db';
|
||||||
|
|
||||||
import ReportEditor from '../report-editor';
|
import ReportEditor from '../report-editor';
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||||
import { getExists } from '@/server/pageExists';
|
import { getExists } from '@/server/pageExists';
|
||||||
import { getOrganizationBySlug } from '@openpanel/db';
|
|
||||||
import { Pencil } from 'lucide-react';
|
import { Pencil } from 'lucide-react';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
|
|
||||||
|
import { getOrganizationBySlug } from '@openpanel/db';
|
||||||
|
|
||||||
import ReportEditor from './report-editor';
|
import ReportEditor from './report-editor';
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
|
|||||||
@@ -22,10 +22,11 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { useDispatch, useSelector } from '@/redux';
|
import { useDispatch, useSelector } from '@/redux';
|
||||||
import type { IServiceReport } from '@openpanel/db';
|
|
||||||
import { endOfDay, startOfDay } from 'date-fns';
|
import { endOfDay, startOfDay } from 'date-fns';
|
||||||
import { GanttChartSquareIcon } from 'lucide-react';
|
import { GanttChartSquareIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
import type { IServiceReport } from '@openpanel/db';
|
||||||
|
|
||||||
interface ReportEditorProps {
|
interface ReportEditorProps {
|
||||||
report: IServiceReport | null;
|
report: IServiceReport | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import { DataTable } from '@/components/DataTable';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { pushModal } from '@/modals';
|
import { pushModal } from '@/modals';
|
||||||
import type { getClientsByOrganizationId } from '@openpanel/db';
|
|
||||||
import { PlusIcon } from 'lucide-react';
|
import { PlusIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
import type { getClientsByOrganizationId } from '@openpanel/db';
|
||||||
|
|
||||||
interface ListClientsProps {
|
interface ListClientsProps {
|
||||||
clients: Awaited<ReturnType<typeof getClientsByOrganizationId>>;
|
clients: Awaited<ReturnType<typeof getClientsByOrganizationId>>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||||
import { getExists } from '@/server/pageExists';
|
import { getExists } from '@/server/pageExists';
|
||||||
|
|
||||||
import { getClientsByOrganizationId } from '@openpanel/db';
|
import { getClientsByOrganizationId } from '@openpanel/db';
|
||||||
|
|
||||||
import ListClients from './list-clients';
|
import ListClients from './list-clients';
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import { api, handleError } from '@/app/_trpc/client';
|
|||||||
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
|
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
|
||||||
import type { getOrganizationBySlug } from '@openpanel/db';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import type { getOrganizationBySlug } from '@openpanel/db';
|
||||||
|
|
||||||
const validator = z.object({
|
const validator = z.object({
|
||||||
id: z.string().min(2),
|
id: z.string().min(2),
|
||||||
name: z.string().min(2),
|
name: z.string().min(2),
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { zInviteUser } from '@openpanel/validation';
|
|
||||||
import { SendIcon } from 'lucide-react';
|
import { SendIcon } from 'lucide-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
|
|
||||||
|
import { zInviteUser } from '@openpanel/validation';
|
||||||
|
|
||||||
type IForm = z.infer<typeof zInviteUser>;
|
type IForm = z.infer<typeof zInviteUser>;
|
||||||
|
|
||||||
export function InviteUser() {
|
export function InviteUser() {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from '@/components/ui/table';
|
} from '@/components/ui/table';
|
||||||
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
|
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
|
||||||
|
|
||||||
import type { IServiceInvites } from '@openpanel/db';
|
import type { IServiceInvites } from '@openpanel/db';
|
||||||
|
|
||||||
import { InviteUser } from './invite-user';
|
import { InviteUser } from './invite-user';
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||||
import { clerkClient } from '@clerk/nextjs';
|
import { clerkClient } from '@clerk/nextjs';
|
||||||
import { getInvites, getOrganizationBySlug } from '@openpanel/db';
|
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
|
|
||||||
|
import { getInvites, getOrganizationBySlug } from '@openpanel/db';
|
||||||
|
|
||||||
import EditOrganization from './edit-organization';
|
import EditOrganization from './edit-organization';
|
||||||
import InvitedUsers from './invited-users';
|
import InvitedUsers from './invited-users';
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
|
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import type { getUserById } from '@openpanel/db';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import type { getUserById } from '@openpanel/db';
|
||||||
|
|
||||||
const validator = z.object({
|
const validator = z.object({
|
||||||
firstName: z.string().min(2),
|
firstName: z.string().min(2),
|
||||||
lastName: z.string().min(2),
|
lastName: z.string().min(2),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||||
import { getExists } from '@/server/pageExists';
|
import { getExists } from '@/server/pageExists';
|
||||||
import { auth } from '@clerk/nextjs';
|
import { auth } from '@clerk/nextjs';
|
||||||
|
|
||||||
import { getUserById } from '@openpanel/db';
|
import { getUserById } from '@openpanel/db';
|
||||||
|
|
||||||
import EditProfile from './edit-profile';
|
import EditProfile from './edit-profile';
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import { columns } from '@/components/projects/table';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { pushModal } from '@/modals';
|
import { pushModal } from '@/modals';
|
||||||
import type { getProjectsByOrganizationSlug } from '@openpanel/db';
|
|
||||||
import { PlusIcon } from 'lucide-react';
|
import { PlusIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
import type { getProjectsByOrganizationSlug } from '@openpanel/db';
|
||||||
|
|
||||||
interface ListProjectsProps {
|
interface ListProjectsProps {
|
||||||
projects: Awaited<ReturnType<typeof getProjectsByOrganizationSlug>>;
|
projects: Awaited<ReturnType<typeof getProjectsByOrganizationSlug>>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||||
import { getExists } from '@/server/pageExists';
|
import { getExists } from '@/server/pageExists';
|
||||||
|
|
||||||
import { getProjectsByOrganizationSlug } from '@openpanel/db';
|
import { getProjectsByOrganizationSlug } from '@openpanel/db';
|
||||||
|
|
||||||
import ListProjects from './list-projects';
|
import ListProjects from './list-projects';
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import { DataTable } from '@/components/DataTable';
|
|||||||
import { columns } from '@/components/references/table';
|
import { columns } from '@/components/references/table';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { pushModal } from '@/modals';
|
import { pushModal } from '@/modals';
|
||||||
import type { IServiceReference } from '@openpanel/db';
|
|
||||||
import { PlusIcon } from 'lucide-react';
|
import { PlusIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
import type { IServiceReference } from '@openpanel/db';
|
||||||
|
|
||||||
interface ListProjectsProps {
|
interface ListProjectsProps {
|
||||||
data: IServiceReference[];
|
data: IServiceReference[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||||
import { getExists } from '@/server/pageExists';
|
import { getExists } from '@/server/pageExists';
|
||||||
|
|
||||||
import { getReferences } from '@openpanel/db';
|
import { getReferences } from '@openpanel/db';
|
||||||
|
|
||||||
import ListReferences from './list-references';
|
import ListReferences from './list-references';
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ import OverviewTopEvents from '@/components/overview/overview-top-events';
|
|||||||
import OverviewTopGeo from '@/components/overview/overview-top-geo';
|
import OverviewTopGeo from '@/components/overview/overview-top-geo';
|
||||||
import OverviewTopPages from '@/components/overview/overview-top-pages';
|
import OverviewTopPages from '@/components/overview/overview-top-pages';
|
||||||
import OverviewTopSources from '@/components/overview/overview-top-sources';
|
import OverviewTopSources from '@/components/overview/overview-top-sources';
|
||||||
import { getOrganizationBySlug, getShareOverviewById } from '@openpanel/db';
|
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
|
|
||||||
|
import { getOrganizationBySlug, getShareOverviewById } from '@openpanel/db';
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
params: {
|
params: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -3,11 +3,12 @@
|
|||||||
import { api } from '@/app/_trpc/client';
|
import { api } from '@/app/_trpc/client';
|
||||||
import { pushModal, showConfirm } from '@/modals';
|
import { pushModal, showConfirm } from '@/modals';
|
||||||
import { clipboard } from '@/utils/clipboard';
|
import { clipboard } from '@/utils/clipboard';
|
||||||
import type { IServiceClientWithProject } from '@openpanel/db';
|
|
||||||
import { MoreHorizontal } from 'lucide-react';
|
import { MoreHorizontal } from 'lucide-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import type { IServiceClientWithProject } from '@openpanel/db';
|
||||||
|
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { formatDate } from '@/utils/date';
|
import { formatDate } from '@/utils/date';
|
||||||
import type { IServiceClientWithProject } from '@openpanel/db';
|
|
||||||
import type { ColumnDef } from '@tanstack/react-table';
|
import type { ColumnDef } from '@tanstack/react-table';
|
||||||
|
|
||||||
|
import type { IServiceClientWithProject } from '@openpanel/db';
|
||||||
|
|
||||||
import { ClientActions } from './ClientActions';
|
import { ClientActions } from './ClientActions';
|
||||||
|
|
||||||
export const columns: ColumnDef<IServiceClientWithProject>[] = [
|
export const columns: ColumnDef<IServiceClientWithProject>[] = [
|
||||||
|
|||||||
@@ -11,13 +11,14 @@ import {
|
|||||||
import { useEventValues } from '@/hooks/useEventValues';
|
import { useEventValues } from '@/hooks/useEventValues';
|
||||||
import { useProfileProperties } from '@/hooks/useProfileProperties';
|
import { useProfileProperties } from '@/hooks/useProfileProperties';
|
||||||
import { useProfileValues } from '@/hooks/useProfileValues';
|
import { useProfileValues } from '@/hooks/useProfileValues';
|
||||||
|
import { XIcon } from 'lucide-react';
|
||||||
|
import type { Options as NuqsOptions } from 'nuqs';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
IChartEventFilter,
|
IChartEventFilter,
|
||||||
IChartEventFilterOperator,
|
IChartEventFilterOperator,
|
||||||
IChartEventFilterValue,
|
IChartEventFilterValue,
|
||||||
} from '@openpanel/validation';
|
} from '@openpanel/validation';
|
||||||
import { XIcon } from 'lucide-react';
|
|
||||||
import type { Options as NuqsOptions } from 'nuqs';
|
|
||||||
|
|
||||||
export interface OverviewFiltersDrawerContentProps {
|
export interface OverviewFiltersDrawerContentProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import { api } from '@/app/_trpc/client';
|
import { api } from '@/app/_trpc/client';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
import type { IChartInput } from '@openpanel/validation';
|
|
||||||
import { ChevronsUpDownIcon } from 'lucide-react';
|
import { ChevronsUpDownIcon } from 'lucide-react';
|
||||||
import AnimateHeight from 'react-animate-height';
|
import AnimateHeight from 'react-animate-height';
|
||||||
|
|
||||||
|
import type { IChartInput } from '@openpanel/validation';
|
||||||
|
|
||||||
import { redisSub } from '../../../../../packages/redis';
|
import { redisSub } from '../../../../../packages/redis';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
|
||||||
import { useOverviewOptions } from './useOverviewOptions';
|
import { useOverviewOptions } from './useOverviewOptions';
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ChartSwitch } from '@/components/report/chart';
|
|||||||
import { Widget, WidgetBody } from '@/components/Widget';
|
import { Widget, WidgetBody } from '@/components/Widget';
|
||||||
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
|
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
|
|
||||||
import type { IChartInput } from '@openpanel/validation';
|
import type { IChartInput } from '@openpanel/validation';
|
||||||
|
|
||||||
interface OverviewMetricsProps {
|
interface OverviewMetricsProps {
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
import { api } from '@/app/_trpc/client';
|
import { api } from '@/app/_trpc/client';
|
||||||
import { pushModal } from '@/modals';
|
import { pushModal } from '@/modals';
|
||||||
import type { ShareOverview } from '@openpanel/db';
|
|
||||||
import { EyeIcon, Globe2Icon, LockIcon } from 'lucide-react';
|
import { EyeIcon, Globe2Icon, LockIcon } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
import type { ShareOverview } from '@openpanel/db';
|
||||||
|
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
import {
|
|
||||||
getDefaultIntervalByDates,
|
|
||||||
getDefaultIntervalByRange,
|
|
||||||
timeRanges,
|
|
||||||
} from '@openpanel/constants';
|
|
||||||
import { mapKeys } from '@openpanel/validation';
|
|
||||||
import {
|
import {
|
||||||
parseAsBoolean,
|
parseAsBoolean,
|
||||||
parseAsInteger,
|
parseAsInteger,
|
||||||
@@ -12,6 +6,13 @@ import {
|
|||||||
useQueryState,
|
useQueryState,
|
||||||
} from 'nuqs';
|
} from 'nuqs';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getDefaultIntervalByDates,
|
||||||
|
getDefaultIntervalByRange,
|
||||||
|
timeRanges,
|
||||||
|
} from '@openpanel/constants';
|
||||||
|
import { mapKeys } from '@openpanel/validation';
|
||||||
|
|
||||||
const nuqsOptions = { history: 'push' } as const;
|
const nuqsOptions = { history: 'push' } as const;
|
||||||
|
|
||||||
export function useOverviewOptions() {
|
export function useOverviewOptions() {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import { parseAsStringEnum, useQueryState } from 'nuqs';
|
||||||
|
|
||||||
import { mapKeys } from '@openpanel/validation';
|
import { mapKeys } from '@openpanel/validation';
|
||||||
import type { IChartInput } from '@openpanel/validation';
|
import type { IChartInput } from '@openpanel/validation';
|
||||||
import { parseAsStringEnum, useQueryState } from 'nuqs';
|
|
||||||
|
|
||||||
export function useOverviewWidget<T extends string>(
|
export function useOverviewWidget<T extends string>(
|
||||||
key: string,
|
key: string,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
import type { IServiceProfile } from '@openpanel/db';
|
|
||||||
import { AvatarImage } from '@radix-ui/react-avatar';
|
import { AvatarImage } from '@radix-ui/react-avatar';
|
||||||
import type { VariantProps } from 'class-variance-authority';
|
import type { VariantProps } from 'class-variance-authority';
|
||||||
import { cva } from 'class-variance-authority';
|
import { cva } from 'class-variance-authority';
|
||||||
|
|
||||||
|
import type { IServiceProfile } from '@openpanel/db';
|
||||||
|
|
||||||
import { Avatar, AvatarFallback } from '../ui/avatar';
|
import { Avatar, AvatarFallback } from '../ui/avatar';
|
||||||
|
|
||||||
interface ProfileAvatarProps
|
interface ProfileAvatarProps
|
||||||
|
|||||||
@@ -3,11 +3,12 @@
|
|||||||
import { api } from '@/app/_trpc/client';
|
import { api } from '@/app/_trpc/client';
|
||||||
import { pushModal, showConfirm } from '@/modals';
|
import { pushModal, showConfirm } from '@/modals';
|
||||||
import { clipboard } from '@/utils/clipboard';
|
import { clipboard } from '@/utils/clipboard';
|
||||||
import type { IServiceProject } from '@openpanel/db';
|
|
||||||
import { MoreHorizontal } from 'lucide-react';
|
import { MoreHorizontal } from 'lucide-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import type { IServiceProject } from '@openpanel/db';
|
||||||
|
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { formatDate } from '@/utils/date';
|
import { formatDate } from '@/utils/date';
|
||||||
|
import type { ColumnDef } from '@tanstack/react-table';
|
||||||
|
|
||||||
import { IServiceProject } from '@openpanel/db';
|
import { IServiceProject } from '@openpanel/db';
|
||||||
import type { Project as IProject } from '@openpanel/db';
|
import type { Project as IProject } from '@openpanel/db';
|
||||||
import type { ColumnDef } from '@tanstack/react-table';
|
|
||||||
|
|
||||||
import { ProjectActions } from './ProjectActions';
|
import { ProjectActions } from './ProjectActions';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { formatDate, formatDateTime } from '@/utils/date';
|
import { formatDate, formatDateTime } from '@/utils/date';
|
||||||
import type { IServiceReference } from '@openpanel/db';
|
|
||||||
import type { ColumnDef } from '@tanstack/react-table';
|
import type { ColumnDef } from '@tanstack/react-table';
|
||||||
|
|
||||||
|
import type { IServiceReference } from '@openpanel/db';
|
||||||
|
|
||||||
export const columns: ColumnDef<IServiceReference>[] = [
|
export const columns: ColumnDef<IServiceReference>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: 'title',
|
accessorKey: 'title',
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { useDispatch, useSelector } from '@/redux';
|
import { useDispatch, useSelector } from '@/redux';
|
||||||
|
import { LineChartIcon } from 'lucide-react';
|
||||||
|
|
||||||
import { chartTypes } from '@openpanel/constants';
|
import { chartTypes } from '@openpanel/constants';
|
||||||
import { objectToZodEnums } from '@openpanel/validation';
|
import { objectToZodEnums } from '@openpanel/validation';
|
||||||
import { LineChartIcon } from 'lucide-react';
|
|
||||||
|
|
||||||
import { Combobox } from '../ui/combobox';
|
import { Combobox } from '../ui/combobox';
|
||||||
import { changeChartType } from './reportSlice';
|
import { changeChartType } from './reportSlice';
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { useDispatch, useSelector } from '@/redux';
|
import { useDispatch, useSelector } from '@/redux';
|
||||||
|
import { ClockIcon } from 'lucide-react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isHourIntervalEnabledByRange,
|
isHourIntervalEnabledByRange,
|
||||||
isMinuteIntervalEnabledByRange,
|
isMinuteIntervalEnabledByRange,
|
||||||
} from '@openpanel/constants';
|
} from '@openpanel/constants';
|
||||||
import type { IInterval } from '@openpanel/validation';
|
import type { IInterval } from '@openpanel/validation';
|
||||||
import { ClockIcon } from 'lucide-react';
|
|
||||||
|
|
||||||
import { Combobox } from '../ui/combobox';
|
import { Combobox } from '../ui/combobox';
|
||||||
import { changeInterval } from './reportSlice';
|
import { changeInterval } from './reportSlice';
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { useDispatch, useSelector } from '@/redux';
|
import { useDispatch, useSelector } from '@/redux';
|
||||||
|
import { Tv2Icon } from 'lucide-react';
|
||||||
|
|
||||||
import { lineTypes } from '@openpanel/constants';
|
import { lineTypes } from '@openpanel/constants';
|
||||||
import { objectToZodEnums } from '@openpanel/validation';
|
import { objectToZodEnums } from '@openpanel/validation';
|
||||||
import { Tv2Icon } from 'lucide-react';
|
|
||||||
|
|
||||||
import { Combobox } from '../ui/combobox';
|
import { Combobox } from '../ui/combobox';
|
||||||
import { changeLineType } from './reportSlice';
|
import { changeLineType } from './reportSlice';
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ import {
|
|||||||
import { useBreakpoint } from '@/hooks/useBreakpoint';
|
import { useBreakpoint } from '@/hooks/useBreakpoint';
|
||||||
import { useDispatch, useSelector } from '@/redux';
|
import { useDispatch, useSelector } from '@/redux';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
import { timeRanges } from '@openpanel/constants';
|
|
||||||
import type { IChartRange } from '@openpanel/validation';
|
|
||||||
import { endOfDay, format, startOfDay } from 'date-fns';
|
import { endOfDay, format, startOfDay } from 'date-fns';
|
||||||
import { CalendarIcon, ChevronsUpDownIcon } from 'lucide-react';
|
import { CalendarIcon, ChevronsUpDownIcon } from 'lucide-react';
|
||||||
import type { SelectRangeEventHandler } from 'react-day-picker';
|
import type { SelectRangeEventHandler } from 'react-day-picker';
|
||||||
|
|
||||||
|
import { timeRanges } from '@openpanel/constants';
|
||||||
|
import type { IChartRange } from '@openpanel/validation';
|
||||||
|
|
||||||
import type { ExtendedComboboxProps } from '../ui/combobox';
|
import type { ExtendedComboboxProps } from '../ui/combobox';
|
||||||
import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group';
|
import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group';
|
||||||
import { changeDates, changeEndDate, changeStartDate } from './reportSlice';
|
import { changeDates, changeEndDate, changeStartDate } from './reportSlice';
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { api } from '@/app/_trpc/client';
|
import { api } from '@/app/_trpc/client';
|
||||||
|
|
||||||
import type { IChartInput } from '@openpanel/validation';
|
import type { IChartInput } from '@openpanel/validation';
|
||||||
|
|
||||||
import { ChartEmpty } from './ChartEmpty';
|
import { ChartEmpty } from './ChartEmpty';
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import type { IChartSerie } from '@/server/api/routers/chart';
|
import type { IChartSerie } from '@/server/api/routers/chart';
|
||||||
|
|
||||||
import type { IChartInput } from '@openpanel/validation';
|
import type { IChartInput } from '@openpanel/validation';
|
||||||
|
|
||||||
import { ChartLoading } from './ChartLoading';
|
import { ChartLoading } from './ChartLoading';
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import type { IChartData } from '@/app/_trpc/client';
|
|||||||
import { ColorSquare } from '@/components/ColorSquare';
|
import { ColorSquare } from '@/components/ColorSquare';
|
||||||
import { fancyMinutes, useNumber } from '@/hooks/useNumerFormatter';
|
import { fancyMinutes, useNumber } from '@/hooks/useNumerFormatter';
|
||||||
import { theme } from '@/utils/theme';
|
import { theme } from '@/utils/theme';
|
||||||
import type { IChartMetric } from '@openpanel/validation';
|
|
||||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
import { Area, AreaChart } from 'recharts';
|
import { Area, AreaChart } from 'recharts';
|
||||||
|
|
||||||
|
import type { IChartMetric } from '@openpanel/validation';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getDiffIndicator,
|
getDiffIndicator,
|
||||||
PreviousDiffIndicatorText,
|
PreviousDiffIndicatorText,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { useNumber } from '@/hooks/useNumerFormatter';
|
|||||||
import { useRechartDataModel } from '@/hooks/useRechartDataModel';
|
import { useRechartDataModel } from '@/hooks/useRechartDataModel';
|
||||||
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
|
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
|
||||||
import { getChartColor } from '@/utils/theme';
|
import { getChartColor } from '@/utils/theme';
|
||||||
import type { IChartLineType, IInterval } from '@openpanel/validation';
|
|
||||||
import {
|
import {
|
||||||
Area,
|
Area,
|
||||||
AreaChart,
|
AreaChart,
|
||||||
@@ -15,6 +14,8 @@ import {
|
|||||||
YAxis,
|
YAxis,
|
||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
|
|
||||||
|
import type { IChartLineType, IInterval } from '@openpanel/validation';
|
||||||
|
|
||||||
import { getYAxisWidth } from './chart-utils';
|
import { getYAxisWidth } from './chart-utils';
|
||||||
import { useChartContext } from './ChartProvider';
|
import { useChartContext } from './ChartProvider';
|
||||||
import { ReportChartTooltip } from './ReportChartTooltip';
|
import { ReportChartTooltip } from './ReportChartTooltip';
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Progress } from '@/components/ui/progress';
|
|||||||
import { useNumber } from '@/hooks/useNumerFormatter';
|
import { useNumber } from '@/hooks/useNumerFormatter';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
import { getChartColor } from '@/utils/theme';
|
import { getChartColor } from '@/utils/theme';
|
||||||
|
|
||||||
import { NOT_SET_VALUE } from '@openpanel/constants';
|
import { NOT_SET_VALUE } from '@openpanel/constants';
|
||||||
|
|
||||||
import { PreviousDiffIndicatorText } from '../PreviousDiffIndicator';
|
import { PreviousDiffIndicatorText } from '../PreviousDiffIndicator';
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import { useNumber } from '@/hooks/useNumerFormatter';
|
|||||||
import { useRechartDataModel } from '@/hooks/useRechartDataModel';
|
import { useRechartDataModel } from '@/hooks/useRechartDataModel';
|
||||||
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
|
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
|
||||||
import { getChartColor, theme } from '@/utils/theme';
|
import { getChartColor, theme } from '@/utils/theme';
|
||||||
import type { IInterval } from '@openpanel/validation';
|
|
||||||
import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts';
|
import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts';
|
||||||
|
|
||||||
|
import type { IInterval } from '@openpanel/validation';
|
||||||
|
|
||||||
import { getYAxisWidth } from './chart-utils';
|
import { getYAxisWidth } from './chart-utils';
|
||||||
import { useChartContext } from './ChartProvider';
|
import { useChartContext } from './ChartProvider';
|
||||||
import { ReportChartTooltip } from './ReportChartTooltip';
|
import { ReportChartTooltip } from './ReportChartTooltip';
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import { useNumber } from '@/hooks/useNumerFormatter';
|
|||||||
import { useRechartDataModel } from '@/hooks/useRechartDataModel';
|
import { useRechartDataModel } from '@/hooks/useRechartDataModel';
|
||||||
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
|
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
|
||||||
import { getChartColor } from '@/utils/theme';
|
import { getChartColor } from '@/utils/theme';
|
||||||
import type { IServiceReference } from '@openpanel/db';
|
|
||||||
import type { IChartLineType, IInterval } from '@openpanel/validation';
|
|
||||||
import {
|
import {
|
||||||
CartesianGrid,
|
CartesianGrid,
|
||||||
Line,
|
Line,
|
||||||
@@ -19,6 +17,9 @@ import {
|
|||||||
YAxis,
|
YAxis,
|
||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
|
|
||||||
|
import type { IServiceReference } from '@openpanel/db';
|
||||||
|
import type { IChartLineType, IInterval } from '@openpanel/validation';
|
||||||
|
|
||||||
import { getYAxisWidth } from './chart-utils';
|
import { getYAxisWidth } from './chart-utils';
|
||||||
import { useChartContext } from './ChartProvider';
|
import { useChartContext } from './ChartProvider';
|
||||||
import { ReportChartTooltip } from './ReportChartTooltip';
|
import { ReportChartTooltip } from './ReportChartTooltip';
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { NOT_SET_VALUE } from '@openpanel/constants';
|
|
||||||
import type { LucideIcon, LucideProps } from 'lucide-react';
|
import type { LucideIcon, LucideProps } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
ActivityIcon,
|
ActivityIcon,
|
||||||
@@ -15,6 +14,8 @@ import {
|
|||||||
TabletIcon,
|
TabletIcon,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
import { NOT_SET_VALUE } from '@openpanel/constants';
|
||||||
|
|
||||||
interface SerieIconProps extends LucideProps {
|
interface SerieIconProps extends LucideProps {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import type { RouterOutputs } from '@/app/_trpc/client';
|
import type { RouterOutputs } from '@/app/_trpc/client';
|
||||||
import { api } from '@/app/_trpc/client';
|
import { api } from '@/app/_trpc/client';
|
||||||
|
|
||||||
import type { IChartInput } from '@openpanel/validation';
|
import type { IChartInput } from '@openpanel/validation';
|
||||||
|
|
||||||
import { ChartEmpty } from '../chart/ChartEmpty';
|
import { ChartEmpty } from '../chart/ChartEmpty';
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { start } from 'repl';
|
import { start } from 'repl';
|
||||||
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
import { isSameDay, isSameMonth } from 'date-fns';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
alphabetIds,
|
alphabetIds,
|
||||||
getDefaultIntervalByDates,
|
getDefaultIntervalByDates,
|
||||||
@@ -15,9 +19,6 @@ import type {
|
|||||||
IChartType,
|
IChartType,
|
||||||
IInterval,
|
IInterval,
|
||||||
} from '@openpanel/validation';
|
} from '@openpanel/validation';
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
|
||||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
|
||||||
import { isSameDay, isSameMonth } from 'date-fns';
|
|
||||||
|
|
||||||
type InitialState = IChartInput & {
|
type InitialState = IChartInput & {
|
||||||
dirty: boolean;
|
dirty: boolean;
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { Combobox } from '@/components/ui/combobox';
|
|||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { useDispatch } from '@/redux';
|
import { useDispatch } from '@/redux';
|
||||||
import { cn } from '@/utils/cn';
|
import { cn } from '@/utils/cn';
|
||||||
import type { IChartEvent } from '@openpanel/validation';
|
|
||||||
import { DatabaseIcon } from 'lucide-react';
|
import { DatabaseIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
import type { IChartEvent } from '@openpanel/validation';
|
||||||
|
|
||||||
import { changeEvent } from '../reportSlice';
|
import { changeEvent } from '../reportSlice';
|
||||||
|
|
||||||
interface EventPropertiesComboboxProps {
|
interface EventPropertiesComboboxProps {
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import { ColorSquare } from '@/components/ColorSquare';
|
|||||||
import { Combobox } from '@/components/ui/combobox';
|
import { Combobox } from '@/components/ui/combobox';
|
||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { useDispatch, useSelector } from '@/redux';
|
import { useDispatch, useSelector } from '@/redux';
|
||||||
import type { IChartBreakdown } from '@openpanel/validation';
|
|
||||||
import { SplitIcon } from 'lucide-react';
|
import { SplitIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
import type { IChartBreakdown } from '@openpanel/validation';
|
||||||
|
|
||||||
import { addBreakdown, changeBreakdown, removeBreakdown } from '../reportSlice';
|
import { addBreakdown, changeBreakdown, removeBreakdown } from '../reportSlice';
|
||||||
import { ReportBreakdownMore } from './ReportBreakdownMore';
|
import { ReportBreakdownMore } from './ReportBreakdownMore';
|
||||||
import type { ReportEventMoreProps } from './ReportEventMore';
|
import type { ReportEventMoreProps } from './ReportEventMore';
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import { useAppParams } from '@/hooks/useAppParams';
|
|||||||
import { useDebounceFn } from '@/hooks/useDebounceFn';
|
import { useDebounceFn } from '@/hooks/useDebounceFn';
|
||||||
import { useEventNames } from '@/hooks/useEventNames';
|
import { useEventNames } from '@/hooks/useEventNames';
|
||||||
import { useDispatch, useSelector } from '@/redux';
|
import { useDispatch, useSelector } from '@/redux';
|
||||||
import type { IChartEvent } from '@openpanel/validation';
|
|
||||||
import { GanttChart, GanttChartIcon, Users } from 'lucide-react';
|
import { GanttChart, GanttChartIcon, Users } from 'lucide-react';
|
||||||
|
|
||||||
|
import type { IChartEvent } from '@openpanel/validation';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addEvent,
|
addEvent,
|
||||||
changeEvent,
|
changeEvent,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { RenderDots } from '@/components/ui/RenderDots';
|
|||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { useMappings } from '@/hooks/useMappings';
|
import { useMappings } from '@/hooks/useMappings';
|
||||||
import { useDispatch } from '@/redux';
|
import { useDispatch } from '@/redux';
|
||||||
|
import { SlidersHorizontal, Trash } from 'lucide-react';
|
||||||
|
|
||||||
import { operators } from '@openpanel/constants';
|
import { operators } from '@openpanel/constants';
|
||||||
import type {
|
import type {
|
||||||
IChartEvent,
|
IChartEvent,
|
||||||
@@ -14,7 +16,6 @@ import type {
|
|||||||
IChartEventFilterValue,
|
IChartEventFilterValue,
|
||||||
} from '@openpanel/validation';
|
} from '@openpanel/validation';
|
||||||
import { mapKeys } from '@openpanel/validation';
|
import { mapKeys } from '@openpanel/validation';
|
||||||
import { SlidersHorizontal, Trash } from 'lucide-react';
|
|
||||||
|
|
||||||
import { changeEvent } from '../../reportSlice';
|
import { changeEvent } from '../../reportSlice';
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import { api } from '@/app/_trpc/client';
|
|||||||
import { Combobox } from '@/components/ui/combobox';
|
import { Combobox } from '@/components/ui/combobox';
|
||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { useDispatch } from '@/redux';
|
import { useDispatch } from '@/redux';
|
||||||
import type { IChartEvent } from '@openpanel/validation';
|
|
||||||
import { FilterIcon } from 'lucide-react';
|
import { FilterIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
import type { IChartEvent } from '@openpanel/validation';
|
||||||
|
|
||||||
import { changeEvent } from '../../reportSlice';
|
import { changeEvent } from '../../reportSlice';
|
||||||
|
|
||||||
interface FiltersComboboxProps {
|
interface FiltersComboboxProps {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react';
|
||||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
import { cn } from '@/utils/cn';
|
||||||
import { ChevronDown } from "lucide-react"
|
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||||
|
import { ChevronDown } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from "@/utils/cn"
|
const Accordion = AccordionPrimitive.Root;
|
||||||
|
|
||||||
const Accordion = AccordionPrimitive.Root
|
|
||||||
|
|
||||||
const AccordionItem = React.forwardRef<
|
const AccordionItem = React.forwardRef<
|
||||||
React.ElementRef<typeof AccordionPrimitive.Item>,
|
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||||
@@ -12,11 +11,11 @@ const AccordionItem = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<AccordionPrimitive.Item
|
<AccordionPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("border-b", className)}
|
className={cn('border-b', className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
AccordionItem.displayName = "AccordionItem"
|
AccordionItem.displayName = 'AccordionItem';
|
||||||
|
|
||||||
const AccordionTrigger = React.forwardRef<
|
const AccordionTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||||
@@ -26,7 +25,7 @@ const AccordionTrigger = React.forwardRef<
|
|||||||
<AccordionPrimitive.Trigger
|
<AccordionPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
'flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -35,8 +34,8 @@ const AccordionTrigger = React.forwardRef<
|
|||||||
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
||||||
</AccordionPrimitive.Trigger>
|
</AccordionPrimitive.Trigger>
|
||||||
</AccordionPrimitive.Header>
|
</AccordionPrimitive.Header>
|
||||||
))
|
));
|
||||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
const AccordionContent = React.forwardRef<
|
const AccordionContent = React.forwardRef<
|
||||||
React.ElementRef<typeof AccordionPrimitive.Content>,
|
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||||
@@ -47,10 +46,10 @@ const AccordionContent = React.forwardRef<
|
|||||||
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
<div className={cn('pb-4 pt-0', className)}>{children}</div>
|
||||||
</AccordionPrimitive.Content>
|
</AccordionPrimitive.Content>
|
||||||
))
|
));
|
||||||
|
|
||||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
||||||
|
|
||||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react';
|
||||||
import { ChevronLeft, ChevronRight } from "lucide-react"
|
import { buttonVariants } from '@/components/ui/button';
|
||||||
import { DayPicker } from "react-day-picker"
|
import { cn } from '@/utils/cn';
|
||||||
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
|
import { DayPicker } from 'react-day-picker';
|
||||||
|
|
||||||
import { cn } from "@/utils/cn"
|
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
|
||||||
import { buttonVariants } from "@/components/ui/button"
|
|
||||||
|
|
||||||
export type CalendarProps = React.ComponentProps<typeof DayPicker>
|
|
||||||
|
|
||||||
function Calendar({
|
function Calendar({
|
||||||
className,
|
className,
|
||||||
@@ -16,39 +15,39 @@ function Calendar({
|
|||||||
return (
|
return (
|
||||||
<DayPicker
|
<DayPicker
|
||||||
showOutsideDays={showOutsideDays}
|
showOutsideDays={showOutsideDays}
|
||||||
className={cn("p-3", className)}
|
className={cn('p-3', className)}
|
||||||
classNames={{
|
classNames={{
|
||||||
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
months: 'flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0',
|
||||||
month: "space-y-4",
|
month: 'space-y-4',
|
||||||
caption: "flex justify-center pt-1 relative items-center",
|
caption: 'flex justify-center pt-1 relative items-center',
|
||||||
caption_label: "text-sm font-medium",
|
caption_label: 'text-sm font-medium',
|
||||||
nav: "space-x-1 flex items-center",
|
nav: 'space-x-1 flex items-center',
|
||||||
nav_button: cn(
|
nav_button: cn(
|
||||||
buttonVariants({ variant: "outline" }),
|
buttonVariants({ variant: 'outline' }),
|
||||||
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100'
|
||||||
),
|
),
|
||||||
nav_button_previous: "absolute left-1",
|
nav_button_previous: 'absolute left-1',
|
||||||
nav_button_next: "absolute right-1",
|
nav_button_next: 'absolute right-1',
|
||||||
table: "w-full border-collapse space-y-1",
|
table: 'w-full border-collapse space-y-1',
|
||||||
head_row: "flex",
|
head_row: 'flex',
|
||||||
head_cell:
|
head_cell:
|
||||||
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
|
'text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]',
|
||||||
row: "flex w-full mt-2",
|
row: 'flex w-full mt-2',
|
||||||
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
|
cell: 'h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20',
|
||||||
day: cn(
|
day: cn(
|
||||||
buttonVariants({ variant: "ghost" }),
|
buttonVariants({ variant: 'ghost' }),
|
||||||
"h-9 w-9 p-0 font-normal aria-selected:opacity-100"
|
'h-9 w-9 p-0 font-normal aria-selected:opacity-100'
|
||||||
),
|
),
|
||||||
day_range_end: "day-range-end",
|
day_range_end: 'day-range-end',
|
||||||
day_selected:
|
day_selected:
|
||||||
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
|
||||||
day_today: "bg-accent text-accent-foreground",
|
day_today: 'bg-accent text-accent-foreground',
|
||||||
day_outside:
|
day_outside:
|
||||||
"day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
|
'day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',
|
||||||
day_disabled: "text-muted-foreground opacity-50",
|
day_disabled: 'text-muted-foreground opacity-50',
|
||||||
day_range_middle:
|
day_range_middle:
|
||||||
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
'aria-selected:bg-accent aria-selected:text-accent-foreground',
|
||||||
day_hidden: "invisible",
|
day_hidden: 'invisible',
|
||||||
...classNames,
|
...classNames,
|
||||||
}}
|
}}
|
||||||
components={{
|
components={{
|
||||||
@@ -57,8 +56,8 @@ function Calendar({
|
|||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
Calendar.displayName = "Calendar"
|
Calendar.displayName = 'Calendar';
|
||||||
|
|
||||||
export { Calendar }
|
export { Calendar };
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react';
|
||||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
import { cn } from '@/utils/cn';
|
||||||
|
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
||||||
|
|
||||||
import { cn } from "@/utils/cn"
|
const Tabs = TabsPrimitive.Root;
|
||||||
|
|
||||||
const Tabs = TabsPrimitive.Root
|
|
||||||
|
|
||||||
const TabsList = React.forwardRef<
|
const TabsList = React.forwardRef<
|
||||||
React.ElementRef<typeof TabsPrimitive.List>,
|
React.ElementRef<typeof TabsPrimitive.List>,
|
||||||
@@ -12,13 +11,13 @@ const TabsList = React.forwardRef<
|
|||||||
<TabsPrimitive.List
|
<TabsPrimitive.List
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
TabsList.displayName = TabsPrimitive.List.displayName
|
TabsList.displayName = TabsPrimitive.List.displayName;
|
||||||
|
|
||||||
const TabsTrigger = React.forwardRef<
|
const TabsTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||||
@@ -27,13 +26,13 @@ const TabsTrigger = React.forwardRef<
|
|||||||
<TabsPrimitive.Trigger
|
<TabsPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
const TabsContent = React.forwardRef<
|
const TabsContent = React.forwardRef<
|
||||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||||
@@ -42,12 +41,12 @@ const TabsContent = React.forwardRef<
|
|||||||
<TabsPrimitive.Content
|
<TabsPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
TabsContent.displayName = TabsPrimitive.Content.displayName
|
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
||||||
|
|
||||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react';
|
||||||
import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
|
import { toggleVariants } from '@/components/ui/toggle';
|
||||||
import { VariantProps } from "class-variance-authority"
|
import { cn } from '@/utils/cn';
|
||||||
|
import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
|
||||||
import { cn } from "@/utils/cn"
|
import { VariantProps } from 'class-variance-authority';
|
||||||
import { toggleVariants } from "@/components/ui/toggle"
|
|
||||||
|
|
||||||
const ToggleGroupContext = React.createContext<
|
const ToggleGroupContext = React.createContext<
|
||||||
VariantProps<typeof toggleVariants>
|
VariantProps<typeof toggleVariants>
|
||||||
>({
|
>({
|
||||||
size: "default",
|
size: 'default',
|
||||||
variant: "default",
|
variant: 'default',
|
||||||
})
|
});
|
||||||
|
|
||||||
const ToggleGroup = React.forwardRef<
|
const ToggleGroup = React.forwardRef<
|
||||||
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
||||||
@@ -19,23 +18,23 @@ const ToggleGroup = React.forwardRef<
|
|||||||
>(({ className, variant, size, children, ...props }, ref) => (
|
>(({ className, variant, size, children, ...props }, ref) => (
|
||||||
<ToggleGroupPrimitive.Root
|
<ToggleGroupPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("flex items-center justify-center gap-1", className)}
|
className={cn('flex items-center justify-center gap-1', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<ToggleGroupContext.Provider value={{ variant, size }}>
|
<ToggleGroupContext.Provider value={{ variant, size }}>
|
||||||
{children}
|
{children}
|
||||||
</ToggleGroupContext.Provider>
|
</ToggleGroupContext.Provider>
|
||||||
</ToggleGroupPrimitive.Root>
|
</ToggleGroupPrimitive.Root>
|
||||||
))
|
));
|
||||||
|
|
||||||
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
|
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
|
||||||
|
|
||||||
const ToggleGroupItem = React.forwardRef<
|
const ToggleGroupItem = React.forwardRef<
|
||||||
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
||||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
||||||
VariantProps<typeof toggleVariants>
|
VariantProps<typeof toggleVariants>
|
||||||
>(({ className, children, variant, size, ...props }, ref) => {
|
>(({ className, children, variant, size, ...props }, ref) => {
|
||||||
const context = React.useContext(ToggleGroupContext)
|
const context = React.useContext(ToggleGroupContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToggleGroupPrimitive.Item
|
<ToggleGroupPrimitive.Item
|
||||||
@@ -51,9 +50,9 @@ const ToggleGroupItem = React.forwardRef<
|
|||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ToggleGroupPrimitive.Item>
|
</ToggleGroupPrimitive.Item>
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
|
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
|
||||||
|
|
||||||
export { ToggleGroup, ToggleGroupItem }
|
export { ToggleGroup, ToggleGroupItem };
|
||||||
|
|||||||
@@ -1,30 +1,29 @@
|
|||||||
import * as React from "react"
|
import * as React from 'react';
|
||||||
import * as TogglePrimitive from "@radix-ui/react-toggle"
|
import { cn } from '@/utils/cn';
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import * as TogglePrimitive from '@radix-ui/react-toggle';
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
import { cn } from "@/utils/cn"
|
|
||||||
|
|
||||||
const toggleVariants = cva(
|
const toggleVariants = cva(
|
||||||
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
|
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-transparent",
|
default: 'bg-transparent',
|
||||||
outline:
|
outline:
|
||||||
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
|
'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground',
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-10 px-3",
|
default: 'h-10 px-3',
|
||||||
sm: "h-9 px-2.5",
|
sm: 'h-9 px-2.5',
|
||||||
lg: "h-11 px-5",
|
lg: 'h-11 px-5',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: 'default',
|
||||||
size: "default",
|
size: 'default',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
const Toggle = React.forwardRef<
|
const Toggle = React.forwardRef<
|
||||||
React.ElementRef<typeof TogglePrimitive.Root>,
|
React.ElementRef<typeof TogglePrimitive.Root>,
|
||||||
@@ -36,8 +35,8 @@ const Toggle = React.forwardRef<
|
|||||||
className={cn(toggleVariants({ variant, size, className }))}
|
className={cn(toggleVariants({ variant, size, className }))}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
|
|
||||||
Toggle.displayName = TogglePrimitive.Root.displayName
|
Toggle.displayName = TogglePrimitive.Root.displayName;
|
||||||
|
|
||||||
export { Toggle, toggleVariants }
|
export { Toggle, toggleVariants };
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import debounce from 'lodash.debounce';
|
|||||||
export function useDebounceFn<T>(fn: T, ms = 500): T {
|
export function useDebounceFn<T>(fn: T, ms = 500): T {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call
|
||||||
const debouncedFn = debounce(fn as any, ms);
|
const debouncedFn = debounce(fn as any, ms);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
@@ -12,5 +12,5 @@ export function useDebounceFn<T>(fn: T, ms = 500): T {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return debouncedFn as T
|
return debouncedFn as T;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -7,12 +7,13 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Calendar } from '@/components/ui/calendar';
|
import { Calendar } from '@/components/ui/calendar';
|
||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { zCreateReference } from '@openpanel/validation';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
|
|
||||||
|
import { zCreateReference } from '@openpanel/validation';
|
||||||
|
|
||||||
import { popModal } from '.';
|
import { popModal } from '.';
|
||||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ import { ButtonContainer } from '@/components/ButtonContainer';
|
|||||||
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import type { IServiceClient } from '@openpanel/db';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import type { IServiceClient } from '@openpanel/db';
|
||||||
|
|
||||||
import { popModal } from '.';
|
import { popModal } from '.';
|
||||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ import { ButtonContainer } from '@/components/ButtonContainer';
|
|||||||
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import type { IServiceDashboard } from '@openpanel/db';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import type { IServiceDashboard } from '@openpanel/db';
|
||||||
|
|
||||||
import { popModal } from '.';
|
import { popModal } from '.';
|
||||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ import { ButtonContainer } from '@/components/ButtonContainer';
|
|||||||
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import type { IServiceProject } from '@openpanel/db';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import type { IServiceProject } from '@openpanel/db';
|
||||||
|
|
||||||
import { popModal } from '.';
|
import { popModal } from '.';
|
||||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ import { Combobox } from '@/components/ui/combobox';
|
|||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import type { IChartInput } from '@openpanel/validation';
|
|
||||||
import { useRouter, useSearchParams } from 'next/navigation';
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import type { IChartInput } from '@openpanel/validation';
|
||||||
|
|
||||||
import { popModal } from '.';
|
import { popModal } from '.';
|
||||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { useAppParams } from '@/hooks/useAppParams';
|
import { useAppParams } from '@/hooks/useAppParams';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { zShareOverview } from '@openpanel/validation';
|
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
|
|
||||||
|
import { zShareOverview } from '@openpanel/validation';
|
||||||
|
|
||||||
import { popModal } from '.';
|
import { popModal } from '.';
|
||||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { getDaysOldDate } from '@/utils/date';
|
import { getDaysOldDate } from '@/utils/date';
|
||||||
import { round } from '@/utils/math';
|
import { round } from '@/utils/math';
|
||||||
|
import * as mathjs from 'mathjs';
|
||||||
|
import { sort } from 'ramda';
|
||||||
|
|
||||||
import { alphabetIds, NOT_SET_VALUE } from '@openpanel/constants';
|
import { alphabetIds, NOT_SET_VALUE } from '@openpanel/constants';
|
||||||
import { chQuery, convertClickhouseDateToJs, getChartSql } from '@openpanel/db';
|
import { chQuery, convertClickhouseDateToJs, getChartSql } from '@openpanel/db';
|
||||||
import type {
|
import type {
|
||||||
@@ -9,8 +12,6 @@ import type {
|
|||||||
IGetChartDataInput,
|
IGetChartDataInput,
|
||||||
IInterval,
|
IInterval,
|
||||||
} from '@openpanel/validation';
|
} from '@openpanel/validation';
|
||||||
import * as mathjs from 'mathjs';
|
|
||||||
import { sort } from 'ramda';
|
|
||||||
|
|
||||||
export type GetChartDataResult = Awaited<ReturnType<typeof getChartData>>;
|
export type GetChartDataResult = Awaited<ReturnType<typeof getChartData>>;
|
||||||
export interface ResultItem {
|
export interface ResultItem {
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import {
|
|||||||
publicProcedure,
|
publicProcedure,
|
||||||
} from '@/server/api/trpc';
|
} from '@/server/api/trpc';
|
||||||
import { average, max, min, round, sum } from '@/utils/math';
|
import { average, max, min, round, sum } from '@/utils/math';
|
||||||
|
import { flatten, map, pipe, prop, repeat, reverse, sort, uniq } from 'ramda';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
chQuery,
|
chQuery,
|
||||||
createSqlBuilder,
|
createSqlBuilder,
|
||||||
@@ -12,8 +15,6 @@ import {
|
|||||||
} from '@openpanel/db';
|
} from '@openpanel/db';
|
||||||
import { zChartInput } from '@openpanel/validation';
|
import { zChartInput } from '@openpanel/validation';
|
||||||
import type { IChartEvent, IChartInput } from '@openpanel/validation';
|
import type { IChartEvent, IChartInput } from '@openpanel/validation';
|
||||||
import { flatten, map, pipe, prop, repeat, reverse, sort, uniq } from 'ramda';
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getChartData,
|
getChartData,
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
|
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
|
||||||
import { db, getId } from '@/server/db';
|
import { db, getId } from '@/server/db';
|
||||||
import type { Prisma } from '@openpanel/db';
|
|
||||||
import { PrismaError } from 'prisma-error-enum';
|
import { PrismaError } from 'prisma-error-enum';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import type { Prisma } from '@openpanel/db';
|
||||||
|
|
||||||
export const dashboardRouter = createTRPCRouter({
|
export const dashboardRouter = createTRPCRouter({
|
||||||
get: protectedProcedure
|
get: protectedProcedure
|
||||||
.input(z.object({ id: z.string() }))
|
.input(z.object({ id: z.string() }))
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
|
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
|
||||||
import { db } from '@openpanel/db';
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { db } from '@openpanel/db';
|
||||||
|
|
||||||
export const eventRouter = createTRPCRouter({
|
export const eventRouter = createTRPCRouter({
|
||||||
updateEventMeta: protectedProcedure
|
updateEventMeta: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
|
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
|
||||||
import { clerkClient } from '@clerk/nextjs';
|
import { clerkClient } from '@clerk/nextjs';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { getOrganizationBySlug } from '@openpanel/db';
|
import { getOrganizationBySlug } from '@openpanel/db';
|
||||||
import { zInviteUser } from '@openpanel/validation';
|
import { zInviteUser } from '@openpanel/validation';
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
export const organizationRouter = createTRPCRouter({
|
export const organizationRouter = createTRPCRouter({
|
||||||
list: protectedProcedure.query(() => {
|
list: protectedProcedure.query(() => {
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import {
|
|||||||
publicProcedure,
|
publicProcedure,
|
||||||
} from '@/server/api/trpc';
|
} from '@/server/api/trpc';
|
||||||
import { db } from '@/server/db';
|
import { db } from '@/server/db';
|
||||||
import { chQuery, createSqlBuilder } from '@openpanel/db';
|
|
||||||
import { flatten, map, pipe, prop, sort, uniq } from 'ramda';
|
import { flatten, map, pipe, prop, sort, uniq } from 'ramda';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { chQuery, createSqlBuilder } from '@openpanel/db';
|
||||||
|
|
||||||
export const profileRouter = createTRPCRouter({
|
export const profileRouter = createTRPCRouter({
|
||||||
list: protectedProcedure
|
list: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
|
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
|
||||||
import { db } from '@/server/db';
|
import { db } from '@/server/db';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { transformReport } from '@openpanel/db';
|
import { transformReport } from '@openpanel/db';
|
||||||
import { zChartInput } from '@openpanel/validation';
|
import { zChartInput } from '@openpanel/validation';
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
export const reportRouter = createTRPCRouter({
|
export const reportRouter = createTRPCRouter({
|
||||||
get: protectedProcedure
|
get: protectedProcedure
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
|
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
|
||||||
import { db } from '@/server/db';
|
import { db } from '@/server/db';
|
||||||
import { zShareOverview } from '@openpanel/validation';
|
|
||||||
import ShortUniqueId from 'short-unique-id';
|
import ShortUniqueId from 'short-unique-id';
|
||||||
|
|
||||||
|
import { zShareOverview } from '@openpanel/validation';
|
||||||
|
|
||||||
const uid = new ShortUniqueId({ length: 6 });
|
const uid = new ShortUniqueId({ length: 6 });
|
||||||
|
|
||||||
export const shareRouter = createTRPCRouter({
|
export const shareRouter = createTRPCRouter({
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
|
import { createTRPCRouter, protectedProcedure } from '@/server/api/trpc';
|
||||||
import { clerkClient } from '@clerk/nextjs';
|
import { clerkClient } from '@clerk/nextjs';
|
||||||
import { transformUser } from '@openpanel/db';
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { transformUser } from '@openpanel/db';
|
||||||
|
|
||||||
export const userRouter = createTRPCRouter({
|
export const userRouter = createTRPCRouter({
|
||||||
update: protectedProcedure
|
update: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { slug } from '@/utils/slug';
|
import { slug } from '@/utils/slug';
|
||||||
|
|
||||||
import { db } from '@openpanel/db';
|
import { db } from '@openpanel/db';
|
||||||
|
|
||||||
export { db } from '@openpanel/db';
|
export { db } from '@openpanel/db';
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user