Add default viewField when creating object (#18441)

as title
This commit is contained in:
martmull
2026-03-06 14:59:31 +01:00
committed by GitHub
parent ef499b6d47
commit 403db7ad3f
45 changed files with 210 additions and 218 deletions
+1
View File
@@ -18,6 +18,7 @@ jobs:
with:
files: |
packages/twenty-sdk/**
packages/twenty-server/**
!packages/twenty-sdk/package.json
sdk-test:
needs: changed-files-check
+4 -19
View File
@@ -66,11 +66,10 @@ yarn twenty app:uninstall
Control which example files are included when creating a new app:
| Flag | Behavior |
| ------------------- | ----------------------------------------------------------------------- |
| `-e, --exhaustive` | **(default)** Creates all example files without prompting |
| `-m, --minimal` | Creates only core files (`application-config.ts` and `default-role.ts`) |
| `-i, --interactive` | Prompts you to select which examples to include |
| Flag | Behavior |
| ------------------ | ----------------------------------------------------------------------- |
| `-e, --exhaustive` | **(default)** Creates all example files |
| `-m, --minimal` | Creates only core files (`application-config.ts` and `default-role.ts`) |
```bash
# Default: all examples included
@@ -78,22 +77,8 @@ npx create-twenty-app@latest my-app
# Minimal: only core files
npx create-twenty-app@latest my-app -m
# Interactive: choose which examples to include
npx create-twenty-app@latest my-app -i
```
In interactive mode, you can pick from:
- **Example object** — a custom CRM object definition (`objects/example-object.ts`)
- **Example field** — a custom field on the example object (`fields/example-field.ts`)
- **Example logic function** — a server-side handler with HTTP trigger (`logic-functions/hello-world.ts`)
- **Example front component** — a React UI component (`front-components/hello-world.tsx`)
- **Example view** — a saved view for the example object (`views/example-view.ts`)
- **Example navigation menu item** — a sidebar link (`navigation-menu-items/example-navigation-menu-item.ts`)
- **Example skill** — an AI agent skill definition (`skills/example-skill.ts`)
- **Integration test** — a vitest integration test verifying app installation (`__tests__/app-install.integration-test.ts`)
## What gets scaffolded
**Core files (always created):**
+3 -16
View File
@@ -18,10 +18,6 @@ const program = new Command(packageJson.name)
'-m, --minimal',
'Create only core entities (application-config and default-role)',
)
.option(
'-i, --interactive',
'Interactively choose which entity examples to include',
)
.helpOption('-h, --help', 'Display this help message.')
.action(
async (
@@ -29,19 +25,14 @@ const program = new Command(packageJson.name)
options?: {
exhaustive?: boolean;
minimal?: boolean;
interactive?: boolean;
},
) => {
const modeFlags = [
options?.exhaustive,
options?.minimal,
options?.interactive,
].filter(Boolean);
const modeFlags = [options?.exhaustive, options?.minimal].filter(Boolean);
if (modeFlags.length > 1) {
console.error(
chalk.red(
'Error: --exhaustive, --minimal, and --interactive are mutually exclusive.',
'Error: --exhaustive and --minimal are mutually exclusive.',
),
);
process.exit(1);
@@ -56,11 +47,7 @@ const program = new Command(packageJson.name)
process.exit(1);
}
const mode: ScaffoldingMode = options?.minimal
? 'minimal'
: options?.interactive
? 'interactive'
: 'exhaustive';
const mode: ScaffoldingMode = options?.minimal ? 'minimal' : 'exhaustive';
await new CreateAppCommand().execute(directory, mode);
},
@@ -24,7 +24,7 @@ export class CreateAppCommand {
const { appName, appDisplayName, appDirectory, appDescription } =
await this.getAppInfos(directory);
const exampleOptions = await this.resolveExampleOptions(mode);
const exampleOptions = this.resolveExampleOptions(mode);
await this.validateDirectory(appDirectory);
@@ -103,9 +103,7 @@ export class CreateAppCommand {
return { appName, appDisplayName, appDirectory, appDescription };
}
private async resolveExampleOptions(
mode: ScaffoldingMode,
): Promise<ExampleOptions> {
private resolveExampleOptions(mode: ScaffoldingMode): ExampleOptions {
if (mode === 'minimal') {
return {
includeExampleObject: false,
@@ -119,94 +117,15 @@ export class CreateAppCommand {
};
}
if (mode === 'exhaustive') {
return {
includeExampleObject: true,
includeExampleField: true,
includeExampleLogicFunction: true,
includeExampleFrontComponent: true,
includeExampleView: true,
includeExampleNavigationMenuItem: true,
includeExampleSkill: true,
includeExampleIntegrationTest: true,
};
}
const { selectedExamples } = await inquirer.prompt([
{
type: 'checkbox',
name: 'selectedExamples',
message: 'Select which example files to include:',
choices: [
{
name: 'Example object (custom object definition)',
value: 'object',
checked: true,
},
{
name: 'Example field (custom field on the example object)',
value: 'field',
checked: true,
},
{
name: 'Example logic function (server-side handler)',
value: 'logicFunction',
checked: true,
},
{
name: 'Example front component (React UI component)',
value: 'frontComponent',
checked: true,
},
{
name: 'Example view (saved view for the example object)',
value: 'view',
checked: true,
},
{
name: 'Example navigation menu item (sidebar link)',
value: 'navigationMenuItem',
checked: true,
},
{
name: 'Example skill (AI agent skill definition)',
value: 'skill',
checked: true,
},
{
name: 'Integration test (vitest test verifying app installation)',
value: 'integrationTest',
checked: true,
},
],
},
]);
const includeField = selectedExamples.includes('field');
const includeView = selectedExamples.includes('view');
const includeExampleIntegrationTest =
selectedExamples.includes('integrationTest');
const includeObject =
selectedExamples.includes('object') || includeField || includeView;
if ((includeField || includeView) && !selectedExamples.includes('object')) {
console.log(
chalk.yellow(
'Note: Example object auto-included because example field/view depends on it.',
),
);
}
return {
includeExampleObject: includeObject,
includeExampleField: includeField,
includeExampleLogicFunction: selectedExamples.includes('logicFunction'),
includeExampleFrontComponent: selectedExamples.includes('frontComponent'),
includeExampleView: includeView,
includeExampleNavigationMenuItem:
selectedExamples.includes('navigationMenuItem'),
includeExampleSkill: selectedExamples.includes('skill'),
includeExampleIntegrationTest,
includeExampleObject: true,
includeExampleField: true,
includeExampleLogicFunction: true,
includeExampleFrontComponent: true,
includeExampleView: true,
includeExampleNavigationMenuItem: true,
includeExampleSkill: true,
includeExampleIntegrationTest: true,
};
}
@@ -1,4 +1,4 @@
export type ScaffoldingMode = 'exhaustive' | 'minimal' | 'interactive';
export type ScaffoldingMode = 'exhaustive' | 'minimal';
export type ExampleOptions = {
includeExampleObject: boolean;
@@ -673,15 +673,24 @@ describe('copyBaseApplicationProject', () => {
const content = await fs.readFile(viewPath, 'utf8');
expect(content).toContain("import { defineView } from 'twenty-sdk'");
expect(content).toContain(
"import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object'",
"import { defineView, ViewKey } from 'twenty-sdk'",
);
expect(content).toContain(
"import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, NAME_FIELD_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object'",
);
expect(content).toContain('export default defineView({');
expect(content).toContain(
'objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER',
);
expect(content).toContain("name: 'example-view'");
expect(content).toContain("name: 'All example items'");
expect(content).toContain('fields: [');
expect(content).toContain(
'fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER',
);
expect(content).toContain('isVisible: true');
expect(content).toContain('key: ViewKey.INDEX');
expect(content).toContain('size: 200');
});
});
@@ -712,6 +721,7 @@ describe('copyBaseApplicationProject', () => {
expect(content).toContain('export default defineNavigationMenuItem({');
expect(content).toContain("name: 'example-navigation-menu-item'");
expect(content).toContain("icon: 'IconList'");
expect(content).toContain("color: 'blue'");
expect(content).toContain('position: 0');
});
});
@@ -431,16 +431,29 @@ const createExampleView = async ({
fileName: string;
}) => {
const universalIdentifier = v4();
const viewFieldUniversalIdentifier = v4();
const content = `import { defineView } from 'twenty-sdk';
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
const content = `import { defineView, ViewKey } from 'twenty-sdk';
import { EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER, NAME_FIELD_UNIVERSAL_IDENTIFIER } from 'src/objects/example-object';
export const EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER = '${universalIdentifier}';
export default defineView({
universalIdentifier: '${universalIdentifier}',
name: 'example-view',
universalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
name: 'All example items',
objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
icon: 'IconList',
key: ViewKey.INDEX,
position: 0,
fields: [
{
universalIdentifier: '${viewFieldUniversalIdentifier}',
fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
position: 0,
isVisible: true,
size: 200,
},
],
});
`;
@@ -460,18 +473,15 @@ const createExampleNavigationMenuItem = async ({
const universalIdentifier = v4();
const content = `import { defineNavigationMenuItem } from 'twenty-sdk';
import { EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER } from 'src/views/example-view';
export default defineNavigationMenuItem({
universalIdentifier: '${universalIdentifier}',
name: 'example-navigation-menu-item',
icon: 'IconList',
color: 'blue',
position: 0,
// Link to a view:
// viewUniversalIdentifier: '...',
// Or link to an object:
// targetObjectUniversalIdentifier: '...',
// Or link to an external URL:
// link: 'https://example.com',
viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
});
`;
@@ -35,7 +35,7 @@ cd my-twenty-app
yarn twenty app:dev
```
The scaffolder supports three modes for controlling which example files are included:
The scaffolder supports two modes for controlling which example files are included:
```bash filename="Terminal"
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill)
@@ -43,9 +43,6 @@ npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interactive: select which examples to include
npx create-twenty-app@latest my-app --interactive
```
From here you can:
@@ -121,7 +118,7 @@ my-twenty-app/
└── example-skill.ts # Example AI agent skill definition
```
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`). With `--interactive`, you choose which example files to include.
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`).
At a high level:
@@ -25,6 +25,7 @@ const APP_FOLDER = 'src';
export class EntityAddCommand {
private lastObjectUniversalIdentifier: string | undefined;
private lastNameFieldUniversalIdentifier: string | undefined;
async execute(entityType?: SyncableEntity, path?: string): Promise<void> {
try {
@@ -75,13 +76,16 @@ export class EntityAddCommand {
const name = entityData.nameSingular;
const objectUniversalIdentifier = v4();
const nameFieldUniversalIdentifier = v4();
this.lastObjectUniversalIdentifier = objectUniversalIdentifier;
this.lastNameFieldUniversalIdentifier = nameFieldUniversalIdentifier;
const file = getObjectBaseFile({
data: entityData,
name,
universalIdentifier: objectUniversalIdentifier,
nameFieldUniversalIdentifier,
});
return { name, file };
@@ -202,6 +206,17 @@ export class EntityAddCommand {
name: `all-${kebabCase(objectName)}`,
universalIdentifier: viewUniversalIdentifier,
objectUniversalIdentifier: this.lastObjectUniversalIdentifier,
fields: this.lastNameFieldUniversalIdentifier
? [
{
fieldMetadataUniversalIdentifier:
this.lastNameFieldUniversalIdentifier,
position: 0,
isVisible: true,
size: 200,
},
]
: [],
});
const viewFolderPath = customPath
@@ -12,15 +12,24 @@ describe('getNewObjectFileContent', () => {
name: 'company',
});
expect(result).toContain("import { defineObject } from 'twenty-sdk'");
expect(result).toContain(
"import { defineObject, FieldType } from 'twenty-sdk'",
);
expect(result).toContain('export default defineObject({');
expect(result).toContain('export const NAME_FIELD_UNIVERSAL_IDENTIFIER');
expect(result).toContain("nameSingular: 'company'");
expect(result).toContain("namePlural: 'companies'");
expect(result).toContain("labelSingular: 'Company'");
expect(result).toContain("labelPlural: 'Companies'");
expect(result).toContain("icon: 'IconBox'");
expect(result).toContain(
'labelIdentifierFieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER',
);
expect(result).toContain('fields: [');
expect(result).toContain('FieldType.TEXT');
expect(result).toContain("name: 'name'");
expect(result).toContain("label: 'Name'");
expect(result).toMatch(
/universalIdentifier: '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'/,
@@ -3,6 +3,7 @@ import { v4 } from 'uuid';
export const getObjectBaseFile = ({
data,
universalIdentifier = v4(),
nameFieldUniversalIdentifier = v4(),
}: {
data: {
nameSingular: string;
@@ -12,8 +13,12 @@ export const getObjectBaseFile = ({
};
name: string;
universalIdentifier?: string;
nameFieldUniversalIdentifier?: string;
}) => {
return `import { defineObject } from 'twenty-sdk';
return `import { defineObject, FieldType } from 'twenty-sdk';
export const NAME_FIELD_UNIVERSAL_IDENTIFIER =
'${nameFieldUniversalIdentifier}';
export default defineObject({
universalIdentifier: '${universalIdentifier}',
@@ -22,15 +27,16 @@ export default defineObject({
labelSingular: '${data.labelSingular}',
labelPlural: '${data.labelPlural}',
icon: 'IconBox',
labelIdentifierFieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
fields: [
// Add your fields here using defineField helper
// Example:
// {
// universalIdentifier: '...',
// type: FieldMetadataType.TEXT,
// name: 'description',
// label: 'Description',
// },
{
universalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
type: FieldType.TEXT,
name: 'name',
label: 'Name',
description: 'Name of the ${data.nameSingular}',
icon: 'IconAbc',
},
],
});
`;
@@ -1,17 +1,56 @@
import { kebabCase } from '@/cli/utilities/string/kebab-case';
import { v4 } from 'uuid';
type ViewFieldTemplate = {
universalIdentifier?: string;
fieldMetadataUniversalIdentifier: string;
position: number;
isVisible?: boolean;
size?: number;
};
export const getViewBaseFile = ({
name,
universalIdentifier = v4(),
objectUniversalIdentifier = 'fill-later',
fields = [],
}: {
name: string;
universalIdentifier?: string;
objectUniversalIdentifier?: string;
fields?: ViewFieldTemplate[];
}) => {
const kebabCaseName = kebabCase(name);
const formattedFields = fields.map((field, index) => {
const uid = field.universalIdentifier ?? v4();
return {
universalIdentifier: uid,
fieldMetadataUniversalIdentifier: field.fieldMetadataUniversalIdentifier,
position: field.position ?? index,
isVisible: field.isVisible ?? true,
size: field.size ?? 200,
};
});
const defaultFields = ` // fields: [
// {
// universalIdentifier: '...',
// fieldMetadataUniversalIdentifier: '...',
// position: 0,
// isVisible: true,
// },
// ],`;
const fieldsBlock =
fields.length > 0
? ` fields: [
${formattedFields
.map((field) => JSON.stringify(field, null, 2))
.join(',\n')},\n
],`
: defaultFields;
return `import { defineView } from 'twenty-sdk';
export default defineView({
@@ -20,14 +59,7 @@ export default defineView({
objectUniversalIdentifier: '${objectUniversalIdentifier}',
icon: 'IconList',
position: 0,
// fields: [
// {
// universalIdentifier: '...',
// fieldMetadataUniversalIdentifier: '...',
// position: 0,
// isVisible: true,
// },
// ],
${fieldsBlock}
// filters: [
// {
// universalIdentifier: '...',
+1
View File
@@ -71,6 +71,7 @@ export { PermissionFlag } from './roles/permission-flag-type';
export { defineSkill } from './skills/define-skill';
export { defineView } from './views/define-view';
export type { ViewConfig } from './views/view-config';
export { ViewKey } from './views/view-key';
// Action components for front components
export {
@@ -0,0 +1 @@
export { ViewKey } from 'twenty-shared/types';
@@ -2,6 +2,7 @@ import {
ViewOpenRecordIn,
ViewType,
ViewVisibility,
ViewKey,
} from 'twenty-shared/types';
import { fromViewManifestToUniversalFlatView } from 'src/engine/core-modules/application/application-manifest/converters/from-view-manifest-to-universal-flat-view.util';
@@ -16,6 +17,7 @@ describe('fromViewManifestToUniversalFlatView', () => {
universalIdentifier: 'view-uuid-1',
name: 'All Records',
objectUniversalIdentifier: 'object-uuid-1',
key: ViewKey.INDEX,
},
applicationUniversalIdentifier,
now,
@@ -34,7 +36,7 @@ describe('fromViewManifestToUniversalFlatView', () => {
expect(result.isCustom).toBe(true);
expect(result.visibility).toBe(ViewVisibility.WORKSPACE);
expect(result.openRecordIn).toBe(ViewOpenRecordIn.SIDE_PANEL);
expect(result.key).toBeNull();
expect(result.key).toBe(ViewKey.INDEX);
expect(result.createdAt).toBe(now);
expect(result.updatedAt).toBe(now);
});
@@ -28,7 +28,7 @@ export const fromViewManifestToUniversalFlatView = ({
isCustom: true,
visibility: viewManifest.visibility ?? ViewVisibility.WORKSPACE,
openRecordIn: viewManifest.openRecordIn ?? ViewOpenRecordIn.SIDE_PANEL,
key: null,
key: viewManifest.key ?? null,
kanbanAggregateOperation: null,
kanbanAggregateOperationFieldMetadataUniversalIdentifier: null,
calendarLayout: null,
@@ -6,6 +6,7 @@ import {
FeatureFlagKey,
ViewOpenRecordIn,
ViewType,
ViewKey,
ViewVisibility,
} from 'twenty-shared/types';
import { fromArrayToUniqueKeyRecord, isDefined } from 'twenty-shared/utils';
@@ -41,7 +42,6 @@ import {
import { computeFlatDefaultRecordPageLayoutToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-default-record-page-layout-to-create.util';
import { computeFlatRecordPageFieldsViewToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-record-page-fields-view-to-create.util';
import { computeFlatViewFieldsToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
@@ -12,6 +12,7 @@ import {
import {
AggregateOperations,
ViewOpenRecordIn,
ViewKey,
ViewType,
ViewVisibility,
} from 'twenty-shared/types';
@@ -19,7 +20,6 @@ import {
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator';
import { ViewCalendarLayout } from 'src/engine/metadata-modules/view/enums/view-calendar-layout.enum';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
@InputType()
export class CreateViewInput {
@@ -6,6 +6,7 @@ import {
ViewOpenRecordIn,
ViewType,
ViewVisibility,
ViewKey,
} from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@@ -16,7 +17,6 @@ import { ViewFilterDTO } from 'src/engine/metadata-modules/view-filter/dtos/view
import { ViewGroupDTO } from 'src/engine/metadata-modules/view-group/dtos/view-group.dto';
import { ViewSortDTO } from 'src/engine/metadata-modules/view-sort/dtos/view-sort.dto';
import { ViewCalendarLayout } from 'src/engine/metadata-modules/view/enums/view-calendar-layout.enum';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
registerEnumType(ViewOpenRecordIn, { name: 'ViewOpenRecordIn' });
registerEnumType(ViewType, { name: 'ViewType' });
@@ -16,6 +16,7 @@ import {
AggregateOperations,
ViewOpenRecordIn,
ViewType,
ViewKey,
ViewVisibility,
} from 'twenty-shared/types';
@@ -29,7 +30,6 @@ import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entiti
import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity';
import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
import { ViewCalendarLayout } from 'src/engine/metadata-modules/view/enums/view-calendar-layout.enum';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
// We could refactor this type to be dynamic to view type
@@ -1,7 +1,6 @@
import { ViewType } from 'twenty-shared/types';
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -1,7 +1,6 @@
import { ViewType } from 'twenty-shared/types';
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -1,7 +1,7 @@
import { ViewType } from 'twenty-shared/types';
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -1,7 +1,6 @@
import { ViewType } from 'twenty-shared/types';
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -1,7 +1,6 @@
import { ViewType } from 'twenty-shared/types';
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -1,7 +1,6 @@
import { ViewType } from 'twenty-shared/types';
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -1,7 +1,6 @@
import { ViewType } from 'twenty-shared/types';
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -1,7 +1,6 @@
import { ViewType } from 'twenty-shared/types';
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -1,7 +1,7 @@
import { AggregateOperations, ViewType } from 'twenty-shared/types';
import { AggregateOperations, ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -1,7 +1,6 @@
import { ViewType } from 'twenty-shared/types';
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -1,7 +1,6 @@
import { ViewType } from 'twenty-shared/types';
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -1,7 +1,7 @@
import { ViewType } from 'twenty-shared/types';
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -1,7 +1,7 @@
import { ViewType } from 'twenty-shared/types';
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -1,7 +1,7 @@
import { ViewType } from 'twenty-shared/types';
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -1,7 +1,7 @@
import { ViewType } from 'twenty-shared/types';
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -1,7 +1,7 @@
import { ViewType } from 'twenty-shared/types';
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -1,7 +1,7 @@
import { ViewType } from 'twenty-shared/types';
import { ViewType, ViewKey } from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import {
createStandardViewFlatMetadata,
type CreateStandardViewArgs,
@@ -3,12 +3,12 @@ import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import {
type AggregateOperations,
type ViewType,
type ViewKey,
ViewOpenRecordIn,
ViewVisibility,
} from 'twenty-shared/types';
import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type';
import { type ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type';
import { type AllStandardObjectName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-name.type';
@@ -11,7 +11,7 @@ import { findCoreViews } from 'test/integration/metadata/suites/view/utils/find-
import { extractRecordIdsAndDatesAsExpectAny } from 'test/utils/extract-record-ids-and-dates-as-expect-any';
import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import { ViewKey } from 'twenty-shared/types';
describe('successful find view with all sub-relations (e2e)', () => {
let companyObjectMetadataId: string;
@@ -13,7 +13,7 @@ import { generateRecordName } from 'test/integration/utils/generate-record-name'
import { assertViewStructure } from 'test/integration/utils/view-test.util';
import { ViewOpenRecordIn, ViewType } from 'twenty-shared/types';
import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
import { ViewKey } from 'twenty-shared/types';
describe('View REST API', () => {
let testObjectMetadataId: string;
@@ -1,5 +1,6 @@
import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsType';
import {
type ViewKey,
type AggregateOperations,
type ViewFilterGroupLogicalOperator,
type ViewFilterOperand,
@@ -55,6 +56,7 @@ export type ViewManifest = SyncableEntityOptions & {
name: string;
objectUniversalIdentifier: string;
type?: ViewType;
key?: ViewKey;
icon?: string;
position?: number;
isCompact?: boolean;
@@ -262,6 +262,7 @@ export { IsValidGraphQLEnumName } from './validators/is-valid-graphql-enum-name.
export { ViewFilterGroupLogicalOperator } from './ViewFilterGroupLogicalOperator';
export { ViewFilterOperand } from './ViewFilterOperand';
export { ViewFilterOperandDeprecated } from './ViewFilterOperandDeprecated';
export { ViewKey } from './ViewKey';
export { ViewOpenRecordIn } from './ViewOpenRecordIn';
export { ViewType } from './ViewType';
export { ViewVisibility } from './ViewVisibility';
+18 -7
View File
@@ -110,8 +110,16 @@
--t-background-overlay-primary: #000000b8;
--t-background-overlay-secondary: #0000005c;
--t-background-overlay-tertiary: #0000005c;
--t-background-radial-gradient: radial-gradient(50% 62.62% at 50% 0%, color(display-p3 0.506 0.506 0.506) 0%, color(display-p3 0.482 0.482 0.482) 100%);
--t-background-radial-gradient-hover: radial-gradient(76.32% 95.59% at 50% 0%, color(display-p3 0.482 0.482 0.482) 0%, color(display-p3 0.702 0.702 0.702) 100%);
--t-background-radial-gradient: radial-gradient(
50% 62.62% at 50% 0%,
color(display-p3 0.506 0.506 0.506) 0%,
color(display-p3 0.482 0.482 0.482) 100%
);
--t-background-radial-gradient-hover: radial-gradient(
76.32% 95.59% at 50% 0%,
color(display-p3 0.482 0.482 0.482) 0%,
color(display-p3 0.702 0.702 0.702) 100%
);
--t-background-primary-inverted: color(display-p3 0.922 0.922 0.922);
--t-background-primary-inverted-hover: color(display-p3 0.702 0.702 0.702);
--t-blur-light: blur(6px) saturate(200%) contrast(100%) brightness(130%);
@@ -132,11 +140,14 @@
--t-border-radius-xxl: 40px;
--t-border-radius-pill: 999px;
--t-border-radius-rounded: 100%;
--t-box-shadow-color: rgba(0,0,0,0.6);
--t-box-shadow-light: 0px 2px 4px 0px rgba(0,0,0,0.04), 0px 0px 4px 0px rgba(0,0,0,0.08);
--t-box-shadow-strong: 2px 4px 16px 0px rgba(0,0,0,0.16), 0px 2px 4px 0px rgba(0,0,0,0.08);
--t-box-shadow-underline: 0px 1px 0px 0px rgba(0,0,0,0.32);
--t-box-shadow-super-heavy: 2px 4px 16px 0px rgba(0,0,0,0.12), 0px 2px 4px 0px rgba(0,0,0,0.04);
--t-box-shadow-color: rgba(0, 0, 0, 0.6);
--t-box-shadow-light: 0px 2px 4px 0px rgba(0, 0, 0, 0.04),
0px 0px 4px 0px rgba(0, 0, 0, 0.08);
--t-box-shadow-strong: 2px 4px 16px 0px rgba(0, 0, 0, 0.16),
0px 2px 4px 0px rgba(0, 0, 0, 0.08);
--t-box-shadow-underline: 0px 1px 0px 0px rgba(0, 0, 0, 0.32);
--t-box-shadow-super-heavy: 2px 4px 16px 0px rgba(0, 0, 0, 0.12),
0px 2px 4px 0px rgba(0, 0, 0, 0.04);
--t-font-color-primary: color(display-p3 0.922 0.922 0.922);
--t-font-color-secondary: color(display-p3 0.702 0.702 0.702);
--t-font-color-tertiary: color(display-p3 0.506 0.506 0.506);
+17 -5
View File
@@ -110,8 +110,16 @@
--t-background-overlay-primary: color(display-p3 0 0 0 / 0.722);
--t-background-overlay-secondary: color(display-p3 0 0 0 / 0.361);
--t-background-overlay-tertiary: color(display-p3 0 0 0 / 0.071);
--t-background-radial-gradient: radial-gradient(50% 62.62% at 50% 0%, color(display-p3 0.6 0.6 0.6) 0%, color(display-p3 0.514 0.514 0.514) 100%);
--t-background-radial-gradient-hover: radial-gradient(76.32% 95.59% at 50% 0%, color(display-p3 0.514 0.514 0.514) 0%, color(display-p3 0.4 0.4 0.4) 100%);
--t-background-radial-gradient: radial-gradient(
50% 62.62% at 50% 0%,
color(display-p3 0.6 0.6 0.6) 0%,
color(display-p3 0.514 0.514 0.514) 100%
);
--t-background-radial-gradient-hover: radial-gradient(
76.32% 95.59% at 50% 0%,
color(display-p3 0.514 0.514 0.514) 0%,
color(display-p3 0.4 0.4 0.4) 100%
);
--t-background-primary-inverted: color(display-p3 0.2 0.2 0.2);
--t-background-primary-inverted-hover: color(display-p3 0.4 0.4 0.4);
--t-blur-light: blur(6px) saturate(200%) contrast(50%) brightness(130%);
@@ -133,10 +141,14 @@
--t-border-radius-pill: 999px;
--t-border-radius-rounded: 100%;
--t-box-shadow-color: color(display-p3 0 0 0 / 0.039);
--t-box-shadow-light: 0px 2px 4px 0px color(display-p3 0 0 0 / 0.039), 0px 0px 4px 0px color(display-p3 0 0 0 / 0.078);
--t-box-shadow-strong: 2px 4px 16px 0px color(display-p3 0 0 0 / 0.161), 0px 2px 4px 0px color(display-p3 0 0 0 / 0.078);
--t-box-shadow-light: 0px 2px 4px 0px color(display-p3 0 0 0 / 0.039),
0px 0px 4px 0px color(display-p3 0 0 0 / 0.078);
--t-box-shadow-strong: 2px 4px 16px 0px color(display-p3 0 0 0 / 0.161),
0px 2px 4px 0px color(display-p3 0 0 0 / 0.078);
--t-box-shadow-underline: 0px 1px 0px 0px color(display-p3 0 0 0 / 0.361);
--t-box-shadow-super-heavy: 0px 0px 8px 0px color(display-p3 0 0 0 / 0.161), 0px 8px 64px -16px color(display-p3 0 0 0 / 0.478), 0px 24px 56px -16px color(display-p3 0 0 0 / 0.078);
--t-box-shadow-super-heavy: 0px 0px 8px 0px color(display-p3 0 0 0 / 0.161),
0px 8px 64px -16px color(display-p3 0 0 0 / 0.478),
0px 24px 56px -16px color(display-p3 0 0 0 / 0.078);
--t-font-color-primary: color(display-p3 0.2 0.2 0.2);
--t-font-color-secondary: color(display-p3 0.4 0.4 0.4);
--t-font-color-tertiary: color(display-p3 0.6 0.6 0.6);