Create resolvers and controllers for core views (#13624)

Created:
- Services
- Resolvers
- Controllers
- Tests for services
- Integration tests for GraphQL and Rest

Updated the Rest API playground

Added new feature flag `IS_CORE_VIEW_ENABLED`

Updated `viewFilter` `operand` and `view` `type` to be enums rather than
strings and generated migration file.

Closes https://github.com/twentyhq/core-team-issues/issues/1259

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Raphaël Bosi
2025-08-07 18:45:04 +02:00
committed by GitHub
parent caabe8738d
commit 593b064448
134 changed files with 10987 additions and 62 deletions
@@ -18,6 +18,7 @@ export type Scalars = {
DateTime: string;
JSON: any;
JSONObject: any;
RawJSONScalar: any;
UUID: any;
Upload: any;
};
@@ -96,6 +97,21 @@ export type AgentIdInput = {
id: Scalars['UUID'];
};
export enum AggregateOperations {
AVG = 'AVG',
COUNT = 'COUNT',
COUNT_EMPTY = 'COUNT_EMPTY',
COUNT_FALSE = 'COUNT_FALSE',
COUNT_NOT_EMPTY = 'COUNT_NOT_EMPTY',
COUNT_TRUE = 'COUNT_TRUE',
COUNT_UNIQUE_VALUES = 'COUNT_UNIQUE_VALUES',
MAX = 'MAX',
MIN = 'MIN',
PERCENTAGE_EMPTY = 'PERCENTAGE_EMPTY',
PERCENTAGE_NOT_EMPTY = 'PERCENTAGE_NOT_EMPTY',
SUM = 'SUM'
}
export type Analytics = {
__typename?: 'Analytics';
/** Boolean that confirms query was dispatched */
@@ -480,6 +496,96 @@ export type ConnectionParametersOutput = {
username?: Maybe<Scalars['String']>;
};
export type CoreView = {
__typename?: 'CoreView';
anyFieldFilterValue?: Maybe<Scalars['String']>;
createdAt: Scalars['DateTime'];
deletedAt?: Maybe<Scalars['DateTime']>;
icon: Scalars['String'];
id: Scalars['UUID'];
isCompact: Scalars['Boolean'];
kanbanAggregateOperation?: Maybe<AggregateOperations>;
kanbanAggregateOperationFieldMetadataId?: Maybe<Scalars['UUID']>;
key?: Maybe<Scalars['String']>;
name: Scalars['String'];
objectMetadataId: Scalars['UUID'];
openRecordIn: ViewOpenRecordIn;
position: Scalars['Float'];
type: ViewType;
updatedAt: Scalars['DateTime'];
workspaceId: Scalars['UUID'];
};
export type CoreViewField = {
__typename?: 'CoreViewField';
aggregateOperation?: Maybe<AggregateOperations>;
createdAt: Scalars['DateTime'];
deletedAt?: Maybe<Scalars['DateTime']>;
fieldMetadataId: Scalars['UUID'];
id: Scalars['UUID'];
isVisible: Scalars['Boolean'];
position: Scalars['Float'];
size: Scalars['Float'];
updatedAt: Scalars['DateTime'];
viewId: Scalars['UUID'];
workspaceId: Scalars['UUID'];
};
export type CoreViewFilter = {
__typename?: 'CoreViewFilter';
createdAt: Scalars['DateTime'];
deletedAt?: Maybe<Scalars['DateTime']>;
fieldMetadataId: Scalars['UUID'];
id: Scalars['UUID'];
operand: ViewFilterOperand;
positionInViewFilterGroup?: Maybe<Scalars['Float']>;
subFieldName?: Maybe<Scalars['String']>;
updatedAt: Scalars['DateTime'];
value: Scalars['RawJSONScalar'];
viewFilterGroupId?: Maybe<Scalars['UUID']>;
viewId: Scalars['UUID'];
workspaceId: Scalars['UUID'];
};
export type CoreViewFilterGroup = {
__typename?: 'CoreViewFilterGroup';
createdAt: Scalars['DateTime'];
deletedAt?: Maybe<Scalars['DateTime']>;
id: Scalars['UUID'];
logicalOperator: ViewFilterGroupLogicalOperator;
parentViewFilterGroupId?: Maybe<Scalars['UUID']>;
positionInViewFilterGroup?: Maybe<Scalars['Float']>;
updatedAt: Scalars['DateTime'];
viewId: Scalars['UUID'];
workspaceId: Scalars['UUID'];
};
export type CoreViewGroup = {
__typename?: 'CoreViewGroup';
createdAt: Scalars['DateTime'];
deletedAt?: Maybe<Scalars['DateTime']>;
fieldMetadataId: Scalars['UUID'];
fieldValue: Scalars['String'];
id: Scalars['UUID'];
isVisible: Scalars['Boolean'];
position: Scalars['Float'];
updatedAt: Scalars['DateTime'];
viewId: Scalars['UUID'];
workspaceId: Scalars['UUID'];
};
export type CoreViewSort = {
__typename?: 'CoreViewSort';
createdAt: Scalars['DateTime'];
deletedAt?: Maybe<Scalars['DateTime']>;
direction: ViewSortDirection;
fieldMetadataId: Scalars['UUID'];
id: Scalars['UUID'];
updatedAt: Scalars['DateTime'];
viewId: Scalars['UUID'];
workspaceId: Scalars['UUID'];
};
export type CreateAgentChatThreadInput = {
agentId: Scalars['UUID'];
};
@@ -565,6 +671,60 @@ export type CreateServerlessFunctionInput = {
timeoutSeconds?: InputMaybe<Scalars['Float']>;
};
export type CreateViewFieldInput = {
aggregateOperation?: InputMaybe<AggregateOperations>;
fieldMetadataId: Scalars['UUID'];
isVisible?: InputMaybe<Scalars['Boolean']>;
position?: InputMaybe<Scalars['Float']>;
size?: InputMaybe<Scalars['Float']>;
viewId: Scalars['UUID'];
};
export type CreateViewFilterGroupInput = {
logicalOperator?: InputMaybe<ViewFilterGroupLogicalOperator>;
parentViewFilterGroupId?: InputMaybe<Scalars['UUID']>;
positionInViewFilterGroup?: InputMaybe<Scalars['Float']>;
viewId: Scalars['UUID'];
};
export type CreateViewFilterInput = {
fieldMetadataId: Scalars['UUID'];
operand?: InputMaybe<ViewFilterOperand>;
positionInViewFilterGroup?: InputMaybe<Scalars['Float']>;
subFieldName?: InputMaybe<Scalars['String']>;
value: Scalars['RawJSONScalar'];
viewFilterGroupId?: InputMaybe<Scalars['UUID']>;
viewId: Scalars['UUID'];
};
export type CreateViewGroupInput = {
fieldMetadataId: Scalars['UUID'];
fieldValue: Scalars['String'];
isVisible?: InputMaybe<Scalars['Boolean']>;
position?: InputMaybe<Scalars['Float']>;
viewId: Scalars['UUID'];
};
export type CreateViewInput = {
anyFieldFilterValue?: InputMaybe<Scalars['String']>;
icon: Scalars['String'];
isCompact?: InputMaybe<Scalars['Boolean']>;
kanbanAggregateOperation?: InputMaybe<AggregateOperations>;
kanbanAggregateOperationFieldMetadataId?: InputMaybe<Scalars['UUID']>;
key?: InputMaybe<Scalars['String']>;
name: Scalars['String'];
objectMetadataId: Scalars['UUID'];
openRecordIn?: InputMaybe<ViewOpenRecordIn>;
position?: InputMaybe<Scalars['Float']>;
type?: InputMaybe<ViewType>;
};
export type CreateViewSortInput = {
direction?: InputMaybe<ViewSortDirection>;
fieldMetadataId: Scalars['UUID'];
viewId: Scalars['UUID'];
};
export type CreateWebhookDto = {
description?: InputMaybe<Scalars['String']>;
operations: Array<Scalars['String']>;
@@ -751,6 +911,7 @@ export enum FeatureFlagKey {
IS_AIRTABLE_INTEGRATION_ENABLED = 'IS_AIRTABLE_INTEGRATION_ENABLED',
IS_AI_ENABLED = 'IS_AI_ENABLED',
IS_API_KEY_ROLES_ENABLED = 'IS_API_KEY_ROLES_ENABLED',
IS_CORE_VIEW_ENABLED = 'IS_CORE_VIEW_ENABLED',
IS_CORE_VIEW_SYNCING_ENABLED = 'IS_CORE_VIEW_SYNCING_ENABLED',
IS_FIELDS_PERMISSIONS_ENABLED = 'IS_FIELDS_PERMISSIONS_ENABLED',
IS_IMAP_SMTP_CALDAV_ENABLED = 'IS_IMAP_SMTP_CALDAV_ENABLED',
@@ -1125,6 +1286,12 @@ export type Mutation = {
createAgentHandoff: Scalars['Boolean'];
createApiKey: ApiKey;
createApprovedAccessDomain: ApprovedAccessDomain;
createCoreView: CoreView;
createCoreViewField: CoreViewField;
createCoreViewFilter: CoreViewFilter;
createCoreViewFilterGroup: CoreViewFilterGroup;
createCoreViewGroup: CoreViewGroup;
createCoreViewSort: CoreViewSort;
createDatabaseConfigVariable: Scalars['Boolean'];
createDraftFromWorkflowVersion: WorkflowVersion;
createFile: File;
@@ -1142,6 +1309,12 @@ export type Mutation = {
createWorkflowVersionStep: WorkflowVersionStepChanges;
deactivateWorkflowVersion: Scalars['Boolean'];
deleteApprovedAccessDomain: Scalars['Boolean'];
deleteCoreView: Scalars['Boolean'];
deleteCoreViewField: Scalars['Boolean'];
deleteCoreViewFilter: Scalars['Boolean'];
deleteCoreViewFilterGroup: Scalars['Boolean'];
deleteCoreViewGroup: Scalars['Boolean'];
deleteCoreViewSort: Scalars['Boolean'];
deleteCurrentWorkspace: Workspace;
deleteDatabaseConfigVariable: Scalars['Boolean'];
deleteFile: File;
@@ -1195,6 +1368,12 @@ export type Mutation = {
switchToYearlyInterval: BillingUpdateOutput;
trackAnalytics: Analytics;
updateApiKey?: Maybe<ApiKey>;
updateCoreView: CoreView;
updateCoreViewField: CoreViewField;
updateCoreViewFilter: CoreViewFilter;
updateCoreViewFilterGroup: CoreViewFilterGroup;
updateCoreViewGroup: CoreViewGroup;
updateCoreViewSort: CoreViewSort;
updateDatabaseConfigVariable: Scalars['Boolean'];
updateLabPublicFeatureFlag: FeatureFlagDto;
updateOneAgent: Agent;
@@ -1285,6 +1464,36 @@ export type MutationCreateApprovedAccessDomainArgs = {
};
export type MutationCreateCoreViewArgs = {
input: CreateViewInput;
};
export type MutationCreateCoreViewFieldArgs = {
input: CreateViewFieldInput;
};
export type MutationCreateCoreViewFilterArgs = {
input: CreateViewFilterInput;
};
export type MutationCreateCoreViewFilterGroupArgs = {
input: CreateViewFilterGroupInput;
};
export type MutationCreateCoreViewGroupArgs = {
input: CreateViewGroupInput;
};
export type MutationCreateCoreViewSortArgs = {
input: CreateViewSortInput;
};
export type MutationCreateDatabaseConfigVariableArgs = {
key: Scalars['String'];
value: Scalars['JSON'];
@@ -1364,6 +1573,36 @@ export type MutationDeleteApprovedAccessDomainArgs = {
};
export type MutationDeleteCoreViewArgs = {
id: Scalars['String'];
};
export type MutationDeleteCoreViewFieldArgs = {
id: Scalars['String'];
};
export type MutationDeleteCoreViewFilterArgs = {
id: Scalars['String'];
};
export type MutationDeleteCoreViewFilterGroupArgs = {
id: Scalars['String'];
};
export type MutationDeleteCoreViewGroupArgs = {
id: Scalars['String'];
};
export type MutationDeleteCoreViewSortArgs = {
id: Scalars['String'];
};
export type MutationDeleteDatabaseConfigVariableArgs = {
key: Scalars['String'];
};
@@ -1609,6 +1848,42 @@ export type MutationUpdateApiKeyArgs = {
};
export type MutationUpdateCoreViewArgs = {
id: Scalars['String'];
input: UpdateViewInput;
};
export type MutationUpdateCoreViewFieldArgs = {
id: Scalars['String'];
input: UpdateViewFieldInput;
};
export type MutationUpdateCoreViewFilterArgs = {
id: Scalars['String'];
input: UpdateViewFilterInput;
};
export type MutationUpdateCoreViewFilterGroupArgs = {
id: Scalars['String'];
input: UpdateViewFilterGroupInput;
};
export type MutationUpdateCoreViewGroupArgs = {
id: Scalars['String'];
input: UpdateViewGroupInput;
};
export type MutationUpdateCoreViewSortArgs = {
id: Scalars['String'];
input: UpdateViewSortInput;
};
export type MutationUpdateDatabaseConfigVariableArgs = {
key: Scalars['String'];
value: Scalars['JSON'];
@@ -2005,6 +2280,18 @@ export type Query = {
getAvailablePackages: Scalars['JSON'];
getConfigVariablesGrouped: ConfigVariablesOutput;
getConnectedImapSmtpCaldavAccount: ConnectedImapSmtpCaldavAccount;
getCoreView?: Maybe<CoreView>;
getCoreViewField?: Maybe<CoreViewField>;
getCoreViewFields: Array<CoreViewField>;
getCoreViewFilter?: Maybe<CoreViewFilter>;
getCoreViewFilterGroup?: Maybe<CoreViewFilterGroup>;
getCoreViewFilterGroups: Array<CoreViewFilterGroup>;
getCoreViewFilters: Array<CoreViewFilter>;
getCoreViewGroup?: Maybe<CoreViewGroup>;
getCoreViewGroups: Array<CoreViewGroup>;
getCoreViewSort?: Maybe<CoreViewSort>;
getCoreViewSorts: Array<CoreViewSort>;
getCoreViews: Array<CoreView>;
getDatabaseConfigVariable: ConfigVariable;
getIndicatorHealthStatus: AdminPanelHealthServiceData;
getMeteredProductsUsage: Array<BillingMeteredProductUsageOutput>;
@@ -2119,6 +2406,66 @@ export type QueryGetConnectedImapSmtpCaldavAccountArgs = {
};
export type QueryGetCoreViewArgs = {
id: Scalars['String'];
};
export type QueryGetCoreViewFieldArgs = {
id: Scalars['String'];
};
export type QueryGetCoreViewFieldsArgs = {
viewId: Scalars['String'];
};
export type QueryGetCoreViewFilterArgs = {
id: Scalars['String'];
};
export type QueryGetCoreViewFilterGroupArgs = {
id: Scalars['String'];
};
export type QueryGetCoreViewFilterGroupsArgs = {
viewId?: InputMaybe<Scalars['String']>;
};
export type QueryGetCoreViewFiltersArgs = {
viewId?: InputMaybe<Scalars['String']>;
};
export type QueryGetCoreViewGroupArgs = {
id: Scalars['String'];
};
export type QueryGetCoreViewGroupsArgs = {
viewId?: InputMaybe<Scalars['String']>;
};
export type QueryGetCoreViewSortArgs = {
id: Scalars['String'];
};
export type QueryGetCoreViewSortsArgs = {
viewId?: InputMaybe<Scalars['String']>;
};
export type QueryGetCoreViewsArgs = {
objectMetadataId?: InputMaybe<Scalars['String']>;
};
export type QueryGetDatabaseConfigVariableArgs = {
key: Scalars['String'];
};
@@ -2729,6 +3076,58 @@ export type UpdateServerlessFunctionInput = {
timeoutSeconds?: InputMaybe<Scalars['Float']>;
};
export type UpdateViewFieldInput = {
aggregateOperation?: InputMaybe<AggregateOperations>;
isVisible?: InputMaybe<Scalars['Boolean']>;
position?: InputMaybe<Scalars['Float']>;
size?: InputMaybe<Scalars['Float']>;
};
export type UpdateViewFilterGroupInput = {
logicalOperator?: InputMaybe<ViewFilterGroupLogicalOperator>;
parentViewFilterGroupId?: InputMaybe<Scalars['UUID']>;
positionInViewFilterGroup?: InputMaybe<Scalars['Float']>;
viewId?: InputMaybe<Scalars['UUID']>;
};
export type UpdateViewFilterInput = {
fieldMetadataId?: InputMaybe<Scalars['UUID']>;
operand?: InputMaybe<ViewFilterOperand>;
positionInViewFilterGroup?: InputMaybe<Scalars['Float']>;
subFieldName?: InputMaybe<Scalars['String']>;
value?: InputMaybe<Scalars['RawJSONScalar']>;
viewFilterGroupId?: InputMaybe<Scalars['UUID']>;
viewId?: InputMaybe<Scalars['UUID']>;
};
export type UpdateViewGroupInput = {
fieldMetadataId?: InputMaybe<Scalars['UUID']>;
fieldValue?: InputMaybe<Scalars['String']>;
isVisible?: InputMaybe<Scalars['Boolean']>;
position?: InputMaybe<Scalars['Float']>;
viewId?: InputMaybe<Scalars['UUID']>;
};
export type UpdateViewInput = {
anyFieldFilterValue?: InputMaybe<Scalars['String']>;
icon?: InputMaybe<Scalars['String']>;
isCompact?: InputMaybe<Scalars['Boolean']>;
kanbanAggregateOperation?: InputMaybe<AggregateOperations>;
kanbanAggregateOperationFieldMetadataId?: InputMaybe<Scalars['UUID']>;
key?: InputMaybe<Scalars['String']>;
name?: InputMaybe<Scalars['String']>;
objectMetadataId?: InputMaybe<Scalars['UUID']>;
openRecordIn?: InputMaybe<ViewOpenRecordIn>;
position?: InputMaybe<Scalars['Float']>;
type?: InputMaybe<ViewType>;
};
export type UpdateViewSortInput = {
direction?: InputMaybe<ViewSortDirection>;
fieldMetadataId?: InputMaybe<Scalars['UUID']>;
viewId?: InputMaybe<Scalars['UUID']>;
};
export type UpdateWebhookDto = {
description?: InputMaybe<Scalars['String']>;
id: Scalars['UUID'];
@@ -2884,6 +3283,46 @@ export type VersionInfo = {
latestVersion: Scalars['String'];
};
export enum ViewFilterGroupLogicalOperator {
AND = 'AND',
NOT = 'NOT',
OR = 'OR'
}
export enum ViewFilterOperand {
CONTAINS = 'CONTAINS',
DOES_NOT_CONTAIN = 'DOES_NOT_CONTAIN',
GREATER_THAN_OR_EQUAL = 'GREATER_THAN_OR_EQUAL',
IS = 'IS',
IS_AFTER = 'IS_AFTER',
IS_BEFORE = 'IS_BEFORE',
IS_EMPTY = 'IS_EMPTY',
IS_IN_FUTURE = 'IS_IN_FUTURE',
IS_IN_PAST = 'IS_IN_PAST',
IS_NOT = 'IS_NOT',
IS_NOT_EMPTY = 'IS_NOT_EMPTY',
IS_NOT_NULL = 'IS_NOT_NULL',
IS_RELATIVE = 'IS_RELATIVE',
IS_TODAY = 'IS_TODAY',
LESS_THAN_OR_EQUAL = 'LESS_THAN_OR_EQUAL',
VECTOR_SEARCH = 'VECTOR_SEARCH'
}
export enum ViewOpenRecordIn {
RECORD_PAGE = 'RECORD_PAGE',
SIDE_PANEL = 'SIDE_PANEL'
}
export enum ViewSortDirection {
ASC = 'ASC',
DESC = 'DESC'
}
export enum ViewType {
KANBAN = 'KANBAN',
TABLE = 'TABLE'
}
export type Webhook = {
__typename?: 'Webhook';
createdAt: Scalars['DateTime'];