Field level permission checks on widgets (#14774)

This commit is contained in:
nitin
2025-09-30 19:30:09 +05:30
committed by GitHub
parent c3b1f6c2b2
commit 274f4333b4
12 changed files with 786 additions and 148 deletions
@@ -12,6 +12,8 @@ export const PAGE_LAYOUT_WIDGET_SEEDS = {
CUSTOMER_ANNUAL_RECURRING_REVENUE: 'CUSTOMER_ANNUAL_RECURRING_REVENUE_WIDGET',
CUSTOMER_REVENUE_DISTRIBUTION: 'CUSTOMER_REVENUE_DISTRIBUTION_WIDGET',
CUSTOMER_AVERAGE_ARR: 'CUSTOMER_AVERAGE_ARR_WIDGET',
CUSTOMER_LINKEDIN_COUNT: 'CUSTOMER_LINKEDIN_COUNT_WIDGET',
CUSTOMER_LINKEDIN_DISTRIBUTION: 'CUSTOMER_LINKEDIN_DISTRIBUTION_WIDGET',
TEAM_SIZE: 'TEAM_SIZE_WIDGET',
TEAM_GEOGRAPHIC_DISTRIBUTION: 'TEAM_GEOGRAPHIC_DISTRIBUTION_WIDGET',
@@ -1,15 +1,11 @@
import { isDefined } from 'twenty-shared/utils';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
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 {
BASE_OBJECT_STANDARD_FIELD_IDS,
COMPANY_STANDARD_FIELD_IDS,
OPPORTUNITY_STANDARD_FIELD_IDS,
PERSON_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
type PageLayoutWidgetDataSeed = {
@@ -27,6 +23,13 @@ type PageLayoutWidgetDataSeed = {
objectMetadataId: string | null;
};
const getFieldId = (
object: ObjectMetadataEntity | undefined,
fieldName: string,
): string | undefined => {
return object?.fields?.find((field) => field.name === fieldName)?.id;
};
export const getPageLayoutWidgetDataSeeds = (
workspaceId: string,
objectMetadataItems: ObjectMetadataEntity[],
@@ -47,6 +50,31 @@ export const getPageLayoutWidgetDataSeeds = (
(obj) => obj.nameSingular === 'rocket',
);
const opportunityAmountFieldId = getFieldId(opportunityObject, 'amount');
const opportunityCloseDateFieldId = getFieldId(
opportunityObject,
'closeDate',
);
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 personIdFieldId = getFieldId(personObject, 'id');
const personCityFieldId = getFieldId(personObject, 'city');
const personJobTitleFieldId = getFieldId(personObject, 'jobTitle');
const opportunityIdFieldId = getFieldId(opportunityObject, 'id');
const taskIdFieldId = getFieldId(taskObject, 'id');
const rocketIdFieldId = getFieldId(rocketObject, 'id');
const rocketCreatedAtFieldId = getFieldId(rocketObject, 'createdAt');
return [
// Sales Overview Tab Widgets
{
@@ -61,11 +89,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Total Pipeline Value',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 0, rowSpan: 2, columnSpan: 3 },
configuration: {
graphType: 'NUMBER',
aggregateFieldMetadataId: OPPORTUNITY_STANDARD_FIELD_IDS.amount,
aggregateOperation: AggregateOperations.SUM,
},
configuration: isDefined(opportunityAmountFieldId)
? {
graphType: 'NUMBER',
aggregateFieldMetadataId: opportunityAmountFieldId,
aggregateOperation: AggregateOperations.SUM,
}
: null,
objectMetadataId: opportunityObject?.id ?? null,
},
{
@@ -77,16 +107,16 @@ export const getPageLayoutWidgetDataSeeds = (
workspaceId,
PAGE_LAYOUT_TAB_SEEDS.SALES_OVERVIEW,
),
title: 'Average Deal Size',
title: 'Rocket Count (Object Permission Test)',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 3, rowSpan: 4, columnSpan: 4 },
configuration: {
graphType: 'GAUGE',
aggregateFieldMetadataId: OPPORTUNITY_STANDARD_FIELD_IDS.amount,
aggregateOperation: AggregateOperations.AVG,
aggregateFieldMetadataIdTotal: OPPORTUNITY_STANDARD_FIELD_IDS.amount,
aggregateOperationTotal: AggregateOperations.MAX,
},
configuration: isDefined(rocketIdFieldId)
? {
graphType: 'NUMBER',
aggregateFieldMetadataId: rocketIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
}
: null,
objectMetadataId: rocketObject?.id ?? null,
},
{
@@ -101,13 +131,17 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Revenue Forecast',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 7, rowSpan: 8, columnSpan: 5 },
configuration: {
graphType: 'LINE',
aggregateFieldMetadataId: OPPORTUNITY_STANDARD_FIELD_IDS.amount,
aggregateOperation: AggregateOperations.SUM,
groupByFieldMetadataIdX: OPPORTUNITY_STANDARD_FIELD_IDS.closeDate,
orderByX: 'FIELD_ASC',
},
configuration:
isDefined(opportunityAmountFieldId) &&
isDefined(opportunityCloseDateFieldId)
? {
graphType: 'LINE',
aggregateFieldMetadataId: opportunityAmountFieldId,
aggregateOperation: AggregateOperations.SUM,
groupByFieldMetadataIdX: opportunityCloseDateFieldId,
orderByX: 'FIELD_ASC',
}
: null,
objectMetadataId: opportunityObject?.id ?? null,
},
{
@@ -122,13 +156,17 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Deals by Stage',
type: WidgetType.GRAPH,
gridPosition: { row: 4, column: 0, rowSpan: 4, columnSpan: 6 },
configuration: {
graphType: 'BAR',
aggregateFieldMetadataId: OPPORTUNITY_STANDARD_FIELD_IDS.amount,
aggregateOperation: AggregateOperations.SUM,
groupByFieldMetadataIdX: OPPORTUNITY_STANDARD_FIELD_IDS.stage,
orderByX: 'FIELD_DESC',
},
configuration:
isDefined(opportunityAmountFieldId) &&
isDefined(opportunityStageFieldId)
? {
graphType: 'BAR',
aggregateFieldMetadataId: opportunityAmountFieldId,
aggregateOperation: AggregateOperations.SUM,
groupByFieldMetadataIdX: opportunityStageFieldId,
orderByX: 'FIELD_DESC',
}
: null,
objectMetadataId: opportunityObject?.id ?? null,
},
@@ -142,16 +180,19 @@ export const getPageLayoutWidgetDataSeeds = (
workspaceId,
PAGE_LAYOUT_TAB_SEEDS.SALES_DETAILS,
),
title: 'Deal Distribution',
title: 'Rockets by Created Date (Object Permission Test)',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 0, rowSpan: 5, columnSpan: 5 },
configuration: {
graphType: 'PIE',
aggregateFieldMetadataId: BASE_OBJECT_STANDARD_FIELD_IDS.id,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataId: OPPORTUNITY_STANDARD_FIELD_IDS.stage,
orderBy: 'VALUE_DESC',
},
configuration:
isDefined(rocketIdFieldId) && isDefined(rocketCreatedAtFieldId)
? {
graphType: 'BAR',
aggregateFieldMetadataId: rocketIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataIdX: rocketCreatedAtFieldId,
orderByX: 'FIELD_ASC',
}
: null,
objectMetadataId: rocketObject?.id ?? null,
},
{
@@ -166,11 +207,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Opportunity Count',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 5, rowSpan: 5, columnSpan: 7 },
configuration: {
graphType: 'NUMBER',
aggregateFieldMetadataId: BASE_OBJECT_STANDARD_FIELD_IDS.id,
aggregateOperation: AggregateOperations.COUNT,
},
configuration: isDefined(opportunityIdFieldId)
? {
graphType: 'NUMBER',
aggregateFieldMetadataId: opportunityIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
}
: null,
objectMetadataId: opportunityObject?.id ?? null,
},
@@ -187,11 +230,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Total Customers',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 0, rowSpan: 2, columnSpan: 3 },
configuration: {
graphType: 'NUMBER',
aggregateFieldMetadataId: BASE_OBJECT_STANDARD_FIELD_IDS.id,
aggregateOperation: AggregateOperations.COUNT,
},
configuration: isDefined(companyIdFieldId)
? {
graphType: 'NUMBER',
aggregateFieldMetadataId: companyIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
}
: null,
objectMetadataId: companyObject?.id ?? null,
},
{
@@ -206,13 +251,16 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'New Customers Over Time',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 3, rowSpan: 6, columnSpan: 5 },
configuration: {
graphType: 'LINE',
aggregateFieldMetadataId: BASE_OBJECT_STANDARD_FIELD_IDS.id,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataIdX: BASE_OBJECT_STANDARD_FIELD_IDS.createdAt,
orderByX: 'FIELD_ASC',
},
configuration:
isDefined(companyIdFieldId) && isDefined(companyCreatedAtFieldId)
? {
graphType: 'LINE',
aggregateFieldMetadataId: companyIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataIdX: companyCreatedAtFieldId,
orderByX: 'FIELD_ASC',
}
: null,
objectMetadataId: companyObject?.id ?? null,
},
{
@@ -227,13 +275,16 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Companies by Size',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 8, rowSpan: 6, columnSpan: 4 },
configuration: {
graphType: 'BAR',
aggregateFieldMetadataId: BASE_OBJECT_STANDARD_FIELD_IDS.id,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataIdX: COMPANY_STANDARD_FIELD_IDS.employees,
orderByX: 'FIELD_ASC',
},
configuration:
isDefined(companyIdFieldId) && isDefined(companyEmployeesFieldId)
? {
graphType: 'BAR',
aggregateFieldMetadataId: companyIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataIdX: companyEmployeesFieldId,
orderByX: 'FIELD_ASC',
}
: null,
objectMetadataId: companyObject?.id ?? null,
},
@@ -250,12 +301,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Annual Recurring Revenue',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 0, rowSpan: 4, columnSpan: 4 },
configuration: {
graphType: 'NUMBER',
aggregateFieldMetadataId:
COMPANY_STANDARD_FIELD_IDS.annualRecurringRevenue,
aggregateOperation: AggregateOperations.SUM,
},
configuration: isDefined(companyArrFieldId)
? {
graphType: 'NUMBER',
aggregateFieldMetadataId: companyArrFieldId,
aggregateOperation: AggregateOperations.SUM,
}
: null,
objectMetadataId: companyObject?.id ?? null,
},
{
@@ -270,14 +322,16 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Revenue Distribution',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 4, rowSpan: 2, columnSpan: 3 },
configuration: {
graphType: 'PIE',
aggregateFieldMetadataId:
COMPANY_STANDARD_FIELD_IDS.annualRecurringRevenue,
aggregateOperation: AggregateOperations.SUM,
groupByFieldMetadataId: COMPANY_STANDARD_FIELD_IDS.name,
orderBy: 'VALUE_DESC',
},
configuration:
isDefined(companyArrFieldId) && isDefined(companyNameFieldId)
? {
graphType: 'PIE',
aggregateFieldMetadataId: companyArrFieldId,
aggregateOperation: AggregateOperations.SUM,
groupByFieldMetadataId: companyNameFieldId,
orderBy: 'VALUE_DESC',
}
: null,
objectMetadataId: companyObject?.id ?? null,
},
{
@@ -292,15 +346,60 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Average ARR',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 7, rowSpan: 6, columnSpan: 5 },
configuration: {
graphType: 'GAUGE',
aggregateFieldMetadataId:
COMPANY_STANDARD_FIELD_IDS.annualRecurringRevenue,
aggregateOperation: AggregateOperations.AVG,
aggregateFieldMetadataIdTotal:
COMPANY_STANDARD_FIELD_IDS.annualRecurringRevenue,
aggregateOperationTotal: AggregateOperations.MAX,
},
configuration: isDefined(companyArrFieldId)
? {
graphType: 'GAUGE',
aggregateFieldMetadataId: companyArrFieldId,
aggregateOperation: AggregateOperations.AVG,
aggregateFieldMetadataIdTotal: companyArrFieldId,
aggregateOperationTotal: AggregateOperations.MAX,
}
: null,
objectMetadataId: companyObject?.id ?? null,
},
{
id: generateSeedId(
workspaceId,
PAGE_LAYOUT_WIDGET_SEEDS.CUSTOMER_LINKEDIN_COUNT,
),
pageLayoutTabId: generateSeedId(
workspaceId,
PAGE_LAYOUT_TAB_SEEDS.CUSTOMER_OVERVIEW,
),
title: 'LinkedIn Profiles Count (Field Permission Test)',
type: WidgetType.GRAPH,
gridPosition: { row: 2, column: 0, rowSpan: 4, columnSpan: 3 },
configuration: isDefined(companyLinkedinLinkFieldId)
? {
graphType: 'NUMBER',
aggregateFieldMetadataId: companyLinkedinLinkFieldId,
aggregateOperation: AggregateOperations.COUNT,
}
: 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',
}
: null,
objectMetadataId: companyObject?.id ?? null,
},
@@ -314,11 +413,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Team Size',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 0, rowSpan: 5, columnSpan: 6 },
configuration: {
graphType: 'NUMBER',
aggregateFieldMetadataId: BASE_OBJECT_STANDARD_FIELD_IDS.id,
aggregateOperation: AggregateOperations.COUNT,
},
configuration: isDefined(personIdFieldId)
? {
graphType: 'NUMBER',
aggregateFieldMetadataId: personIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
}
: null,
objectMetadataId: personObject?.id ?? null,
},
{
@@ -333,13 +434,16 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Geographic Distribution',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 6, rowSpan: 5, columnSpan: 6 },
configuration: {
graphType: 'BAR',
aggregateFieldMetadataId: BASE_OBJECT_STANDARD_FIELD_IDS.id,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataIdX: PERSON_STANDARD_FIELD_IDS.city,
orderByX: 'FIELD_DESC',
},
configuration:
isDefined(personIdFieldId) && isDefined(personCityFieldId)
? {
graphType: 'BAR',
aggregateFieldMetadataId: personIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataIdX: personCityFieldId,
orderByX: 'FIELD_DESC',
}
: null,
objectMetadataId: personObject?.id ?? null,
},
@@ -356,13 +460,16 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Contact Roles',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 0, rowSpan: 4, columnSpan: 6 },
configuration: {
graphType: 'PIE',
aggregateFieldMetadataId: BASE_OBJECT_STANDARD_FIELD_IDS.id,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataId: PERSON_STANDARD_FIELD_IDS.jobTitle,
orderBy: 'VALUE_DESC',
},
configuration:
isDefined(personIdFieldId) && isDefined(personJobTitleFieldId)
? {
graphType: 'PIE',
aggregateFieldMetadataId: personIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
groupByFieldMetadataId: personJobTitleFieldId,
orderBy: 'VALUE_DESC',
}
: null,
objectMetadataId: personObject?.id ?? null,
},
{
@@ -374,11 +481,13 @@ export const getPageLayoutWidgetDataSeeds = (
title: 'Open Tasks',
type: WidgetType.GRAPH,
gridPosition: { row: 0, column: 6, rowSpan: 6, columnSpan: 6 },
configuration: {
graphType: 'NUMBER',
aggregateFieldMetadataId: BASE_OBJECT_STANDARD_FIELD_IDS.id,
aggregateOperation: AggregateOperations.COUNT,
},
configuration: isDefined(taskIdFieldId)
? {
graphType: 'NUMBER',
aggregateFieldMetadataId: taskIdFieldId,
aggregateOperation: AggregateOperations.COUNT,
}
: null,
objectMetadataId: taskObject?.id ?? null,
},
];
@@ -84,6 +84,7 @@ export class DevSeederService {
this.coreDataSource.getRepository(ObjectMetadataEntity);
const objectMetadataItems = await objectMetadataRepository.find({
where: { workspaceId },
relations: { fields: true },
});
await seedPageLayoutWidgets(