feat: dashboard v2, esm, upgrades (#211)
* esm * wip * wip * wip * wip * wip * wip * subscription notice * wip * wip * wip * fix envs * fix: update docker build * fix * esm/types * delete dashboard :D * add patches to dockerfiles * update packages + catalogs + ts * wip * remove native libs * ts * improvements * fix redirects and fetching session * try fix favicon * fixes * fix * order and resize reportds within a dashboard * improvements * wip * added userjot to dashboard * fix * add op * wip * different cache key * improve date picker * fix table * event details loading * redo onboarding completely * fix login * fix * fix * extend session, billing and improve bars * fix * reduce price on 10M
This commit is contained in:
committed by
GitHub
parent
436e81ecc9
commit
81a7e5d62e
@@ -0,0 +1,29 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."_IntegrationToNotificationRule" ADD CONSTRAINT "_IntegrationToNotificationRule_AB_pkey" PRIMARY KEY ("A", "B");
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "public"."_IntegrationToNotificationRule_AB_unique";
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."report_layouts" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"reportId" UUID NOT NULL,
|
||||
"x" INTEGER NOT NULL DEFAULT 0,
|
||||
"y" INTEGER NOT NULL DEFAULT 0,
|
||||
"w" INTEGER NOT NULL DEFAULT 4,
|
||||
"h" INTEGER NOT NULL DEFAULT 3,
|
||||
"minW" INTEGER DEFAULT 2,
|
||||
"minH" INTEGER DEFAULT 2,
|
||||
"maxW" INTEGER,
|
||||
"maxH" INTEGER,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "report_layouts_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "report_layouts_reportId_key" ON "public"."report_layouts"("reportId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."report_layouts" ADD CONSTRAINT "report_layouts_reportId_fkey" FOREIGN KEY ("reportId") REFERENCES "public"."reports"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -1,3 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
|
||||
210
packages/db/prisma/prisma-json-types.ts
Normal file
210
packages/db/prisma/prisma-json-types.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
import { readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
interface JsonFieldMapping {
|
||||
model: string;
|
||||
field: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
function parseSchemaForJsonTypes(schemaPath: string): JsonFieldMapping[] {
|
||||
const schemaContent = readFileSync(schemaPath, 'utf-8');
|
||||
const lines = schemaContent.split('\n');
|
||||
const mappings: JsonFieldMapping[] = [];
|
||||
|
||||
let currentModel = '';
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]?.trim() || '';
|
||||
|
||||
// Track current model
|
||||
if (line.startsWith('model ')) {
|
||||
const parts = line.split(' ');
|
||||
currentModel = parts[1] || '';
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for Json fields with type comments
|
||||
if (line.includes('Json') && i > 0) {
|
||||
const prevLine = lines[i - 1]?.trim() || '';
|
||||
const typeMatch = prevLine.match(/\/\/\/ \[([^\]]+)\]/);
|
||||
|
||||
if (typeMatch) {
|
||||
const fieldMatch = line.match(/(\w+)\s+Json/);
|
||||
if (fieldMatch?.[1] && typeMatch[1]) {
|
||||
mappings.push({
|
||||
model: currentModel,
|
||||
field: fieldMatch[1],
|
||||
type: typeMatch[1],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mappings;
|
||||
}
|
||||
|
||||
function processGeneratedFiles(
|
||||
generatedDir: string,
|
||||
mappings: JsonFieldMapping[],
|
||||
): void {
|
||||
// Process the main files in the generated directory
|
||||
const mainFiles = [
|
||||
'client.ts',
|
||||
'commonInputTypes.ts',
|
||||
'enums.ts',
|
||||
'models.ts',
|
||||
];
|
||||
|
||||
for (const fileName of mainFiles) {
|
||||
const filePath = join(generatedDir, fileName);
|
||||
try {
|
||||
replaceJsonValueInFileForModel(filePath, mappings);
|
||||
} catch (error) {
|
||||
console.log(`Skipping ${filePath}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Process files in the models subdirectory - each file corresponds to one model
|
||||
const modelsDir = join(generatedDir, 'models');
|
||||
try {
|
||||
const modelFiles = readdirSync(modelsDir);
|
||||
for (const fileName of modelFiles) {
|
||||
if (fileName.endsWith('.ts')) {
|
||||
const filePath = join(modelsDir, fileName);
|
||||
try {
|
||||
// Extract model name from filename (e.g., "Notification.ts" -> "Notification")
|
||||
const modelName = fileName.replace('.ts', '');
|
||||
|
||||
// Only process mappings for this specific model
|
||||
const modelMappings = mappings.filter((m) => m.model === modelName);
|
||||
|
||||
if (modelMappings.length > 0) {
|
||||
replaceJsonValueInFileForModel(filePath, modelMappings);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Skipping ${filePath}: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not read models directory: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
function replaceJsonValueInFileForModel(
|
||||
filePath: string,
|
||||
mappings: JsonFieldMapping[],
|
||||
): void {
|
||||
let content = readFileSync(filePath, 'utf-8');
|
||||
let modified = false;
|
||||
|
||||
for (const mapping of mappings) {
|
||||
// Pattern 1: Simple runtime.JsonValue replacement (for select/return types)
|
||||
const simpleJsonValueRegex = new RegExp(
|
||||
`\\b${mapping.field}:\\s*runtime\\.JsonValue\\b`,
|
||||
'g',
|
||||
);
|
||||
if (simpleJsonValueRegex.test(content)) {
|
||||
content = content.replace(
|
||||
simpleJsonValueRegex,
|
||||
`${mapping.field}: PrismaJson.${mapping.type}`,
|
||||
);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// Pattern 2: runtime.InputJsonValue with optional JsonNullValueInput (for create/update inputs)
|
||||
const inputJsonValueRegex = new RegExp(
|
||||
`\\b${mapping.field}:\\s*(?:Prisma\\.JsonNullValueInput\\s*\\|\\s*)?runtime\\.InputJsonValue\\b`,
|
||||
'g',
|
||||
);
|
||||
if (inputJsonValueRegex.test(content)) {
|
||||
content = content.replace(
|
||||
inputJsonValueRegex,
|
||||
`${mapping.field}: PrismaJson.${mapping.type}`,
|
||||
);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// Pattern 3: Optional runtime.InputJsonValue with optional JsonNullValueInput
|
||||
const optionalInputJsonValueRegex = new RegExp(
|
||||
`\\b${mapping.field}\\?:\\s*(?:Prisma\\.JsonNullValueInput\\s*\\|\\s*)?runtime\\.InputJsonValue\\b`,
|
||||
'g',
|
||||
);
|
||||
if (optionalInputJsonValueRegex.test(content)) {
|
||||
content = content.replace(
|
||||
optionalInputJsonValueRegex,
|
||||
`${mapping.field}?: PrismaJson.${mapping.type}`,
|
||||
);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// Pattern 4: Union types with JsonNullValueInput | runtime.InputJsonValue
|
||||
const unionJsonValueRegex =
|
||||
/(Prisma\.JsonNullValueInput\s*\|\s*)runtime\.InputJsonValue/g;
|
||||
if (unionJsonValueRegex.test(content)) {
|
||||
content = content.replace(
|
||||
unionJsonValueRegex,
|
||||
`$1PrismaJson.${mapping.type}`,
|
||||
);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// Pattern 5: Just runtime.InputJsonValue in unions
|
||||
const simpleInputJsonValueRegex = /\|\s*runtime\.InputJsonValue/g;
|
||||
if (simpleInputJsonValueRegex.test(content)) {
|
||||
content = content.replace(
|
||||
simpleInputJsonValueRegex,
|
||||
`| PrismaJson.${mapping.type}`,
|
||||
);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
// Pattern 6: Optional union types with JsonNullValueInput | runtime.InputJsonValue
|
||||
const optionalUnionJsonValueRegex = new RegExp(
|
||||
`\\b${mapping.field}\\?:\\s*(?:Prisma\\.JsonNullValueInput\\s*\\|\\s*)?runtime\\.InputJsonValue\\b`,
|
||||
'g',
|
||||
);
|
||||
if (optionalUnionJsonValueRegex.test(content)) {
|
||||
content = content.replace(
|
||||
optionalUnionJsonValueRegex,
|
||||
`${mapping.field}?: PrismaJson.${mapping.type}`,
|
||||
);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
writeFileSync(filePath, content, 'utf-8');
|
||||
console.log(`Updated ${filePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const schemaPath = join(__dirname, '../prisma/schema.prisma');
|
||||
const generatedDir = join(__dirname, '../src/generated/prisma');
|
||||
|
||||
console.log('Parsing schema for Json type mappings...');
|
||||
const mappings = parseSchemaForJsonTypes(schemaPath);
|
||||
|
||||
console.log('Found Json type mappings:');
|
||||
mappings.forEach((m) => console.log(` ${m.model}.${m.field} -> ${m.type}`));
|
||||
|
||||
if (mappings.length === 0) {
|
||||
console.log('No mappings found!');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Processing generated files...');
|
||||
processGeneratedFiles(generatedDir, mappings);
|
||||
|
||||
console.log('Post-codegen script completed!');
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -2,12 +2,16 @@
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
provider = "prisma-client"
|
||||
output = "../src/generated/prisma"
|
||||
moduleFormat = "esm"
|
||||
generatedFileExtension = "ts"
|
||||
importFileExtension = "ts"
|
||||
}
|
||||
|
||||
generator json {
|
||||
provider = "prisma-json-types-generator"
|
||||
}
|
||||
// generator json {
|
||||
// provider = "prisma-json-types-generator"
|
||||
// }
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
@@ -315,7 +319,8 @@ model Report {
|
||||
funnelWindow Float?
|
||||
|
||||
dashboardId String
|
||||
dashboard Dashboard @relation(fields: [dashboardId], references: [id], onDelete: Cascade)
|
||||
dashboard Dashboard @relation(fields: [dashboardId], references: [id], onDelete: Cascade)
|
||||
layout ReportLayout?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
@@ -323,6 +328,29 @@ model Report {
|
||||
@@map("reports")
|
||||
}
|
||||
|
||||
model ReportLayout {
|
||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
reportId String @unique @db.Uuid
|
||||
report Report @relation(fields: [reportId], references: [id], onDelete: Cascade)
|
||||
|
||||
// Grid position and size
|
||||
x Int @default(0)
|
||||
y Int @default(0)
|
||||
w Int @default(4)
|
||||
h Int @default(3)
|
||||
|
||||
// Optional: store additional layout preferences
|
||||
minW Int? @default(2)
|
||||
minH Int? @default(2)
|
||||
maxW Int?
|
||||
maxH Int?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
|
||||
@@map("report_layouts")
|
||||
}
|
||||
|
||||
model ShareOverview {
|
||||
id String @unique
|
||||
projectId String @unique
|
||||
|
||||
Reference in New Issue
Block a user