fix: improve types for chart/reports
This commit is contained in:
88
packages/db/code-migrations/9-migrate-options.ts
Normal file
88
packages/db/code-migrations/9-migrate-options.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { IReportOptions } from '@openpanel/validation';
|
||||
import { db } from '../index';
|
||||
import { printBoxMessage } from './helpers';
|
||||
|
||||
export async function up() {
|
||||
printBoxMessage('🔄 Migrating Legacy Fields to Options', []);
|
||||
|
||||
// Get all reports
|
||||
const reports = await db.report.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
chartType: true,
|
||||
funnelGroup: true,
|
||||
funnelWindow: true,
|
||||
criteria: true,
|
||||
options: true,
|
||||
name: true,
|
||||
},
|
||||
});
|
||||
|
||||
let migratedCount = 0;
|
||||
let skippedCount = 0;
|
||||
|
||||
for (const report of reports) {
|
||||
const currentOptions = report.options as IReportOptions | null | undefined;
|
||||
|
||||
// Skip if options already exists and is valid
|
||||
if (currentOptions && typeof currentOptions === 'object' && 'type' in currentOptions) {
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
let newOptions: IReportOptions | null = null;
|
||||
|
||||
// Migrate based on chart type
|
||||
if (report.chartType === 'funnel') {
|
||||
// Only create options if we have legacy fields to migrate
|
||||
if (report.funnelGroup || report.funnelWindow !== null) {
|
||||
newOptions = {
|
||||
type: 'funnel',
|
||||
funnelGroup: report.funnelGroup ?? undefined,
|
||||
funnelWindow: report.funnelWindow ?? undefined,
|
||||
};
|
||||
}
|
||||
} else if (report.chartType === 'retention') {
|
||||
// Only create options if we have criteria to migrate
|
||||
if (report.criteria) {
|
||||
newOptions = {
|
||||
type: 'retention',
|
||||
criteria: report.criteria as 'on_or_after' | 'on' | undefined,
|
||||
};
|
||||
}
|
||||
} else if (report.chartType === 'sankey') {
|
||||
// Sankey should already have options, but if not, skip
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only update if we have new options to set
|
||||
if (newOptions) {
|
||||
console.log(
|
||||
`Migrating report ${report.name} (${report.id}) - chartType: ${report.chartType}`,
|
||||
);
|
||||
|
||||
await db.report.update({
|
||||
where: { id: report.id },
|
||||
data: {
|
||||
options: newOptions,
|
||||
// Set legacy fields to null after migration
|
||||
funnelGroup: null,
|
||||
funnelWindow: null,
|
||||
criteria: report.chartType === 'retention' ? null : report.criteria,
|
||||
},
|
||||
});
|
||||
|
||||
migratedCount++;
|
||||
} else {
|
||||
skippedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
printBoxMessage('✅ Migration Complete', [
|
||||
`Migrated: ${migratedCount} reports`,
|
||||
`Skipped: ${skippedCount} reports (already migrated or no legacy fields)`,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,9 +53,6 @@ export async function fetch(plan: Plan): Promise<ConcreteSeries[]> {
|
||||
previous: plan.input.previous ?? false,
|
||||
limit: plan.input.limit,
|
||||
offset: plan.input.offset,
|
||||
criteria: plan.input.criteria,
|
||||
funnelGroup: plan.input.funnelGroup,
|
||||
funnelWindow: plan.input.funnelWindow,
|
||||
};
|
||||
|
||||
// Execute query
|
||||
|
||||
@@ -4,7 +4,7 @@ import { alphabetIds } from '@openpanel/constants';
|
||||
import type {
|
||||
FinalChart,
|
||||
IChartEventItem,
|
||||
IChartInput,
|
||||
IReportInput,
|
||||
} from '@openpanel/validation';
|
||||
import { chQuery } from '../clickhouse/client';
|
||||
import {
|
||||
@@ -26,7 +26,7 @@ import type { ConcreteSeries } from './types';
|
||||
* Chart Engine - Main entry point
|
||||
* Executes the pipeline: normalize -> plan -> fetch -> compute -> format
|
||||
*/
|
||||
export async function executeChart(input: IChartInput): Promise<FinalChart> {
|
||||
export async function executeChart(input: IReportInput): Promise<FinalChart> {
|
||||
// Stage 1: Normalize input
|
||||
const normalized = await normalize(input);
|
||||
|
||||
@@ -83,7 +83,7 @@ export async function executeChart(input: IChartInput): Promise<FinalChart> {
|
||||
* Executes a simplified pipeline: normalize -> fetch aggregate -> format
|
||||
*/
|
||||
export async function executeAggregateChart(
|
||||
input: IChartInput,
|
||||
input: IReportInput,
|
||||
): Promise<FinalChart> {
|
||||
// Stage 1: Normalize input
|
||||
const normalized = await normalize(input);
|
||||
|
||||
@@ -2,8 +2,8 @@ import { alphabetIds } from '@openpanel/constants';
|
||||
import type {
|
||||
IChartEvent,
|
||||
IChartEventItem,
|
||||
IChartInput,
|
||||
IChartInputWithDates,
|
||||
IReportInput,
|
||||
IReportInputWithDates,
|
||||
} from '@openpanel/validation';
|
||||
import { getChartStartEndDate } from '../services/chart.service';
|
||||
import { getSettingsForProject } from '../services/organization.service';
|
||||
@@ -15,8 +15,8 @@ export type NormalizedInput = Awaited<ReturnType<typeof normalize>>;
|
||||
* Normalize a chart input into a clean structure with dates and normalized series
|
||||
*/
|
||||
export async function normalize(
|
||||
input: IChartInput,
|
||||
): Promise<IChartInputWithDates & { series: SeriesDefinition[] }> {
|
||||
input: IReportInput,
|
||||
): Promise<IReportInputWithDates & { series: SeriesDefinition[] }> {
|
||||
const { timezone } = await getSettingsForProject(input.projectId);
|
||||
const { startDate, endDate } = getChartStartEndDate(
|
||||
{
|
||||
|
||||
@@ -4,8 +4,8 @@ import type {
|
||||
IChartEventFilter,
|
||||
IChartEventItem,
|
||||
IChartFormula,
|
||||
IChartInput,
|
||||
IChartInputWithDates,
|
||||
IReportInput,
|
||||
IReportInputWithDates,
|
||||
} from '@openpanel/validation';
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ export type ConcreteSeries = {
|
||||
export type Plan = {
|
||||
concreteSeries: ConcreteSeries[];
|
||||
definitions: SeriesDefinition[];
|
||||
input: IChartInputWithDates;
|
||||
input: IReportInputWithDates;
|
||||
timezone: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import sqlstring from 'sqlstring';
|
||||
import { DateTime, stripLeadingAndTrailingSlashes } from '@openpanel/common';
|
||||
import type {
|
||||
IChartEventFilter,
|
||||
IChartInput,
|
||||
IReportInput,
|
||||
IChartRange,
|
||||
IGetChartDataInput,
|
||||
} from '@openpanel/validation';
|
||||
@@ -973,7 +973,7 @@ export function getChartStartEndDate(
|
||||
startDate,
|
||||
endDate,
|
||||
range,
|
||||
}: Pick<IChartInput, 'endDate' | 'startDate' | 'range'>,
|
||||
}: Pick<IReportInput, 'endDate' | 'startDate' | 'range'>,
|
||||
timezone: string,
|
||||
) {
|
||||
if (startDate && endDate) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NOT_SET_VALUE } from '@openpanel/constants';
|
||||
import type { IChartEvent, IChartInput } from '@openpanel/validation';
|
||||
import type { IChartEvent, IChartBreakdown, IReportInput } from '@openpanel/validation';
|
||||
import { omit } from 'ramda';
|
||||
import { TABLE_NAMES, ch } from '../clickhouse/client';
|
||||
import { clix } from '../clickhouse/query-builder';
|
||||
@@ -16,21 +16,23 @@ export class ConversionService {
|
||||
projectId,
|
||||
startDate,
|
||||
endDate,
|
||||
funnelGroup,
|
||||
funnelWindow = 24,
|
||||
options,
|
||||
series,
|
||||
breakdowns = [],
|
||||
limit,
|
||||
interval,
|
||||
timezone,
|
||||
}: Omit<IChartInput, 'range' | 'previous' | 'metric' | 'chartType'> & {
|
||||
}: Omit<IReportInput, 'range' | 'previous' | 'metric' | 'chartType'> & {
|
||||
timezone: string;
|
||||
}) {
|
||||
const funnelOptions = options?.type === 'funnel' ? options : undefined;
|
||||
const funnelGroup = funnelOptions?.funnelGroup;
|
||||
const funnelWindow = funnelOptions?.funnelWindow ?? 24;
|
||||
const group = funnelGroup === 'profile_id' ? 'profile_id' : 'session_id';
|
||||
const breakdownColumns = breakdowns.map(
|
||||
(b, index) => `${getSelectPropertyKey(b.name)} as b_${index}`,
|
||||
(b: IChartBreakdown, index: number) => `${getSelectPropertyKey(b.name)} as b_${index}`,
|
||||
);
|
||||
const breakdownGroupBy = breakdowns.map((b, index) => `b_${index}`);
|
||||
const breakdownGroupBy = breakdowns.map((b: IChartBreakdown, index: number) => `b_${index}`);
|
||||
|
||||
const events = onlyReportEvents(series);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ifNaN } from '@openpanel/common';
|
||||
import type {
|
||||
IChartEvent,
|
||||
IChartEventItem,
|
||||
IChartInput,
|
||||
IReportInput,
|
||||
} from '@openpanel/validation';
|
||||
import { last, reverse, uniq } from 'ramda';
|
||||
import sqlstring from 'sqlstring';
|
||||
@@ -185,16 +185,19 @@ export class FunnelService {
|
||||
startDate,
|
||||
endDate,
|
||||
series,
|
||||
funnelWindow = 24,
|
||||
funnelGroup,
|
||||
options,
|
||||
breakdowns = [],
|
||||
limit,
|
||||
timezone = 'UTC',
|
||||
}: IChartInput & { timezone: string; events?: IChartEvent[] }) {
|
||||
}: IReportInput & { timezone: string; events?: IChartEvent[] }) {
|
||||
if (!startDate || !endDate) {
|
||||
throw new Error('startDate and endDate are required');
|
||||
}
|
||||
|
||||
const funnelOptions = options?.type === 'funnel' ? options : undefined;
|
||||
const funnelWindow = funnelOptions?.funnelWindow ?? 24;
|
||||
const funnelGroup = funnelOptions?.funnelGroup;
|
||||
|
||||
const eventSeries = onlyReportEvents(series);
|
||||
|
||||
if (eventSeries.length === 0) {
|
||||
|
||||
@@ -8,8 +8,8 @@ import type {
|
||||
IChartEventFilter,
|
||||
IChartEventItem,
|
||||
IChartLineType,
|
||||
IChartProps,
|
||||
IChartRange,
|
||||
IReport,
|
||||
IReportOptions,
|
||||
} from '@openpanel/validation';
|
||||
|
||||
@@ -65,23 +65,22 @@ export function transformReportEventItem(
|
||||
|
||||
export function transformReport(
|
||||
report: DbReport & { layout?: ReportLayout | null },
|
||||
): IChartProps & {
|
||||
): IReport & {
|
||||
id: string;
|
||||
layout?: ReportLayout | null;
|
||||
} {
|
||||
// Parse options from JSON field, fallback to legacy fields for backward compatibility
|
||||
const options = report.options as IReportOptions | null | undefined;
|
||||
|
||||
return {
|
||||
id: report.id,
|
||||
projectId: report.projectId,
|
||||
series:
|
||||
(report.events as IChartEventItem[]).map(transformReportEventItem) ?? [],
|
||||
breakdowns: report.breakdowns as IChartBreakdown[],
|
||||
name: report.name || 'Untitled',
|
||||
chartType: report.chartType,
|
||||
lineType: (report.lineType as IChartLineType) ?? lineTypes.monotone,
|
||||
interval: report.interval,
|
||||
name: report.name || 'Untitled',
|
||||
series:
|
||||
(report.events as IChartEventItem[]).map(transformReportEventItem) ?? [],
|
||||
breakdowns: report.breakdowns as IChartBreakdown[],
|
||||
range:
|
||||
report.range in deprecated_timeRanges
|
||||
? '30d'
|
||||
@@ -90,15 +89,8 @@ export function transformReport(
|
||||
formula: report.formula ?? undefined,
|
||||
metric: report.metric ?? 'sum',
|
||||
unit: report.unit ?? undefined,
|
||||
criteria: (report.criteria ?? 'on_or_after') as
|
||||
| 'on_or_after'
|
||||
| 'on'
|
||||
| undefined,
|
||||
layout: report.layout ?? undefined,
|
||||
options: options ?? undefined,
|
||||
// Depercated, just for frontend backward compatibility (will be removed)
|
||||
funnelGroup: report.funnelGroup ?? undefined,
|
||||
funnelWindow: report.funnelWindow ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
} from '@openpanel/db';
|
||||
import {
|
||||
type IChartEvent,
|
||||
zChartInput,
|
||||
zReportInput,
|
||||
zChartSeries,
|
||||
zCriteria,
|
||||
zRange,
|
||||
@@ -335,7 +335,7 @@ export const chartRouter = createTRPCRouter({
|
||||
|
||||
funnel: publicProcedure
|
||||
.input(
|
||||
zChartInput.and(
|
||||
zReportInput.and(
|
||||
z.object({
|
||||
shareId: z.string().optional(),
|
||||
reportId: z.string().optional(),
|
||||
@@ -417,7 +417,7 @@ export const chartRouter = createTRPCRouter({
|
||||
|
||||
conversion: publicProcedure
|
||||
.input(
|
||||
zChartInput.and(
|
||||
zReportInput.and(
|
||||
z.object({
|
||||
shareId: z.string().optional(),
|
||||
reportId: z.string().optional(),
|
||||
@@ -511,7 +511,7 @@ export const chartRouter = createTRPCRouter({
|
||||
};
|
||||
}),
|
||||
|
||||
sankey: protectedProcedure.input(zChartInput).query(async ({ input }) => {
|
||||
sankey: protectedProcedure.input(zReportInput).query(async ({ input }) => {
|
||||
const { timezone } = await getSettingsForProject(input.projectId);
|
||||
const currentPeriod = getChartStartEndDate(input, timezone);
|
||||
|
||||
@@ -546,7 +546,7 @@ export const chartRouter = createTRPCRouter({
|
||||
chart: publicProcedure
|
||||
// .use(cacher)
|
||||
.input(
|
||||
zChartInput.and(
|
||||
zReportInput.and(
|
||||
z.object({
|
||||
shareId: z.string().optional(),
|
||||
reportId: z.string().optional(),
|
||||
@@ -606,7 +606,7 @@ export const chartRouter = createTRPCRouter({
|
||||
|
||||
aggregate: publicProcedure
|
||||
.input(
|
||||
zChartInput.and(
|
||||
zReportInput.and(
|
||||
z.object({
|
||||
shareId: z.string().optional(),
|
||||
reportId: z.string().optional(),
|
||||
@@ -721,7 +721,8 @@ export const chartRouter = createTRPCRouter({
|
||||
}
|
||||
|
||||
projectId = report.projectId;
|
||||
criteria = report.criteria ?? criteria;
|
||||
const retentionOptions = report.options?.type === 'retention' ? report.options : undefined;
|
||||
criteria = retentionOptions?.criteria ?? criteria;
|
||||
dateRange = input.range ?? report.range;
|
||||
startDate = input.startDate ?? report.startDate;
|
||||
endDate = input.endDate ?? report.endDate;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { db, getReportById, getReportsByDashboardId } from '@openpanel/db';
|
||||
import { zReportInput } from '@openpanel/validation';
|
||||
import { zReport } from '@openpanel/validation';
|
||||
|
||||
import { getProjectAccess } from '../access';
|
||||
import { TRPCAccessError } from '../errors';
|
||||
@@ -21,7 +21,7 @@ export const reportRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
report: zReportInput.omit({ projectId: true }),
|
||||
report: zReport.omit({ projectId: true }),
|
||||
dashboardId: z.string(),
|
||||
}),
|
||||
)
|
||||
@@ -55,10 +55,7 @@ export const reportRouter = createTRPCRouter({
|
||||
formula: report.formula,
|
||||
previous: report.previous ?? false,
|
||||
unit: report.unit,
|
||||
criteria: report.criteria,
|
||||
metric: report.metric === 'count' ? 'sum' : report.metric,
|
||||
funnelGroup: report.funnelGroup,
|
||||
funnelWindow: report.funnelWindow,
|
||||
options: report.options,
|
||||
},
|
||||
});
|
||||
@@ -67,7 +64,7 @@ export const reportRouter = createTRPCRouter({
|
||||
.input(
|
||||
z.object({
|
||||
reportId: z.string(),
|
||||
report: zReportInput.omit({ projectId: true }),
|
||||
report: zReport.omit({ projectId: true }),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input: { report, reportId }, ctx }) => {
|
||||
@@ -101,10 +98,7 @@ export const reportRouter = createTRPCRouter({
|
||||
formula: report.formula,
|
||||
previous: report.previous ?? false,
|
||||
unit: report.unit,
|
||||
criteria: report.criteria,
|
||||
metric: report.metric === 'count' ? 'sum' : report.metric,
|
||||
funnelGroup: report.funnelGroup,
|
||||
funnelWindow: report.funnelWindow,
|
||||
options: report.options,
|
||||
},
|
||||
});
|
||||
@@ -173,10 +167,7 @@ export const reportRouter = createTRPCRouter({
|
||||
formula: report.formula,
|
||||
previous: report.previous,
|
||||
unit: report.unit,
|
||||
criteria: report.criteria,
|
||||
metric: report.metric,
|
||||
funnelGroup: report.funnelGroup,
|
||||
funnelWindow: report.funnelWindow,
|
||||
options: report.options,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -135,7 +135,9 @@ export const zReportOptions = z.discriminatedUnion('type', [
|
||||
export type IReportOptions = z.infer<typeof zReportOptions>;
|
||||
export type ISankeyOptions = z.infer<typeof zSankeyOptions>;
|
||||
|
||||
export const zChartInputBase = z.object({
|
||||
// Base input schema - for API calls, engine, chart queries
|
||||
export const zReportInput = z.object({
|
||||
projectId: z.string().describe('The ID of the project this chart belongs to'),
|
||||
chartType: zChartType
|
||||
.default('linear')
|
||||
.describe('What type of chart should be displayed'),
|
||||
@@ -153,6 +155,18 @@ export const zChartInputBase = z.object({
|
||||
range: zRange
|
||||
.default('30d')
|
||||
.describe('The time range for which data should be displayed'),
|
||||
startDate: z
|
||||
.string()
|
||||
.nullish()
|
||||
.describe(
|
||||
'Custom start date for the data range (overrides range if provided)',
|
||||
),
|
||||
endDate: z
|
||||
.string()
|
||||
.nullish()
|
||||
.describe(
|
||||
'Custom end date for the data range (overrides range if provided)',
|
||||
),
|
||||
previous: z
|
||||
.boolean()
|
||||
.default(false)
|
||||
@@ -166,19 +180,6 @@ export const zChartInputBase = z.object({
|
||||
.describe(
|
||||
'The aggregation method for the metric (e.g., sum, count, average)',
|
||||
),
|
||||
projectId: z.string().describe('The ID of the project this chart belongs to'),
|
||||
startDate: z
|
||||
.string()
|
||||
.nullish()
|
||||
.describe(
|
||||
'Custom start date for the data range (overrides range if provided)',
|
||||
),
|
||||
endDate: z
|
||||
.string()
|
||||
.nullish()
|
||||
.describe(
|
||||
'Custom end date for the data range (overrides range if provided)',
|
||||
),
|
||||
limit: z
|
||||
.number()
|
||||
.optional()
|
||||
@@ -187,27 +188,12 @@ export const zChartInputBase = z.object({
|
||||
.number()
|
||||
.optional()
|
||||
.describe('Skip how many series should be returned'),
|
||||
criteria: zCriteria
|
||||
options: zReportOptions
|
||||
.optional()
|
||||
.describe('Filtering criteria for retention chart (e.g., on_or_after, on)'),
|
||||
funnelGroup: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Group identifier for funnel analysis, e.g. "profile_id" or "session_id"',
|
||||
),
|
||||
funnelWindow: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe('Time window in hours for funnel analysis'),
|
||||
options: zReportOptions.optional(),
|
||||
});
|
||||
|
||||
export const zChartInput = zChartInputBase;
|
||||
|
||||
export const zReportInput = zChartInputBase.extend({
|
||||
name: z.string().describe('The user-defined name for the report'),
|
||||
lineType: zLineType.describe('The visual style of the line in the chart'),
|
||||
.describe('Chart-specific options (funnel, retention, sankey)'),
|
||||
// Optional display fields
|
||||
name: z.string().optional().describe('The user-defined name for the report'),
|
||||
lineType: zLineType.optional().describe('The visual style of the line in the chart'),
|
||||
unit: z
|
||||
.string()
|
||||
.optional()
|
||||
@@ -216,17 +202,14 @@ export const zReportInput = zChartInputBase.extend({
|
||||
),
|
||||
});
|
||||
|
||||
export const zChartInputAI = zReportInput
|
||||
.omit({
|
||||
startDate: true,
|
||||
endDate: true,
|
||||
lineType: true,
|
||||
unit: true,
|
||||
})
|
||||
.extend({
|
||||
startDate: z.string().describe('The start date for the report'),
|
||||
endDate: z.string().describe('The end date for the report'),
|
||||
});
|
||||
// Complete report schema - for saved reports
|
||||
export const zReport = zReportInput.extend({
|
||||
name: z.string().default('Untitled').describe('The user-defined name for the report'),
|
||||
lineType: zLineType.default('monotone').describe('The visual style of the line in the chart'),
|
||||
});
|
||||
|
||||
// Alias for backward compatibility
|
||||
export const zChartInput = zReportInput;
|
||||
|
||||
export const zInviteUser = z.object({
|
||||
email: z.string().email(),
|
||||
|
||||
@@ -10,25 +10,28 @@ import type {
|
||||
zChartEventItem,
|
||||
zChartEventSegment,
|
||||
zChartFormula,
|
||||
zChartInput,
|
||||
zChartInputAI,
|
||||
zChartSeries,
|
||||
zChartType,
|
||||
zCriteria,
|
||||
zLineType,
|
||||
zMetric,
|
||||
zRange,
|
||||
zReport,
|
||||
zReportInput,
|
||||
zTimeInterval,
|
||||
} from './index';
|
||||
|
||||
export type IChartInput = z.infer<typeof zChartInput>;
|
||||
export type IChartInputAi = z.infer<typeof zChartInputAI>;
|
||||
export type IChartProps = z.infer<typeof zReportInput> & {
|
||||
name: string;
|
||||
lineType: IChartLineType;
|
||||
unit?: string;
|
||||
};
|
||||
// For saved reports - complete report with required display fields
|
||||
export type IReport = z.infer<typeof zReport>;
|
||||
|
||||
// For API/engine use - flexible input
|
||||
export type IReportInput = z.infer<typeof zReportInput>;
|
||||
|
||||
// With resolved dates (engine internal)
|
||||
export interface IReportInputWithDates extends IReportInput {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
}
|
||||
export type IChartEvent = z.infer<typeof zChartEvent>;
|
||||
export type IChartFormula = z.infer<typeof zChartFormula>;
|
||||
export type IChartEventItem = z.infer<typeof zChartEventItem>;
|
||||
@@ -47,16 +50,12 @@ export type IChartType = z.infer<typeof zChartType>;
|
||||
export type IChartMetric = z.infer<typeof zMetric>;
|
||||
export type IChartLineType = z.infer<typeof zLineType>;
|
||||
export type IChartRange = z.infer<typeof zRange>;
|
||||
export interface IChartInputWithDates extends IChartInput {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
}
|
||||
export type IGetChartDataInput = {
|
||||
event: IChartEvent;
|
||||
projectId: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
} & Omit<IChartInput, 'series' | 'name' | 'startDate' | 'endDate' | 'range'>;
|
||||
} & Omit<IReportInput, 'series' | 'startDate' | 'endDate' | 'range'>;
|
||||
export type ICriteria = z.infer<typeof zCriteria>;
|
||||
|
||||
export type PreviousValue =
|
||||
|
||||
Reference in New Issue
Block a user