feat: Add filter button to objects table and enable system objects access (#15856)

## Summary
This PR replaces the Active/Inactive accordion sections with a modern
filter dropdown button and enables full access to system objects in
advanced mode.

## Changes

### UI Improvements
-  Replaced accordion sections with a filter button dropdown (matching
the design pattern from the Group filter)
-  Added 'Deactivated' toggle filter (hidden by default, uses
IconArchive)
-  Added 'System objects' toggle filter (only visible in advanced mode,
uses IconSettings)
-  Fixed search input width to properly fill available space
-  Proper button sizing and alignment

### System Objects Support
-  Made system objects visible when 'System objects' filter is toggled
on
-  System objects are now fully clickable and accessible
-  Updated object detail page to support system objects
-  Updated field creation/edit pages to support system objects
-  System objects can now have custom fields added

### Architecture
-  Implemented scalable filter architecture using a single filtered
list
-  Easy to add more filters in the future (e.g., show remote objects)
-  All filters work independently and can be combined

## Testing
- [x] Tested deactivated objects toggle
- [x] Tested system objects toggle (only shows in advanced mode)
- [x] Tested clicking on system objects
- [x] Tested adding custom fields to system objects
- [x] No linter errors

## Screenshots
See attached screenshots in the conversation for the new filter UI.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Adds a filter dropdown to the Settings Objects table (incl.
deactivated/system toggles), enables system objects across
settings/field flows, seeds core views (workspace members, messages,
threads, calendar events), and adds actions for Workspace Members.
> 
> - **Settings UI**
> - **Objects Table**: Replaces Active/Inactive sections with a single
filterable list and dropdown (`Deactivated`, `System objects` in
advanced mode); updates props to `objectMetadataItems` and removes
accordion sections.
> - **Search/UX**: Search input fills available space; inactive rows
show activation/delete menu; active rows remain navigable.
> - **Pages Updated**: `SettingsObjects`,
`SettingsApplicationDetailContentTab` switch to new table API; object
detail and new-field flows use `findObjectMetadataItemByNamePlural`
(works with system objects).
> - **Action Menu**
> - **Workspace Members**: Adds `WORKSPACE_MEMBERS_ACTIONS_CONFIG` with
"Manage members in settings" action; wired into `getActionConfig` for
`WorkspaceMember`.
> - **Server (Core Views Seed)**
> - Adds default views: `workspaceMembersAllView`, `messagesAllView`,
`messageThreadsAllView`, `calendarEventsAllView`; included in
`prefillCoreViews`.
> - Marks workflow entities as system (`WorkflowRun`,
`WorkflowVersion`).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
6a2856df85104665bd986cc63f2d54f19bf668d0. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
This commit is contained in:
Félix Malfait
2025-11-17 19:10:19 +01:00
committed by GitHub
parent 60ed9b53f4
commit 0fa2a4524a
16 changed files with 690 additions and 118 deletions
@@ -16,8 +16,11 @@ import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manage
import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite';
import { prefillWorkspaceFavorites } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-workspace-favorites';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import { calendarEventsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/calendar-events-all.view';
import { companiesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/companies-all.view';
import { dashboardsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/dashboards-all.view';
import { messageThreadsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/message-threads-all.view';
import { messagesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/messages-all.view';
import { notesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view';
import { opportunitiesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/opportunities-all.view';
import { opportunitiesByStageView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-by-stage.view';
@@ -28,6 +31,7 @@ import { tasksByStatusView } from 'src/engine/workspace-manager/standard-objects
import { workflowRunsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-runs-all.view';
import { workflowVersionsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflow-versions-all.view';
import { workflowsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view';
import { workspaceMembersAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workspace-members-all.view';
type PrefillCoreViewsArgs = {
coreDataSource: DataSource;
@@ -56,6 +60,10 @@ export const prefillCoreViews = async ({
workflowVersionsAllView(objectMetadataItems, true),
workflowRunsAllView(objectMetadataItems, true),
dashboardsAllView(objectMetadataItems, true),
workspaceMembersAllView(objectMetadataItems, true),
messagesAllView(objectMetadataItems, true),
messageThreadsAllView(objectMetadataItems, true),
calendarEventsAllView(objectMetadataItems, true),
];
const queryRunner = coreDataSource.createQueryRunner();
@@ -0,0 +1,122 @@
import { msg } from '@lingui/core/macro';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/constants/DEFAULT_VIEW_FIELD_SIZE';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
CALENDAR_EVENT_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';
export const calendarEventsAllView = (
objectMetadataItems: ObjectMetadataEntity[],
useCoreNaming = false,
) => {
const calendarEventObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.calendarEvent,
);
if (!calendarEventObjectMetadata) {
throw new Error('CalendarEvent object metadata not found');
}
return {
name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All Calendar Events',
objectMetadataId: calendarEventObjectMetadata.id ?? '',
type: 'calendar',
key: 'INDEX',
position: 0,
icon: 'IconCalendar',
kanbanFieldMetadataId: '',
calendarFieldMetadataId:
calendarEventObjectMetadata.fields.find(
(field) =>
field.standardId === CALENDAR_EVENT_STANDARD_FIELD_IDS.startsAt,
)?.id ?? '',
calendarLayout: 'MONTH',
filters: [],
fields: [
{
fieldMetadataId:
calendarEventObjectMetadata.fields.find(
(field) =>
field.standardId === CALENDAR_EVENT_STANDARD_FIELD_IDS.title,
)?.id ?? '',
position: 0,
isVisible: true,
size: DEFAULT_VIEW_FIELD_SIZE,
},
{
fieldMetadataId:
calendarEventObjectMetadata.fields.find(
(field) =>
field.standardId === CALENDAR_EVENT_STANDARD_FIELD_IDS.startsAt,
)?.id ?? '',
position: 1,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
calendarEventObjectMetadata.fields.find(
(field) =>
field.standardId === CALENDAR_EVENT_STANDARD_FIELD_IDS.endsAt,
)?.id ?? '',
position: 2,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
calendarEventObjectMetadata.fields.find(
(field) =>
field.standardId === CALENDAR_EVENT_STANDARD_FIELD_IDS.isFullDay,
)?.id ?? '',
position: 3,
isVisible: true,
size: 100,
},
{
fieldMetadataId:
calendarEventObjectMetadata.fields.find(
(field) =>
field.standardId === CALENDAR_EVENT_STANDARD_FIELD_IDS.location,
)?.id ?? '',
position: 4,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
calendarEventObjectMetadata.fields.find(
(field) =>
field.standardId ===
CALENDAR_EVENT_STANDARD_FIELD_IDS.conferenceLink,
)?.id ?? '',
position: 5,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
calendarEventObjectMetadata.fields.find(
(field) =>
field.standardId === CALENDAR_EVENT_STANDARD_FIELD_IDS.isCanceled,
)?.id ?? '',
position: 6,
isVisible: true,
size: 100,
},
{
fieldMetadataId:
calendarEventObjectMetadata.fields.find(
(field) =>
field.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.createdAt,
)?.id ?? '',
position: 7,
isVisible: true,
size: 150,
},
],
};
};
@@ -0,0 +1,55 @@
import { msg } from '@lingui/core/macro';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/constants/DEFAULT_VIEW_FIELD_SIZE';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
MESSAGE_THREAD_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';
export const messageThreadsAllView = (
objectMetadataItems: ObjectMetadataEntity[],
useCoreNaming = false,
) => {
const messageThreadObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.messageThread,
);
if (!messageThreadObjectMetadata) {
throw new Error('MessageThread object metadata not found');
}
return {
name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All Message Threads',
objectMetadataId: messageThreadObjectMetadata.id ?? '',
type: 'table',
key: 'INDEX',
position: 0,
icon: 'IconList',
kanbanFieldMetadataId: '',
filters: [],
fields: [
{
fieldMetadataId:
messageThreadObjectMetadata.fields.find(
(field) =>
field.standardId === MESSAGE_THREAD_STANDARD_FIELD_IDS.messages,
)?.id ?? '',
position: 0,
isVisible: true,
size: DEFAULT_VIEW_FIELD_SIZE,
},
{
fieldMetadataId:
messageThreadObjectMetadata.fields.find(
(field) =>
field.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.createdAt,
)?.id ?? '',
position: 1,
isVisible: true,
size: 150,
},
],
};
};
@@ -0,0 +1,104 @@
import { msg } from '@lingui/core/macro';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/constants/DEFAULT_VIEW_FIELD_SIZE';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
MESSAGE_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';
export const messagesAllView = (
objectMetadataItems: ObjectMetadataEntity[],
useCoreNaming = false,
) => {
const messageObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.message,
);
if (!messageObjectMetadata) {
throw new Error('Message object metadata not found');
}
return {
name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All Messages',
objectMetadataId: messageObjectMetadata.id ?? '',
type: 'table',
key: 'INDEX',
position: 0,
icon: 'IconList',
kanbanFieldMetadataId: '',
filters: [],
fields: [
{
fieldMetadataId:
messageObjectMetadata.fields.find(
(field) => field.standardId === MESSAGE_STANDARD_FIELD_IDS.subject,
)?.id ?? '',
position: 0,
isVisible: true,
size: DEFAULT_VIEW_FIELD_SIZE,
},
{
fieldMetadataId:
messageObjectMetadata.fields.find(
(field) =>
field.standardId === MESSAGE_STANDARD_FIELD_IDS.messageThread,
)?.id ?? '',
position: 1,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
messageObjectMetadata.fields.find(
(field) =>
field.standardId ===
MESSAGE_STANDARD_FIELD_IDS.messageParticipants,
)?.id ?? '',
position: 2,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
messageObjectMetadata.fields.find(
(field) =>
field.standardId === MESSAGE_STANDARD_FIELD_IDS.receivedAt,
)?.id ?? '',
position: 3,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
messageObjectMetadata.fields.find(
(field) =>
field.standardId === MESSAGE_STANDARD_FIELD_IDS.headerMessageId,
)?.id ?? '',
position: 4,
isVisible: true,
size: 180,
},
{
fieldMetadataId:
messageObjectMetadata.fields.find(
(field) => field.standardId === MESSAGE_STANDARD_FIELD_IDS.text,
)?.id ?? '',
position: 5,
isVisible: true,
size: 200,
},
{
fieldMetadataId:
messageObjectMetadata.fields.find(
(field) =>
field.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.createdAt,
)?.id ?? '',
position: 6,
isVisible: true,
size: 150,
},
],
};
};
@@ -0,0 +1,132 @@
import { msg } from '@lingui/core/macro';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/constants/DEFAULT_VIEW_FIELD_SIZE';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
WORKSPACE_MEMBER_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';
export const workspaceMembersAllView = (
objectMetadataItems: ObjectMetadataEntity[],
useCoreNaming = false,
) => {
const workspaceMemberObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.workspaceMember,
);
if (!workspaceMemberObjectMetadata) {
throw new Error('WorkspaceMember object metadata not found');
}
return {
name: useCoreNaming
? msg`All {objectLabelPlural}`
: 'All Workspace Members',
objectMetadataId: workspaceMemberObjectMetadata.id ?? '',
type: 'table',
key: 'INDEX',
position: 0,
icon: 'IconList',
kanbanFieldMetadataId: '',
filters: [],
fields: [
{
fieldMetadataId:
workspaceMemberObjectMetadata.fields.find(
(field) =>
field.standardId === WORKSPACE_MEMBER_STANDARD_FIELD_IDS.name,
)?.id ?? '',
position: 0,
isVisible: true,
size: DEFAULT_VIEW_FIELD_SIZE,
},
{
fieldMetadataId:
workspaceMemberObjectMetadata.fields.find(
(field) =>
field.standardId ===
WORKSPACE_MEMBER_STANDARD_FIELD_IDS.userEmail,
)?.id ?? '',
position: 1,
isVisible: true,
size: 180,
},
{
fieldMetadataId:
workspaceMemberObjectMetadata.fields.find(
(field) =>
field.standardId ===
WORKSPACE_MEMBER_STANDARD_FIELD_IDS.avatarUrl,
)?.id ?? '',
position: 2,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
workspaceMemberObjectMetadata.fields.find(
(field) =>
field.standardId ===
WORKSPACE_MEMBER_STANDARD_FIELD_IDS.colorScheme,
)?.id ?? '',
position: 3,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
workspaceMemberObjectMetadata.fields.find(
(field) =>
field.standardId === WORKSPACE_MEMBER_STANDARD_FIELD_IDS.locale,
)?.id ?? '',
position: 4,
isVisible: true,
size: 120,
},
{
fieldMetadataId:
workspaceMemberObjectMetadata.fields.find(
(field) =>
field.standardId === WORKSPACE_MEMBER_STANDARD_FIELD_IDS.timeZone,
)?.id ?? '',
position: 5,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
workspaceMemberObjectMetadata.fields.find(
(field) =>
field.standardId ===
WORKSPACE_MEMBER_STANDARD_FIELD_IDS.dateFormat,
)?.id ?? '',
position: 6,
isVisible: true,
size: 120,
},
{
fieldMetadataId:
workspaceMemberObjectMetadata.fields.find(
(field) =>
field.standardId ===
WORKSPACE_MEMBER_STANDARD_FIELD_IDS.timeFormat,
)?.id ?? '',
position: 7,
isVisible: true,
size: 120,
},
{
fieldMetadataId:
workspaceMemberObjectMetadata.fields.find(
(field) =>
field.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.createdAt,
)?.id ?? '',
position: 8,
isVisible: true,
size: 150,
},
],
};
};