diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql
index d14922c0815..6a05e4f1690 100644
--- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql
+++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql
@@ -1741,7 +1741,6 @@ enum FeatureFlagKey {
IS_JUNCTION_RELATIONS_ENABLED
IS_DRAFT_EMAIL_ENABLED
IS_CONNECTED_ACCOUNT_MIGRATED
- IS_USAGE_ANALYTICS_ENABLED
IS_RICH_TEXT_V1_MIGRATED
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED
IS_RECORD_TABLE_WIDGET_ENABLED
diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts
index 1d99496c36d..b99a7eb632b 100644
--- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts
+++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts
@@ -1437,7 +1437,7 @@ export interface PublicFeatureFlag {
__typename: 'PublicFeatureFlag'
}
-export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED'
+export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED'
export interface ClientConfigMaintenanceMode {
startAt: Scalars['DateTime']
@@ -9321,7 +9321,6 @@ export const enumFeatureFlagKey = {
IS_JUNCTION_RELATIONS_ENABLED: 'IS_JUNCTION_RELATIONS_ENABLED' as const,
IS_DRAFT_EMAIL_ENABLED: 'IS_DRAFT_EMAIL_ENABLED' as const,
IS_CONNECTED_ACCOUNT_MIGRATED: 'IS_CONNECTED_ACCOUNT_MIGRATED' as const,
- IS_USAGE_ANALYTICS_ENABLED: 'IS_USAGE_ANALYTICS_ENABLED' as const,
IS_RICH_TEXT_V1_MIGRATED: 'IS_RICH_TEXT_V1_MIGRATED' as const,
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' as const,
IS_RECORD_TABLE_WIDGET_ENABLED: 'IS_RECORD_TABLE_WIDGET_ENABLED' as const,
diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts
index 7bc85642e3f..705dbfb46bd 100644
--- a/packages/twenty-front/src/generated-metadata/graphql.ts
+++ b/packages/twenty-front/src/generated-metadata/graphql.ts
@@ -1767,8 +1767,7 @@ export enum FeatureFlagKey {
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED = 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED',
IS_RECORD_TABLE_WIDGET_ENABLED = 'IS_RECORD_TABLE_WIDGET_ENABLED',
IS_RICH_TEXT_V1_MIGRATED = 'IS_RICH_TEXT_V1_MIGRATED',
- IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED',
- IS_USAGE_ANALYTICS_ENABLED = 'IS_USAGE_ANALYTICS_ENABLED'
+ IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED'
}
export type Field = {
diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx
index f13f24d4141..be5c5dec0d0 100644
--- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx
+++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx
@@ -6,10 +6,7 @@ import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLo
import { SettingPublicDomain } from '@/settings/domains/components/SettingPublicDomain';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
-import {
- FeatureFlagKey,
- PermissionFlagType,
-} from '~/generated-metadata/graphql';
+import { PermissionFlagType } from '~/generated-metadata/graphql';
const SettingsGraphQLPlayground = lazy(() =>
import(
@@ -561,19 +558,11 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => (
element={}
/>
} />
+ } />
- }
- >
- } />
- }
- />
-
+ path={SettingsPath.UsageUserDetail}
+ element={}
+ />
}
diff --git a/packages/twenty-front/src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx b/packages/twenty-front/src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
index bd34339ff5b..8631fed8135 100644
--- a/packages/twenty-front/src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
+++ b/packages/twenty-front/src/modules/settings/billing/components/SettingsBillingCreditsSection.tsx
@@ -7,7 +7,6 @@ import { useBillingWording } from '@/settings/billing/hooks/useBillingWording';
import { useCurrentBillingFlags } from '@/settings/billing/hooks/useCurrentBillingFlags';
import { useCurrentMetered } from '@/settings/billing/hooks/useCurrentMetered';
import { useGetWorkflowNodeExecutionUsage } from '@/settings/billing/hooks/useGetWorkflowNodeExecutionUsage';
-import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
@@ -20,10 +19,7 @@ import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { UndecoratedLink } from 'twenty-ui/navigation';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
-import {
- FeatureFlagKey,
- SubscriptionStatus,
-} from '~/generated-metadata/graphql';
+import { SubscriptionStatus } from '~/generated-metadata/graphql';
const StyledCreditUsageFooterActions = styled.div`
margin-top: ${themeCssVariables.spacing[4]};
@@ -44,10 +40,6 @@ export const SettingsBillingCreditsSection = ({
const { getCurrentMeteredPricesByInterval } = useCurrentMetered();
- const isUsageAnalyticsEnabled = useIsFeatureEnabled(
- FeatureFlagKey.IS_USAGE_ANALYTICS_ENABLED,
- );
-
const { getIntervalLabel } = useBillingWording();
const isTrialing = subscriptionStatus === SubscriptionStatus.Trialing;
@@ -143,17 +135,15 @@ export const SettingsBillingCreditsSection = ({
)}
- {isUsageAnalyticsEnabled && (
-
-
-
-
-
- )}
+
+
+
+
+
{
const isClickHouseConfigured = useAtomStateValue(isClickHouseConfiguredState);
+ const { analytics, isInitialLoading } = useUsageAnalyticsData({
+ skip: !isClickHouseConfigured,
+ });
+
if (!isClickHouseConfigured) {
return (
@@ -34,6 +40,33 @@ export const SettingsUsageAnalyticsSection = () => {
);
}
+ if (isInitialLoading) {
+ return ;
+ }
+
+ const hasData =
+ analytics &&
+ (analytics.timeSeries.length > 0 ||
+ analytics.usageByOperationType.length > 0 ||
+ analytics.usageByUser.length > 0);
+
+ if (!hasData) {
+ return (
+
+ );
+ }
+
return (
<>
{
SETTINGS_AI_TABS.COMPONENT_INSTANCE_ID,
);
- const isUsageAnalyticsEnabled = useIsFeatureEnabled(
- FeatureFlagKey.IS_USAGE_ANALYTICS_ENABLED,
- );
-
const handleCreateTool = async () => {
setIsCreatingTool(true);
try {
@@ -100,15 +94,11 @@ export const SettingsAI = () => {
title: t`Tools`,
Icon: IconTool,
},
- ...(isUsageAnalyticsEnabled
- ? [
- {
- id: SETTINGS_AI_TABS.TABS_IDS.USAGE,
- title: t`Usage`,
- Icon: IconChartBar,
- },
- ]
- : []),
+ {
+ id: SETTINGS_AI_TABS.TABS_IDS.USAGE,
+ title: t`Usage`,
+ Icon: IconChartBar,
+ },
{
id: SETTINGS_AI_TABS.TABS_IDS.MORE,
title: t`More`,
diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsAIUsageTab.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsAIUsageTab.tsx
index 49256edb8c4..a345e5d8539 100644
--- a/packages/twenty-front/src/pages/settings/ai/components/SettingsAIUsageTab.tsx
+++ b/packages/twenty-front/src/pages/settings/ai/components/SettingsAIUsageTab.tsx
@@ -8,6 +8,8 @@ import { UsageBreakdownPieSection } from '@/settings/usage/components/UsageBreak
import { UsageByUserTableSection } from '@/settings/usage/components/UsageByUserTableSection';
import { UsageDailyChartSection } from '@/settings/usage/components/UsageDailyChartSection';
import { AI_OPERATION_TYPES } from '@/settings/usage/constants/AiOperationTypes';
+import { useUsageAnalyticsData } from '@/settings/usage/hooks/useUsageAnalyticsData';
+import { UsageSectionSkeleton } from '@/settings/usage/components/UsageSectionSkeleton';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { t } from '@lingui/core/macro';
import { SettingsPath } from 'twenty-shared/types';
@@ -25,6 +27,13 @@ export const SettingsAIUsageTab = () => {
const hasEnterpriseAccess =
isBillingEnabled || currentWorkspace?.hasValidEnterpriseKey === true;
+ const shouldSkipQuery = !hasEnterpriseAccess || !isClickHouseConfigured;
+
+ const { analytics, isInitialLoading } = useUsageAnalyticsData({
+ operationTypes: AI_OPERATION_TYPES,
+ skip: shouldSkipQuery,
+ });
+
if (!hasEnterpriseAccess) {
return (
@@ -64,6 +73,34 @@ export const SettingsAIUsageTab = () => {
);
}
+ if (isInitialLoading) {
+ return ;
+ }
+
+ const hasData =
+ analytics &&
+ (analytics.timeSeries.length > 0 ||
+ analytics.usageByOperationType.length > 0 ||
+ analytics.usageByModel.length > 0 ||
+ analytics.usageByUser.length > 0);
+
+ if (!hasData) {
+ return (
+
+ );
+ }
+
return (
<>
UsageAnalyticsDTO)
@UseGuards(
WorkspaceAuthGuard,
- FeatureFlagGuard,
SettingsPermissionGuard(PermissionFlagType.WORKSPACE),
)
- @RequireFeatureFlag(FeatureFlagKey.IS_USAGE_ANALYTICS_ENABLED)
async getUsageAnalytics(
@AuthWorkspace() workspace: WorkspaceEntity,
@Args('input', { nullable: true }) input?: UsageAnalyticsInput,
diff --git a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts
index 8deff0cbb86..c5ce5d4ae10 100644
--- a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts
+++ b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts
@@ -240,7 +240,6 @@ describe('WorkspaceEntityManager', () => {
IS_JUNCTION_RELATIONS_ENABLED: false,
IS_DRAFT_EMAIL_ENABLED: false,
IS_CONNECTED_ACCOUNT_MIGRATED: false,
- IS_USAGE_ANALYTICS_ENABLED: false,
IS_RICH_TEXT_V1_MIGRATED: false,
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: false,
IS_RECORD_TABLE_WIDGET_ENABLED: false,
diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts
index 8c9d29224bf..b842a2f2386 100644
--- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts
@@ -55,11 +55,6 @@ export const seedFeatureFlags = async ({
workspaceId: workspaceId,
value: true,
},
- {
- key: FeatureFlagKey.IS_USAGE_ANALYTICS_ENABLED,
- workspaceId: workspaceId,
- value: true,
- },
{
key: FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED,
workspaceId: workspaceId,
diff --git a/packages/twenty-shared/src/types/FeatureFlagKey.ts b/packages/twenty-shared/src/types/FeatureFlagKey.ts
index 3037f663012..19959e0d8fc 100644
--- a/packages/twenty-shared/src/types/FeatureFlagKey.ts
+++ b/packages/twenty-shared/src/types/FeatureFlagKey.ts
@@ -10,7 +10,6 @@ export enum FeatureFlagKey {
IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED',
IS_DRAFT_EMAIL_ENABLED = 'IS_DRAFT_EMAIL_ENABLED',
IS_CONNECTED_ACCOUNT_MIGRATED = 'IS_CONNECTED_ACCOUNT_MIGRATED',
- IS_USAGE_ANALYTICS_ENABLED = 'IS_USAGE_ANALYTICS_ENABLED',
IS_RICH_TEXT_V1_MIGRATED = 'IS_RICH_TEXT_V1_MIGRATED',
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED = 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED',
IS_RECORD_TABLE_WIDGET_ENABLED = 'IS_RECORD_TABLE_WIDGET_ENABLED',