From 21cbb4f81e8775b9350ecf25971037d3e02cfa97 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Tue, 2 Jul 2024 00:31:30 +0200 Subject: [PATCH 01/63] Add reports page --- packages/twenty-front/src/App.tsx | 2 + .../reports/components/ReportGroup.tsx | 4 ++ .../reports/constants/ReportGroups.ts | 40 +++++++++++++++++ .../activities/reports/types/ReportGroup.ts | 4 ++ .../components/MainNavigationDrawerItems.tsx | 19 +++++++- .../navigation/hooks/useIsReportsPage.ts | 4 ++ .../ObjectFilterDropdownRecordSelect.tsx | 5 ++- ...SingleEntityObjectFilterDropdownButton.tsx | 4 +- .../twenty-front/src/modules/types/AppPath.ts | 1 + .../modules/workspace/types/FeatureFlagKey.ts | 3 +- .../src/pages/reports/Reports.tsx | 45 +++++++++++++++++++ .../feature-flag/feature-flag.entity.ts | 1 + .../commands/add-standard-id.command.ts | 2 + .../display/icon/components/TablerIcons.ts | 1 + 14 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 packages/twenty-front/src/modules/activities/reports/components/ReportGroup.tsx create mode 100644 packages/twenty-front/src/modules/activities/reports/constants/ReportGroups.ts create mode 100644 packages/twenty-front/src/modules/activities/reports/types/ReportGroup.ts create mode 100644 packages/twenty-front/src/modules/navigation/hooks/useIsReportsPage.ts create mode 100644 packages/twenty-front/src/pages/reports/Reports.tsx diff --git a/packages/twenty-front/src/App.tsx b/packages/twenty-front/src/App.tsx index 956a6c59e33a..8c0562531a0d 100644 --- a/packages/twenty-front/src/App.tsx +++ b/packages/twenty-front/src/App.tsx @@ -51,6 +51,7 @@ import { CreateWorkspace } from '~/pages/onboarding/CreateWorkspace'; import { InviteTeam } from '~/pages/onboarding/InviteTeam'; import { PaymentSuccess } from '~/pages/onboarding/PaymentSuccess'; import { SyncEmails } from '~/pages/onboarding/SyncEmails'; +import { Reports } from '~/pages/reports/Reports'; import { SettingsAccounts } from '~/pages/settings/accounts/SettingsAccounts'; import { SettingsAccountsCalendars } from '~/pages/settings/accounts/SettingsAccountsCalendars'; import { SettingsAccountsCalendarsSettings } from '~/pages/settings/accounts/SettingsAccountsCalendarsSettings'; @@ -152,6 +153,7 @@ const createRouter = (isBillingEnabled?: boolean) => /> } /> } /> + } /> } /> } /> } /> diff --git a/packages/twenty-front/src/modules/activities/reports/components/ReportGroup.tsx b/packages/twenty-front/src/modules/activities/reports/components/ReportGroup.tsx new file mode 100644 index 000000000000..735b853479c7 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/reports/components/ReportGroup.tsx @@ -0,0 +1,4 @@ +import React from 'react'; + +// TODO: Implement based on TaskGroups.tsx +export const ReportGroup = () =>
; diff --git a/packages/twenty-front/src/modules/activities/reports/constants/ReportGroups.ts b/packages/twenty-front/src/modules/activities/reports/constants/ReportGroups.ts new file mode 100644 index 000000000000..c19bb7ebc8fc --- /dev/null +++ b/packages/twenty-front/src/modules/activities/reports/constants/ReportGroups.ts @@ -0,0 +1,40 @@ +import { ReportGroup } from '@/activities/reports/types/ReportGroup'; + +export const REPORT_GROUPS: ReportGroup[] = [ + { + name: 'today', + minSinceMs: 0, + }, + { + name: 'over a day ago', + minSinceMs: 1000 * 60 * 60 * 24, + }, + { + name: 'over a week ago', + minSinceMs: 1000 * 60 * 60 * 24 * 7, + }, + { + name: 'over a month ago', + minSinceMs: 1000 * 60 * 60 * 24 * 30, + }, + { + name: 'over a year ago', + minSinceMs: 1000 * 60 * 60 * 24 * 365, + }, + ...[ + 'two', + 'three', + 'four', + 'five', + 'six', + 'sever', + 'eight', + 'nine', + 'ten', + ].map((yearCount, i) => { + return { + name: `over ${yearCount} years ago`, + minSinceMs: 1000 * 60 * 60 * 24 * 365 * (i + 2), + }; + }), +]; diff --git a/packages/twenty-front/src/modules/activities/reports/types/ReportGroup.ts b/packages/twenty-front/src/modules/activities/reports/types/ReportGroup.ts new file mode 100644 index 000000000000..647bec856b06 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/reports/types/ReportGroup.ts @@ -0,0 +1,4 @@ +export interface ReportGroup { + name: string; + minSinceMs: number; +} diff --git a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx index 43d7b3da3eb9..194b06e45d37 100644 --- a/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx +++ b/packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerItems.tsx @@ -1,16 +1,23 @@ import { useLocation } from 'react-router-dom'; import { useRecoilValue, useSetRecoilState } from 'recoil'; -import { IconCheckbox, IconSearch, IconSettings } from 'twenty-ui'; +import { + IconCheckbox, + IconReportAnalytics, + IconSearch, + IconSettings, +} from 'twenty-ui'; import { CurrentUserDueTaskCountEffect } from '@/activities/tasks/components/CurrentUserDueTaskCountEffect'; import { currentUserDueTaskCountState } from '@/activities/tasks/states/currentUserTaskCountState'; import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu'; import { Favorites } from '@/favorites/components/Favorites'; +import { useIsReportsPage } from '@/navigation/hooks/useIsReportsPage'; import { ObjectMetadataNavItems } from '@/object-metadata/components/ObjectMetadataNavItems'; import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsTasksPage } from '../hooks/useIsTasksPage'; @@ -19,6 +26,8 @@ export const MainNavigationDrawerItems = () => { const { toggleCommandMenu } = useCommandMenu(); const isTasksPage = useIsTasksPage(); const currentUserDueTaskCount = useRecoilValue(currentUserDueTaskCountState); + const isReportsEnabled = useIsFeatureEnabled('IS_REPORTS_ENABLED'); + const isReportsPage = useIsReportsPage(); const location = useLocation(); const setNavigationMemorizedUrl = useSetRecoilState( navigationMemorizedUrlState, @@ -50,6 +59,14 @@ export const MainNavigationDrawerItems = () => { Icon={IconCheckbox} count={currentUserDueTaskCount} /> + {isReportsEnabled && ( + + )} )} diff --git a/packages/twenty-front/src/modules/navigation/hooks/useIsReportsPage.ts b/packages/twenty-front/src/modules/navigation/hooks/useIsReportsPage.ts new file mode 100644 index 000000000000..998ba2d8cf4a --- /dev/null +++ b/packages/twenty-front/src/modules/navigation/hooks/useIsReportsPage.ts @@ -0,0 +1,4 @@ +import { useLocation } from 'react-router-dom'; + +export const useIsReportsPage = () => + useLocation().pathname.startsWith('/reports'); diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx index c1f0cf2d1431..a5cf8185455d 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect.tsx @@ -15,7 +15,7 @@ export const MAX_RECORDS_TO_DISPLAY = 3; type ObjectFilterDropdownRecordSelectProps = { viewComponentId?: string; -} +}; export const ObjectFilterDropdownRecordSelect = ({ viewComponentId, }: ObjectFilterDropdownRecordSelectProps) => { @@ -31,7 +31,8 @@ export const ObjectFilterDropdownRecordSelect = ({ } = useFilterDropdown(); const { removeCombinedViewFilter } = useCombinedViewFilters(viewComponentId); - const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView(viewComponentId); + const { currentViewWithCombinedFiltersAndSorts } = + useGetCurrentView(viewComponentId); const filterDefinitionUsedInDropdown = useRecoilValue( filterDefinitionUsedInDropdownState, diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx index 690ed90376a2..3c2ecc6f1270 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/SingleEntityObjectFilterDropdownButton.tsx @@ -75,7 +75,9 @@ export const SingleEntityObjectFilterDropdownButton = ({ - + } /> diff --git a/packages/twenty-front/src/modules/types/AppPath.ts b/packages/twenty-front/src/modules/types/AppPath.ts index ca6f77009d8d..36c586a31164 100644 --- a/packages/twenty-front/src/modules/types/AppPath.ts +++ b/packages/twenty-front/src/modules/types/AppPath.ts @@ -16,6 +16,7 @@ export enum AppPath { // Onboarded Index = '/', TasksPage = '/tasks', + ReportsPage = '/reports', OpportunitiesPage = '/objects/opportunities', RecordIndexPage = '/objects/:objectNamePlural', diff --git a/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts b/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts index cb6262c2d008..3788c29c39ad 100644 --- a/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts +++ b/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts @@ -4,4 +4,5 @@ export type FeatureFlagKey = | 'IS_EVENT_OBJECT_ENABLED' | 'IS_AIRTABLE_INTEGRATION_ENABLED' | 'IS_POSTGRESQL_INTEGRATION_ENABLED' - | 'IS_STRIPE_INTEGRATION_ENABLED'; + | 'IS_STRIPE_INTEGRATION_ENABLED' + | 'IS_REPORTS_ENABLED'; diff --git a/packages/twenty-front/src/pages/reports/Reports.tsx b/packages/twenty-front/src/pages/reports/Reports.tsx new file mode 100644 index 000000000000..67cdb6a5cb7a --- /dev/null +++ b/packages/twenty-front/src/pages/reports/Reports.tsx @@ -0,0 +1,45 @@ +import styled from '@emotion/styled'; +import { IconReportAnalytics } from 'twenty-ui'; + +import { REPORT_GROUPS } from '@/activities/reports/constants/ReportGroups'; +// import { PageAddReportButton } from '@/activities/reports/components/PageAddReportButton'; +import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; +import { PageBody } from '@/ui/layout/page/PageBody'; +import { PageContainer } from '@/ui/layout/page/PageContainer'; +import { PageHeader } from '@/ui/layout/page/PageHeader'; + +const StyledReportsContainer = styled.div` + display: flex; + flex: 1; + flex-direction: column; + height: 100%; + overflow: auto; +`; + +export const Reports = () => { + const groupedReports: { groupName: string; reports: any[] }[] = + REPORT_GROUPS.map((groupedReport) => { + // TODO + return { + groupName: groupedReport.name, + reports: [], // TODO + }; + }).filter((groupedReport) => groupedReport.reports.length); + + return ( + + + + {/* */} + + + + {groupedReports.map((groupedReport) => ( +
{groupedReport.groupName}
+ ))} +
+
+
+
+ ); +}; diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts index 4f3264baa090..46361558036d 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts @@ -24,6 +24,7 @@ export enum FeatureFlagKeys { IsContactCreationForSentAndReceivedEmailsEnabled = 'IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED', IsGoogleCalendarSyncV2Enabled = 'IS_GOOGLE_CALENDAR_SYNC_V2_ENABLED', IsFreeAccessEnabled = 'IS_FREE_ACCESS_ENABLED', + IsReportsEnabled = 'IS_REPORTS_ENABLED', } @Entity({ name: 'featureFlag', schema: 'core' }) diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command.ts index 804759f55ebb..50be6e626736 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command.ts @@ -61,6 +61,7 @@ export class AddStandardIdCommand extends CommandRunner { IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED: true, IS_GOOGLE_CALENDAR_SYNC_V2_ENABLED: true, IS_FREE_ACCESS_ENABLED: false, + IS_REPORTS_ENABLED: false, }, ); const standardFieldMetadataCollection = this.standardFieldFactory.create( @@ -78,6 +79,7 @@ export class AddStandardIdCommand extends CommandRunner { IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED: true, IS_GOOGLE_CALENDAR_SYNC_V2_ENABLED: true, IS_FREE_ACCESS_ENABLED: false, + IS_REPORTS_ENABLED: false, }, ); diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts index 9ff7c5008bbf..619c8481d5e8 100644 --- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts +++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts @@ -136,6 +136,7 @@ export { IconRelationOneToOne, IconReload, IconRepeat, + IconReportAnalytics, IconRocket, IconSearch, IconSend, From a5d97fb98c66ca407bda006c5b53d01e0119e65d Mon Sep 17 00:00:00 2001 From: ad-elias Date: Thu, 4 Jul 2024 19:50:15 +0200 Subject: [PATCH 02/63] Reports data model draft --- .../constants/standard-field-ids.ts | 30 ++++++ .../constants/standard-object-ids.ts | 4 + .../standard-objects/index.ts | 8 ++ ...analytics-query-filter.workspace-entity.ts | 65 +++++++++++++ .../analytics-query.workspace-entity.ts | 93 ++++++++++++++++++ .../chart.workspace-entity.ts | 95 +++++++++++++++++++ .../report.workspace-entity.ts | 49 ++++++++++ .../display/icon/components/TablerIcons.ts | 6 ++ 8 files changed, 350 insertions(+) create mode 100644 packages/twenty-server/src/modules/reports/standard-objects/analytics-query-filter.workspace-entity.ts create mode 100644 packages/twenty-server/src/modules/reports/standard-objects/analytics-query.workspace-entity.ts create mode 100644 packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts create mode 100644 packages/twenty-server/src/modules/reports/standard-objects/report.workspace-entity.ts diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts index 30c07bac18a2..dced1264956e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts @@ -27,6 +27,22 @@ export const ACTIVITY_STANDARD_FIELD_IDS = { assignee: '20202020-4259-48e4-9e77-6b92991906d5', }; +export const ANALYTICS_QUERY_STANDARD_FIELD_IDS = { + chart: '20202020-9084-4c96-944c-3a359c3f6b03', + measure: '20202020-9573-4359-b500-f8a9895c848f', + sourceObjectNameSingular: '20202020-7acd-412f-af03-62b5630df552', + field: '20202020-5206-419e-9df6-d8f8f75e75e9', + analyticsQueryFilters: '20202020-2229-4c35-9f3b-9e1199ec3c7c', + groupBy: '20202020-4a7b-4021-b1d4-6e6f282cedee', +}; + +export const ANALYTICS_QUERY_FILTER_STANDARD_FIELD_IDS = { + analyticsQuery: '20202020-7023-4c2c-ae1e-8bdaea1d6dbf', + field: '20202020-24f0-4f4d-a845-b7d80e5c661b', + operator: '20202020-3994-49f3-a0c0-9bd77978879a', + value: '20202020-2b74-47ea-8a9d-f5d97f3f95f3', +}; + export const API_KEY_STANDARD_FIELD_IDS = { name: '20202020-72e6-4079-815b-436ce8a62f23', expiresAt: '20202020-659b-4241-af59-66515b8e7d40', @@ -56,6 +72,15 @@ export const BLOCKLIST_STANDARD_FIELD_IDS = { workspaceMember: '20202020-548d-4084-a947-fa20a39f7c06', }; +export const CHART_STANDARD_FIELD_IDS = { + title: '20202020-e5aa-45b1-aec0-431420660570', + description: '20202020-71b2-4df1-8cb3-120d55272b12', + report: '20202020-ed98-46de-a874-eb65660e3b13', + analyticsQuery: '20202020-13c2-4d66-a631-605c85953648', + analyticsQueryResult: '20202020-4db0-4a48-b742-3da6cfbd146d', + analyticsQueryResultCreatedAt: '20202020-c8fb-437d-9994-1720eac571ec', +}; + export const CALENDAR_CHANNEL_EVENT_ASSOCIATION_STANDARD_FIELD_IDS = { calendarChannel: '20202020-93ee-4da4-8d58-0282c4a9cb7d', calendarEvent: '20202020-5aa5-437e-bb86-f42d457783e3', @@ -279,6 +304,11 @@ export const PERSON_STANDARD_FIELD_IDS = { timelineActivities: '20202020-a43e-4873-9c23-e522de906ce5', }; +export const REPORT_STANDARD_FIELD_IDS = { + title: '20202020-4eb0-4241-bbf8-2a20edb5658d', + charts: '20202020-af27-44f3-9826-5b3ac2266853', +}; + export const VIEW_FIELD_STANDARD_FIELD_IDS = { fieldMetadataId: '20202020-135f-4c5b-b361-15f24870473c', isVisible: '20202020-e966-473c-9c18-f00d3347e0ba', diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids.ts index 19ad7b4e2627..b00ee08c50b4 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids.ts @@ -8,6 +8,8 @@ export const STANDARD_OBJECT_IDS = { activityTarget: '20202020-2945-440e-8d1a-f84672d33d5e', activity: '20202020-39aa-4a89-843b-eb5f2a8b677f', + analyticsQuery: '20202020-6cad-4f2d-8164-d70f000ccbb7', + analyticsQueryFilter: '20202020-2712-40b2-9063-0ab434ed6990', apiKey: '20202020-4c00-401d-8cda-ec6a4c41cd7d', attachment: '20202020-bd3d-4c60-8dca-571c71d4447a', blocklist: '20202020-0408-4f38-b8a8-4d5e3e26e24d', @@ -16,6 +18,7 @@ export const STANDARD_OBJECT_IDS = { calendarChannel: '20202020-e8f2-40e1-a39c-c0e0039c5034', calendarEventParticipant: '20202020-a1c3-47a6-9732-27e5b1e8436d', calendarEvent: '20202020-8f1d-4eef-9f85-0d1965e27221', + chart: '20202020-79fa-42d8-87f0-430e97cdc74d', comment: '20202020-435f-4de9-89b5-97e32233bf5f', company: '20202020-b374-4779-a561-80086cb2e17f', connectedAccount: '20202020-977e-46b2-890b-c3002ddfd5c5', @@ -28,6 +31,7 @@ export const STANDARD_OBJECT_IDS = { message: '20202020-3f6b-4425-80ab-e468899ab4b2', opportunity: '20202020-9549-49dd-b2b2-883999db8938', person: '20202020-e674-48e5-a542-72570eee7213', + report: '20202020-69ea-4d7b-b8a6-b1d57b361ebb', timelineActivity: '20202020-6736-4337-b5c4-8b39fae325a5', viewField: '20202020-4d19-4655-95bf-b2a04cf206d4', viewFilter: '20202020-6fb6-4631-aded-b7d67e952ec8', diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts index 39ef93760e7f..2b501559b99c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts @@ -27,10 +27,16 @@ import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/stan import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity'; import { MessageThreadWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread.workspace-entity'; import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity'; +import { ReportWorkspaceEntity } from 'src/modules/reports/standard-objects/report.workspace-entity'; +import { ChartWorkspaceEntity } from 'src/modules/reports/standard-objects/chart.workspace-entity'; +import { AnalyticsQueryWorkspaceEntity } from 'src/modules/reports/standard-objects/analytics-query.workspace-entity'; +import { AnalyticsQueryFilterWorkspaceEntity } from 'src/modules/reports/standard-objects/analytics-query-filter.workspace-entity'; export const standardObjectMetadataDefinitions = [ ActivityTargetWorkspaceEntity, ActivityWorkspaceEntity, + AnalyticsQueryWorkspaceEntity, + AnalyticsQueryFilterWorkspaceEntity, ApiKeyWorkspaceEntity, AuditLogWorkspaceEntity, AttachmentWorkspaceEntity, @@ -40,12 +46,14 @@ export const standardObjectMetadataDefinitions = [ CalendarChannelWorkspaceEntity, CalendarChannelEventAssociationWorkspaceEntity, CalendarEventParticipantWorkspaceEntity, + ChartWorkspaceEntity, CommentWorkspaceEntity, CompanyWorkspaceEntity, ConnectedAccountWorkspaceEntity, FavoriteWorkspaceEntity, OpportunityWorkspaceEntity, PersonWorkspaceEntity, + ReportWorkspaceEntity, TimelineActivityWorkspaceEntity, ViewFieldWorkspaceEntity, ViewFilterWorkspaceEntity, diff --git a/packages/twenty-server/src/modules/reports/standard-objects/analytics-query-filter.workspace-entity.ts b/packages/twenty-server/src/modules/reports/standard-objects/analytics-query-filter.workspace-entity.ts new file mode 100644 index 000000000000..0693296406e5 --- /dev/null +++ b/packages/twenty-server/src/modules/reports/standard-objects/analytics-query-filter.workspace-entity.ts @@ -0,0 +1,65 @@ +import { Relation } from 'typeorm'; + +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { ANALYTICS_QUERY_FILTER_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'; +import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; +import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; +import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; +import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; +import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; +import { AnalyticsQueryWorkspaceEntity } from 'src/modules/reports/standard-objects/analytics-query.workspace-entity'; +import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; + +@WorkspaceEntity({ + standardId: STANDARD_OBJECT_IDS.analyticsQueryFilter, + namePlural: 'analyticsQueryFilters', + labelSingular: 'Analytics query filter', + labelPlural: 'Analytics query filters', + description: 'A filter for an analytics query', + icon: 'IconFilter', +}) +export class AnalyticsQueryFilterWorkspaceEntity extends BaseWorkspaceEntity { + @WorkspaceRelation({ + standardId: ANALYTICS_QUERY_FILTER_STANDARD_FIELD_IDS.analyticsQuery, + type: RelationMetadataType.MANY_TO_ONE, + label: 'Analytics query', + description: 'Filter field', + icon: 'IconDatabaseSearch', + inverseSideTarget: () => AnalyticsQueryWorkspaceEntity, + inverseSideFieldKey: 'analyticsQueryFilters', + }) + analyticsQuery: Relation; + + @WorkspaceJoinColumn('analyticsQuery') + analyticsQueryId: string; + + @WorkspaceField({ + standardId: ANALYTICS_QUERY_FILTER_STANDARD_FIELD_IDS.field, + type: FieldMetadataType.TEXT, + label: 'Field', + description: 'Filter field', + icon: 'IconForms', + }) + field: string; + + @WorkspaceField({ + standardId: ANALYTICS_QUERY_FILTER_STANDARD_FIELD_IDS.operator, + type: FieldMetadataType.TEXT, + label: 'Operator', + description: 'Filter operator', + icon: 'IconMathFunction', + }) + operator: string; + + @WorkspaceField({ + standardId: ANALYTICS_QUERY_FILTER_STANDARD_FIELD_IDS.value, + type: FieldMetadataType.TEXT, + label: 'Value', + description: 'Filter value', + icon: 'IconInputSearch', + }) + @WorkspaceIsNullable() + value: string; +} diff --git a/packages/twenty-server/src/modules/reports/standard-objects/analytics-query.workspace-entity.ts b/packages/twenty-server/src/modules/reports/standard-objects/analytics-query.workspace-entity.ts new file mode 100644 index 000000000000..be53040a824f --- /dev/null +++ b/packages/twenty-server/src/modules/reports/standard-objects/analytics-query.workspace-entity.ts @@ -0,0 +1,93 @@ +import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; + +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { + RelationMetadataType, + RelationOnDeleteAction, +} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { ANALYTICS_QUERY_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'; +import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; +import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; +import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; +import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; +import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; +import { AnalyticsQueryFilterWorkspaceEntity } from 'src/modules/reports/standard-objects/analytics-query-filter.workspace-entity'; +import { ChartWorkspaceEntity } from 'src/modules/reports/standard-objects/chart.workspace-entity'; +import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; + +@WorkspaceEntity({ + standardId: STANDARD_OBJECT_IDS.analyticsQuery, + namePlural: 'analyticsQueries', + labelSingular: 'Analytics Query', + labelPlural: 'Analytics Queries', + description: 'A query for analytics', + icon: 'IconDatabaseSearch', +}) +export class AnalyticsQueryWorkspaceEntity extends BaseWorkspaceEntity { + @WorkspaceRelation({ + standardId: ANALYTICS_QUERY_STANDARD_FIELD_IDS.chart, + label: 'Chart', + description: 'The chart this analytics query belongs to', + icon: 'IconChartBar', + type: RelationMetadataType.ONE_TO_ONE, + inverseSideTarget: () => ChartWorkspaceEntity, + inverseSideFieldKey: 'analyticsQuery', + onDelete: RelationOnDeleteAction.SET_NULL, + }) + @WorkspaceIsNullable() + chart: Relation; + + @WorkspaceJoinColumn('chart') + chartId: string; + + @WorkspaceField({ + standardId: ANALYTICS_QUERY_STANDARD_FIELD_IDS.measure, + type: FieldMetadataType.TEXT, + label: 'Measure', + description: 'Aggregate function of the analytics query', + icon: 'IconRulerMeasure', + }) + measure: string; + + @WorkspaceField({ + standardId: ANALYTICS_QUERY_STANDARD_FIELD_IDS.sourceObjectNameSingular, + type: FieldMetadataType.TEXT, + label: 'Source object name (singular)', + description: 'Singular name of the source object', + icon: 'IconAbc', + }) + sourceObjectNameSingular: string; + + @WorkspaceField({ + standardId: ANALYTICS_QUERY_STANDARD_FIELD_IDS.field, + type: FieldMetadataType.TEXT, + label: 'Field', + description: 'Field of the source object', + icon: 'IconForms', + }) + field: string; + + @WorkspaceRelation({ + standardId: ANALYTICS_QUERY_STANDARD_FIELD_IDS.analyticsQueryFilters, + label: 'Analytics query filters', + description: 'Filters of the analytics query', + icon: 'IconFilter', + type: RelationMetadataType.ONE_TO_MANY, + inverseSideTarget: () => AnalyticsQueryFilterWorkspaceEntity, + inverseSideFieldKey: 'analyticsQuery', + onDelete: RelationOnDeleteAction.CASCADE, + }) + @WorkspaceIsNullable() + analyticsQueryFilters: Relation; + + @WorkspaceField({ + standardId: ANALYTICS_QUERY_STANDARD_FIELD_IDS.groupBy, + type: FieldMetadataType.TEXT, + label: 'Group By', + description: 'Group by clause for the analytics query', + icon: 'IconStack2', + }) + @WorkspaceIsNullable() + groupBy: string; +} diff --git a/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts b/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts new file mode 100644 index 000000000000..7685ee80f952 --- /dev/null +++ b/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts @@ -0,0 +1,95 @@ +import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; + +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { + RelationMetadataType, + RelationOnDeleteAction, +} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { CHART_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'; +import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; +import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; +import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; +import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; +import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; +import { AnalyticsQueryWorkspaceEntity } from 'src/modules/reports/standard-objects/analytics-query.workspace-entity'; +import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; + +import { ReportWorkspaceEntity } from './report.workspace-entity'; + +@WorkspaceEntity({ + standardId: STANDARD_OBJECT_IDS.chart, + namePlural: 'charts', + labelSingular: 'Chart', + labelPlural: 'Charts', + description: 'A chart for data visualization', + icon: 'IconChartBar', +}) +export class ChartWorkspaceEntity extends BaseWorkspaceEntity { + @WorkspaceField({ + standardId: CHART_STANDARD_FIELD_IDS.title, + type: FieldMetadataType.TEXT, + label: 'Title', + description: 'Chart title', + icon: 'IconNotes', + }) + title: string; + + @WorkspaceField({ + standardId: CHART_STANDARD_FIELD_IDS.description, + type: FieldMetadataType.TEXT, + label: 'Description', + description: 'Chart description', + icon: 'IconNotes', + }) + @WorkspaceIsNullable() + description: string; + + @WorkspaceRelation({ + standardId: CHART_STANDARD_FIELD_IDS.report, + label: 'Report', + description: 'The report the chart belongs to', + icon: 'IconReportAnalytics', + type: RelationMetadataType.MANY_TO_ONE, + inverseSideTarget: () => ReportWorkspaceEntity, + inverseSideFieldKey: 'charts', + onDelete: RelationOnDeleteAction.SET_NULL, + }) + @WorkspaceIsNullable() + report: Relation; + + @WorkspaceJoinColumn('report') + reportId: string; + + @WorkspaceRelation({ + standardId: CHART_STANDARD_FIELD_IDS.analyticsQuery, + label: 'Analytics query', + description: 'Associated analytics query', + icon: 'IconDatabaseSearch', + type: RelationMetadataType.ONE_TO_ONE, + inverseSideTarget: () => AnalyticsQueryWorkspaceEntity, + inverseSideFieldKey: 'chart', + onDelete: RelationOnDeleteAction.CASCADE, + }) + analyticsQuery: Relation; + + @WorkspaceField({ + standardId: CHART_STANDARD_FIELD_IDS.analyticsQueryResult, + type: FieldMetadataType.RAW_JSON, + label: 'Analytics query result', + description: 'Result of the analytics query', + icon: 'IconTable', + }) + @WorkspaceIsNullable() + analyticsQueryResult: object; + + @WorkspaceField({ + standardId: CHART_STANDARD_FIELD_IDS.analyticsQueryResultCreatedAt, + type: FieldMetadataType.DATE_TIME, + label: 'Analytics query result created at', + description: 'Timestamp of when the analytics query was successfully run', + icon: 'IconCalendarTime', + }) + @WorkspaceIsNullable() + analyticsQueryResultCreatedAt: Date; +} diff --git a/packages/twenty-server/src/modules/reports/standard-objects/report.workspace-entity.ts b/packages/twenty-server/src/modules/reports/standard-objects/report.workspace-entity.ts new file mode 100644 index 000000000000..68e282aa824d --- /dev/null +++ b/packages/twenty-server/src/modules/reports/standard-objects/report.workspace-entity.ts @@ -0,0 +1,49 @@ +import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; + +import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { + RelationMetadataType, + RelationOnDeleteAction, +} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { REPORT_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'; +import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; +import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; +import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; +import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; +import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; +import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; +import { ChartWorkspaceEntity } from 'src/modules/reports/standard-objects/chart.workspace-entity'; + +@WorkspaceEntity({ + standardId: STANDARD_OBJECT_IDS.chart, + namePlural: 'reports', + labelSingular: 'Report', + labelPlural: 'Reports', + description: 'A report', + icon: 'IconReportAnalytics', +}) +@WorkspaceIsSystem() +export class ReportWorkspaceEntity extends BaseWorkspaceEntity { + @WorkspaceField({ + standardId: REPORT_STANDARD_FIELD_IDS.title, + type: FieldMetadataType.TEXT, + label: 'Title', + description: 'Report title', + icon: 'IconNotes', + }) + title: string; + + @WorkspaceRelation({ + standardId: REPORT_STANDARD_FIELD_IDS.charts, + label: 'Charts', + description: 'Report charts', + icon: 'IconGraph', + type: RelationMetadataType.ONE_TO_MANY, + inverseSideTarget: () => ChartWorkspaceEntity, + inverseSideFieldKey: 'report', + onDelete: RelationOnDeleteAction.CASCADE, + }) + @WorkspaceIsNullable() + charts: Relation; +} diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts index 619c8481d5e8..f3674ac4bc93 100644 --- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts +++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts @@ -66,6 +66,7 @@ export { IconCurrencyYen, IconCurrencyYuan, IconDatabase, + IconDatabaseSearch, IconDeviceFloppy, IconDoorEnter, IconDotsVertical, @@ -84,6 +85,8 @@ export { IconFilterOff, IconFocusCentered, IconForbid, + IconForms, + IconGraph, IconGripVertical, IconH1, IconH2, @@ -113,6 +116,7 @@ export { IconMail, IconMailCog, IconMap, + IconMathFunction, IconMaximize, IconMinus, IconMoneybag, @@ -138,11 +142,13 @@ export { IconRepeat, IconReportAnalytics, IconRocket, + IconRulerMeasure, IconSearch, IconSend, IconSettings, IconSortDescending, IconSquareRoundedCheck, + IconStack2, IconTable, IconTag, IconTags, From 0b51b485215a6ade64e2aa2b519b10614af6cfa3 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Fri, 5 Jul 2024 13:31:02 +0200 Subject: [PATCH 03/63] Disable chart entity to make workspace:sync-metadata work --- .../constants/standard-field-ids.ts | 6 ++-- .../standard-objects/index.ts | 3 +- .../analytics-query.workspace-entity.ts | 30 +++++++++++++++--- ....ts => chart.workspace-entity-disabled.ts} | 31 ++++--------------- .../report.workspace-entity.ts | 14 ++------- 5 files changed, 38 insertions(+), 46 deletions(-) rename packages/twenty-server/src/modules/reports/standard-objects/{chart.workspace-entity.ts => chart.workspace-entity-disabled.ts} (75%) diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts index dced1264956e..66f20585f2de 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts @@ -34,6 +34,8 @@ export const ANALYTICS_QUERY_STANDARD_FIELD_IDS = { field: '20202020-5206-419e-9df6-d8f8f75e75e9', analyticsQueryFilters: '20202020-2229-4c35-9f3b-9e1199ec3c7c', groupBy: '20202020-4a7b-4021-b1d4-6e6f282cedee', + result: '20202020-4db0-4a48-b742-3da6cfbd146d', + resultCreatedAt: '20202020-c8fb-437d-9994-1720eac571ec', }; export const ANALYTICS_QUERY_FILTER_STANDARD_FIELD_IDS = { @@ -76,9 +78,7 @@ export const CHART_STANDARD_FIELD_IDS = { title: '20202020-e5aa-45b1-aec0-431420660570', description: '20202020-71b2-4df1-8cb3-120d55272b12', report: '20202020-ed98-46de-a874-eb65660e3b13', - analyticsQuery: '20202020-13c2-4d66-a631-605c85953648', - analyticsQueryResult: '20202020-4db0-4a48-b742-3da6cfbd146d', - analyticsQueryResultCreatedAt: '20202020-c8fb-437d-9994-1720eac571ec', + analyticsQueries: '20202020-13c2-4d66-a631-605c85953648', }; export const CALENDAR_CHANNEL_EVENT_ASSOCIATION_STANDARD_FIELD_IDS = { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts index 2b501559b99c..c330ddac4f4e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts @@ -28,7 +28,6 @@ import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/ import { MessageThreadWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread.workspace-entity'; import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity'; import { ReportWorkspaceEntity } from 'src/modules/reports/standard-objects/report.workspace-entity'; -import { ChartWorkspaceEntity } from 'src/modules/reports/standard-objects/chart.workspace-entity'; import { AnalyticsQueryWorkspaceEntity } from 'src/modules/reports/standard-objects/analytics-query.workspace-entity'; import { AnalyticsQueryFilterWorkspaceEntity } from 'src/modules/reports/standard-objects/analytics-query-filter.workspace-entity'; @@ -46,7 +45,7 @@ export const standardObjectMetadataDefinitions = [ CalendarChannelWorkspaceEntity, CalendarChannelEventAssociationWorkspaceEntity, CalendarEventParticipantWorkspaceEntity, - ChartWorkspaceEntity, + // ChartWorkspaceEntity, CommentWorkspaceEntity, CompanyWorkspaceEntity, ConnectedAccountWorkspaceEntity, diff --git a/packages/twenty-server/src/modules/reports/standard-objects/analytics-query.workspace-entity.ts b/packages/twenty-server/src/modules/reports/standard-objects/analytics-query.workspace-entity.ts index be53040a824f..e9ca9f902120 100644 --- a/packages/twenty-server/src/modules/reports/standard-objects/analytics-query.workspace-entity.ts +++ b/packages/twenty-server/src/modules/reports/standard-objects/analytics-query.workspace-entity.ts @@ -13,7 +13,7 @@ import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-re import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { AnalyticsQueryFilterWorkspaceEntity } from 'src/modules/reports/standard-objects/analytics-query-filter.workspace-entity'; -import { ChartWorkspaceEntity } from 'src/modules/reports/standard-objects/chart.workspace-entity'; +// import { ChartWorkspaceEntity } from 'src/modules/reports/standard-objects/chart.workspace-entity'; import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; @WorkspaceEntity({ @@ -25,18 +25,18 @@ import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace- icon: 'IconDatabaseSearch', }) export class AnalyticsQueryWorkspaceEntity extends BaseWorkspaceEntity { - @WorkspaceRelation({ + /* @WorkspaceRelation({ standardId: ANALYTICS_QUERY_STANDARD_FIELD_IDS.chart, label: 'Chart', description: 'The chart this analytics query belongs to', icon: 'IconChartBar', - type: RelationMetadataType.ONE_TO_ONE, + type: RelationMetadataType.MANY_TO_ONE, inverseSideTarget: () => ChartWorkspaceEntity, - inverseSideFieldKey: 'analyticsQuery', + inverseSideFieldKey: 'analyticsQueries', onDelete: RelationOnDeleteAction.SET_NULL, }) @WorkspaceIsNullable() - chart: Relation; + chart: Relation; */ @WorkspaceJoinColumn('chart') chartId: string; @@ -90,4 +90,24 @@ export class AnalyticsQueryWorkspaceEntity extends BaseWorkspaceEntity { }) @WorkspaceIsNullable() groupBy: string; + + @WorkspaceField({ + standardId: ANALYTICS_QUERY_STANDARD_FIELD_IDS.result, + type: FieldMetadataType.RAW_JSON, + label: 'Query result', + description: 'Result of the analytics query', + icon: 'IconTable', + }) + @WorkspaceIsNullable() + analyticsQueryResult: object; + + @WorkspaceField({ + standardId: ANALYTICS_QUERY_STANDARD_FIELD_IDS.resultCreatedAt, + type: FieldMetadataType.DATE_TIME, + label: 'Query result created at', + description: 'Timestamp of when the analytics query was successfully run', + icon: 'IconCalendarTime', + }) + @WorkspaceIsNullable() + analyticsQueryResultCreatedAt: Date; } diff --git a/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts b/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity-disabled.ts similarity index 75% rename from packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts rename to packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity-disabled.ts index 7685ee80f952..cc89914de9fa 100644 --- a/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts +++ b/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity-disabled.ts @@ -56,40 +56,21 @@ export class ChartWorkspaceEntity extends BaseWorkspaceEntity { onDelete: RelationOnDeleteAction.SET_NULL, }) @WorkspaceIsNullable() - report: Relation; + report: Relation | null; @WorkspaceJoinColumn('report') reportId: string; @WorkspaceRelation({ - standardId: CHART_STANDARD_FIELD_IDS.analyticsQuery, - label: 'Analytics query', - description: 'Associated analytics query', + standardId: CHART_STANDARD_FIELD_IDS.analyticsQueries, + label: 'Analytics queries', + description: 'Associated analytics queries', icon: 'IconDatabaseSearch', - type: RelationMetadataType.ONE_TO_ONE, + type: RelationMetadataType.ONE_TO_MANY, // TODO: Change to RelationMetadataType.ONE_TO_ONE inverseSideTarget: () => AnalyticsQueryWorkspaceEntity, inverseSideFieldKey: 'chart', onDelete: RelationOnDeleteAction.CASCADE, }) - analyticsQuery: Relation; - - @WorkspaceField({ - standardId: CHART_STANDARD_FIELD_IDS.analyticsQueryResult, - type: FieldMetadataType.RAW_JSON, - label: 'Analytics query result', - description: 'Result of the analytics query', - icon: 'IconTable', - }) - @WorkspaceIsNullable() - analyticsQueryResult: object; - - @WorkspaceField({ - standardId: CHART_STANDARD_FIELD_IDS.analyticsQueryResultCreatedAt, - type: FieldMetadataType.DATE_TIME, - label: 'Analytics query result created at', - description: 'Timestamp of when the analytics query was successfully run', - icon: 'IconCalendarTime', - }) @WorkspaceIsNullable() - analyticsQueryResultCreatedAt: Date; + analyticsQueries: Relation; } diff --git a/packages/twenty-server/src/modules/reports/standard-objects/report.workspace-entity.ts b/packages/twenty-server/src/modules/reports/standard-objects/report.workspace-entity.ts index 68e282aa824d..b9a86c2539a8 100644 --- a/packages/twenty-server/src/modules/reports/standard-objects/report.workspace-entity.ts +++ b/packages/twenty-server/src/modules/reports/standard-objects/report.workspace-entity.ts @@ -1,19 +1,11 @@ -import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; - import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { - RelationMetadataType, - RelationOnDeleteAction, -} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { REPORT_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'; import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; -import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; -import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; -import { ChartWorkspaceEntity } from 'src/modules/reports/standard-objects/chart.workspace-entity'; +// import { ChartWorkspaceEntity } from 'src/modules/reports/standard-objects/chart.workspace-entity'; @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.chart, @@ -34,7 +26,7 @@ export class ReportWorkspaceEntity extends BaseWorkspaceEntity { }) title: string; - @WorkspaceRelation({ + /* @WorkspaceRelation({ standardId: REPORT_STANDARD_FIELD_IDS.charts, label: 'Charts', description: 'Report charts', @@ -45,5 +37,5 @@ export class ReportWorkspaceEntity extends BaseWorkspaceEntity { onDelete: RelationOnDeleteAction.CASCADE, }) @WorkspaceIsNullable() - charts: Relation; + charts: Relation; */ } From 80ae1676d6c936d7961479bff8209aaed22b025b Mon Sep 17 00:00:00 2001 From: ad-elias Date: Fri, 5 Jul 2024 13:38:32 +0200 Subject: [PATCH 04/63] Add chart entity --- .../standard-objects/index.ts | 3 ++- ...-disabled.ts => chart.workspace-entity.ts} | 19 ++++--------------- 2 files changed, 6 insertions(+), 16 deletions(-) rename packages/twenty-server/src/modules/reports/standard-objects/{chart.workspace-entity-disabled.ts => chart.workspace-entity.ts} (75%) diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts index c330ddac4f4e..7625021a8c4f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-objects/index.ts @@ -30,6 +30,7 @@ import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-ob import { ReportWorkspaceEntity } from 'src/modules/reports/standard-objects/report.workspace-entity'; import { AnalyticsQueryWorkspaceEntity } from 'src/modules/reports/standard-objects/analytics-query.workspace-entity'; import { AnalyticsQueryFilterWorkspaceEntity } from 'src/modules/reports/standard-objects/analytics-query-filter.workspace-entity'; +import { ChartWorkspaceEntity } from 'src/modules/reports/standard-objects/chart.workspace-entity'; export const standardObjectMetadataDefinitions = [ ActivityTargetWorkspaceEntity, @@ -45,7 +46,7 @@ export const standardObjectMetadataDefinitions = [ CalendarChannelWorkspaceEntity, CalendarChannelEventAssociationWorkspaceEntity, CalendarEventParticipantWorkspaceEntity, - // ChartWorkspaceEntity, + ChartWorkspaceEntity, CommentWorkspaceEntity, CompanyWorkspaceEntity, ConnectedAccountWorkspaceEntity, diff --git a/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity-disabled.ts b/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts similarity index 75% rename from packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity-disabled.ts rename to packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts index cc89914de9fa..3b10ef6cc2bd 100644 --- a/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity-disabled.ts +++ b/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts @@ -1,21 +1,10 @@ -import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface'; - import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { - RelationMetadataType, - RelationOnDeleteAction, -} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { CHART_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'; import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; -import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; -import { AnalyticsQueryWorkspaceEntity } from 'src/modules/reports/standard-objects/analytics-query.workspace-entity'; -import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; - -import { ReportWorkspaceEntity } from './report.workspace-entity'; @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.chart, @@ -45,7 +34,7 @@ export class ChartWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsNullable() description: string; - @WorkspaceRelation({ + /* @WorkspaceRelation({ standardId: CHART_STANDARD_FIELD_IDS.report, label: 'Report', description: 'The report the chart belongs to', @@ -59,9 +48,9 @@ export class ChartWorkspaceEntity extends BaseWorkspaceEntity { report: Relation | null; @WorkspaceJoinColumn('report') - reportId: string; + reportId: string; */ - @WorkspaceRelation({ + /* @WorkspaceRelation({ standardId: CHART_STANDARD_FIELD_IDS.analyticsQueries, label: 'Analytics queries', description: 'Associated analytics queries', @@ -72,5 +61,5 @@ export class ChartWorkspaceEntity extends BaseWorkspaceEntity { onDelete: RelationOnDeleteAction.CASCADE, }) @WorkspaceIsNullable() - analyticsQueries: Relation; + analyticsQueries: Relation; */ } From ee43744d53feb576dd1e507e3c745a886adb2c1d Mon Sep 17 00:00:00 2001 From: ad-elias Date: Fri, 5 Jul 2024 13:54:52 +0200 Subject: [PATCH 05/63] Fix report standardId --- .../standard-objects/chart.workspace-entity.ts | 13 +++++++++++-- .../standard-objects/report.workspace-entity.ts | 16 ++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts b/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts index 3b10ef6cc2bd..5e66a52393fb 100644 --- a/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts +++ b/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts @@ -1,3 +1,5 @@ +import { Relation } from 'typeorm'; + import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { CHART_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'; @@ -5,6 +7,13 @@ import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-enti import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; +import { + RelationMetadataType, + RelationOnDeleteAction, +} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; +import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; +import { ReportWorkspaceEntity } from 'src/modules/reports/standard-objects/report.workspace-entity'; @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.chart, @@ -34,7 +43,7 @@ export class ChartWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceIsNullable() description: string; - /* @WorkspaceRelation({ + @WorkspaceRelation({ standardId: CHART_STANDARD_FIELD_IDS.report, label: 'Report', description: 'The report the chart belongs to', @@ -48,7 +57,7 @@ export class ChartWorkspaceEntity extends BaseWorkspaceEntity { report: Relation | null; @WorkspaceJoinColumn('report') - reportId: string; */ + reportId: string; /* @WorkspaceRelation({ standardId: CHART_STANDARD_FIELD_IDS.analyticsQueries, diff --git a/packages/twenty-server/src/modules/reports/standard-objects/report.workspace-entity.ts b/packages/twenty-server/src/modules/reports/standard-objects/report.workspace-entity.ts index b9a86c2539a8..3241b695562e 100644 --- a/packages/twenty-server/src/modules/reports/standard-objects/report.workspace-entity.ts +++ b/packages/twenty-server/src/modules/reports/standard-objects/report.workspace-entity.ts @@ -1,3 +1,5 @@ +import { Relation } from 'typeorm'; + import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { REPORT_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'; @@ -5,10 +7,16 @@ import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-enti import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator'; import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; -// import { ChartWorkspaceEntity } from 'src/modules/reports/standard-objects/chart.workspace-entity'; +import { + RelationMetadataType, + RelationOnDeleteAction, +} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; +import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; +import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; +import { ChartWorkspaceEntity } from 'src/modules/reports/standard-objects/chart.workspace-entity'; @WorkspaceEntity({ - standardId: STANDARD_OBJECT_IDS.chart, + standardId: STANDARD_OBJECT_IDS.report, namePlural: 'reports', labelSingular: 'Report', labelPlural: 'Reports', @@ -26,7 +34,7 @@ export class ReportWorkspaceEntity extends BaseWorkspaceEntity { }) title: string; - /* @WorkspaceRelation({ + @WorkspaceRelation({ standardId: REPORT_STANDARD_FIELD_IDS.charts, label: 'Charts', description: 'Report charts', @@ -37,5 +45,5 @@ export class ReportWorkspaceEntity extends BaseWorkspaceEntity { onDelete: RelationOnDeleteAction.CASCADE, }) @WorkspaceIsNullable() - charts: Relation; */ + charts: Relation; } From 388a062fd367bf8ecfcce9ac6d0f6768b66633eb Mon Sep 17 00:00:00 2001 From: ad-elias Date: Fri, 5 Jul 2024 16:59:07 +0200 Subject: [PATCH 06/63] Add relation between chart and analyticsQuery --- .../standard-objects/analytics-query.workspace-entity.ts | 8 ++++---- .../reports/standard-objects/chart.workspace-entity.ts | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/twenty-server/src/modules/reports/standard-objects/analytics-query.workspace-entity.ts b/packages/twenty-server/src/modules/reports/standard-objects/analytics-query.workspace-entity.ts index e9ca9f902120..57c9b3f03953 100644 --- a/packages/twenty-server/src/modules/reports/standard-objects/analytics-query.workspace-entity.ts +++ b/packages/twenty-server/src/modules/reports/standard-objects/analytics-query.workspace-entity.ts @@ -13,7 +13,7 @@ import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-re import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { AnalyticsQueryFilterWorkspaceEntity } from 'src/modules/reports/standard-objects/analytics-query-filter.workspace-entity'; -// import { ChartWorkspaceEntity } from 'src/modules/reports/standard-objects/chart.workspace-entity'; +import { ChartWorkspaceEntity } from 'src/modules/reports/standard-objects/chart.workspace-entity'; import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; @WorkspaceEntity({ @@ -25,7 +25,7 @@ import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace- icon: 'IconDatabaseSearch', }) export class AnalyticsQueryWorkspaceEntity extends BaseWorkspaceEntity { - /* @WorkspaceRelation({ + @WorkspaceRelation({ standardId: ANALYTICS_QUERY_STANDARD_FIELD_IDS.chart, label: 'Chart', description: 'The chart this analytics query belongs to', @@ -36,10 +36,10 @@ export class AnalyticsQueryWorkspaceEntity extends BaseWorkspaceEntity { onDelete: RelationOnDeleteAction.SET_NULL, }) @WorkspaceIsNullable() - chart: Relation; */ + chart: Relation | null; @WorkspaceJoinColumn('chart') - chartId: string; + chartId: string | null; @WorkspaceField({ standardId: ANALYTICS_QUERY_STANDARD_FIELD_IDS.measure, diff --git a/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts b/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts index 5e66a52393fb..2885be1b26ea 100644 --- a/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts +++ b/packages/twenty-server/src/modules/reports/standard-objects/chart.workspace-entity.ts @@ -14,6 +14,7 @@ import { import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator'; import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator'; import { ReportWorkspaceEntity } from 'src/modules/reports/standard-objects/report.workspace-entity'; +import { AnalyticsQueryWorkspaceEntity } from 'src/modules/reports/standard-objects/analytics-query.workspace-entity'; @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.chart, @@ -59,16 +60,16 @@ export class ChartWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceJoinColumn('report') reportId: string; - /* @WorkspaceRelation({ + @WorkspaceRelation({ standardId: CHART_STANDARD_FIELD_IDS.analyticsQueries, label: 'Analytics queries', description: 'Associated analytics queries', icon: 'IconDatabaseSearch', - type: RelationMetadataType.ONE_TO_MANY, // TODO: Change to RelationMetadataType.ONE_TO_ONE + type: RelationMetadataType.ONE_TO_MANY, // TODO: Change to RelationMetadataType.ONE_TO_ONE and figure out why workspace:sync-metadata command fails inverseSideTarget: () => AnalyticsQueryWorkspaceEntity, inverseSideFieldKey: 'chart', onDelete: RelationOnDeleteAction.CASCADE, }) @WorkspaceIsNullable() - analyticsQueries: Relation; */ + analyticsQueries: Relation; } From 0c6e7a40c7b43d688c1851bf814eca65d83167aa Mon Sep 17 00:00:00 2001 From: ad-elias Date: Sat, 6 Jul 2024 20:12:14 +0200 Subject: [PATCH 07/63] Add create report button and load reports --- packages/twenty-front/src/App.tsx | 2 ++ .../activities/reports/components/Charts.tsx | 1 + .../components/PageAddReportButton.tsx | 28 +++++++++++++++++ .../reports/components/ReportGroup.tsx | 4 --- .../reports/components/ReportGroups.tsx | 13 ++++++++ ...eportGroups.ts => ReportGroupTimeSpans.ts} | 7 +++-- .../reports/types/AnalyticsQuery.ts | 18 +++++++++++ .../reports/types/AnalyticsQueryFilter.ts | 31 +++++++++++++++++++ .../modules/activities/reports/types/Chart.ts | 11 +++++++ .../activities/reports/types/Report.ts | 9 ++++++ .../activities/reports/types/ReportGroup.ts | 4 --- .../types/CoreObjectNameSingular.ts | 4 +++ .../twenty-front/src/modules/types/AppPath.ts | 2 ++ .../src/pages/reports/Reports.tsx | 18 ++++++----- .../sync-workspace-metadata.command.ts | 4 ++- ...orkspace-sync-relation-metadata.service.ts | 9 ++++++ 16 files changed, 147 insertions(+), 18 deletions(-) create mode 100644 packages/twenty-front/src/modules/activities/reports/components/Charts.tsx create mode 100644 packages/twenty-front/src/modules/activities/reports/components/PageAddReportButton.tsx delete mode 100644 packages/twenty-front/src/modules/activities/reports/components/ReportGroup.tsx create mode 100644 packages/twenty-front/src/modules/activities/reports/components/ReportGroups.tsx rename packages/twenty-front/src/modules/activities/reports/constants/{ReportGroups.ts => ReportGroupTimeSpans.ts} (82%) create mode 100644 packages/twenty-front/src/modules/activities/reports/types/AnalyticsQuery.ts create mode 100644 packages/twenty-front/src/modules/activities/reports/types/AnalyticsQueryFilter.ts create mode 100644 packages/twenty-front/src/modules/activities/reports/types/Chart.ts create mode 100644 packages/twenty-front/src/modules/activities/reports/types/Report.ts delete mode 100644 packages/twenty-front/src/modules/activities/reports/types/ReportGroup.ts diff --git a/packages/twenty-front/src/App.tsx b/packages/twenty-front/src/App.tsx index 8c0562531a0d..3a3d18fb6af1 100644 --- a/packages/twenty-front/src/App.tsx +++ b/packages/twenty-front/src/App.tsx @@ -11,6 +11,7 @@ import { } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; +import { Charts } from '@/activities/reports/components/Charts'; import { ApolloProvider } from '@/apollo/components/ApolloProvider'; import { VerifyEffect } from '@/auth/components/VerifyEffect'; import { ChromeExtensionSidecarEffect } from '@/chrome-extension-sidecar/components/ChromeExtensionSidecarEffect'; @@ -154,6 +155,7 @@ const createRouter = (isBillingEnabled?: boolean) => } /> } /> } /> + } /> } /> } /> } /> diff --git a/packages/twenty-front/src/modules/activities/reports/components/Charts.tsx b/packages/twenty-front/src/modules/activities/reports/components/Charts.tsx new file mode 100644 index 000000000000..2da584ff0f0d --- /dev/null +++ b/packages/twenty-front/src/modules/activities/reports/components/Charts.tsx @@ -0,0 +1 @@ +export const Charts = () =>
Charts
; diff --git a/packages/twenty-front/src/modules/activities/reports/components/PageAddReportButton.tsx b/packages/twenty-front/src/modules/activities/reports/components/PageAddReportButton.tsx new file mode 100644 index 000000000000..6832b55dac8c --- /dev/null +++ b/packages/twenty-front/src/modules/activities/reports/components/PageAddReportButton.tsx @@ -0,0 +1,28 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { Report } from '@/activities/reports/types/Report'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; +import { PageAddButton } from '@/ui/layout/page/PageAddButton'; + +export const PageAddReportButton = () => { + const [creating, setCreating] = useState(false); + const navigate = useNavigate(); + + const { createOneRecord: createOneReport } = useCreateOneRecord({ + objectNameSingular: CoreObjectNameSingular.Report, + }); + + return ( + { + if (creating) return; + setCreating(true); + const report = await createOneReport({ title: 'New Report' }); + await navigate(`/reports/${report.id}/charts`); + setCreating(false); + }} + /> + ); +}; diff --git a/packages/twenty-front/src/modules/activities/reports/components/ReportGroup.tsx b/packages/twenty-front/src/modules/activities/reports/components/ReportGroup.tsx deleted file mode 100644 index 735b853479c7..000000000000 --- a/packages/twenty-front/src/modules/activities/reports/components/ReportGroup.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import React from 'react'; - -// TODO: Implement based on TaskGroups.tsx -export const ReportGroup = () =>
; diff --git a/packages/twenty-front/src/modules/activities/reports/components/ReportGroups.tsx b/packages/twenty-front/src/modules/activities/reports/components/ReportGroups.tsx new file mode 100644 index 000000000000..9d9cd58682c6 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/reports/components/ReportGroups.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; + +// TODO: Implement based on TaskGroups.tsx +export const ReportGroups = () => { + const { records } = useFindManyRecords({ + objectNameSingular: CoreObjectNameSingular.Report, + }); + + return
{JSON.stringify(records)}
; +}; diff --git a/packages/twenty-front/src/modules/activities/reports/constants/ReportGroups.ts b/packages/twenty-front/src/modules/activities/reports/constants/ReportGroupTimeSpans.ts similarity index 82% rename from packages/twenty-front/src/modules/activities/reports/constants/ReportGroups.ts rename to packages/twenty-front/src/modules/activities/reports/constants/ReportGroupTimeSpans.ts index c19bb7ebc8fc..9bf78c08b7fe 100644 --- a/packages/twenty-front/src/modules/activities/reports/constants/ReportGroups.ts +++ b/packages/twenty-front/src/modules/activities/reports/constants/ReportGroupTimeSpans.ts @@ -1,6 +1,9 @@ -import { ReportGroup } from '@/activities/reports/types/ReportGroup'; +interface ReportGroupTimeSpan { + name: string; + minSinceMs: number; +} -export const REPORT_GROUPS: ReportGroup[] = [ +export const REPORT_GROUP_TIME_SPANS: ReportGroupTimeSpan[] = [ { name: 'today', minSinceMs: 0, diff --git a/packages/twenty-front/src/modules/activities/reports/types/AnalyticsQuery.ts b/packages/twenty-front/src/modules/activities/reports/types/AnalyticsQuery.ts new file mode 100644 index 000000000000..80997daa33fa --- /dev/null +++ b/packages/twenty-front/src/modules/activities/reports/types/AnalyticsQuery.ts @@ -0,0 +1,18 @@ +import { AnalyticsQueryFilter } from '@/activities/reports/types/AnalyticsQueryFilter'; +import { Chart } from '@/activities/reports/types/Chart'; + +export interface AnalyticsQuery { + id: string; + chart: Chart; + chartId: string; + measure: AnalyticsQueryMeasure; + sourceObjectNameSingular: string; + field: string; + analyticsQueryFilters: AnalyticsQueryFilter[]; + groupBy: string; + analyticsQueryResult: any; // TODO + analyticsQueryResultCreatedAt: string; + __typename: string; +} + +export type AnalyticsQueryMeasure = 'average' | 'sum' | 'min' | 'max' | 'count'; diff --git a/packages/twenty-front/src/modules/activities/reports/types/AnalyticsQueryFilter.ts b/packages/twenty-front/src/modules/activities/reports/types/AnalyticsQueryFilter.ts new file mode 100644 index 000000000000..4c3510b98ebf --- /dev/null +++ b/packages/twenty-front/src/modules/activities/reports/types/AnalyticsQueryFilter.ts @@ -0,0 +1,31 @@ +import { AnalyticsQuery } from '@/activities/reports/types/AnalyticsQuery'; + +export interface AnalyticsQueryFilter { + id: string; + analyticsQuery: AnalyticsQuery; + analyticsQueryId: string; + field: string; + operator: AnalyticsQueryFilterOperator; + value: string; + __typename: string; +} + +type NumberOperator = + | 'is' + | 'is not' + | 'less than' + | 'greater than' + | 'empty' + | 'not empty'; + +type StringOperator = + | 'contains' + | 'not contains' + | 'starts with' + | 'end with' + | 'is' + | 'is not' + | 'empty' + | 'not empty'; + +export type AnalyticsQueryFilterOperator = NumberOperator | StringOperator; diff --git a/packages/twenty-front/src/modules/activities/reports/types/Chart.ts b/packages/twenty-front/src/modules/activities/reports/types/Chart.ts new file mode 100644 index 000000000000..a9c9f07b743f --- /dev/null +++ b/packages/twenty-front/src/modules/activities/reports/types/Chart.ts @@ -0,0 +1,11 @@ +import { AnalyticsQuery } from '@/activities/reports/types/AnalyticsQuery'; + +export interface Chart { + id: string; + title: string; + description: string; + report: Report; + reportId: string; + analyticsQueries: AnalyticsQuery[]; + __typename: string; +} diff --git a/packages/twenty-front/src/modules/activities/reports/types/Report.ts b/packages/twenty-front/src/modules/activities/reports/types/Report.ts new file mode 100644 index 000000000000..9b5ca251cfe3 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/reports/types/Report.ts @@ -0,0 +1,9 @@ +import { Chart } from '@/activities/reports/types/Chart'; + +export interface Report { + id: string; + title: string; + createdAt: string; + charts: Chart[]; // TODO + __typename: string; +} diff --git a/packages/twenty-front/src/modules/activities/reports/types/ReportGroup.ts b/packages/twenty-front/src/modules/activities/reports/types/ReportGroup.ts deleted file mode 100644 index 647bec856b06..000000000000 --- a/packages/twenty-front/src/modules/activities/reports/types/ReportGroup.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface ReportGroup { - name: string; - minSinceMs: number; -} diff --git a/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts b/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts index 5fafc5c6465a..71ac71e324ce 100644 --- a/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts +++ b/packages/twenty-front/src/modules/object-metadata/types/CoreObjectNameSingular.ts @@ -1,11 +1,14 @@ export enum CoreObjectNameSingular { Activity = 'activity', ActivityTarget = 'activityTarget', + AnalyticsQuery = 'analyticsQuery', + AnalyticsQueryFilter = 'analyticsQueryFilter', ApiKey = 'apiKey', Attachment = 'attachment', Blocklist = 'blocklist', CalendarChannel = 'calendarChannel', CalendarEvent = 'calendarEvent', + Chart = 'chart', Comment = 'comment', Company = 'company', ConnectedAccount = 'connectedAccount', @@ -17,6 +20,7 @@ export enum CoreObjectNameSingular { MessageThread = 'messageThread', Opportunity = 'opportunity', Person = 'person', + Report = 'report', View = 'view', ViewField = 'viewField', ViewFilter = 'viewFilter', diff --git a/packages/twenty-front/src/modules/types/AppPath.ts b/packages/twenty-front/src/modules/types/AppPath.ts index 36c586a31164..aac20cfd78a2 100644 --- a/packages/twenty-front/src/modules/types/AppPath.ts +++ b/packages/twenty-front/src/modules/types/AppPath.ts @@ -17,6 +17,8 @@ export enum AppPath { Index = '/', TasksPage = '/tasks', ReportsPage = '/reports', + ChartsPage = '/reports/:reportId/charts', + ChartPage = '/reports/:reportId/charts/:chartId', OpportunitiesPage = '/objects/opportunities', RecordIndexPage = '/objects/:objectNamePlural', diff --git a/packages/twenty-front/src/pages/reports/Reports.tsx b/packages/twenty-front/src/pages/reports/Reports.tsx index 67cdb6a5cb7a..0f8f37acbdec 100644 --- a/packages/twenty-front/src/pages/reports/Reports.tsx +++ b/packages/twenty-front/src/pages/reports/Reports.tsx @@ -1,13 +1,16 @@ import styled from '@emotion/styled'; import { IconReportAnalytics } from 'twenty-ui'; -import { REPORT_GROUPS } from '@/activities/reports/constants/ReportGroups'; +import { PageAddReportButton } from '@/activities/reports/components/PageAddReportButton'; +import { REPORT_GROUP_TIME_SPANS } from '@/activities/reports/constants/ReportGroupTimeSpans'; // import { PageAddReportButton } from '@/activities/reports/components/PageAddReportButton'; import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; import { PageBody } from '@/ui/layout/page/PageBody'; import { PageContainer } from '@/ui/layout/page/PageContainer'; import { PageHeader } from '@/ui/layout/page/PageHeader'; +import { ReportGroups } from '../../modules/activities/reports/components/ReportGroups'; + const StyledReportsContainer = styled.div` display: flex; flex: 1; @@ -17,26 +20,27 @@ const StyledReportsContainer = styled.div` `; export const Reports = () => { - const groupedReports: { groupName: string; reports: any[] }[] = - REPORT_GROUPS.map((groupedReport) => { + const reportGroups: { groupName: string; reports: any[] }[] = + REPORT_GROUP_TIME_SPANS.map((reportGroupTimeSpan) => { // TODO return { - groupName: groupedReport.name, + groupName: reportGroupTimeSpan.name, reports: [], // TODO }; - }).filter((groupedReport) => groupedReport.reports.length); + }).filter((reportGroup) => reportGroup.reports.length); return ( - {/* */} + - {groupedReports.map((groupedReport) => ( + {reportGroups.map((groupedReport) => (
{groupedReport.groupName}
))} +
diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts index 106a0d876045..4302f9c994d4 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command.ts @@ -47,7 +47,9 @@ export class SyncWorkspaceMetadataCommand extends CommandRunner { if (issues.length > 0) { if (!options.force) { this.logger.error( - `Workspace contains ${issues.length} issues, aborting.`, + `Workspace contains ${ + issues.length + } issues, aborting. ${JSON.stringify(issues, undefined, 2)}`, ); this.logger.log( diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-relation-metadata.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-relation-metadata.service.ts index 86d46f01a853..a066cc698d37 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-relation-metadata.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-relation-metadata.service.ts @@ -75,6 +75,15 @@ export class WorkspaceSyncRelationMetadataService { }, }); + console.log( + 'bbbbbbbbb', + JSON.stringify( + originalObjectMetadataCollection.map((o) => o.nameSingular), + undefined, + 2, + ), // Chart is missing ??? + ); + // Create standard relation metadata collection const standardRelationMetadataCollection = this.standardRelationFactory.create( From 398598c84bb00ac0cacf85ab1f36776b64415ba3 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Sun, 7 Jul 2024 01:02:39 +0200 Subject: [PATCH 08/63] Draft reports UI --- packages/twenty-front/src/App.tsx | 4 +- .../components/AnalyticsQueryEditor.tsx | 1 + .../activities/reports/components/Chart.tsx | 13 ++++ .../activities/reports/components/Charts.tsx | 1 - .../reports/components/ReportGroup.tsx | 65 ++++++++++++++++ .../reports/components/ReportGroups.tsx | 43 ++++++++++- .../reports/components/ReportRow.tsx | 77 +++++++++++++++++++ .../reports/components/ReportsLayout.tsx | 27 +++++++ .../reports/constants/ReportGroupTimeSpans.ts | 16 ++-- .../twenty-front/src/modules/types/AppPath.ts | 2 +- .../src/pages/reports/ChartEditor.tsx | 34 ++++++++ .../twenty-front/src/pages/reports/Charts.tsx | 73 ++++++++++++++++++ .../src/pages/reports/Reports.tsx | 38 ++------- 13 files changed, 348 insertions(+), 46 deletions(-) create mode 100644 packages/twenty-front/src/modules/activities/reports/components/AnalyticsQueryEditor.tsx create mode 100644 packages/twenty-front/src/modules/activities/reports/components/Chart.tsx delete mode 100644 packages/twenty-front/src/modules/activities/reports/components/Charts.tsx create mode 100644 packages/twenty-front/src/modules/activities/reports/components/ReportGroup.tsx create mode 100644 packages/twenty-front/src/modules/activities/reports/components/ReportRow.tsx create mode 100644 packages/twenty-front/src/modules/activities/reports/components/ReportsLayout.tsx create mode 100644 packages/twenty-front/src/pages/reports/ChartEditor.tsx create mode 100644 packages/twenty-front/src/pages/reports/Charts.tsx diff --git a/packages/twenty-front/src/App.tsx b/packages/twenty-front/src/App.tsx index 3a3d18fb6af1..35af35f59e53 100644 --- a/packages/twenty-front/src/App.tsx +++ b/packages/twenty-front/src/App.tsx @@ -11,7 +11,6 @@ import { } from 'react-router-dom'; import { useRecoilValue } from 'recoil'; -import { Charts } from '@/activities/reports/components/Charts'; import { ApolloProvider } from '@/apollo/components/ApolloProvider'; import { VerifyEffect } from '@/auth/components/VerifyEffect'; import { ChromeExtensionSidecarEffect } from '@/chrome-extension-sidecar/components/ChromeExtensionSidecarEffect'; @@ -52,6 +51,8 @@ import { CreateWorkspace } from '~/pages/onboarding/CreateWorkspace'; import { InviteTeam } from '~/pages/onboarding/InviteTeam'; import { PaymentSuccess } from '~/pages/onboarding/PaymentSuccess'; import { SyncEmails } from '~/pages/onboarding/SyncEmails'; +import { ChartEditor } from '~/pages/reports/ChartEditor'; +import { Charts } from '~/pages/reports/Charts'; import { Reports } from '~/pages/reports/Reports'; import { SettingsAccounts } from '~/pages/settings/accounts/SettingsAccounts'; import { SettingsAccountsCalendars } from '~/pages/settings/accounts/SettingsAccountsCalendars'; @@ -156,6 +157,7 @@ const createRouter = (isBillingEnabled?: boolean) => } /> } /> } /> + } /> } /> } /> } /> diff --git a/packages/twenty-front/src/modules/activities/reports/components/AnalyticsQueryEditor.tsx b/packages/twenty-front/src/modules/activities/reports/components/AnalyticsQueryEditor.tsx new file mode 100644 index 000000000000..d4d837d190f7 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/reports/components/AnalyticsQueryEditor.tsx @@ -0,0 +1 @@ +export const AnalyticsQueryEditor = () =>
Analytics query editor
; diff --git a/packages/twenty-front/src/modules/activities/reports/components/Chart.tsx b/packages/twenty-front/src/modules/activities/reports/components/Chart.tsx new file mode 100644 index 000000000000..47fda5540021 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/reports/components/Chart.tsx @@ -0,0 +1,13 @@ +import { Chart as ChartType } from '@/activities/reports/types/Chart'; + +interface ChartProps { + chart: ChartType; +} + +export const Chart = (props: ChartProps) => ( +
+

{props.chart.title}

+

{props.chart.title}

+ {/* TODO: Nivo charts */} +
+); diff --git a/packages/twenty-front/src/modules/activities/reports/components/Charts.tsx b/packages/twenty-front/src/modules/activities/reports/components/Charts.tsx deleted file mode 100644 index 2da584ff0f0d..000000000000 --- a/packages/twenty-front/src/modules/activities/reports/components/Charts.tsx +++ /dev/null @@ -1 +0,0 @@ -export const Charts = () =>
Charts
; diff --git a/packages/twenty-front/src/modules/activities/reports/components/ReportGroup.tsx b/packages/twenty-front/src/modules/activities/reports/components/ReportGroup.tsx new file mode 100644 index 000000000000..c276a684ab01 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/reports/components/ReportGroup.tsx @@ -0,0 +1,65 @@ +import styled from '@emotion/styled'; + +import { ReportRow } from '@/activities/reports/components/ReportRow'; +import { Report } from '@/activities/reports/types/Report'; + +interface ReportGroupProps { + title?: string; + reports: Report[]; +} + +const StyledContainer = styled.div` + align-items: flex-start; + align-self: stretch; + display: flex; + flex-direction: column; + justify-content: center; + padding: 8px 24px; +`; + +const StyledTitleBar = styled.div` + display: flex; + justify-content: space-between; + margin-bottom: ${({ theme }) => theme.spacing(4)}; + margin-top: ${({ theme }) => theme.spacing(4)}; + place-items: center; + width: 100%; +`; + +const StyledTitle = styled.h3` + color: ${({ theme }) => theme.font.color.primary}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; +`; + +const StyledCount = styled.span` + color: ${({ theme }) => theme.font.color.light}; + margin-left: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledReportRows = styled.div` + background-color: ${({ theme }) => theme.background.secondary}; + border: 1px solid ${({ theme }) => theme.border.color.light}; + border-radius: ${({ theme }) => theme.border.radius.md}; + width: 100%; +`; + +export const ReportGroup = (props: ReportGroupProps) => ( + <> + {props.reports && props.reports.length > 0 && ( + + + {props.title && ( + + {props.title} {props.reports.length} + + )} + + + {props.reports.map((report) => ( + + ))} + + + )} + +); diff --git a/packages/twenty-front/src/modules/activities/reports/components/ReportGroups.tsx b/packages/twenty-front/src/modules/activities/reports/components/ReportGroups.tsx index 9d9cd58682c6..ba924449aaeb 100644 --- a/packages/twenty-front/src/modules/activities/reports/components/ReportGroups.tsx +++ b/packages/twenty-front/src/modules/activities/reports/components/ReportGroups.tsx @@ -1,13 +1,50 @@ import React from 'react'; +import { ReportGroup } from '@/activities/reports/components/ReportGroup'; +import { + REPORT_GROUP_TIME_SPANS, + ReportGroupTimeSpan, +} from '@/activities/reports/constants/ReportGroupTimeSpans'; +import { Report } from '@/activities/reports/types/Report'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; +import { capitalize } from '~/utils/string/capitalize'; -// TODO: Implement based on TaskGroups.tsx export const ReportGroups = () => { - const { records } = useFindManyRecords({ + const { records: reports } = useFindManyRecords({ objectNameSingular: CoreObjectNameSingular.Report, }); - return
{JSON.stringify(records)}
; + const reportGroups: { title: string; reports: any[] }[] = + REPORT_GROUP_TIME_SPANS.map((reportGroupTimeSpan, i) => { + const largerReportGroupTimeSpan: ReportGroupTimeSpan | undefined = + REPORT_GROUP_TIME_SPANS[i + 1]; + + const groupReports = reports.filter((report) => { + const createdAt = new Date(report.createdAt); + const now = new Date(); + const sinceMs = +now - +createdAt; + + return ( + sinceMs >= reportGroupTimeSpan.minSinceMs && + sinceMs < largerReportGroupTimeSpan.minSinceMs + ); + }); + + return { + title: reportGroupTimeSpan.title, + reports: groupReports, + }; + }).filter((reportGroup) => reportGroup.reports.length); + + return ( +
+ {reportGroups.map((reportGroup) => ( + + ))} +
+ ); }; diff --git a/packages/twenty-front/src/modules/activities/reports/components/ReportRow.tsx b/packages/twenty-front/src/modules/activities/reports/components/ReportRow.tsx new file mode 100644 index 000000000000..6b6cbe00f3db --- /dev/null +++ b/packages/twenty-front/src/modules/activities/reports/components/ReportRow.tsx @@ -0,0 +1,77 @@ +import { useNavigate } from 'react-router-dom'; +import styled from '@emotion/styled'; + +import { Report } from '@/activities/reports/types/Report'; +import { beautifyExactDate } from '~/utils/date-utils'; + +const StyledContainer = styled.div` + align-items: center; + justify-content: space-between; + border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; + cursor: pointer; + display: inline-flex; + height: ${({ theme }) => theme.spacing(12)}; + min-width: calc(100% - ${({ theme }) => theme.spacing(8)}); + padding: 0 ${({ theme }) => theme.spacing(4)}; + + &:last-child { + border-bottom: 0; + } +`; + +const StyledReportTitle = styled.div` + color: ${({ theme }) => theme.font.color.primary}; + font-weight: ${({ theme }) => theme.font.weight.medium}; + padding: 0 ${({ theme }) => theme.spacing(2)}; +`; + +const StyledDueDate = styled.div` + align-items: center; + color: ${({ theme }) => theme.font.color.secondary}; + display: flex; + gap: ${({ theme }) => theme.spacing(1)}; + padding-left: ${({ theme }) => theme.spacing(2)}; + white-space: nowrap; +`; + +const StyledRightSideContainer = styled.div` + display: flex; +`; + +const StyledPlaceholder = styled.div` + color: ${({ theme }) => theme.font.color.light}; +`; + +const StyledLeftSideContainer = styled.div` + display: flex; +`; + +interface ReportRowProps { + report: Report; +} + +export const ReportRow = (props: ReportRowProps) => { + const navigate = useNavigate(); + + return ( + { + await navigate(`/reports/${props.report.id}/charts`); + }} + > + + + {props.report.title || ( + Report title + )} + + {/* TODO: List chart names and +1 sign if not fitting in view */} + + + + {props.report.createdAt && beautifyExactDate(props.report.createdAt)} + + + + ); +}; diff --git a/packages/twenty-front/src/modules/activities/reports/components/ReportsLayout.tsx b/packages/twenty-front/src/modules/activities/reports/components/ReportsLayout.tsx new file mode 100644 index 000000000000..02af1aa030e1 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/reports/components/ReportsLayout.tsx @@ -0,0 +1,27 @@ +import { PropsWithChildren } from 'react'; +import { IconReportAnalytics } from 'twenty-ui'; + +import { PageAddReportButton } from '@/activities/reports/components/PageAddReportButton'; +import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; +import { PageBody } from '@/ui/layout/page/PageBody'; +import { PageContainer } from '@/ui/layout/page/PageContainer'; +import { PageHeader } from '@/ui/layout/page/PageHeader'; + +interface ReportsLayoutProps extends PropsWithChildren { + hasBackButton?: boolean; +} + +export const ReportsLayout = (props: ReportsLayoutProps) => ( + + + + + + {props.children} + + +); diff --git a/packages/twenty-front/src/modules/activities/reports/constants/ReportGroupTimeSpans.ts b/packages/twenty-front/src/modules/activities/reports/constants/ReportGroupTimeSpans.ts index 9bf78c08b7fe..ff9630f0719c 100644 --- a/packages/twenty-front/src/modules/activities/reports/constants/ReportGroupTimeSpans.ts +++ b/packages/twenty-front/src/modules/activities/reports/constants/ReportGroupTimeSpans.ts @@ -1,27 +1,27 @@ -interface ReportGroupTimeSpan { - name: string; +export interface ReportGroupTimeSpan { + title: string; minSinceMs: number; } export const REPORT_GROUP_TIME_SPANS: ReportGroupTimeSpan[] = [ { - name: 'today', + title: 'today', minSinceMs: 0, }, { - name: 'over a day ago', + title: 'one day ago', minSinceMs: 1000 * 60 * 60 * 24, }, { - name: 'over a week ago', + title: 'one week ago', minSinceMs: 1000 * 60 * 60 * 24 * 7, }, { - name: 'over a month ago', + title: 'one month ago', minSinceMs: 1000 * 60 * 60 * 24 * 30, }, { - name: 'over a year ago', + title: 'one year ago', minSinceMs: 1000 * 60 * 60 * 24 * 365, }, ...[ @@ -36,7 +36,7 @@ export const REPORT_GROUP_TIME_SPANS: ReportGroupTimeSpan[] = [ 'ten', ].map((yearCount, i) => { return { - name: `over ${yearCount} years ago`, + title: `${yearCount} years ago`, minSinceMs: 1000 * 60 * 60 * 24 * 365 * (i + 2), }; }), diff --git a/packages/twenty-front/src/modules/types/AppPath.ts b/packages/twenty-front/src/modules/types/AppPath.ts index aac20cfd78a2..a99ed0a7b491 100644 --- a/packages/twenty-front/src/modules/types/AppPath.ts +++ b/packages/twenty-front/src/modules/types/AppPath.ts @@ -18,7 +18,7 @@ export enum AppPath { TasksPage = '/tasks', ReportsPage = '/reports', ChartsPage = '/reports/:reportId/charts', - ChartPage = '/reports/:reportId/charts/:chartId', + ChartEditorPage = '/reports/:reportId/charts/:chartId', OpportunitiesPage = '/objects/opportunities', RecordIndexPage = '/objects/:objectNamePlural', diff --git a/packages/twenty-front/src/pages/reports/ChartEditor.tsx b/packages/twenty-front/src/pages/reports/ChartEditor.tsx new file mode 100644 index 000000000000..26d81295f518 --- /dev/null +++ b/packages/twenty-front/src/pages/reports/ChartEditor.tsx @@ -0,0 +1,34 @@ +import { useParams } from 'react-router-dom'; +import styled from '@emotion/styled'; + +import { AnalyticsQueryEditor } from '@/activities/reports/components/AnalyticsQueryEditor'; +import { Chart } from '@/activities/reports/components/Chart'; +import { ReportsLayout } from '@/activities/reports/components/ReportsLayout'; +import { Chart as ChartType } from '@/activities/reports/types/Chart'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; + +const StyledChartEditorContainer = styled.div` + display: flex; + flex: 1; + height: 100%; + overflow: auto; +`; + +export const ChartEditor = () => { + const { chartId } = useParams(); + + const { record: chart } = useFindOneRecord({ + objectNameSingular: CoreObjectNameSingular.Chart, + objectRecordId: chartId, + }); + + return ( + + + {chart && } + + + + ); +}; diff --git a/packages/twenty-front/src/pages/reports/Charts.tsx b/packages/twenty-front/src/pages/reports/Charts.tsx new file mode 100644 index 000000000000..3b7b3a18657d --- /dev/null +++ b/packages/twenty-front/src/pages/reports/Charts.tsx @@ -0,0 +1,73 @@ +import { useNavigate, useParams } from 'react-router-dom'; +import styled from '@emotion/styled'; +import { IconPlus } from 'twenty-ui'; + +import { Chart } from '@/activities/reports/components/Chart'; +import { ReportsLayout } from '@/activities/reports/components/ReportsLayout'; +import { Chart as ChartType } from '@/activities/reports/types/Chart'; +import { Report } from '@/activities/reports/types/Report'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; +import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; +import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; +import { Button } from '@/ui/input/button/components/Button'; + +const StyledChartsContainer = styled.div` + display: flex; + flex: 1; + flex-direction: column; + height: 100%; + overflow: auto; +`; + +const StyledChartContainer = styled.div` + display: flex; + flex-direction: column; +`; + +export const Charts = () => { + const reportId = useParams().reportId ?? ''; + + const navigate = useNavigate(); + + // TODO: Combine find queries into one graphql query? + + const { record: report } = useFindOneRecord({ + objectNameSingular: CoreObjectNameSingular.Report, + objectRecordId: reportId, + }); + + const { records: charts } = useFindManyRecords({ + objectNameSingular: CoreObjectNameSingular.Chart, + }); + + const { createOneRecord: createOneChart } = useCreateOneRecord({ + objectNameSingular: CoreObjectNameSingular.Chart, + }); + + return ( + + +
{report?.title}
+ {charts.map((chart) => ( + { + await navigate(`/reports/${reportId}/charts/${chart.id}`); + }} + > + + + ))} + + + + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts b/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts index 152df7ea8441..0afcd1a20121 100644 --- a/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts +++ b/packages/twenty-front/src/modules/object-record/utils/generateEmptyFieldValue.ts @@ -84,7 +84,7 @@ export const generateEmptyFieldValue = ( case FieldMetadataType.RawJson: { return null; } - case FieldMetadataType.FieldPath: { + case FieldMetadataType.DataExplorerQuery: { return null; } case FieldMetadataType.RichText: { diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx index 2d2f22d33f5a..3e28d99b3160 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldSettingsFormCard.tsx @@ -96,7 +96,7 @@ const previewableTypes = [ FieldMetadataType.Relation, FieldMetadataType.Select, FieldMetadataType.Text, - FieldMetadataType.FieldPath, + FieldMetadataType.DataExplorerQuery, ]; export const SettingsDataModelFieldSettingsFormCard = ({ diff --git a/packages/twenty-front/src/modules/settings/data-model/types/SettingsSupportedFieldType.ts b/packages/twenty-front/src/modules/settings/data-model/types/SettingsSupportedFieldType.ts index 9edf70455a78..1d91a44da2d2 100644 --- a/packages/twenty-front/src/modules/settings/data-model/types/SettingsSupportedFieldType.ts +++ b/packages/twenty-front/src/modules/settings/data-model/types/SettingsSupportedFieldType.ts @@ -2,5 +2,5 @@ import { FieldMetadataType } from '~/generated-metadata/graphql'; export type SettingsSupportedFieldType = Exclude< FieldMetadataType, - FieldMetadataType.Position | FieldMetadataType.FieldPath + FieldMetadataType.Position | FieldMetadataType.DataExplorerQuery >; diff --git a/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts b/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts index 4fb34099f118..a47cac575f44 100644 --- a/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts +++ b/packages/twenty-server/src/engine/api/__mocks__/object-metadata-item.mock.ts @@ -190,9 +190,9 @@ const fieldRawJsonMock = { defaultValue: null, }; -const fieldfieldPathMock = { - name: 'fieldPath', - type: FieldMetadataType.FIELD_PATH, +const fieldDataExplorerQueryMock = { + name: 'fieldDataExplorerQuery', + type: FieldMetadataType.DATA_EXPLORER_QUERY, isNullable: true, defaultValue: null, }; @@ -235,7 +235,7 @@ export const fields = [ fieldPositionMock, fieldAddressMock, fieldRawJsonMock, - fieldfieldPathMock, + fieldDataExplorerQueryMock, fieldRichTextMock, fieldActorMock, ]; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/input/field-path-filter.input-type.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/input/field-path-filter.input-type.ts deleted file mode 100644 index 07a58f6287ad..000000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/input/field-path-filter.input-type.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { GraphQLInputObjectType } from 'graphql'; - -import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type'; - -export const FieldPathFilterType = new GraphQLInputObjectType({ - name: 'FieldPathFilter', - fields: { - is: { type: FilterIs }, - }, -}); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/field-path.scalar.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/field-path.scalar.ts deleted file mode 100644 index eb4d4334b5cc..000000000000 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/field-path.scalar.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { GraphQLScalarType, Kind } from 'graphql'; -import { validate as uuidValidate } from 'uuid'; - -const checkFieldPath = (value: any): string[] => { - if (!Array.isArray(value)) { - throw new Error('Field path must be a string'); - } - - value.forEach((fieldMetadataIdCandidate) => { - if (!uuidValidate(fieldMetadataIdCandidate)) { - throw new Error('Invalid field path'); - } - }); - - return value; -}; - -export const FieldPathScalarType = new GraphQLScalarType({ - name: 'FieldPath', - description: 'A field path scalar type', - serialize: checkFieldPath, - parseValue: checkFieldPath, - parseLiteral(ast): string[] { - if (ast.kind !== Kind.LIST) { - throw new Error('Field path must be a list'); - } - - return ast.values.map((value) => { - if (value.kind !== Kind.STRING) { - throw new Error('Each UUID in the field path must be a string'); - } - - return value.value; - }); - }, -}); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/index.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/index.ts index 1e49f89977c3..36819a7a24e6 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/index.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/index.ts @@ -1,5 +1,3 @@ -import { FieldPathScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/field-path.scalar'; - import { BigFloatScalarType } from './big-float.scalar'; import { BigIntScalarType } from './big-int.scalar'; import { CursorScalarType } from './cursor.scalar'; @@ -28,5 +26,4 @@ export const scalars = [ CursorScalarType, PositionScalarType, RawJSONScalar, - FieldPathScalarType, ]; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts index 43013c62be11..c3cb1c91f35d 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts @@ -25,13 +25,11 @@ import { RawJsonFilterType, StringFilterType, } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input'; -import { FieldPathFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/field-path-filter.input-type'; import { IDFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/id-filter.input-type'; import { BigFloatScalarType, UUIDScalarType, } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; -import { FieldPathScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/field-path.scalar'; import { PositionScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/position.scalar'; import { RawJSONScalar } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/raw-json.scalar'; import { getNumberFilterType } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-number-filter-type.util'; @@ -76,7 +74,7 @@ export class TypeMapperService { [FieldMetadataType.NUMERIC, BigFloatScalarType], [FieldMetadataType.POSITION, PositionScalarType], [FieldMetadataType.RAW_JSON, RawJSONScalar], - [FieldMetadataType.FIELD_PATH, FieldPathScalarType], + [FieldMetadataType.DATA_EXPLORER_QUERY, RawJSONScalar], [FieldMetadataType.RICH_TEXT, GraphQLString], ]); @@ -113,7 +111,7 @@ export class TypeMapperService { [FieldMetadataType.NUMERIC, BigFloatFilterType], [FieldMetadataType.POSITION, FloatFilterType], [FieldMetadataType.RAW_JSON, RawJsonFilterType], - [FieldMetadataType.FIELD_PATH, FieldPathFilterType], + [FieldMetadataType.DATA_EXPLORER_QUERY, RawJsonFilterType], [FieldMetadataType.RICH_TEXT, StringFilterType], ]); @@ -138,7 +136,7 @@ export class TypeMapperService { [FieldMetadataType.MULTI_SELECT, OrderByDirectionType], [FieldMetadataType.POSITION, OrderByDirectionType], [FieldMetadataType.RAW_JSON, OrderByDirectionType], - [FieldMetadataType.FIELD_PATH, OrderByDirectionType], + [FieldMetadataType.DATA_EXPLORER_QUERY, OrderByDirectionType], [FieldMetadataType.RICH_TEXT, OrderByDirectionType], ]); diff --git a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils.ts b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils.ts index 2a2505a3cf91..a744ba327470 100644 --- a/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils.ts +++ b/packages/twenty-server/src/engine/api/rest/core/query-builder/utils/map-field-metadata-to-graphql-query.utils.ts @@ -30,7 +30,7 @@ export const mapFieldMetadataToGraphqlQuery = ( FieldMetadataType.MULTI_SELECT, FieldMetadataType.POSITION, FieldMetadataType.RAW_JSON, - FieldMetadataType.FIELD_PATH, + FieldMetadataType.DATA_EXPLORER_QUERY, FieldMetadataType.RICH_TEXT, ].includes(fieldType); diff --git a/packages/twenty-server/src/engine/core-modules/chart/chart.service.ts b/packages/twenty-server/src/engine/core-modules/chart/chart.service.ts index 416621b8fe04..3de6e5ce2f1f 100644 --- a/packages/twenty-server/src/engine/core-modules/chart/chart.service.ts +++ b/packages/twenty-server/src/engine/core-modules/chart/chart.service.ts @@ -1,13 +1,10 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import console from 'console'; - import { Repository } from 'typeorm'; import { ChartResult } from 'src/engine/core-modules/chart/dtos/chart-result.dto'; import { AliasPrefix } from 'src/engine/core-modules/chart/types/alias-prefix.type'; -import { ChartQuery } from 'src/engine/core-modules/chart/types/chart-query'; import { CommonTableExpressionDefinition } from 'src/engine/core-modules/chart/types/common-table-expression-definition.type'; import { QueryRelation } from 'src/engine/core-modules/chart/types/query-relation.type'; import { @@ -25,10 +22,7 @@ import { import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; -import { - ChartQueryMeasure, - ChartWorkspaceEntity, -} from 'src/modules/charts/standard-objects/chart.workspace-entity'; +import { ChartWorkspaceEntity } from 'src/modules/charts/standard-objects/chart.workspace-entity'; @Injectable() export class ChartService { @@ -222,7 +216,7 @@ export class ChartService { }); } - private getTargetSelectColumn( + /* private getTargetSelectColumn( chartQueryMeasure?: ChartQueryMeasure, qualifiedColumn?: string, ) { @@ -245,7 +239,7 @@ export class ChartService { case ChartQueryMeasure.SUM: return `SUM(${qualifiedColumn}) as measure`; } - } + } */ private async getFieldMetadata(workspaceId, fieldMetadataId) { if (!fieldMetadataId) return; @@ -369,14 +363,13 @@ export class ChartService { }; } - // getChartQuery will be removed after FIELD_PATH is transformed into CHART_QUERY - private async getChartQuery(workspaceId: string, chartId: string) { + private async getQuery(workspaceId: string, chartId: string) { const repository = await this.twentyORMManager.getRepository(ChartWorkspaceEntity); const chart = await repository.findOneByOrFail({ id: chartId }); - const sourceObjectMetadata = + /* const sourceObjectMetadata = await this.objectMetadataService.findOneOrFailWithinWorkspace( workspaceId, { @@ -406,45 +399,18 @@ export class ChartService { const groupByMeasureFieldMetadata = (lastGroupByFieldMetadata?.type !== FieldMetadataType.RELATION && lastGroupByFieldMetadata) || - undefined; - - const chartQuery: ChartQuery = { - sourceObjectMetadataId: sourceObjectMetadata.id, - target: { - relationFieldMetadataIds: targetMeasureFieldMetadata - ? chart.target.slice(0, -1) - : (chart.target ?? []), - measureFieldMetadataId: targetMeasureFieldMetadata?.id, - measure: chart.measure, - }, - groupBy: { - relationFieldMetadataIds: groupByMeasureFieldMetadata - ? chart.groupBy.slice(0, -1) - : (chart.groupBy ?? []), - measureFieldMetadataId: groupByMeasureFieldMetadata?.id, - measure: undefined, - groups: undefined, - includeNulls: undefined, - }, - }; + undefined; */ - return chartQuery; + return chart.query; } async run(workspaceId: string, chartId: string): Promise { - const chartQuery = await this.getChartQuery(workspaceId, chartId); + const query = await this.getQuery(workspaceId, chartId); - console.log('chartQuery', chartQuery); - - if ( - !chartQuery.target?.measureFieldMetadataId && - chartQuery.target?.measure !== ChartQueryMeasure.COUNT - ) { - throw new Error( - "Field 'measure' must be count when field 'measureFieldMetadataId' is undefined", - ); - } + console.log('query', query); + return { chartResult: JSON.stringify([{ measure: 3 }]) }; + /* const dataSourceSchemaName = this.workspaceDataSourceService.getSchemaName(workspaceId); @@ -599,7 +565,7 @@ export class ChartService { console.log('result', JSON.stringify(result, undefined, 2)); - return { chartResult: JSON.stringify(result) }; + return { chartResult: JSON.stringify(result) }; */ } } diff --git a/packages/twenty-server/src/engine/core-modules/chart/types/chart-query.ts b/packages/twenty-server/src/engine/core-modules/chart/types/chart-query.ts index 8288517d037f..ebc82973d4ee 100644 --- a/packages/twenty-server/src/engine/core-modules/chart/types/chart-query.ts +++ b/packages/twenty-server/src/engine/core-modules/chart/types/chart-query.ts @@ -1,17 +1,36 @@ -import { ChartQueryMeasure } from 'src/modules/charts/standard-objects/chart.workspace-entity'; +interface DataExplorerQueryChildJoin { + type: 'join'; + children: DataExplorerQueryChild; + fieldMetadataId?: string; + measure?: 'COUNT'; +} + +interface DataExplorerQueryChildSelect { + type: 'select'; + children: DataExplorerQueryChild; + fieldMetadataId?: string; + measure?: 'AVG' | 'MAX' | 'MIN' | 'SUM'; +} + +interface DataExplorerQueryGroupBy { + type: 'groupBy'; + groupBy?: boolean; + groups?: { upperLimit: number; lowerLimit: number }[]; + includeNulls?: boolean; +} + +interface DataExplorerQuerySort { + type: 'sort'; + sortBy?: 'ASC' | 'DESC'; +} + +type DataExplorerQueryChild = + | DataExplorerQueryChildJoin + | DataExplorerQueryChildSelect + | DataExplorerQueryGroupBy + | DataExplorerQuerySort; -export interface ChartQuery { +export interface DataExplorerQuery { sourceObjectMetadataId?: string; - target?: { - relationFieldMetadataIds?: string[]; - measureFieldMetadataId?: string; - measure?: ChartQueryMeasure; - }; - groupBy?: { - relationFieldMetadataIds?: string[]; - measureFieldMetadataId?: string; - measure?: ChartQueryMeasure; - groups?: { upperLimit: number; lowerLimit: number }[]; - includeNulls?: boolean; - }; + children: DataExplorerQueryChild[]; } diff --git a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts index fd6e8b0bcb4b..55337ba2e1b7 100644 --- a/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts +++ b/packages/twenty-server/src/engine/core-modules/open-api/utils/components.utils.ts @@ -43,8 +43,8 @@ const getFieldProperties = (type: FieldMetadataType): Property => { return { type: 'boolean' }; case FieldMetadataType.RAW_JSON: return { type: 'object' }; - case FieldMetadataType.FIELD_PATH: - return { type: 'array', items: { type: 'string' } }; + case FieldMetadataType.DATA_EXPLORER_QUERY: + return { type: 'object' }; default: return { type: 'string' }; } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts index d3b93ddcff0a..944a09f3a3d2 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.entity.ts @@ -42,7 +42,7 @@ export enum FieldMetadataType { POSITION = 'POSITION', ADDRESS = 'ADDRESS', RAW_JSON = 'RAW_JSON', - FIELD_PATH = 'FIELD_PATH', + DATA_EXPLORER_QUERY = 'DATA_EXPLORER_QUERY', RICH_TEXT = 'RICH_TEXT', ACTOR = 'ACTOR', } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface.ts index edce5cf9101a..33280cd2767b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface.ts @@ -12,7 +12,6 @@ import { FieldMetadataDefaultValueRawJson, FieldMetadataDefaultValueRichText, FieldMetadataDefaultValueString, - FieldMetadataDefaultValueStringArray, FieldMetadataDefaultValueUuidFunction, } from 'src/engine/metadata-modules/field-metadata/dtos/default-value.input'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; @@ -47,7 +46,7 @@ type FieldMetadataDefaultValueMapping = { [FieldMetadataType.SELECT]: FieldMetadataDefaultValueString; [FieldMetadataType.MULTI_SELECT]: FieldMetadataDefaultValueString; [FieldMetadataType.RAW_JSON]: FieldMetadataDefaultValueRawJson; - [FieldMetadataType.FIELD_PATH]: FieldMetadataDefaultValueStringArray; + [FieldMetadataType.DATA_EXPLORER_QUERY]: FieldMetadataDefaultValueRawJson; [FieldMetadataType.RICH_TEXT]: FieldMetadataDefaultValueRichText; [FieldMetadataType.ACTOR]: FieldMetadataDefaultActor; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util.ts index a849295903a3..1ba574596205 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-default-value-for-type.util.ts @@ -52,7 +52,7 @@ export const defaultValueValidatorsMap = { [FieldMetadataType.RICH_TEXT]: [FieldMetadataDefaultValueString], [FieldMetadataType.RAW_JSON]: [FieldMetadataDefaultValueRawJson], [FieldMetadataType.LINKS]: [FieldMetadataDefaultValueLinks], - [FieldMetadataType.FIELD_PATH]: [FieldMetadataDefaultValueStringArray], + [FieldMetadataType.DATA_EXPLORER_QUERY]: [FieldMetadataDefaultValueRawJson], [FieldMetadataType.ACTOR]: [FieldMetadataDefaultActor], }; diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory.ts index 5a84cddd43b0..f44115669dd7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory.ts @@ -50,7 +50,6 @@ export class BasicColumnActionFactory extends ColumnActionAbstractFactory( return 'enum'; case FieldMetadataType.RAW_JSON: return 'jsonb'; - case FieldMetadataType.FIELD_PATH: - return 'text'; + case FieldMetadataType.DATA_EXPLORER_QUERY: + return 'jsonb'; default: throw new WorkspaceMigrationException( `Cannot convert ${fieldMetadataType} to column type.`, diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.factory.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.factory.ts index fcec5c835206..c5aa928b2f0f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.factory.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.factory.ts @@ -73,7 +73,7 @@ export class WorkspaceMigrationFactory { [FieldMetadataType.POSITION, { factory: this.basicColumnActionFactory }], [FieldMetadataType.RAW_JSON, { factory: this.basicColumnActionFactory }], [ - FieldMetadataType.FIELD_PATH, + FieldMetadataType.DATA_EXPLORER_QUERY, { factory: this.basicColumnActionFactory }, ], [FieldMetadataType.RICH_TEXT, { factory: this.basicColumnActionFactory }], diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts index 3fbce5f92ffc..f1609459cf2b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts @@ -64,10 +64,7 @@ export const BLOCKLIST_STANDARD_FIELD_IDS = { export const CHART_STANDARD_FIELD_IDS = { name: '20202020-e5aa-45b1-aec0-431420660570', description: '20202020-71b2-4df1-8cb3-120d55272b12', - measure: '20202020-e764-4295-bb56-9b757287174b', - sourceObjectNameSingular: '20202020-1e61-41f2-b921-4b2b21b25002', - target: '20202020-b301-49fc-a26e-55f1a482a4c8', - groupBy: '20202020-c5e7-4158-bd3e-3ab1283b52a3', + query: '20202020-b301-49fc-a26e-55f1a482a4c8', position: '20202020-b014-4ead-b2f4-5cbb9c67cd05', }; diff --git a/packages/twenty-server/src/modules/charts/standard-objects/chart.workspace-entity.ts b/packages/twenty-server/src/modules/charts/standard-objects/chart.workspace-entity.ts index 3f02c06f819d..210f6573c76d 100644 --- a/packages/twenty-server/src/modules/charts/standard-objects/chart.workspace-entity.ts +++ b/packages/twenty-server/src/modules/charts/standard-objects/chart.workspace-entity.ts @@ -1,3 +1,4 @@ +import { DataExplorerQuery } from 'src/engine/core-modules/chart/types/chart-query'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; @@ -9,14 +10,6 @@ import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is import { CHART_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 enum ChartQueryMeasure { - AVERAGE = 'AVERAGE', - SUM = 'SUM', - MIN = 'MIN', - MAX = 'MAX', - COUNT = 'COUNT', -} - @WorkspaceEntity({ standardId: STANDARD_OBJECT_IDS.chart, namePlural: 'charts', @@ -51,75 +44,14 @@ export class ChartWorkspaceEntity extends BaseWorkspaceEntity { description: string; @WorkspaceField({ - standardId: CHART_STANDARD_FIELD_IDS.measure, - type: FieldMetadataType.SELECT, - label: 'Measure', - description: 'Aggregate function of the chart', - icon: 'IconRulerMeasure', - options: [ - { - value: ChartQueryMeasure.AVERAGE, - label: 'Average', - position: 0, - color: 'blue', - }, - { - value: ChartQueryMeasure.SUM, - label: 'Sum', - position: 1, - color: 'green', - }, - { - value: ChartQueryMeasure.MIN, - label: 'Minimum', - position: 2, - color: 'orange', - }, - { - value: ChartQueryMeasure.MAX, - label: 'Maximum', - position: 3, - color: 'red', - }, - { - value: ChartQueryMeasure.COUNT, - label: 'Count', - position: 4, - color: 'purple', - }, - ], - defaultValue: `'${ChartQueryMeasure.COUNT}'`, - }) - measure: ChartQueryMeasure; - - @WorkspaceField({ - standardId: CHART_STANDARD_FIELD_IDS.sourceObjectNameSingular, - type: FieldMetadataType.TEXT, - label: 'Source object name (singular)', - description: 'Singular name of the source object', - icon: 'IconAbc', - }) - sourceObjectNameSingular: string; - - @WorkspaceField({ - standardId: CHART_STANDARD_FIELD_IDS.target, - type: FieldMetadataType.FIELD_PATH, - label: 'Target', - description: 'Path from source object to the target field.', + standardId: CHART_STANDARD_FIELD_IDS.query, + type: FieldMetadataType.DATA_EXPLORER_QUERY, + label: 'Query', + description: 'Query', icon: 'IconForms', }) @WorkspaceIsNullable() - target: string[]; - - @WorkspaceField({ - standardId: CHART_STANDARD_FIELD_IDS.groupBy, - type: FieldMetadataType.FIELD_PATH, - label: 'Group By', - description: 'Group by clause for the chart', - icon: 'IconStack2', - }) - @WorkspaceIsNullable() - groupBy: string[]; + query: DataExplorerQuery; @WorkspaceField({ standardId: CHART_STANDARD_FIELD_IDS.position, From 27bee2af3911855582b21f1936f7265cc07fe2a3 Mon Sep 17 00:00:00 2001 From: ad-elias Date: Wed, 21 Aug 2024 22:27:00 +0200 Subject: [PATCH 63/63] Persist data explorer query --- .../guards/isFieldDataExplorerQueryValue.ts | 73 ++++++++++--------- .../core-modules/chart/types/chart-query.ts | 6 +- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldDataExplorerQueryValue.ts b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldDataExplorerQueryValue.ts index a9513d52549c..fce97a3cf077 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldDataExplorerQueryValue.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/types/guards/isFieldDataExplorerQueryValue.ts @@ -1,21 +1,21 @@ import { FieldJsonValue } from '@/object-record/record-field/types/FieldMetadata'; import { z } from 'zod'; -interface ChartQueryChildJoin { +interface DataExplorerQueryChildJoin { type: 'join'; - children: ChartQueryChild; + children: DataExplorerQueryChild[]; fieldMetadataId?: string; measure?: 'COUNT'; } -interface ChartQueryChildSelect { +interface DataExplorerQueryChildSelect { type: 'select'; - children: ChartQueryChild; + children?: DataExplorerQueryChild[]; fieldMetadataId?: string; measure?: 'AVG' | 'MAX' | 'MIN' | 'SUM'; } -const chartQueryGroupBySchema = z.object({ +const dataExplorerQueryGroupBySchema = z.object({ type: z.literal('groupBy'), groupBy: z.boolean().optional(), groups: z @@ -29,50 +29,51 @@ const chartQueryGroupBySchema = z.object({ includeNulls: z.boolean().optional(), }); -const chartQuerySortSchema = z.object({ +const dataExplorerQuerySortSchema = z.object({ type: z.literal('sort'), sortBy: z.enum(['ASC', 'DESC']).optional(), }); -type ChartQueryChild = - | ChartQueryChildJoin - | ChartQueryChildSelect - | z.infer - | z.infer; +type DataExplorerQueryChild = + | DataExplorerQueryChildJoin + | DataExplorerQueryChildSelect + | z.infer + | z.infer; -const chartQueryChildSchema: z.ZodType = z.lazy(() => - z.union([ - chartQueryChildJoinSchema, - chartQueryChildSelectSchema, - chartQueryGroupBySchema, - chartQuerySortSchema, - ]), +const dataExplorerQueryChildSchema: z.ZodType = z.lazy( + () => + z.union([ + dataExplorerQueryChildJoinSchema, + dataExplorerQueryChildSelectSchema, + dataExplorerQueryGroupBySchema, + dataExplorerQuerySortSchema, + ]), ); -const chartQueryChildJoinSchema: z.ZodType = z.object({ - type: z.literal('join'), - children: chartQueryChildSchema, - fieldMetadataId: z.string().optional(), - measure: z.literal('COUNT').optional(), -}); +const dataExplorerQueryChildJoinSchema: z.ZodType = + z.object({ + type: z.literal('join'), + children: z.array(dataExplorerQueryChildSchema), + fieldMetadataId: z.string().optional(), + measure: z.literal('COUNT').optional(), + }); -const chartQueryChildSelectSchema: z.ZodType = z.object({ - type: z.literal('select'), - children: chartQueryChildSchema, - fieldMetadataId: z.string().optional(), - measure: z.enum(['AVG', 'MAX', 'MIN', 'SUM']).optional(), -}); +const dataExplorerQueryChildSelectSchema: z.ZodType = + z.object({ + type: z.literal('select'), + children: z.array(dataExplorerQueryChildSchema).optional(), + fieldMetadataId: z.string().optional(), + measure: z.enum(['AVG', 'MAX', 'MIN', 'SUM']).optional(), + }); -export const chartQuerySchema = z.object({ +export const dataExplorerQuerySchema = z.object({ sourceObjectMetadataId: z.string().optional(), - children: z.array(chartQueryChildSchema), + children: z.array(dataExplorerQueryChildSchema).optional(), }); -export type ChartQuery = z.infer; - -const fieldPathSchema = z.array(z.string()).nullable(); +export type DataExplorerQuery = z.infer; export const isFieldDataExplorerQueryValue = ( fieldValue: unknown, ): fieldValue is FieldJsonValue => - fieldPathSchema.safeParse(fieldValue).success; + dataExplorerQuerySchema.safeParse(fieldValue).success; diff --git a/packages/twenty-server/src/engine/core-modules/chart/types/chart-query.ts b/packages/twenty-server/src/engine/core-modules/chart/types/chart-query.ts index ebc82973d4ee..076e8627fccc 100644 --- a/packages/twenty-server/src/engine/core-modules/chart/types/chart-query.ts +++ b/packages/twenty-server/src/engine/core-modules/chart/types/chart-query.ts @@ -1,13 +1,13 @@ interface DataExplorerQueryChildJoin { type: 'join'; - children: DataExplorerQueryChild; + children: DataExplorerQueryChild[]; fieldMetadataId?: string; measure?: 'COUNT'; } interface DataExplorerQueryChildSelect { type: 'select'; - children: DataExplorerQueryChild; + children?: DataExplorerQueryChild[]; fieldMetadataId?: string; measure?: 'AVG' | 'MAX' | 'MIN' | 'SUM'; } @@ -32,5 +32,5 @@ type DataExplorerQueryChild = export interface DataExplorerQuery { sourceObjectMetadataId?: string; - children: DataExplorerQueryChild[]; + children?: DataExplorerQueryChild[]; }