rename mixan to OPENPANEL!
This commit is contained in:
48
README.md
48
README.md
@@ -1,10 +1,6 @@
|
||||
<p align="center">
|
||||
<img src="images/mixan.svg">
|
||||
</p>
|
||||
# openpanel
|
||||
|
||||
# mixan
|
||||
|
||||
Mixan is a simple analytics tool for logging events on web and react-native. My goal is to make a minimal mixpanel copy with the most basic features (for now).
|
||||
Openpanel is a simple analytics tool for logging events on web and react-native. My goal is to make a minimal mixpanel copy with the most basic features (for now).
|
||||
|
||||
- Easy to use
|
||||
- Fully responsive UI
|
||||
@@ -71,24 +67,24 @@ As of today (~~2023-12-12~~ 2024-01-16) I have more then ~~1.2~~ 2.8 million eve
|
||||
- [x] Create web sdk
|
||||
- [x] Screen view function should take in title, path and parse query string (especially utm tags)
|
||||
|
||||
## @mixan/sdk
|
||||
## @openpanel/sdk
|
||||
|
||||
For pushing events
|
||||
|
||||
### Install
|
||||
|
||||
- npm: `npm install @mixan/sdk`
|
||||
- pnpm: `pnpm add @mixan/sdk`
|
||||
- yarn: `yarn add @mixan/sdk`
|
||||
- npm: `npm install @openpanel/sdk`
|
||||
- pnpm: `pnpm add @openpanel/sdk`
|
||||
- yarn: `yarn add @openpanel/sdk`
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { MixanWeb } from '@mixan/web';
|
||||
import { OpenpanelWeb } from '@openpanel/web';
|
||||
|
||||
// import { MixanNative } from '@mixan/sdk-native';
|
||||
// import { OpenpanelNative } from '@openpanel/sdk-native';
|
||||
|
||||
const mixan = new MixanWeb({
|
||||
const openpanel = new OpenpanelWeb({
|
||||
clientId: 'uuid',
|
||||
url: 'http://localhost:8080/api/sdk',
|
||||
batchInterval: 10000,
|
||||
@@ -96,7 +92,7 @@ const mixan = new MixanWeb({
|
||||
trackIp: true,
|
||||
});
|
||||
|
||||
// const mixan = new MixanNative({
|
||||
// const openpanel = new OpenpanelNative({
|
||||
// clientId: 'uuid',
|
||||
// clientSecret: 'uuid',
|
||||
// url: 'http://localhost:8080/api/sdk',
|
||||
@@ -108,12 +104,12 @@ const mixan = new MixanWeb({
|
||||
// Call this before you send any events
|
||||
// It will create a anonymous profile
|
||||
// This profile will be merged if you call `setUser` in a later stage
|
||||
mixan.init();
|
||||
openpanel.init();
|
||||
|
||||
// tracks all outgoing links as a `link_out` event
|
||||
mixan.trackOutgoingLinks();
|
||||
openpanel.trackOutgoingLinks();
|
||||
|
||||
mixan.setUser({
|
||||
openpanel.setUser({
|
||||
id: 'id',
|
||||
first_name: 'John',
|
||||
last_name: 'Doe',
|
||||
@@ -122,25 +118,25 @@ mixan.setUser({
|
||||
});
|
||||
|
||||
// will upsert 'app_open' on user property and increment it
|
||||
mixan.increment('app_open');
|
||||
openpanel.increment('app_open');
|
||||
// will upsert 'app_open' on user property and increment it by 10
|
||||
mixan.increment('app_open', 10);
|
||||
openpanel.increment('app_open', 10);
|
||||
// will upsert 'app_open' on user property and decrement it by 2
|
||||
mixan.decrement('app_open', 2);
|
||||
openpanel.decrement('app_open', 2);
|
||||
|
||||
// send a sign_in event
|
||||
mixan.event('sign_in');
|
||||
openpanel.event('sign_in');
|
||||
|
||||
// send a sign_in event with properties
|
||||
mixan.event('sign_in', {
|
||||
openpanel.event('sign_in', {
|
||||
provider: 'gmail',
|
||||
});
|
||||
|
||||
// Screen view for web
|
||||
mixan.screenView();
|
||||
openpanel.screenView();
|
||||
|
||||
// Screen view for native
|
||||
mixan.screenView('Article', {
|
||||
openpanel.screenView('Article', {
|
||||
id: '3',
|
||||
title: 'Nice article here',
|
||||
});
|
||||
@@ -148,10 +144,10 @@ mixan.screenView('Article', {
|
||||
// Call this when a user is logged out.
|
||||
// This will just make sure you do not send
|
||||
// the associated profile id for the next events
|
||||
mixan.clear();
|
||||
openpanel.clear();
|
||||
```
|
||||
|
||||
## @mixan/dashboard
|
||||
## @openpanel/dashboard
|
||||
|
||||
A nextjs web app. Collects all events and your gui to analyze your data.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@mixan/api",
|
||||
"name": "@openpanel/api",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "dotenv -e ../../.env -c -v WATCH=1 tsup",
|
||||
@@ -14,10 +14,10 @@
|
||||
"@fastify/cors": "^9.0.0",
|
||||
"@fastify/websocket": "^8.3.1",
|
||||
"@logtail/pino": "^0.4.19",
|
||||
"@mixan/common": "workspace:*",
|
||||
"@mixan/db": "workspace:*",
|
||||
"@mixan/queue": "workspace:*",
|
||||
"@mixan/redis": "workspace:*",
|
||||
"@openpanel/common": "workspace:*",
|
||||
"@openpanel/db": "workspace:*",
|
||||
"@openpanel/queue": "workspace:*",
|
||||
"@openpanel/redis": "workspace:*",
|
||||
"fastify": "^4.25.2",
|
||||
"ico-to-png": "^0.2.1",
|
||||
"pino": "^8.17.2",
|
||||
@@ -28,10 +28,10 @@
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mixan/eslint-config": "workspace:*",
|
||||
"@mixan/prettier-config": "workspace:*",
|
||||
"@mixan/sdk": "workspace:*",
|
||||
"@mixan/tsconfig": "workspace:*",
|
||||
"@openpanel/eslint-config": "workspace:*",
|
||||
"@openpanel/prettier-config": "workspace:*",
|
||||
"@openpanel/sdk": "workspace:*",
|
||||
"@openpanel/tsconfig": "workspace:*",
|
||||
"@types/ramda": "^0.29.6",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"@types/uuid": "^9.0.8",
|
||||
@@ -44,8 +44,8 @@
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": [
|
||||
"@mixan/eslint-config/base"
|
||||
"@openpanel/eslint-config/base"
|
||||
]
|
||||
},
|
||||
"prettier": "@mixan/prettier-config"
|
||||
"prettier": "@openpanel/prettier-config"
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
import { omit, prop, uniqBy } from 'ramda';
|
||||
import { omit, prop, uniqBy } from "ramda";
|
||||
|
||||
import { generateProfileId, getTime, toISOString } from '@mixan/common';
|
||||
import type { Event, IServiceCreateEventPayload } from '@mixan/db';
|
||||
import { generateProfileId, getTime, toISOString } from "@openpanel/common";
|
||||
import type { Event, IServiceCreateEventPayload } from "@openpanel/db";
|
||||
import {
|
||||
createEvent as createClickhouseEvent,
|
||||
db,
|
||||
formatClickhouseDate,
|
||||
getSalts,
|
||||
} from '@mixan/db';
|
||||
} from "@openpanel/db";
|
||||
|
||||
import { parseIp } from '../src/utils/parseIp';
|
||||
import { parseUserAgent } from '../src/utils/parseUserAgent';
|
||||
import { parseIp } from "../src/utils/parseIp";
|
||||
import { parseUserAgent } from "../src/utils/parseUserAgent";
|
||||
|
||||
const clean = omit([
|
||||
'ip',
|
||||
'os',
|
||||
'ua',
|
||||
'url',
|
||||
'hash',
|
||||
'host',
|
||||
'path',
|
||||
'device',
|
||||
'screen',
|
||||
'hostname',
|
||||
'language',
|
||||
'referrer',
|
||||
'timezone',
|
||||
"ip",
|
||||
"os",
|
||||
"ua",
|
||||
"url",
|
||||
"hash",
|
||||
"host",
|
||||
"path",
|
||||
"device",
|
||||
"screen",
|
||||
"hostname",
|
||||
"language",
|
||||
"referrer",
|
||||
"timezone",
|
||||
]);
|
||||
async function main() {
|
||||
const events = await db.event.findMany({
|
||||
where: {
|
||||
project_id: '4e2798cb-e255-4e9d-960d-c9ad095aabd7',
|
||||
name: 'screen_view',
|
||||
project_id: "4e2798cb-e255-4e9d-960d-c9ad095aabd7",
|
||||
name: "screen_view",
|
||||
createdAt: {
|
||||
gte: new Date('2024-01-01'),
|
||||
lt: new Date('2024-02-04'),
|
||||
gte: new Date("2024-01-01"),
|
||||
lt: new Date("2024-02-04"),
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'asc',
|
||||
createdAt: "asc",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -50,7 +50,7 @@ async function main() {
|
||||
|
||||
const properties = event.properties as Record<string, any>;
|
||||
|
||||
if (properties.ua?.includes('bot')) {
|
||||
if (properties.ua?.includes("bot")) {
|
||||
// console.log('IGNORE', event.id);
|
||||
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;
|
||||
for (const profile_id of Object.keys(grouped)) {
|
||||
uidx++;
|
||||
console.log(`User index ${uidx}`);
|
||||
|
||||
const events = uniqBy(prop('createdAt'), grouped[profile_id] || []);
|
||||
const events = uniqBy(prop("createdAt"), grouped[profile_id] || []);
|
||||
|
||||
if (events) {
|
||||
let lastSessionStart = null;
|
||||
let screenViews = 0;
|
||||
let totalDuration = 0;
|
||||
console.log('new user...');
|
||||
console.log("new user...");
|
||||
let eidx = -1;
|
||||
for (const event of events) {
|
||||
eidx++;
|
||||
@@ -93,7 +93,7 @@ async function main() {
|
||||
const projectId = event.project_id;
|
||||
const path = properties.path!;
|
||||
const ip = properties.ip!;
|
||||
const origin = 'https://mixan.kiddo.se';
|
||||
const origin = "https://openpanel.kiddo.se";
|
||||
const ua = properties.ua!;
|
||||
const uaInfo = parseUserAgent(ua);
|
||||
const salts = await getSalts();
|
||||
@@ -133,8 +133,8 @@ async function main() {
|
||||
? nextEvent.createdAt.getTime() - event.createdAt.getTime()
|
||||
: 0,
|
||||
path,
|
||||
referrer: properties?.referrer?.host ?? '', // TODO
|
||||
referrerName: properties?.referrer?.host ?? '', // TODO
|
||||
referrer: properties?.referrer?.host ?? "", // TODO
|
||||
referrerName: properties?.referrer?.host ?? "", // TODO
|
||||
};
|
||||
|
||||
if (!prevEventAt) {
|
||||
@@ -173,8 +173,8 @@ async function main() {
|
||||
async function createEvent(event: IServiceCreateEventPayload) {
|
||||
console.log(
|
||||
`Create ${event.name} - ${event.path} - ${formatClickhouseDate(
|
||||
event.createdAt
|
||||
)} - ${event.duration / 1000} sec`
|
||||
event.createdAt,
|
||||
)} - ${event.duration / 1000} sec`,
|
||||
);
|
||||
await createClickhouseEvent(event);
|
||||
}
|
||||
@@ -183,7 +183,7 @@ async function createSessionStart(event: IServiceCreateEventPayload) {
|
||||
const session: IServiceCreateEventPayload = {
|
||||
...event,
|
||||
duration: 0,
|
||||
name: 'session_start',
|
||||
name: "session_start",
|
||||
createdAt: toISOString(getTime(event.createdAt) - 100),
|
||||
};
|
||||
|
||||
@@ -197,7 +197,7 @@ async function createSessionEnd(
|
||||
options: {
|
||||
screenViews: number;
|
||||
totalDuration: number;
|
||||
}
|
||||
},
|
||||
) {
|
||||
const properties: Record<string, unknown> = {};
|
||||
if (options.screenViews === 1) {
|
||||
@@ -213,7 +213,7 @@ async function createSessionEnd(
|
||||
...sessionStart.properties,
|
||||
},
|
||||
duration: options.totalDuration,
|
||||
name: 'session_end',
|
||||
name: "session_end",
|
||||
createdAt: toISOString(prevEventAt.getTime() + 10),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { logger, logInfo, noop } from '@/utils/logger';
|
||||
import { getClientIp, parseIp } from '@/utils/parseIp';
|
||||
import { getReferrerWithQuery, parseReferrer } from '@/utils/parseReferrer';
|
||||
import { isUserAgentSet, parseUserAgent } from '@/utils/parseUserAgent';
|
||||
import { isSameDomain, parsePath } from '@/utils/url';
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { omit } from 'ramda';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { logger, logInfo, noop } from "@/utils/logger";
|
||||
import { getClientIp, parseIp } from "@/utils/parseIp";
|
||||
import { getReferrerWithQuery, parseReferrer } from "@/utils/parseReferrer";
|
||||
import { isUserAgentSet, parseUserAgent } from "@/utils/parseUserAgent";
|
||||
import { isSameDomain, parsePath } from "@/utils/url";
|
||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||
import { omit } from "ramda";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
import { generateDeviceId, getTime, toISOString } from '@mixan/common';
|
||||
import type { IServiceCreateEventPayload } from '@mixan/db';
|
||||
import { createEvent, getEvents, getSalts } from '@mixan/db';
|
||||
import type { JobsOptions } from '@mixan/queue';
|
||||
import { eventsQueue } from '@mixan/queue';
|
||||
import { findJobByPrefix } from '@mixan/queue/src/utils';
|
||||
import type { PostEventPayload } from '@mixan/sdk';
|
||||
import { generateDeviceId, getTime, toISOString } from "@openpanel/common";
|
||||
import type { IServiceCreateEventPayload } from "@openpanel/db";
|
||||
import { createEvent, getEvents, getSalts } from "@openpanel/db";
|
||||
import type { JobsOptions } from "@openpanel/queue";
|
||||
import { eventsQueue } from "@openpanel/queue";
|
||||
import { findJobByPrefix } from "@openpanel/queue/src/utils";
|
||||
import type { PostEventPayload } from "@openpanel/sdk";
|
||||
|
||||
const SESSION_TIMEOUT = 1000 * 60 * 30;
|
||||
const SESSION_END_TIMEOUT = SESSION_TIMEOUT + 1000;
|
||||
@@ -55,7 +55,7 @@ export async function postEvent(
|
||||
request: FastifyRequest<{
|
||||
Body: PostEventPayload;
|
||||
}>,
|
||||
reply: FastifyReply
|
||||
reply: FastifyReply,
|
||||
) {
|
||||
const contextLogger = createContextLogger(request);
|
||||
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 `__`
|
||||
// remove when kiddokitchen app (24.09.02) is not used anymore
|
||||
return (
|
||||
((properties[name] || properties[name.replace('__', '')]) as
|
||||
((properties[name] || properties[name.replace("__", "")]) as
|
||||
| string
|
||||
| null
|
||||
| undefined) ?? undefined
|
||||
);
|
||||
};
|
||||
const profileId = body.profileId ?? '';
|
||||
const profileId = body.profileId ?? "";
|
||||
const createdAt = new Date(body.timestamp);
|
||||
const url = getProperty('__path');
|
||||
const url = getProperty("__path");
|
||||
const { path, hash, query } = parsePath(url);
|
||||
const referrer = isSameDomain(getProperty('__referrer'), url)
|
||||
const referrer = isSameDomain(getProperty("__referrer"), url)
|
||||
? null
|
||||
: parseReferrer(getProperty('__referrer'));
|
||||
: parseReferrer(getProperty("__referrer"));
|
||||
const utmReferrer = getReferrerWithQuery(query);
|
||||
const ip = getClientIp(request)!;
|
||||
const origin = request.headers.origin!;
|
||||
const ua = request.headers['user-agent']!;
|
||||
const ua = request.headers["user-agent"]!;
|
||||
const uaInfo = parseUserAgent(ua);
|
||||
const salts = await getSalts();
|
||||
const currentDeviceId = generateDeviceId({
|
||||
@@ -101,66 +101,66 @@ export async function postEvent(
|
||||
|
||||
if (isServerEvent) {
|
||||
const [event] = await withTiming(
|
||||
'Get last event (server-event)',
|
||||
"Get last event (server-event)",
|
||||
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`,
|
||||
),
|
||||
);
|
||||
|
||||
eventsQueue.add('event', {
|
||||
type: 'createEvent',
|
||||
eventsQueue.add("event", {
|
||||
type: "createEvent",
|
||||
payload: {
|
||||
name: body.name,
|
||||
deviceId: event?.deviceId || '',
|
||||
sessionId: event?.sessionId || '',
|
||||
deviceId: event?.deviceId || "",
|
||||
sessionId: event?.sessionId || "",
|
||||
profileId,
|
||||
projectId,
|
||||
properties: Object.assign(
|
||||
{},
|
||||
omit(['__path', '__referrer'], properties),
|
||||
omit(["__path", "__referrer"], properties),
|
||||
{
|
||||
hash,
|
||||
query,
|
||||
}
|
||||
},
|
||||
),
|
||||
createdAt,
|
||||
country: event?.country ?? '',
|
||||
city: event?.city ?? '',
|
||||
region: event?.region ?? '',
|
||||
continent: event?.continent ?? '',
|
||||
os: event?.os ?? '',
|
||||
osVersion: event?.osVersion ?? '',
|
||||
browser: event?.browser ?? '',
|
||||
browserVersion: event?.browserVersion ?? '',
|
||||
device: event?.device ?? '',
|
||||
brand: event?.brand ?? '',
|
||||
model: event?.model ?? '',
|
||||
country: event?.country ?? "",
|
||||
city: event?.city ?? "",
|
||||
region: event?.region ?? "",
|
||||
continent: event?.continent ?? "",
|
||||
os: event?.os ?? "",
|
||||
osVersion: event?.osVersion ?? "",
|
||||
browser: event?.browser ?? "",
|
||||
browserVersion: event?.browserVersion ?? "",
|
||||
device: event?.device ?? "",
|
||||
brand: event?.brand ?? "",
|
||||
model: event?.model ?? "",
|
||||
duration: 0,
|
||||
path: event?.path ?? '',
|
||||
referrer: event?.referrer ?? '',
|
||||
referrerName: event?.referrerName ?? '',
|
||||
referrerType: event?.referrerType ?? '',
|
||||
path: event?.path ?? "",
|
||||
referrer: event?.referrer ?? "",
|
||||
referrerName: event?.referrerName ?? "",
|
||||
referrerType: event?.referrerType ?? "",
|
||||
profile: undefined,
|
||||
meta: undefined,
|
||||
},
|
||||
});
|
||||
return reply.status(200).send('');
|
||||
return reply.status(200).send("");
|
||||
}
|
||||
|
||||
const [geo, sessionEndJobCurrentDeviceId, sessionEndJobPreviousDeviceId] =
|
||||
await withTiming(
|
||||
'Get geo and jobs from queue',
|
||||
"Get geo and jobs from queue",
|
||||
Promise.all([
|
||||
parseIp(ip),
|
||||
findJobByPrefix(
|
||||
eventsQueue,
|
||||
`sessionEnd:${projectId}:${currentDeviceId}:`
|
||||
`sessionEnd:${projectId}:${currentDeviceId}:`,
|
||||
),
|
||||
findJobByPrefix(
|
||||
eventsQueue,
|
||||
`sessionEnd:${projectId}:${previousDeviceId}:`
|
||||
`sessionEnd:${projectId}:${previousDeviceId}:`,
|
||||
),
|
||||
])
|
||||
]),
|
||||
);
|
||||
|
||||
const createSessionStart =
|
||||
@@ -178,9 +178,9 @@ export async function postEvent(
|
||||
deviceId = currentDeviceId;
|
||||
// Queue session end
|
||||
eventsQueue.add(
|
||||
'event',
|
||||
"event",
|
||||
{
|
||||
type: 'createSessionEnd',
|
||||
type: "createSessionEnd",
|
||||
payload: {
|
||||
deviceId,
|
||||
},
|
||||
@@ -188,27 +188,27 @@ export async function postEvent(
|
||||
{
|
||||
delay: SESSION_END_TIMEOUT,
|
||||
jobId: `sessionEnd:${projectId}:${deviceId}:${Date.now()}`,
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const [[sessionStartEvent], prevEventJob] = await withTiming(
|
||||
'Get session start event',
|
||||
"Get session start event",
|
||||
Promise.all([
|
||||
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}:`),
|
||||
])
|
||||
]),
|
||||
);
|
||||
|
||||
const payload: Omit<IServiceCreateEventPayload, 'id'> = {
|
||||
const payload: Omit<IServiceCreateEventPayload, "id"> = {
|
||||
name: body.name,
|
||||
deviceId,
|
||||
profileId,
|
||||
projectId,
|
||||
sessionId: createSessionStart ? uuid() : sessionStartEvent?.sessionId ?? '',
|
||||
properties: Object.assign({}, omit(['__path', '__referrer'], properties), {
|
||||
sessionId: createSessionStart ? uuid() : sessionStartEvent?.sessionId ?? "",
|
||||
properties: Object.assign({}, omit(["__path", "__referrer"], properties), {
|
||||
hash,
|
||||
query,
|
||||
}),
|
||||
@@ -227,27 +227,27 @@ export async function postEvent(
|
||||
duration: 0,
|
||||
path: path,
|
||||
referrer: referrer?.url,
|
||||
referrerName: referrer?.name ?? utmReferrer?.name ?? '',
|
||||
referrerType: referrer?.type ?? utmReferrer?.type ?? '',
|
||||
referrerName: referrer?.name ?? utmReferrer?.name ?? "",
|
||||
referrerType: referrer?.type ?? utmReferrer?.type ?? "",
|
||||
profile: undefined,
|
||||
meta: undefined,
|
||||
};
|
||||
|
||||
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 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
|
||||
if (payload.name != 'screen_view') {
|
||||
if (payload.name != "screen_view") {
|
||||
payload.path = prevEvent.path;
|
||||
}
|
||||
|
||||
if (payload.name === 'screen_view') {
|
||||
if (payload.name === "screen_view") {
|
||||
if (duration < 0) {
|
||||
contextLogger.send('duration is wrong', {
|
||||
contextLogger.send("duration is wrong", {
|
||||
payload,
|
||||
duration,
|
||||
});
|
||||
@@ -255,21 +255,21 @@ export async function postEvent(
|
||||
// Skip update duration if it's wrong
|
||||
// Seems like request is not in right order
|
||||
await withTiming(
|
||||
'Update previous job with duration',
|
||||
"Update previous job with duration",
|
||||
prevEventJob.updateData({
|
||||
type: 'createEvent',
|
||||
type: "createEvent",
|
||||
payload: {
|
||||
...prevEvent,
|
||||
duration,
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
await withTiming('Promote previous job', prevEventJob.promote());
|
||||
await withTiming("Promote previous job", prevEventJob.promote());
|
||||
}
|
||||
} else if (payload.name !== 'screen_view') {
|
||||
contextLogger.send('no previous job', {
|
||||
} else if (payload.name !== "screen_view") {
|
||||
contextLogger.send("no previous job", {
|
||||
prevEventJob,
|
||||
payload,
|
||||
});
|
||||
@@ -278,23 +278,23 @@ export async function postEvent(
|
||||
if (createSessionStart) {
|
||||
// We do not need to queue session_start
|
||||
await withTiming(
|
||||
'Create session start event',
|
||||
"Create session start event",
|
||||
createEvent({
|
||||
...payload,
|
||||
name: 'session_start',
|
||||
name: "session_start",
|
||||
// @ts-expect-error
|
||||
createdAt: toISOString(getTime(payload.createdAt) - 100),
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const options: JobsOptions = {};
|
||||
if (payload.name === 'screen_view') {
|
||||
if (payload.name === "screen_view") {
|
||||
options.delay = SESSION_TIMEOUT;
|
||||
options.jobId = `event:${projectId}:${deviceId}:${Date.now()}`;
|
||||
}
|
||||
|
||||
contextLogger.send('event is queued', {
|
||||
contextLogger.send("event is queued", {
|
||||
ip,
|
||||
origin,
|
||||
ua,
|
||||
@@ -312,14 +312,14 @@ export async function postEvent(
|
||||
// Queue current event
|
||||
eventsQueue
|
||||
.add(
|
||||
'event',
|
||||
"event",
|
||||
{
|
||||
type: 'createEvent',
|
||||
type: "createEvent",
|
||||
payload,
|
||||
},
|
||||
options
|
||||
options,
|
||||
)
|
||||
.catch(noop('Failed to queue event'));
|
||||
.catch(noop("Failed to queue event"));
|
||||
|
||||
reply.status(202).send(deviceId);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import type * as WebSocket from 'ws';
|
||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||
import type * as WebSocket from "ws";
|
||||
|
||||
import { getSafeJson } from '@mixan/common';
|
||||
import type { IServiceCreateEventPayload } from '@mixan/db';
|
||||
import { getEvents, getLiveVisitors } from '@mixan/db';
|
||||
import { redis, redisPub, redisSub } from '@mixan/redis';
|
||||
import { getSafeJson } from "@openpanel/common";
|
||||
import type { IServiceCreateEventPayload } from "@openpanel/db";
|
||||
import { getEvents, getLiveVisitors } from "@openpanel/db";
|
||||
import { redis, redisPub, redisSub } from "@openpanel/redis";
|
||||
|
||||
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(
|
||||
@@ -16,20 +16,20 @@ export async function test(
|
||||
projectId: string;
|
||||
};
|
||||
}>,
|
||||
reply: FastifyReply
|
||||
reply: FastifyReply,
|
||||
) {
|
||||
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) {
|
||||
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(
|
||||
`live:event:${event.projectId}:${Math.random() * 1000}`,
|
||||
'',
|
||||
'EX',
|
||||
10
|
||||
"",
|
||||
"EX",
|
||||
10,
|
||||
);
|
||||
reply.status(202).send(event);
|
||||
}
|
||||
@@ -42,15 +42,15 @@ export function wsVisitors(
|
||||
Params: {
|
||||
projectId: string;
|
||||
};
|
||||
}>
|
||||
}>,
|
||||
) {
|
||||
const { params } = req;
|
||||
|
||||
redisSub.subscribe('event');
|
||||
redisSub.psubscribe('__key*:expired');
|
||||
redisSub.subscribe("event");
|
||||
redisSub.psubscribe("__key*:expired");
|
||||
|
||||
const message = (channel: string, message: string) => {
|
||||
if (channel === 'event') {
|
||||
if (channel === "event") {
|
||||
const event = getSafeJson<IServiceCreateEventPayload>(message);
|
||||
if (event?.projectId === params.projectId) {
|
||||
getLiveVisitors(params.projectId).then((count) => {
|
||||
@@ -68,14 +68,14 @@ export function wsVisitors(
|
||||
}
|
||||
};
|
||||
|
||||
redisSub.on('message', message);
|
||||
redisSub.on('pmessage', pmessage);
|
||||
redisSub.on("message", message);
|
||||
redisSub.on("pmessage", pmessage);
|
||||
|
||||
connection.socket.on('close', () => {
|
||||
redisSub.unsubscribe('event');
|
||||
redisSub.punsubscribe('__key*:expired');
|
||||
redisSub.off('message', message);
|
||||
redisSub.off('pmessage', pmessage);
|
||||
connection.socket.on("close", () => {
|
||||
redisSub.unsubscribe("event");
|
||||
redisSub.punsubscribe("__key*:expired");
|
||||
redisSub.off("message", message);
|
||||
redisSub.off("pmessage", pmessage);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,11 +87,11 @@ export function wsEvents(
|
||||
Params: {
|
||||
projectId: string;
|
||||
};
|
||||
}>
|
||||
}>,
|
||||
) {
|
||||
const { params } = req;
|
||||
|
||||
redisSub.subscribe('event');
|
||||
redisSub.subscribe("event");
|
||||
|
||||
const message = (channel: string, message: string) => {
|
||||
const event = getSafeJson<IServiceCreateEventPayload>(message);
|
||||
@@ -100,10 +100,10 @@ export function wsEvents(
|
||||
}
|
||||
};
|
||||
|
||||
redisSub.on('message', message);
|
||||
redisSub.on("message", message);
|
||||
|
||||
connection.socket.on('close', () => {
|
||||
redisSub.unsubscribe('event');
|
||||
redisSub.off('message', message);
|
||||
connection.socket.on("close", () => {
|
||||
redisSub.unsubscribe("event");
|
||||
redisSub.off("message", message);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import icoToPng from 'ico-to-png';
|
||||
import sharp from 'sharp';
|
||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||
import icoToPng from "ico-to-png";
|
||||
import sharp from "sharp";
|
||||
|
||||
import { createHash } from '@mixan/common';
|
||||
import { redis } from '@mixan/redis';
|
||||
import { createHash } from "@openpanel/common";
|
||||
import { redis } from "@openpanel/redis";
|
||||
|
||||
interface GetFaviconParams {
|
||||
url: string;
|
||||
@@ -12,9 +12,9 @@ interface GetFaviconParams {
|
||||
async function getImageBuffer(url: string) {
|
||||
try {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ async function getImageBuffer(url: string) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (contentType === 'image/x-icon' || url.endsWith('.ico')) {
|
||||
if (contentType === "image/x-icon" || url.endsWith(".ico")) {
|
||||
const arrayBuffer = await res.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
return await icoToPng(buffer, 30);
|
||||
@@ -30,36 +30,36 @@ async function getImageBuffer(url: string) {
|
||||
|
||||
return await sharp(await res.arrayBuffer())
|
||||
.resize(30, 30, {
|
||||
fit: 'cover',
|
||||
fit: "cover",
|
||||
})
|
||||
.png()
|
||||
.toBuffer();
|
||||
} catch (e) {
|
||||
console.log('Failed to get image from url', url);
|
||||
console.log("Failed to get image from url", url);
|
||||
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(
|
||||
request: FastifyRequest<{
|
||||
Querystring: GetFaviconParams;
|
||||
}>,
|
||||
reply: FastifyReply
|
||||
reply: FastifyReply,
|
||||
) {
|
||||
function sendBuffer(buffer: Buffer, cacheKey?: string) {
|
||||
if (cacheKey) {
|
||||
redis.set(`favicon:${cacheKey}`, buffer.toString('base64'));
|
||||
redis.set(`favicon:${cacheKey}`, buffer.toString("base64"));
|
||||
}
|
||||
reply.type('image/png');
|
||||
console.log('buffer', buffer.byteLength);
|
||||
reply.type("image/png");
|
||||
console.log("buffer", buffer.byteLength);
|
||||
|
||||
return reply.send(buffer);
|
||||
}
|
||||
|
||||
if (!request.query.url) {
|
||||
return reply.status(404).send('Not found');
|
||||
return reply.status(404).send("Not found");
|
||||
}
|
||||
|
||||
const url = decodeURIComponent(request.query.url);
|
||||
@@ -69,7 +69,7 @@ export async function getFavicon(
|
||||
const cacheKey = createHash(url, 32);
|
||||
const cache = await redis.get(`favicon:${cacheKey}`);
|
||||
if (cache) {
|
||||
return sendBuffer(Buffer.from(cache, 'base64'));
|
||||
return sendBuffer(Buffer.from(cache, "base64"));
|
||||
}
|
||||
const buffer = await getImageBuffer(url);
|
||||
if (buffer && buffer.byteLength > 0) {
|
||||
@@ -80,7 +80,7 @@ export async function getFavicon(
|
||||
const { hostname, origin } = new URL(url);
|
||||
const cache = await redis.get(`favicon:${hostname}`);
|
||||
if (cache) {
|
||||
return sendBuffer(Buffer.from(cache, 'base64'));
|
||||
return sendBuffer(Buffer.from(cache, "base64"));
|
||||
}
|
||||
|
||||
// TRY FAVICON.ICO
|
||||
@@ -94,7 +94,7 @@ export async function getFavicon(
|
||||
|
||||
function findFavicon(res: string) {
|
||||
const match = res.match(
|
||||
/(\<link(.+?)image\/x-icon(.+?)\>|\<link(.+?)shortcut\sicon(.+?)\>)/
|
||||
/(\<link(.+?)image\/x-icon(.+?)\>|\<link(.+?)shortcut\sicon(.+?)\>)/,
|
||||
);
|
||||
if (!match) {
|
||||
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(
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply
|
||||
reply: FastifyReply,
|
||||
) {
|
||||
const keys = await redis.keys('favicon:*');
|
||||
const keys = await redis.keys("favicon:*");
|
||||
for (const key of keys) {
|
||||
await redis.del(key);
|
||||
}
|
||||
return reply.status(404).send('OK');
|
||||
return reply.status(404).send("OK");
|
||||
}
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
import { getClientIp, parseIp } from '@/utils/parseIp';
|
||||
import { isUserAgentSet, parseUserAgent } from '@/utils/parseUserAgent';
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { assocPath, pathOr } from 'ramda';
|
||||
import { getClientIp, parseIp } from "@/utils/parseIp";
|
||||
import { isUserAgentSet, parseUserAgent } from "@/utils/parseUserAgent";
|
||||
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||
import { assocPath, pathOr } from "ramda";
|
||||
|
||||
import { getProfileById, upsertProfile } from '@mixan/db';
|
||||
import type { IncrementProfilePayload, UpdateProfilePayload } from '@mixan/sdk';
|
||||
import { getProfileById, upsertProfile } from "@openpanel/db";
|
||||
import type {
|
||||
IncrementProfilePayload,
|
||||
UpdateProfilePayload,
|
||||
} from "@openpanel/sdk";
|
||||
|
||||
export async function updateProfile(
|
||||
request: FastifyRequest<{
|
||||
Body: UpdateProfilePayload;
|
||||
}>,
|
||||
reply: FastifyReply
|
||||
reply: FastifyReply,
|
||||
) {
|
||||
const { profileId, properties, ...rest } = request.body;
|
||||
const projectId = request.projectId;
|
||||
const ip = getClientIp(request)!;
|
||||
const ua = request.headers['user-agent']!;
|
||||
const ua = request.headers["user-agent"]!;
|
||||
const uaInfo = parseUserAgent(ua);
|
||||
const geo = await parseIp(ip);
|
||||
|
||||
@@ -37,29 +40,29 @@ export async function incrementProfileProperty(
|
||||
request: FastifyRequest<{
|
||||
Body: IncrementProfilePayload;
|
||||
}>,
|
||||
reply: FastifyReply
|
||||
reply: FastifyReply,
|
||||
) {
|
||||
const { profileId, property, value } = request.body;
|
||||
const projectId = request.projectId;
|
||||
|
||||
const profile = await getProfileById(profileId);
|
||||
if (!profile) {
|
||||
return reply.status(404).send('Not found');
|
||||
return reply.status(404).send("Not found");
|
||||
}
|
||||
|
||||
const parsed = parseInt(
|
||||
pathOr<string>('0', property.split('.'), profile.properties),
|
||||
10
|
||||
pathOr<string>("0", property.split("."), profile.properties),
|
||||
10,
|
||||
);
|
||||
|
||||
if (isNaN(parsed)) {
|
||||
return reply.status(400).send('Not number');
|
||||
return reply.status(400).send("Not number");
|
||||
}
|
||||
|
||||
profile.properties = assocPath(
|
||||
property.split('.'),
|
||||
property.split("."),
|
||||
parsed + value,
|
||||
profile.properties
|
||||
profile.properties,
|
||||
);
|
||||
|
||||
await upsertProfile({
|
||||
@@ -75,29 +78,29 @@ export async function decrementProfileProperty(
|
||||
request: FastifyRequest<{
|
||||
Body: IncrementProfilePayload;
|
||||
}>,
|
||||
reply: FastifyReply
|
||||
reply: FastifyReply,
|
||||
) {
|
||||
const { profileId, property, value } = request.body;
|
||||
const projectId = request.projectId;
|
||||
|
||||
const profile = await getProfileById(profileId);
|
||||
if (!profile) {
|
||||
return reply.status(404).send('Not found');
|
||||
return reply.status(404).send("Not found");
|
||||
}
|
||||
|
||||
const parsed = parseInt(
|
||||
pathOr<string>('0', property.split('.'), profile.properties),
|
||||
10
|
||||
pathOr<string>("0", property.split("."), profile.properties),
|
||||
10,
|
||||
);
|
||||
|
||||
if (isNaN(parsed)) {
|
||||
return reply.status(400).send('Not number');
|
||||
return reply.status(400).send("Not number");
|
||||
}
|
||||
|
||||
profile.properties = assocPath(
|
||||
property.split('.'),
|
||||
property.split("."),
|
||||
parsed - value,
|
||||
profile.properties
|
||||
profile.properties,
|
||||
);
|
||||
|
||||
await upsertProfile({
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
import cors from '@fastify/cors';
|
||||
import Fastify from 'fastify';
|
||||
import cors from "@fastify/cors";
|
||||
import Fastify from "fastify";
|
||||
|
||||
import { redisPub } from '@mixan/redis';
|
||||
import { redisPub } from "@openpanel/redis";
|
||||
|
||||
import eventRouter from './routes/event.router';
|
||||
import liveRouter from './routes/live.router';
|
||||
import miscRouter from './routes/misc.router';
|
||||
import profileRouter from './routes/profile.router';
|
||||
import { logger, logInfo } from './utils/logger';
|
||||
import eventRouter from "./routes/event.router";
|
||||
import liveRouter from "./routes/live.router";
|
||||
import miscRouter from "./routes/misc.router";
|
||||
import profileRouter from "./routes/profile.router";
|
||||
import { logger, logInfo } from "./utils/logger";
|
||||
|
||||
declare module 'fastify' {
|
||||
declare module "fastify" {
|
||||
interface FastifyRequest {
|
||||
projectId: string;
|
||||
}
|
||||
}
|
||||
|
||||
const port = parseInt(process.env.API_PORT || '3000', 10);
|
||||
const port = parseInt(process.env.API_PORT || "3000", 10);
|
||||
|
||||
const startServer = async () => {
|
||||
logInfo('Starting server');
|
||||
logInfo("Starting server");
|
||||
try {
|
||||
const fastify = Fastify({
|
||||
logger: logger,
|
||||
});
|
||||
|
||||
fastify.register(cors, {
|
||||
origin: '*',
|
||||
origin: "*",
|
||||
});
|
||||
|
||||
fastify.decorateRequest('projectId', '');
|
||||
fastify.register(eventRouter, { prefix: '/event' });
|
||||
fastify.register(profileRouter, { prefix: '/profile' });
|
||||
fastify.register(liveRouter, { prefix: '/live' });
|
||||
fastify.register(miscRouter, { prefix: '/misc' });
|
||||
fastify.decorateRequest("projectId", "");
|
||||
fastify.register(eventRouter, { prefix: "/event" });
|
||||
fastify.register(profileRouter, { prefix: "/profile" });
|
||||
fastify.register(liveRouter, { prefix: "/live" });
|
||||
fastify.register(miscRouter, { prefix: "/misc" });
|
||||
fastify.setErrorHandler((error, request, reply) => {
|
||||
fastify.log.error(error);
|
||||
});
|
||||
fastify.get('/', (request, reply) => {
|
||||
reply.send({ name: 'openpanel sdk api' });
|
||||
fastify.get("/", (request, reply) => {
|
||||
reply.send({ name: "openpanel sdk api" });
|
||||
});
|
||||
// fastify.get('/health-check', async (request, reply) => {
|
||||
// try {
|
||||
@@ -47,8 +47,8 @@ const startServer = async () => {
|
||||
// reply.status(500).send()
|
||||
// }
|
||||
// })
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
for (const signal of ['SIGINT', 'SIGTERM']) {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
for (const signal of ["SIGINT", "SIGTERM"]) {
|
||||
process.on(signal, (err) => {
|
||||
logger.fatal(err, `uncaught exception detected ${signal}`);
|
||||
fastify.close().then((err) => {
|
||||
@@ -59,12 +59,12 @@ const startServer = async () => {
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
// Notify when keys expires
|
||||
redisPub.config('SET', 'notify-keyspace-events', 'Ex');
|
||||
redisPub.config("SET", "notify-keyspace-events", "Ex");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
@@ -1,56 +1,56 @@
|
||||
import { isBot } from '@/bots';
|
||||
import * as controller from '@/controllers/event.controller';
|
||||
import { validateSdkRequest } from '@/utils/auth';
|
||||
import type { FastifyPluginCallback, FastifyRequest } from 'fastify';
|
||||
import { isBot } from "@/bots";
|
||||
import * as controller from "@/controllers/event.controller";
|
||||
import { validateSdkRequest } from "@/utils/auth";
|
||||
import type { FastifyPluginCallback, FastifyRequest } from "fastify";
|
||||
|
||||
import { createBotEvent } from '@mixan/db';
|
||||
import type { PostEventPayload } from '@mixan/sdk';
|
||||
import { createBotEvent } from "@openpanel/db";
|
||||
import type { PostEventPayload } from "@openpanel/sdk";
|
||||
|
||||
const eventRouter: FastifyPluginCallback = (fastify, opts, done) => {
|
||||
fastify.addHook(
|
||||
'preHandler',
|
||||
"preHandler",
|
||||
async (
|
||||
req: FastifyRequest<{
|
||||
Body: PostEventPayload;
|
||||
}>,
|
||||
reply
|
||||
reply,
|
||||
) => {
|
||||
try {
|
||||
const projectId = await validateSdkRequest(req.headers);
|
||||
req.projectId = projectId;
|
||||
|
||||
const bot = req.headers['user-agent']
|
||||
? isBot(req.headers['user-agent'])
|
||||
const bot = req.headers["user-agent"]
|
||||
? isBot(req.headers["user-agent"])
|
||||
: null;
|
||||
|
||||
if (bot) {
|
||||
const path = (req.body?.properties?.__path ||
|
||||
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({
|
||||
...bot,
|
||||
projectId,
|
||||
path: path ?? '',
|
||||
path: path ?? "",
|
||||
createdAt: new Date(req.body?.timestamp),
|
||||
});
|
||||
reply.status(202).send('OK');
|
||||
reply.status(202).send("OK");
|
||||
}
|
||||
} catch (e) {
|
||||
req.log.error(e, 'Failed to create bot event');
|
||||
req.log.error(e, "Failed to create bot event");
|
||||
reply.status(401).send();
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
fastify.route({
|
||||
method: 'POST',
|
||||
url: '/',
|
||||
method: "POST",
|
||||
url: "/",
|
||||
handler: controller.postEvent,
|
||||
});
|
||||
fastify.route({
|
||||
method: 'GET',
|
||||
url: '/',
|
||||
method: "GET",
|
||||
url: "/",
|
||||
handler: controller.postEvent,
|
||||
});
|
||||
done();
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { RawRequestDefaultExpression } from 'fastify';
|
||||
|
||||
import { verifyPassword } from '@mixan/common';
|
||||
import { db } from '@mixan/db';
|
||||
import { verifyPassword } from '@openpanel/common';
|
||||
import { db } from '@openpanel/db';
|
||||
|
||||
export async function validateSdkRequest(
|
||||
headers: RawRequestDefaultExpression['headers']
|
||||
): Promise<string> {
|
||||
const clientId = headers['mixan-client-id'] as string;
|
||||
const clientSecret = headers['mixan-client-secret'] as string;
|
||||
const clientId = headers['openpanel-client-id'] as string;
|
||||
const clientSecret = headers['openpanel-client-secret'] as string;
|
||||
const origin = headers.origin;
|
||||
if (!clientId) {
|
||||
throw new Error('Misisng client id');
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import { stripTrailingSlash } from '@mixan/common';
|
||||
import { stripTrailingSlash } from "@openpanel/common";
|
||||
|
||||
import referrers from '../referrers';
|
||||
import referrers from "../referrers";
|
||||
|
||||
function getHostname(url: string | undefined) {
|
||||
if (!url) {
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
return new URL(url).hostname;
|
||||
} catch (e) {
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export function parseReferrer(url: string | undefined) {
|
||||
const hostname = getHostname(url);
|
||||
const match = referrers[hostname] ?? referrers[hostname.replace('www.', '')];
|
||||
const match = referrers[hostname] ?? referrers[hostname.replace("www.", "")];
|
||||
|
||||
return {
|
||||
name: match?.name ?? '',
|
||||
type: match?.type ?? 'unknown',
|
||||
url: stripTrailingSlash(url ?? ''),
|
||||
name: match?.name ?? "",
|
||||
type: match?.type ?? "unknown",
|
||||
url: stripTrailingSlash(url ?? ""),
|
||||
};
|
||||
}
|
||||
|
||||
export function getReferrerWithQuery(
|
||||
query: Record<string, string> | undefined
|
||||
query: Record<string, string> | undefined,
|
||||
) {
|
||||
if (!query) {
|
||||
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(
|
||||
(referrer) => referrer.name.toLowerCase() === source?.toLowerCase()
|
||||
(referrer) => referrer.name.toLowerCase() === source?.toLowerCase(),
|
||||
);
|
||||
|
||||
if (!match) {
|
||||
@@ -45,6 +45,6 @@ export function getReferrerWithQuery(
|
||||
return {
|
||||
name: match.name,
|
||||
type: match.type,
|
||||
url: '',
|
||||
url: "",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "@mixan/tsconfig/base.json",
|
||||
"extends": "@openpanel/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { Options } from 'tsup';
|
||||
const options: Options = {
|
||||
clean: true,
|
||||
entry: ['src/index.ts'],
|
||||
noExternal: [/^@mixan\/.*$/u, /^@\/.*$/u],
|
||||
noExternal: [/^@openpanel\/.*$/u, /^@\/.*$/u],
|
||||
sourcemap: true,
|
||||
splitting: false,
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ await import('./src/env.mjs');
|
||||
/** @type {import("next").NextConfig} */
|
||||
const config = {
|
||||
reactStrictMode: true,
|
||||
transpilePackages: ['@mixan/queue'],
|
||||
transpilePackages: ['@openpanel/queue'],
|
||||
eslint: { ignoreDuringBuilds: true },
|
||||
typescript: { ignoreBuildErrors: true },
|
||||
experimental: {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@mixan/dashboard",
|
||||
"name": "@openpanel/dashboard",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -16,11 +16,11 @@
|
||||
"@clerk/nextjs": "^4.29.7",
|
||||
"@clickhouse/client": "^0.2.9",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@mixan/common": "workspace:^",
|
||||
"@mixan/constants": "workspace:^",
|
||||
"@mixan/db": "workspace:^",
|
||||
"@mixan/queue": "workspace:^",
|
||||
"@mixan/validation": "workspace:^",
|
||||
"@openpanel/common": "workspace:^",
|
||||
"@openpanel/constants": "workspace:^",
|
||||
"@openpanel/db": "workspace:^",
|
||||
"@openpanel/queue": "workspace:^",
|
||||
"@openpanel/validation": "workspace:^",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
"@radix-ui/react-aspect-ratio": "^1.0.3",
|
||||
@@ -93,9 +93,9 @@
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mixan/eslint-config": "workspace:*",
|
||||
"@mixan/prettier-config": "workspace:*",
|
||||
"@mixan/tsconfig": "workspace:*",
|
||||
"@openpanel/eslint-config": "workspace:*",
|
||||
"@openpanel/prettier-config": "workspace:*",
|
||||
"@openpanel/tsconfig": "workspace:*",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/lodash.debounce": "^4.0.9",
|
||||
"@types/lodash.throttle": "^4.1.9",
|
||||
@@ -121,10 +121,10 @@
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": [
|
||||
"@mixan/eslint-config/base",
|
||||
"@mixan/eslint-config/react",
|
||||
"@mixan/eslint-config/nextjs"
|
||||
"@openpanel/eslint-config/base",
|
||||
"@openpanel/eslint-config/react",
|
||||
"@openpanel/eslint-config/nextjs"
|
||||
]
|
||||
},
|
||||
"prettier": "@mixan/prettier-config"
|
||||
"prettier": "@openpanel/prettier-config"
|
||||
}
|
||||
|
||||
@@ -13,15 +13,14 @@ import {
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { ChevronRight, MoreHorizontal, PlusIcon, Trash } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import {
|
||||
getDefaultIntervalByDates,
|
||||
getDefaultIntervalByRange,
|
||||
} from '@mixan/constants';
|
||||
import type { getReportsByDashboardId } from '@mixan/db';
|
||||
} from '@openpanel/constants';
|
||||
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';
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||
import { getExists } from '@/server/pageExists';
|
||||
import { getDashboardById, getReportsByDashboardId } from '@openpanel/db';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import { getDashboardById, getReportsByDashboardId } from '@mixan/db';
|
||||
|
||||
import { ListReports } from './list-reports';
|
||||
|
||||
interface PageProps {
|
||||
|
||||
@@ -7,13 +7,12 @@ import { Button } from '@/components/ui/button';
|
||||
import { ToastAction } from '@/components/ui/toast';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { pushModal } from '@/modals';
|
||||
import type { IServiceDashboards } from '@openpanel/db';
|
||||
import { LayoutPanelTopIcon, Pencil, PlusIcon, Trash } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import type { IServiceDashboards } from '@mixan/db';
|
||||
|
||||
interface ListDashboardsProps {
|
||||
dashboards: IServiceDashboards;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||
import { getExists } from '@/server/pageExists';
|
||||
|
||||
import { getDashboardsByProjectId } from '@mixan/db';
|
||||
import { getDashboardsByProjectId } from '@openpanel/db';
|
||||
|
||||
import { HeaderDashboards } from './header-dashboards';
|
||||
import { ListDashboards } from './list-dashboards';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ChartSwitchShortcut } from '@/components/report/chart';
|
||||
|
||||
import type { IChartEvent } from '@mixan/validation';
|
||||
import type { IChartEvent } from '@openpanel/validation';
|
||||
|
||||
interface Props {
|
||||
projectId: string;
|
||||
|
||||
@@ -13,10 +13,9 @@ import {
|
||||
useEventQueryFilters,
|
||||
useEventQueryNamesFilter,
|
||||
} from '@/hooks/useEventQueryFilters';
|
||||
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
||||
import { round } from 'mathjs';
|
||||
|
||||
import type { IServiceCreateEventPayload } from '@mixan/db';
|
||||
|
||||
interface Props {
|
||||
event: IServiceCreateEventPayload;
|
||||
open: boolean;
|
||||
|
||||
@@ -12,11 +12,10 @@ import {
|
||||
SheetTitle,
|
||||
} from '@/components/ui/sheet';
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import type { IServiceCreateEventPayload } from '@mixan/db';
|
||||
|
||||
import {
|
||||
EventIconColors,
|
||||
EventIconMapper,
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
SheetTrigger,
|
||||
} from '@/components/ui/sheet';
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { EventMeta } from '@openpanel/db';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
@@ -19,8 +20,6 @@ import * as Icons from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import type { EventMeta } from '@mixan/db';
|
||||
|
||||
const variants = cva('flex items-center justify-center shrink-0 rounded-full', {
|
||||
variants: {
|
||||
size: {
|
||||
|
||||
@@ -9,8 +9,7 @@ import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
|
||||
import { useNumber } from '@/hooks/useNumerFormatter';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { getProfileName } from '@/utils/getters';
|
||||
|
||||
import type { IServiceCreateEventPayload } from '@mixan/db';
|
||||
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
||||
|
||||
import { EventDetails } from './event-details';
|
||||
import { EventEdit } from './event-edit';
|
||||
|
||||
@@ -8,11 +8,10 @@ import { Button } from '@/components/ui/button';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { useCursor } from '@/hooks/useCursor';
|
||||
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
|
||||
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
||||
import { isSameDay } from 'date-fns';
|
||||
import { GanttChartIcon } from 'lucide-react';
|
||||
|
||||
import type { IServiceCreateEventPayload } from '@mixan/db';
|
||||
|
||||
import { EventListItem } from './event-list-item';
|
||||
import EventListener from './event-listener';
|
||||
|
||||
|
||||
@@ -8,14 +8,13 @@ import {
|
||||
} from '@/components/ui/tooltip';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { IServiceCreateEventPayload } from '@openpanel/db';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import useWebSocket from 'react-use-websocket';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import type { IServiceCreateEventPayload } from '@mixan/db';
|
||||
|
||||
const AnimatedNumbers = dynamic(() => import('react-animated-numbers'), {
|
||||
ssr: false,
|
||||
loading: () => <div>0</div>,
|
||||
|
||||
@@ -6,10 +6,9 @@ import {
|
||||
eventQueryNamesFilter,
|
||||
} from '@/hooks/useEventQueryFilters';
|
||||
import { getExists } from '@/server/pageExists';
|
||||
import { getEventList, getEventsCount } from '@openpanel/db';
|
||||
import { parseAsInteger } from 'nuqs';
|
||||
|
||||
import { getEventList, getEventsCount } from '@mixan/db';
|
||||
|
||||
import { StickyBelowHeader } from '../layout-sticky-below-header';
|
||||
import { EventChart } from './event-chart';
|
||||
import { EventList } from './event-list';
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useEffect } from 'react';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { useUser } from '@clerk/nextjs';
|
||||
import type { IServiceDashboards } from '@openpanel/db';
|
||||
import {
|
||||
BuildingIcon,
|
||||
CogIcon,
|
||||
@@ -20,8 +21,6 @@ import type { LucideProps } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import type { IServiceDashboards } from '@mixan/db';
|
||||
|
||||
function LinkWithIcon({
|
||||
href,
|
||||
icon: Icon,
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
import { Combobox } from '@/components/ui/combobox';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import type { IServiceOrganization } from '@openpanel/db';
|
||||
import { Building } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import type { IServiceOrganization } from '@mixan/db';
|
||||
|
||||
interface LayoutOrganizationSelectorProps {
|
||||
organizations: IServiceOrganization[];
|
||||
}
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
import { Combobox } from '@/components/ui/combobox';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import type { getProjectsByOrganizationSlug } from '@openpanel/db';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
|
||||
import type { getProjectsByOrganizationSlug } from '@mixan/db';
|
||||
|
||||
interface LayoutProjectSelectorProps {
|
||||
projects: Awaited<ReturnType<typeof getProjectsByOrganizationSlug>>;
|
||||
}
|
||||
|
||||
@@ -4,13 +4,12 @@ import { useEffect, useState } from 'react';
|
||||
import { Logo } from '@/components/Logo';
|
||||
import { buttonVariants } from '@/components/ui/button';
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { IServiceDashboards, IServiceOrganization } from '@openpanel/db';
|
||||
import { Rotate as Hamburger } from 'hamburger-react';
|
||||
import { PlusIcon } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import type { IServiceDashboards, IServiceOrganization } from '@mixan/db';
|
||||
|
||||
import LayoutMenu from './layout-menu';
|
||||
import LayoutOrganizationSelector from './layout-organization-selector';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
getCurrentOrganizations,
|
||||
getDashboardsByOrganization,
|
||||
getDashboardsByProjectId,
|
||||
} from '@mixan/db';
|
||||
} from '@openpanel/db';
|
||||
|
||||
import { LayoutSidebar } from './layout-sidebar';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getProjectsByOrganizationSlug } from '@mixan/db';
|
||||
import { getProjectsByOrganizationSlug } from '@openpanel/db';
|
||||
|
||||
import LayoutProjectSelector from './layout-project-selector';
|
||||
|
||||
|
||||
@@ -10,8 +10,7 @@ import OverviewTopGeo from '@/components/overview/overview-top-geo';
|
||||
import OverviewTopPages from '@/components/overview/overview-top-pages';
|
||||
import OverviewTopSources from '@/components/overview/overview-top-sources';
|
||||
import { getExists } from '@/server/pageExists';
|
||||
|
||||
import { db } from '@mixan/db';
|
||||
import { db } from '@openpanel/db';
|
||||
|
||||
import OverviewMetrics from '../../../../components/overview/overview-metrics';
|
||||
import { CreateClient } from './create-client';
|
||||
|
||||
@@ -12,17 +12,16 @@ import {
|
||||
import { getExists } from '@/server/pageExists';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { getProfileName } from '@/utils/getters';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { parseAsInteger, parseAsString } from 'nuqs';
|
||||
|
||||
import type { GetEventListOptions } from '@mixan/db';
|
||||
import type { GetEventListOptions } from '@openpanel/db';
|
||||
import {
|
||||
getConversionEventNames,
|
||||
getEventList,
|
||||
getEventsCount,
|
||||
getProfileById,
|
||||
} from '@mixan/db';
|
||||
import type { IChartEvent, IChartInput } from '@mixan/validation';
|
||||
} from '@openpanel/db';
|
||||
import type { IChartEvent, IChartInput } from '@openpanel/validation';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { parseAsInteger, parseAsString } from 'nuqs';
|
||||
|
||||
import { EventList } from '../../events/event-list';
|
||||
import { StickyBelowHeader } from '../../layout-sticky-below-header';
|
||||
|
||||
@@ -3,10 +3,9 @@ import { OverviewFiltersButtons } from '@/components/overview/filters/overview-f
|
||||
import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer';
|
||||
import { eventQueryFiltersParser } from '@/hooks/useEventQueryFilters';
|
||||
import { getExists } from '@/server/pageExists';
|
||||
import { getProfileList, getProfileListCount } from '@openpanel/db';
|
||||
import { parseAsInteger } from 'nuqs';
|
||||
|
||||
import { getProfileList, getProfileListCount } from '@mixan/db';
|
||||
|
||||
import { StickyBelowHeader } from '../layout-sticky-below-header';
|
||||
import { ProfileList } from './profile-list';
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@ import { KeyValue, KeyValueSubtle } from '@/components/ui/key-value';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
|
||||
import { getProfileName } from '@/utils/getters';
|
||||
|
||||
import type { IServiceProfile } from '@mixan/db';
|
||||
import type { IServiceProfile } from '@openpanel/db';
|
||||
|
||||
type ProfileListItemProps = IServiceProfile;
|
||||
|
||||
|
||||
@@ -6,10 +6,9 @@ import { Pagination } from '@/components/Pagination';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useCursor } from '@/hooks/useCursor';
|
||||
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
|
||||
import type { IServiceProfile } from '@openpanel/db';
|
||||
import { UsersIcon } from 'lucide-react';
|
||||
|
||||
import type { IServiceProfile } from '@mixan/db';
|
||||
|
||||
import { ProfileListItem } from './profile-list-item';
|
||||
|
||||
interface ProfileListProps {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||
import { getExists } from '@/server/pageExists';
|
||||
import { getOrganizationBySlug, getReportById } from '@openpanel/db';
|
||||
import { Pencil } from 'lucide-react';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import { getOrganizationBySlug, getReportById } from '@mixan/db';
|
||||
|
||||
import ReportEditor from '../report-editor';
|
||||
|
||||
interface PageProps {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||
import { getExists } from '@/server/pageExists';
|
||||
import { getOrganizationBySlug } from '@openpanel/db';
|
||||
import { Pencil } from 'lucide-react';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import { getOrganizationBySlug } from '@mixan/db';
|
||||
|
||||
import ReportEditor from './report-editor';
|
||||
|
||||
interface PageProps {
|
||||
|
||||
@@ -22,11 +22,10 @@ import { Button } from '@/components/ui/button';
|
||||
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { useDispatch, useSelector } from '@/redux';
|
||||
import type { IServiceReport } from '@openpanel/db';
|
||||
import { endOfDay, startOfDay } from 'date-fns';
|
||||
import { GanttChartSquareIcon } from 'lucide-react';
|
||||
|
||||
import type { IServiceReport } from '@mixan/db';
|
||||
|
||||
interface ReportEditorProps {
|
||||
report: IServiceReport | null;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,9 @@ import { DataTable } from '@/components/DataTable';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { pushModal } from '@/modals';
|
||||
import type { getClientsByOrganizationId } from '@openpanel/db';
|
||||
import { PlusIcon } from 'lucide-react';
|
||||
|
||||
import type { getClientsByOrganizationId } from '@mixan/db';
|
||||
|
||||
interface ListClientsProps {
|
||||
clients: Awaited<ReturnType<typeof getClientsByOrganizationId>>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||
import { getExists } from '@/server/pageExists';
|
||||
|
||||
import { getClientsByOrganizationId } from '@mixan/db';
|
||||
import { getClientsByOrganizationId } from '@openpanel/db';
|
||||
|
||||
import ListClients from './list-clients';
|
||||
|
||||
|
||||
@@ -4,13 +4,12 @@ import { api, handleError } from '@/app/_trpc/client';
|
||||
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
|
||||
import type { getOrganizationBySlug } from '@openpanel/db';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { getOrganizationBySlug } from '@mixan/db';
|
||||
|
||||
const validator = z.object({
|
||||
id: z.string().min(2),
|
||||
name: z.string().min(2),
|
||||
|
||||
@@ -3,14 +3,13 @@ import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { zInviteUser } from '@openpanel/validation';
|
||||
import { SendIcon } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { zInviteUser } from '@mixan/validation';
|
||||
|
||||
type IForm = z.infer<typeof zInviteUser>;
|
||||
|
||||
export function InviteUser() {
|
||||
|
||||
@@ -9,8 +9,7 @@ import {
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
|
||||
|
||||
import type { IServiceInvites } from '@mixan/db';
|
||||
import type { IServiceInvites } from '@openpanel/db';
|
||||
|
||||
import { InviteUser } from './invite-user';
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||
import { clerkClient } from '@clerk/nextjs';
|
||||
import { getInvites, getOrganizationBySlug } from '@openpanel/db';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import { getInvites, getOrganizationBySlug } from '@mixan/db';
|
||||
|
||||
import EditOrganization from './edit-organization';
|
||||
import InvitedUsers from './invited-users';
|
||||
|
||||
|
||||
@@ -5,13 +5,12 @@ import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import type { getUserById } from '@openpanel/db';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { getUserById } from '@mixan/db';
|
||||
|
||||
const validator = z.object({
|
||||
firstName: z.string().min(2),
|
||||
lastName: z.string().min(2),
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||
import { getExists } from '@/server/pageExists';
|
||||
import { auth } from '@clerk/nextjs';
|
||||
|
||||
import { getUserById } from '@mixan/db';
|
||||
import { getUserById } from '@openpanel/db';
|
||||
|
||||
import EditProfile from './edit-profile';
|
||||
import { Logout } from './logout';
|
||||
|
||||
@@ -6,10 +6,9 @@ import { columns } from '@/components/projects/table';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { pushModal } from '@/modals';
|
||||
import type { getProjectsByOrganizationSlug } from '@openpanel/db';
|
||||
import { PlusIcon } from 'lucide-react';
|
||||
|
||||
import type { getProjectsByOrganizationSlug } from '@mixan/db';
|
||||
|
||||
interface ListProjectsProps {
|
||||
projects: Awaited<ReturnType<typeof getProjectsByOrganizationSlug>>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||
import { getExists } from '@/server/pageExists';
|
||||
|
||||
import { getProjectsByOrganizationSlug } from '@mixan/db';
|
||||
import { getProjectsByOrganizationSlug } from '@openpanel/db';
|
||||
|
||||
import ListProjects from './list-projects';
|
||||
|
||||
|
||||
@@ -5,10 +5,9 @@ import { DataTable } from '@/components/DataTable';
|
||||
import { columns } from '@/components/references/table';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { pushModal } from '@/modals';
|
||||
import type { IServiceReference } from '@openpanel/db';
|
||||
import { PlusIcon } from 'lucide-react';
|
||||
|
||||
import type { IServiceReference } from '@mixan/db';
|
||||
|
||||
interface ListProjectsProps {
|
||||
data: IServiceReference[];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
|
||||
import { getExists } from '@/server/pageExists';
|
||||
|
||||
import { getReferences } from '@mixan/db';
|
||||
import { getReferences } from '@openpanel/db';
|
||||
|
||||
import ListReferences from './list-references';
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { LogoSquare } from '@/components/Logo';
|
||||
import { ProjectCard } from '@/components/projects/project-card';
|
||||
import { notFound, redirect } from 'next/navigation';
|
||||
|
||||
import {
|
||||
getOrganizationBySlug,
|
||||
getProjectsByOrganizationSlug,
|
||||
} from '@mixan/db';
|
||||
} from '@openpanel/db';
|
||||
import { notFound, redirect } from 'next/navigation';
|
||||
|
||||
import { CreateProject } from './create-project';
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// import { CreateOrganization } from '@clerk/nextjs';
|
||||
|
||||
import { LogoSquare } from '@/components/Logo';
|
||||
import { getCurrentOrganizations } from '@openpanel/db';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
import { getCurrentOrganizations } from '@mixan/db';
|
||||
|
||||
import { CreateOrganization } from './create-organization';
|
||||
|
||||
export default async function Page() {
|
||||
|
||||
@@ -11,10 +11,9 @@ import OverviewTopEvents from '@/components/overview/overview-top-events';
|
||||
import OverviewTopGeo from '@/components/overview/overview-top-geo';
|
||||
import OverviewTopPages from '@/components/overview/overview-top-pages';
|
||||
import OverviewTopSources from '@/components/overview/overview-top-sources';
|
||||
import { getOrganizationBySlug, getShareOverviewById } from '@openpanel/db';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import { getOrganizationBySlug, getShareOverviewById } from '@mixan/db';
|
||||
|
||||
interface PageProps {
|
||||
params: {
|
||||
id: string;
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
import { api } from '@/app/_trpc/client';
|
||||
import { pushModal, showConfirm } from '@/modals';
|
||||
import { clipboard } from '@/utils/clipboard';
|
||||
import type { IServiceClientWithProject } from '@openpanel/db';
|
||||
import { MoreHorizontal } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import type { IServiceClientWithProject } from '@mixan/db';
|
||||
|
||||
import { Button } from '../ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { formatDate } from '@/utils/date';
|
||||
import type { IServiceClientWithProject } from '@openpanel/db';
|
||||
import type { ColumnDef } from '@tanstack/react-table';
|
||||
|
||||
import type { IServiceClientWithProject } from '@mixan/db';
|
||||
|
||||
import { ClientActions } from './ClientActions';
|
||||
|
||||
export const columns: ColumnDef<IServiceClientWithProject>[] = [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { toDots } from '@mixan/common';
|
||||
import { toDots } from '@openpanel/common';
|
||||
|
||||
import { Table, TableBody, TableCell, TableRow } from '../ui/table';
|
||||
|
||||
|
||||
@@ -11,14 +11,13 @@ import {
|
||||
import { useEventValues } from '@/hooks/useEventValues';
|
||||
import { useProfileProperties } from '@/hooks/useProfileProperties';
|
||||
import { useProfileValues } from '@/hooks/useProfileValues';
|
||||
import { XIcon } from 'lucide-react';
|
||||
import type { Options as NuqsOptions } from 'nuqs';
|
||||
|
||||
import type {
|
||||
IChartEventFilter,
|
||||
IChartEventFilterOperator,
|
||||
IChartEventFilterValue,
|
||||
} from '@mixan/validation';
|
||||
} from '@openpanel/validation';
|
||||
import { XIcon } from 'lucide-react';
|
||||
import type { Options as NuqsOptions } from 'nuqs';
|
||||
|
||||
export interface OverviewFiltersDrawerContentProps {
|
||||
projectId: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getLiveVisitors } from '@mixan/db';
|
||||
import { getLiveVisitors } from '@openpanel/db';
|
||||
|
||||
import type { LiveCounterProps } from './live-counter';
|
||||
import LiveCounter from './live-counter';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getConversionEventNames } from '@mixan/db';
|
||||
import { getConversionEventNames } from '@openpanel/db';
|
||||
|
||||
import type { OverviewLatestEventsProps } from './overview-latest-events';
|
||||
import OverviewLatestEvents from './overview-latest-events';
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
import { Fragment } from 'react';
|
||||
import { api } from '@/app/_trpc/client';
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { IChartInput } from '@openpanel/validation';
|
||||
import { ChevronsUpDownIcon } from 'lucide-react';
|
||||
import AnimateHeight from 'react-animate-height';
|
||||
|
||||
import type { IChartInput } from '@mixan/validation';
|
||||
|
||||
import { redisSub } from '../../../../../packages/redis';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
|
||||
import { useOverviewOptions } from './useOverviewOptions';
|
||||
|
||||
@@ -6,8 +6,7 @@ import { ChartSwitch } from '@/components/report/chart';
|
||||
import { Widget, WidgetBody } from '@/components/Widget';
|
||||
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
import type { IChartInput } from '@mixan/validation';
|
||||
import type { IChartInput } from '@openpanel/validation';
|
||||
|
||||
interface OverviewMetricsProps {
|
||||
projectId: string;
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
|
||||
import { api } from '@/app/_trpc/client';
|
||||
import { pushModal } from '@/modals';
|
||||
import type { ShareOverview } from '@openpanel/db';
|
||||
import { EyeIcon, Globe2Icon, LockIcon } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import type { ShareOverview } from '@mixan/db';
|
||||
|
||||
import { Button } from '../ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getConversionEventNames } from '@mixan/db';
|
||||
import { getConversionEventNames } from '@openpanel/db';
|
||||
|
||||
import type { OverviewTopEventsProps } from './overview-top-events';
|
||||
import OverviewTopEvents from './overview-top-events';
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import {
|
||||
getDefaultIntervalByDates,
|
||||
getDefaultIntervalByRange,
|
||||
timeRanges,
|
||||
} from '@openpanel/constants';
|
||||
import { mapKeys } from '@openpanel/validation';
|
||||
import {
|
||||
parseAsBoolean,
|
||||
parseAsInteger,
|
||||
@@ -6,13 +12,6 @@ import {
|
||||
useQueryState,
|
||||
} from 'nuqs';
|
||||
|
||||
import {
|
||||
getDefaultIntervalByDates,
|
||||
getDefaultIntervalByRange,
|
||||
timeRanges,
|
||||
} from '@mixan/constants';
|
||||
import { mapKeys } from '@mixan/validation';
|
||||
|
||||
const nuqsOptions = { history: 'push' } as const;
|
||||
|
||||
export function useOverviewOptions() {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { mapKeys } from '@openpanel/validation';
|
||||
import type { IChartInput } from '@openpanel/validation';
|
||||
import { parseAsStringEnum, useQueryState } from 'nuqs';
|
||||
|
||||
import { mapKeys } from '@mixan/validation';
|
||||
import type { IChartInput } from '@mixan/validation';
|
||||
|
||||
export function useOverviewWidget<T extends string>(
|
||||
key: string,
|
||||
widgets: Record<
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { IServiceProfile } from '@openpanel/db';
|
||||
import { AvatarImage } from '@radix-ui/react-avatar';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import type { IServiceProfile } from '@mixan/db';
|
||||
|
||||
import { Avatar, AvatarFallback } from '../ui/avatar';
|
||||
|
||||
interface ProfileAvatarProps
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
import { api } from '@/app/_trpc/client';
|
||||
import { pushModal, showConfirm } from '@/modals';
|
||||
import { clipboard } from '@/utils/clipboard';
|
||||
import type { IServiceProject } from '@openpanel/db';
|
||||
import { MoreHorizontal } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import type { IServiceProject } from '@mixan/db';
|
||||
|
||||
import { Button } from '../ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { shortNumber } from '@/hooks/useNumerFormatter';
|
||||
import Link from 'next/link';
|
||||
|
||||
import type { IServiceProject } from '@mixan/db';
|
||||
import { chQuery } from '@mixan/db';
|
||||
import type { IServiceProject } from '@openpanel/db';
|
||||
import { chQuery } from '@openpanel/db';
|
||||
|
||||
import { ChartSSR } from '../chart-ssr';
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { IServiceProject } from '@openpanel/db';
|
||||
import type { Project as IProject } from '@openpanel/db';
|
||||
import type { ColumnDef } from '@tanstack/react-table';
|
||||
|
||||
import { IServiceProject } from '@mixan/db';
|
||||
import type { Project as IProject } from '@mixan/db';
|
||||
|
||||
import { ProjectActions } from './ProjectActions';
|
||||
|
||||
export type Project = IProject;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { formatDate, formatDateTime } from '@/utils/date';
|
||||
import type { IServiceReference } from '@openpanel/db';
|
||||
import type { ColumnDef } from '@tanstack/react-table';
|
||||
|
||||
import type { IServiceReference } from '@mixan/db';
|
||||
|
||||
export const columns: ColumnDef<IServiceReference>[] = [
|
||||
{
|
||||
accessorKey: 'title',
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useDispatch, useSelector } from '@/redux';
|
||||
import { chartTypes } from '@openpanel/constants';
|
||||
import { objectToZodEnums } from '@openpanel/validation';
|
||||
import { LineChartIcon } from 'lucide-react';
|
||||
|
||||
import { chartTypes } from '@mixan/constants';
|
||||
import { objectToZodEnums } from '@mixan/validation';
|
||||
|
||||
import { Combobox } from '../ui/combobox';
|
||||
import { changeChartType } from './reportSlice';
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useDispatch, useSelector } from '@/redux';
|
||||
import { ClockIcon } from 'lucide-react';
|
||||
|
||||
import {
|
||||
isHourIntervalEnabledByRange,
|
||||
isMinuteIntervalEnabledByRange,
|
||||
} from '@mixan/constants';
|
||||
import type { IInterval } from '@mixan/validation';
|
||||
} from '@openpanel/constants';
|
||||
import type { IInterval } from '@openpanel/validation';
|
||||
import { ClockIcon } from 'lucide-react';
|
||||
|
||||
import { Combobox } from '../ui/combobox';
|
||||
import { changeInterval } from './reportSlice';
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useDispatch, useSelector } from '@/redux';
|
||||
import { lineTypes } from '@openpanel/constants';
|
||||
import { objectToZodEnums } from '@openpanel/validation';
|
||||
import { Tv2Icon } from 'lucide-react';
|
||||
|
||||
import { lineTypes } from '@mixan/constants';
|
||||
import { objectToZodEnums } from '@mixan/validation';
|
||||
|
||||
import { Combobox } from '../ui/combobox';
|
||||
import { changeLineType } from './reportSlice';
|
||||
|
||||
|
||||
@@ -9,13 +9,12 @@ import {
|
||||
import { useBreakpoint } from '@/hooks/useBreakpoint';
|
||||
import { useDispatch, useSelector } from '@/redux';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { timeRanges } from '@openpanel/constants';
|
||||
import type { IChartRange } from '@openpanel/validation';
|
||||
import { endOfDay, format, startOfDay } from 'date-fns';
|
||||
import { CalendarIcon, ChevronsUpDownIcon } from 'lucide-react';
|
||||
import type { SelectRangeEventHandler } from 'react-day-picker';
|
||||
|
||||
import { timeRanges } from '@mixan/constants';
|
||||
import type { IChartRange } from '@mixan/validation';
|
||||
|
||||
import type { ExtendedComboboxProps } from '../ui/combobox';
|
||||
import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group';
|
||||
import { changeDates, changeEndDate, changeStartDate } from './reportSlice';
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { api } from '@/app/_trpc/client';
|
||||
|
||||
import type { IChartInput } from '@mixan/validation';
|
||||
import type { IChartInput } from '@openpanel/validation';
|
||||
|
||||
import { ChartEmpty } from './ChartEmpty';
|
||||
import { ReportAreaChart } from './ReportAreaChart';
|
||||
|
||||
@@ -10,8 +10,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import type { IChartSerie } from '@/server/api/routers/chart';
|
||||
|
||||
import type { IChartInput } from '@mixan/validation';
|
||||
import type { IChartInput } from '@openpanel/validation';
|
||||
|
||||
import { ChartLoading } from './ChartLoading';
|
||||
import { MetricCardLoading } from './MetricCard';
|
||||
|
||||
@@ -4,11 +4,10 @@ import type { IChartData } from '@/app/_trpc/client';
|
||||
import { ColorSquare } from '@/components/ColorSquare';
|
||||
import { fancyMinutes, useNumber } from '@/hooks/useNumerFormatter';
|
||||
import { theme } from '@/utils/theme';
|
||||
import type { IChartMetric } from '@openpanel/validation';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { Area, AreaChart } from 'recharts';
|
||||
|
||||
import type { IChartMetric } from '@mixan/validation';
|
||||
|
||||
import {
|
||||
getDiffIndicator,
|
||||
PreviousDiffIndicatorText,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useNumber } from '@/hooks/useNumerFormatter';
|
||||
import { useRechartDataModel } from '@/hooks/useRechartDataModel';
|
||||
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
|
||||
import { getChartColor } from '@/utils/theme';
|
||||
import type { IChartLineType, IInterval } from '@openpanel/validation';
|
||||
import {
|
||||
Area,
|
||||
AreaChart,
|
||||
@@ -14,8 +15,6 @@ import {
|
||||
YAxis,
|
||||
} from 'recharts';
|
||||
|
||||
import type { IChartLineType, IInterval } from '@mixan/validation';
|
||||
|
||||
import { getYAxisWidth } from './chart-utils';
|
||||
import { useChartContext } from './ChartProvider';
|
||||
import { ReportChartTooltip } from './ReportChartTooltip';
|
||||
|
||||
@@ -6,8 +6,7 @@ import { Progress } from '@/components/ui/progress';
|
||||
import { useNumber } from '@/hooks/useNumerFormatter';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { getChartColor } from '@/utils/theme';
|
||||
|
||||
import { NOT_SET_VALUE } from '@mixan/constants';
|
||||
import { NOT_SET_VALUE } from '@openpanel/constants';
|
||||
|
||||
import { PreviousDiffIndicatorText } from '../PreviousDiffIndicator';
|
||||
import { useChartContext } from './ChartProvider';
|
||||
|
||||
@@ -5,10 +5,9 @@ import { useNumber } from '@/hooks/useNumerFormatter';
|
||||
import { useRechartDataModel } from '@/hooks/useRechartDataModel';
|
||||
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
|
||||
import { getChartColor, theme } from '@/utils/theme';
|
||||
import type { IInterval } from '@openpanel/validation';
|
||||
import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts';
|
||||
|
||||
import type { IInterval } from '@mixan/validation';
|
||||
|
||||
import { getYAxisWidth } from './chart-utils';
|
||||
import { useChartContext } from './ChartProvider';
|
||||
import { ReportChartTooltip } from './ReportChartTooltip';
|
||||
|
||||
@@ -7,6 +7,8 @@ import { useNumber } from '@/hooks/useNumerFormatter';
|
||||
import { useRechartDataModel } from '@/hooks/useRechartDataModel';
|
||||
import { useVisibleSeries } from '@/hooks/useVisibleSeries';
|
||||
import { getChartColor } from '@/utils/theme';
|
||||
import type { IServiceReference } from '@openpanel/db';
|
||||
import type { IChartLineType, IInterval } from '@openpanel/validation';
|
||||
import {
|
||||
CartesianGrid,
|
||||
Line,
|
||||
@@ -17,9 +19,6 @@ import {
|
||||
YAxis,
|
||||
} from 'recharts';
|
||||
|
||||
import type { IServiceReference } from '@mixan/db';
|
||||
import type { IChartLineType, IInterval } from '@mixan/validation';
|
||||
|
||||
import { getYAxisWidth } from './chart-utils';
|
||||
import { useChartContext } from './ChartProvider';
|
||||
import { ReportChartTooltip } from './ReportChartTooltip';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import { NOT_SET_VALUE } from '@openpanel/constants';
|
||||
import type { LucideIcon, LucideProps } from 'lucide-react';
|
||||
import {
|
||||
ActivityIcon,
|
||||
@@ -14,8 +15,6 @@ import {
|
||||
TabletIcon,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { NOT_SET_VALUE } from '@mixan/constants';
|
||||
|
||||
interface SerieIconProps extends LucideProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import type { IChartInput } from '@mixan/validation';
|
||||
import type { IChartInput } from '@openpanel/validation';
|
||||
|
||||
import { Funnel } from '../funnel';
|
||||
import { Chart } from './Chart';
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
import type { RouterOutputs } from '@/app/_trpc/client';
|
||||
import { api } from '@/app/_trpc/client';
|
||||
|
||||
import type { IChartInput } from '@mixan/validation';
|
||||
import type { IChartInput } from '@openpanel/validation';
|
||||
|
||||
import { ChartEmpty } from '../chart/ChartEmpty';
|
||||
import { withChartProivder } from '../chart/ChartProvider';
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { start } from 'repl';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { isSameDay, isSameMonth } from 'date-fns';
|
||||
|
||||
import {
|
||||
alphabetIds,
|
||||
getDefaultIntervalByDates,
|
||||
getDefaultIntervalByRange,
|
||||
isHourIntervalEnabledByRange,
|
||||
isMinuteIntervalEnabledByRange,
|
||||
} from '@mixan/constants';
|
||||
} from '@openpanel/constants';
|
||||
import type {
|
||||
IChartBreakdown,
|
||||
IChartEvent,
|
||||
@@ -18,7 +14,10 @@ import type {
|
||||
IChartRange,
|
||||
IChartType,
|
||||
IInterval,
|
||||
} from '@mixan/validation';
|
||||
} from '@openpanel/validation';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { isSameDay, isSameMonth } from 'date-fns';
|
||||
|
||||
type InitialState = IChartInput & {
|
||||
dirty: boolean;
|
||||
|
||||
@@ -3,10 +3,9 @@ import { Combobox } from '@/components/ui/combobox';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { useDispatch } from '@/redux';
|
||||
import { cn } from '@/utils/cn';
|
||||
import type { IChartEvent } from '@openpanel/validation';
|
||||
import { DatabaseIcon } from 'lucide-react';
|
||||
|
||||
import type { IChartEvent } from '@mixan/validation';
|
||||
|
||||
import { changeEvent } from '../reportSlice';
|
||||
|
||||
interface EventPropertiesComboboxProps {
|
||||
|
||||
@@ -5,10 +5,9 @@ import { ColorSquare } from '@/components/ColorSquare';
|
||||
import { Combobox } from '@/components/ui/combobox';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { useDispatch, useSelector } from '@/redux';
|
||||
import type { IChartBreakdown } from '@openpanel/validation';
|
||||
import { SplitIcon } from 'lucide-react';
|
||||
|
||||
import type { IChartBreakdown } from '@mixan/validation';
|
||||
|
||||
import { addBreakdown, changeBreakdown, removeBreakdown } from '../reportSlice';
|
||||
import { ReportBreakdownMore } from './ReportBreakdownMore';
|
||||
import type { ReportEventMoreProps } from './ReportEventMore';
|
||||
|
||||
@@ -9,10 +9,9 @@ import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { useDebounceFn } from '@/hooks/useDebounceFn';
|
||||
import { useEventNames } from '@/hooks/useEventNames';
|
||||
import { useDispatch, useSelector } from '@/redux';
|
||||
import type { IChartEvent } from '@openpanel/validation';
|
||||
import { GanttChart, GanttChartIcon, Users } from 'lucide-react';
|
||||
|
||||
import type { IChartEvent } from '@mixan/validation';
|
||||
|
||||
import {
|
||||
addEvent,
|
||||
changeEvent,
|
||||
|
||||
@@ -7,15 +7,14 @@ import { RenderDots } from '@/components/ui/RenderDots';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { useMappings } from '@/hooks/useMappings';
|
||||
import { useDispatch } from '@/redux';
|
||||
import { SlidersHorizontal, Trash } from 'lucide-react';
|
||||
|
||||
import { operators } from '@mixan/constants';
|
||||
import { operators } from '@openpanel/constants';
|
||||
import type {
|
||||
IChartEvent,
|
||||
IChartEventFilterOperator,
|
||||
IChartEventFilterValue,
|
||||
} from '@mixan/validation';
|
||||
import { mapKeys } from '@mixan/validation';
|
||||
} from '@openpanel/validation';
|
||||
import { mapKeys } from '@openpanel/validation';
|
||||
import { SlidersHorizontal, Trash } from 'lucide-react';
|
||||
|
||||
import { changeEvent } from '../../reportSlice';
|
||||
|
||||
|
||||
@@ -2,10 +2,9 @@ import { api } from '@/app/_trpc/client';
|
||||
import { Combobox } from '@/components/ui/combobox';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { useDispatch } from '@/redux';
|
||||
import type { IChartEvent } from '@openpanel/validation';
|
||||
import { FilterIcon } from 'lucide-react';
|
||||
|
||||
import type { IChartEvent } from '@mixan/validation';
|
||||
|
||||
import { changeEvent } from '../../reportSlice';
|
||||
|
||||
interface FiltersComboboxProps {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { IChartEvent } from '@mixan/validation';
|
||||
import type { IChartEvent } from '@openpanel/validation';
|
||||
|
||||
import { FilterItem } from './FilterItem';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { IInterval } from '@mixan/validation';
|
||||
import type { IInterval } from '@openpanel/validation';
|
||||
|
||||
export function formatDateInterval(interval: IInterval, date: Date): string {
|
||||
if (interval === 'hour' || interval === 'minute') {
|
||||
|
||||
@@ -7,13 +7,12 @@ import { Button } from '@/components/ui/button';
|
||||
import { Calendar } from '@/components/ui/calendar';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { zCreateReference } from '@openpanel/validation';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import { zCreateReference } from '@mixan/validation';
|
||||
|
||||
import { popModal } from '.';
|
||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||
|
||||
|
||||
@@ -5,13 +5,12 @@ import { ButtonContainer } from '@/components/ButtonContainer';
|
||||
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import type { IServiceClient } from '@openpanel/db';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { IServiceClient } from '@mixan/db';
|
||||
|
||||
import { popModal } from '.';
|
||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||
|
||||
|
||||
@@ -5,13 +5,12 @@ import { ButtonContainer } from '@/components/ButtonContainer';
|
||||
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import type { IServiceDashboard } from '@openpanel/db';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { IServiceDashboard } from '@mixan/db';
|
||||
|
||||
import { popModal } from '.';
|
||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||
|
||||
|
||||
@@ -5,13 +5,12 @@ import { ButtonContainer } from '@/components/ButtonContainer';
|
||||
import { InputWithLabel } from '@/components/forms/InputWithLabel';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import type { IServiceProject } from '@openpanel/db';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { IServiceProject } from '@mixan/db';
|
||||
|
||||
import { popModal } from '.';
|
||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||
|
||||
|
||||
@@ -8,13 +8,12 @@ import { Combobox } from '@/components/ui/combobox';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { useAppParams } from '@/hooks/useAppParams';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import type { IChartInput } from '@openpanel/validation';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { IChartInput } from '@mixan/validation';
|
||||
|
||||
import { popModal } from '.';
|
||||
import { ModalContent, ModalHeader } from './Modal/Container';
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user