seperate v1 and v2 seeds, make groupMode to add default in case its not provided through api but primary groupBy is present (#15815)

Context - 
- refactoring v1 and v2 seeds to test v2 dashboard flag both on and off
Right now, the db reset command fails if the feature flag is off

- whenever there is a groupBy field -- we should add a default groupMode
- we already do that on the front -- but implementing the same on server
so that api users get the same response

This is also the reason why the second seed on dashboards lags a lot --
because on that particular dashboard's widget -- we set groupBy but no
groupMode (in seeding util) -- and the effect of limiting the bars to
fifty runs depending on the groupMode
This commit is contained in:
nitin
2025-11-18 16:59:12 +05:30
committed by GitHub
parent 6209ba539b
commit 8434610f18
4 changed files with 266 additions and 166 deletions
@@ -1,5 +1,6 @@
import { plainToInstance } from 'class-transformer';
import { validateSync, type ValidationError } from 'class-validator';
import { isDefined } from 'twenty-shared/utils';
import { AggregateChartConfigurationDTO } from 'src/engine/core-modules/page-layout/dtos/aggregate-chart-configuration.dto';
import { BarChartConfigurationDTO } from 'src/engine/core-modules/page-layout/dtos/bar-chart-configuration.dto';
@@ -8,6 +9,7 @@ import { IframeConfigurationDTO } from 'src/engine/core-modules/page-layout/dtos
import { LineChartConfigurationDTO } from 'src/engine/core-modules/page-layout/dtos/line-chart-configuration.dto';
import { PieChartConfigurationDTO } from 'src/engine/core-modules/page-layout/dtos/pie-chart-configuration.dto';
import { type WidgetConfigurationInterface } from 'src/engine/core-modules/page-layout/dtos/widget-configuration.interface';
import { BarChartGroupMode } from 'src/engine/core-modules/page-layout/enums/bar-chart-group-mode.enum';
import { GraphType } from 'src/engine/core-modules/page-layout/enums/graph-type.enum';
import { WidgetType } from 'src/engine/core-modules/page-layout/enums/widget-type.enum';
@@ -58,6 +60,13 @@ const validateGraphConfiguration = ({
throw errors;
}
if (
isDefined(instance.secondaryAxisGroupByFieldMetadataId) &&
!isDefined(instance.groupMode)
) {
instance.groupMode = BarChartGroupMode.STACKED;
}
return instance;
}
case GraphType.LINE: {
@@ -75,6 +84,13 @@ const validateGraphConfiguration = ({
throw errors;
}
if (
isDefined(instance.secondaryAxisGroupByFieldMetadataId) &&
!isDefined(instance.isStacked)
) {
instance.isStacked = true;
}
return instance;
}
case GraphType.PIE: {
@@ -0,0 +1,239 @@
import { CalendarStartDay } from 'twenty-shared/constants';
import { isDefined } from 'twenty-shared/utils';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { AxisNameDisplay } from 'src/engine/core-modules/page-layout/enums/axis-name-display.enum';
import { WidgetType } from 'src/engine/core-modules/page-layout/enums/widget-type.enum';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { PAGE_LAYOUT_TAB_SEEDS } from 'src/engine/workspace-manager/dev-seeder/core/constants/page-layout-tab-seeds.constant';
import { PAGE_LAYOUT_WIDGET_SEEDS } from 'src/engine/workspace-manager/dev-seeder/core/constants/page-layout-widget-seeds.constant';
import { generateSeedId } from 'src/engine/workspace-manager/dev-seeder/core/utils/generate-seed-id.util';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
type PageLayoutWidgetDataSeed = {
id: string;
pageLayoutTabId: string;
title: string;
type: WidgetType;
gridPosition: {
row: number;
column: number;
rowSpan: number;
columnSpan: number;
};
configuration: Record<string, unknown> | null;
objectMetadataId: string | null;
};
const getFieldId = (
object: ObjectMetadataEntity | undefined,
fieldName: string,
): string | undefined => {
return object?.fields?.find((field) => field.name === fieldName)?.id;
};
export const getPageLayoutWidgetDataSeedsV2 = (
workspaceId: string,
objectMetadataItems: ObjectMetadataEntity[],
): PageLayoutWidgetDataSeed[] => {
const opportunityObject = objectMetadataItems.find(
(obj) => obj.standardId === STANDARD_OBJECT_IDS.opportunity,
);
const companyObject = objectMetadataItems.find(
(obj) => obj.standardId === STANDARD_OBJECT_IDS.company,
);
const personObject = objectMetadataItems.find(
(obj) => obj.standardId === STANDARD_OBJECT_IDS.person,
);
const opportunityAmountFieldId = getFieldId(opportunityObject, 'amount');
const opportunityCloseDateFieldId = getFieldId(
opportunityObject,
'closeDate',
);
const companyIdFieldId = getFieldId(companyObject, 'id');
const companyCreatedAtFieldId = getFieldId(companyObject, 'createdAt');
const companyArrFieldId = getFieldId(companyObject, 'annualRecurringRevenue');
const companyNameFieldId = getFieldId(companyObject, 'name');
const companyLinkedinLinkFieldId = getFieldId(companyObject, 'linkedinLink');
const personIdFieldId = getFieldId(personObject, 'id');
const personJobTitleFieldId = getFieldId(personObject, 'jobTitle');
return [
// LINE chart: Revenue Forecast (Sales Overview)
{
id: generateSeedId(
workspaceId,
PAGE_LAYOUT_WIDGET_SEEDS.SALES_REVENUE_FORECAST,
),
pageLayoutTabId: generateSeedId(
workspaceId,
PAGE_LAYOUT_TAB_SEEDS.SALES_OVERVIEW,
),
title: 'Revenue Forecast',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 7, rowSpan: 8, columnSpan: 5 },
configuration:
isDefined(opportunityAmountFieldId) &&
isDefined(opportunityCloseDateFieldId)
? {
graphType: 'LINE',
aggregateFieldMetadataId: opportunityAmountFieldId,
aggregateOperation: AggregateOperations.SUM,
primaryAxisGroupByFieldMetadataId: opportunityCloseDateFieldId,
primaryAxisOrderBy: 'FIELD_ASC',
axisNameDisplay: AxisNameDisplay.NONE,
displayDataLabel: false,
timezone: 'UTC',
firstDayOfTheWeek: CalendarStartDay.MONDAY,
}
: null,
objectMetadataId: opportunityObject?.id ?? null,
},
// LINE chart: New Customers Over Time (Customer Overview)
{
id: generateSeedId(
workspaceId,
PAGE_LAYOUT_WIDGET_SEEDS.CUSTOMER_NEW_OVER_TIME,
),
pageLayoutTabId: generateSeedId(
workspaceId,
PAGE_LAYOUT_TAB_SEEDS.CUSTOMER_OVERVIEW,
),
title: 'New Customers Over Time',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 3, rowSpan: 6, columnSpan: 5 },
configuration:
isDefined(companyIdFieldId) && isDefined(companyCreatedAtFieldId)
? {
graphType: 'LINE',
aggregateFieldMetadataId: companyIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
primaryAxisGroupByFieldMetadataId: companyCreatedAtFieldId,
primaryAxisOrderBy: 'FIELD_ASC',
axisNameDisplay: AxisNameDisplay.NONE,
displayDataLabel: false,
timezone: 'UTC',
firstDayOfTheWeek: CalendarStartDay.MONDAY,
}
: null,
objectMetadataId: companyObject?.id ?? null,
},
// PIE chart: Revenue Distribution (Customer Analytics)
{
id: generateSeedId(
workspaceId,
PAGE_LAYOUT_WIDGET_SEEDS.CUSTOMER_REVENUE_DISTRIBUTION,
),
pageLayoutTabId: generateSeedId(
workspaceId,
PAGE_LAYOUT_TAB_SEEDS.CUSTOMER_ANALYTICS,
),
title: 'Revenue Distribution',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 4, rowSpan: 2, columnSpan: 3 },
configuration:
isDefined(companyArrFieldId) && isDefined(companyNameFieldId)
? {
graphType: 'PIE',
aggregateFieldMetadataId: companyArrFieldId,
aggregateOperation: AggregateOperations.SUM,
groupByFieldMetadataId: companyNameFieldId,
orderBy: 'VALUE_DESC',
displayDataLabel: true,
timezone: 'UTC',
firstDayOfTheWeek: CalendarStartDay.MONDAY,
}
: null,
objectMetadataId: companyObject?.id ?? null,
},
// GAUGE chart: Average ARR (Customer Analytics)
{
id: generateSeedId(
workspaceId,
PAGE_LAYOUT_WIDGET_SEEDS.CUSTOMER_AVERAGE_ARR,
),
pageLayoutTabId: generateSeedId(
workspaceId,
PAGE_LAYOUT_TAB_SEEDS.CUSTOMER_ANALYTICS,
),
title: 'Average ARR',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 7, rowSpan: 6, columnSpan: 5 },
configuration: isDefined(companyArrFieldId)
? {
graphType: 'GAUGE',
aggregateFieldMetadataId: companyArrFieldId,
aggregateOperation: AggregateOperations.AVG,
displayDataLabel: true,
timezone: 'UTC',
firstDayOfTheWeek: CalendarStartDay.MONDAY,
}
: null,
objectMetadataId: companyObject?.id ?? null,
},
// PIE chart: Companies by LinkedIn (Customer Overview)
{
id: generateSeedId(
workspaceId,
PAGE_LAYOUT_WIDGET_SEEDS.CUSTOMER_LINKEDIN_DISTRIBUTION,
),
pageLayoutTabId: generateSeedId(
workspaceId,
PAGE_LAYOUT_TAB_SEEDS.CUSTOMER_OVERVIEW,
),
title: 'Companies by LinkedIn (Field Permission Test)',
type: WidgetType.GRAPH,
gridPosition: { row: 6, column: 0, rowSpan: 4, columnSpan: 6 },
configuration:
isDefined(companyIdFieldId) && isDefined(companyLinkedinLinkFieldId)
? {
graphType: 'PIE',
aggregateFieldMetadataId: companyIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataId: companyLinkedinLinkFieldId,
orderBy: 'VALUE_DESC',
displayDataLabel: true,
timezone: 'UTC',
firstDayOfTheWeek: CalendarStartDay.MONDAY,
}
: null,
objectMetadataId: companyObject?.id ?? null,
},
// PIE chart: Contact Roles (Team Metrics)
{
id: generateSeedId(
workspaceId,
PAGE_LAYOUT_WIDGET_SEEDS.TEAM_CONTACT_ROLES,
),
pageLayoutTabId: generateSeedId(
workspaceId,
PAGE_LAYOUT_TAB_SEEDS.TEAM_METRICS,
),
title: 'Contact Roles',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 0, rowSpan: 4, columnSpan: 6 },
configuration:
isDefined(personIdFieldId) && isDefined(personJobTitleFieldId)
? {
graphType: 'PIE',
aggregateFieldMetadataId: personIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataId: personJobTitleFieldId,
orderBy: 'VALUE_DESC',
displayDataLabel: true,
timezone: 'UTC',
firstDayOfTheWeek: CalendarStartDay.MONDAY,
}
: null,
objectMetadataId: personObject?.id ?? null,
},
];
};
@@ -9,6 +9,7 @@ import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-me
import { PAGE_LAYOUT_TAB_SEEDS } from 'src/engine/workspace-manager/dev-seeder/core/constants/page-layout-tab-seeds.constant';
import { PAGE_LAYOUT_WIDGET_SEEDS } from 'src/engine/workspace-manager/dev-seeder/core/constants/page-layout-widget-seeds.constant';
import { generateSeedId } from 'src/engine/workspace-manager/dev-seeder/core/utils/generate-seed-id.util';
import { getPageLayoutWidgetDataSeedsV2 } from 'src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-widget-data-seeds-v2.util';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
type PageLayoutWidgetDataSeed = {
@@ -36,6 +37,7 @@ const getFieldId = (
export const getPageLayoutWidgetDataSeeds = (
workspaceId: string,
objectMetadataItems: ObjectMetadataEntity[],
isDashboardV2Enabled: boolean,
): PageLayoutWidgetDataSeed[] => {
const opportunityObject = objectMetadataItems.find(
(obj) => obj.standardId === STANDARD_OBJECT_IDS.opportunity,
@@ -61,16 +63,13 @@ export const getPageLayoutWidgetDataSeeds = (
const opportunityStageFieldId = getFieldId(opportunityObject, 'stage');
const companyIdFieldId = getFieldId(companyObject, 'id');
const companyCreatedAtFieldId = getFieldId(companyObject, 'createdAt');
const companyEmployeesFieldId = getFieldId(companyObject, 'employees');
const companyArrFieldId = getFieldId(companyObject, 'annualRecurringRevenue');
const companyNameFieldId = getFieldId(companyObject, 'name');
const companyLinkedinLinkFieldId = getFieldId(companyObject, 'linkedinLink');
const companyAddressFieldId = getFieldId(companyObject, 'address');
const personIdFieldId = getFieldId(personObject, 'id');
const personCityFieldId = getFieldId(personObject, 'city');
const personJobTitleFieldId = getFieldId(personObject, 'jobTitle');
const opportunityIdFieldId = getFieldId(opportunityObject, 'id');
@@ -79,7 +78,7 @@ export const getPageLayoutWidgetDataSeeds = (
const rocketIdFieldId = getFieldId(rocketObject, 'id');
const rocketCreatedAtFieldId = getFieldId(rocketObject, 'createdAt');
return [
const v1Widgets = [
// Sales Overview Tab Widgets
{
id: generateSeedId(
@@ -129,35 +128,6 @@ export const getPageLayoutWidgetDataSeeds = (
: null,
objectMetadataId: rocketObject?.id ?? null,
},
{
id: generateSeedId(
workspaceId,
PAGE_LAYOUT_WIDGET_SEEDS.SALES_REVENUE_FORECAST,
),
pageLayoutTabId: generateSeedId(
workspaceId,
PAGE_LAYOUT_TAB_SEEDS.SALES_OVERVIEW,
),
title: 'Revenue Forecast',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 7, rowSpan: 8, columnSpan: 5 },
configuration:
isDefined(opportunityAmountFieldId) &&
isDefined(opportunityCloseDateFieldId)
? {
graphType: 'LINE',
aggregateFieldMetadataId: opportunityAmountFieldId,
aggregateOperation: AggregateOperations.SUM,
primaryAxisGroupByFieldMetadataId: opportunityCloseDateFieldId,
primaryAxisOrderBy: 'FIELD_ASC',
axisNameDisplay: AxisNameDisplay.NONE,
displayDataLabel: false,
timezone: 'UTC',
firstDayOfTheWeek: CalendarStartDay.MONDAY,
}
: null,
objectMetadataId: opportunityObject?.id ?? null,
},
{
id: generateSeedId(
workspaceId,
@@ -271,34 +241,6 @@ export const getPageLayoutWidgetDataSeeds = (
: null,
objectMetadataId: companyObject?.id ?? null,
},
{
id: generateSeedId(
workspaceId,
PAGE_LAYOUT_WIDGET_SEEDS.CUSTOMER_NEW_OVER_TIME,
),
pageLayoutTabId: generateSeedId(
workspaceId,
PAGE_LAYOUT_TAB_SEEDS.CUSTOMER_OVERVIEW,
),
title: 'New Customers Over Time',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 3, rowSpan: 6, columnSpan: 5 },
configuration:
isDefined(companyIdFieldId) && isDefined(companyCreatedAtFieldId)
? {
graphType: 'LINE',
aggregateFieldMetadataId: companyIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
primaryAxisGroupByFieldMetadataId: companyCreatedAtFieldId,
primaryAxisOrderBy: 'FIELD_ASC',
axisNameDisplay: AxisNameDisplay.NONE,
displayDataLabel: false,
timezone: 'UTC',
firstDayOfTheWeek: CalendarStartDay.MONDAY,
}
: null,
objectMetadataId: companyObject?.id ?? null,
},
{
id: generateSeedId(
workspaceId,
@@ -359,57 +301,6 @@ export const getPageLayoutWidgetDataSeeds = (
: null,
objectMetadataId: companyObject?.id ?? null,
},
{
id: generateSeedId(
workspaceId,
PAGE_LAYOUT_WIDGET_SEEDS.CUSTOMER_REVENUE_DISTRIBUTION,
),
pageLayoutTabId: generateSeedId(
workspaceId,
PAGE_LAYOUT_TAB_SEEDS.CUSTOMER_ANALYTICS,
),
title: 'Revenue Distribution',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 4, rowSpan: 2, columnSpan: 3 },
configuration:
isDefined(companyArrFieldId) && isDefined(companyNameFieldId)
? {
graphType: 'PIE',
aggregateFieldMetadataId: companyArrFieldId,
aggregateOperation: AggregateOperations.SUM,
groupByFieldMetadataId: companyNameFieldId,
orderBy: 'VALUE_DESC',
displayDataLabel: true,
timezone: 'UTC',
firstDayOfTheWeek: CalendarStartDay.MONDAY,
}
: null,
objectMetadataId: companyObject?.id ?? null,
},
{
id: generateSeedId(
workspaceId,
PAGE_LAYOUT_WIDGET_SEEDS.CUSTOMER_AVERAGE_ARR,
),
pageLayoutTabId: generateSeedId(
workspaceId,
PAGE_LAYOUT_TAB_SEEDS.CUSTOMER_ANALYTICS,
),
title: 'Average ARR',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 7, rowSpan: 6, columnSpan: 5 },
configuration: isDefined(companyArrFieldId)
? {
graphType: 'GAUGE',
aggregateFieldMetadataId: companyArrFieldId,
aggregateOperation: AggregateOperations.AVG,
displayDataLabel: true,
timezone: 'UTC',
firstDayOfTheWeek: CalendarStartDay.MONDAY,
}
: null,
objectMetadataId: companyObject?.id ?? null,
},
{
id: generateSeedId(
workspaceId,
@@ -434,33 +325,6 @@ export const getPageLayoutWidgetDataSeeds = (
: null,
objectMetadataId: companyObject?.id ?? null,
},
{
id: generateSeedId(
workspaceId,
PAGE_LAYOUT_WIDGET_SEEDS.CUSTOMER_LINKEDIN_DISTRIBUTION,
),
pageLayoutTabId: generateSeedId(
workspaceId,
PAGE_LAYOUT_TAB_SEEDS.CUSTOMER_OVERVIEW,
),
title: 'Companies by LinkedIn (Field Permission Test)',
type: WidgetType.GRAPH,
gridPosition: { row: 6, column: 0, rowSpan: 4, columnSpan: 6 },
configuration:
isDefined(companyIdFieldId) && isDefined(companyLinkedinLinkFieldId)
? {
graphType: 'PIE',
aggregateFieldMetadataId: companyIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataId: companyLinkedinLinkFieldId,
orderBy: 'VALUE_DESC',
displayDataLabel: true,
timezone: 'UTC',
firstDayOfTheWeek: CalendarStartDay.MONDAY,
}
: null,
objectMetadataId: companyObject?.id ?? null,
},
// Team Overview Tab Widgets
{
@@ -515,33 +379,6 @@ export const getPageLayoutWidgetDataSeeds = (
},
// Team Metrics Tab Widgets
{
id: generateSeedId(
workspaceId,
PAGE_LAYOUT_WIDGET_SEEDS.TEAM_CONTACT_ROLES,
),
pageLayoutTabId: generateSeedId(
workspaceId,
PAGE_LAYOUT_TAB_SEEDS.TEAM_METRICS,
),
title: 'Contact Roles',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 0, rowSpan: 4, columnSpan: 6 },
configuration:
isDefined(personIdFieldId) && isDefined(personJobTitleFieldId)
? {
graphType: 'PIE',
aggregateFieldMetadataId: personIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataId: personJobTitleFieldId,
orderBy: 'VALUE_DESC',
displayDataLabel: true,
timezone: 'UTC',
firstDayOfTheWeek: CalendarStartDay.MONDAY,
}
: null,
objectMetadataId: personObject?.id ?? null,
},
{
id: generateSeedId(workspaceId, PAGE_LAYOUT_WIDGET_SEEDS.TEAM_OPEN_TASKS),
pageLayoutTabId: generateSeedId(
@@ -564,4 +401,11 @@ export const getPageLayoutWidgetDataSeeds = (
objectMetadataId: taskObject?.id ?? null,
},
];
return [
...v1Widgets,
...(isDashboardV2Enabled
? getPageLayoutWidgetDataSeedsV2(workspaceId, objectMetadataItems)
: []),
];
};
@@ -20,6 +20,7 @@ export const seedPageLayoutWidgets = async ({
const pageLayoutWidgets = getPageLayoutWidgetDataSeeds(
workspaceId,
objectMetadataItems,
isDashboardV2Enabled,
).map((widget) => {
const validatedConfiguration = widget.configuration
? validateAndTransformWidgetConfiguration({