From 80c5cfc2ece68bc24632b25330d19d13f4747ece Mon Sep 17 00:00:00 2001 From: martmull Date: Sat, 4 Apr 2026 09:20:11 +0200 Subject: [PATCH] Add npm packages app settings list (#19324) solves https://discord.com/channels/1130383047699738754/1488499318762635344 --- .../src/metadata/generated/schema.graphql | 1 + .../src/metadata/generated/schema.ts | 3 +- .../src/generated-metadata/graphql.ts | 1 + .../applications/SettingsApplications.tsx | 22 +-- .../components/SettingsApplicationsTable.tsx | 81 +++++------ .../tabs/SettingsApplicationsDeveloperTab.tsx | 134 +++++++++++++++++- .../workspace-entity-manager.spec.ts | 1 + .../core/utils/seed-feature-flags.util.ts | 5 + .../twenty-shared/src/types/FeatureFlagKey.ts | 1 + .../command-block/components/CommandBlock.tsx | 2 +- 10 files changed, 196 insertions(+), 55 deletions(-) diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index d2e049d16af..d1bcde89179 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -1724,6 +1724,7 @@ enum FeatureFlagKey { IS_AI_ENABLED IS_COMMAND_MENU_ITEM_ENABLED IS_MARKETPLACE_ENABLED + IS_MARKETPLACE_SETTING_TAB_VISIBLE IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED IS_PUBLIC_DOMAIN_ENABLED IS_EMAILING_DOMAIN_ENABLED diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index 3d2d71a5b29..dcc9001b10f 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -1424,7 +1424,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_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | '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_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_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED' export interface ClientConfigMaintenanceMode { startAt: Scalars['DateTime'] @@ -9092,6 +9092,7 @@ export const enumFeatureFlagKey = { IS_AI_ENABLED: 'IS_AI_ENABLED' as const, IS_COMMAND_MENU_ITEM_ENABLED: 'IS_COMMAND_MENU_ITEM_ENABLED' as const, IS_MARKETPLACE_ENABLED: 'IS_MARKETPLACE_ENABLED' as const, + IS_MARKETPLACE_SETTING_TAB_VISIBLE: 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' as const, IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' as const, IS_PUBLIC_DOMAIN_ENABLED: 'IS_PUBLIC_DOMAIN_ENABLED' as const, IS_EMAILING_DOMAIN_ENABLED: 'IS_EMAILING_DOMAIN_ENABLED' as const, diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index d82b144a653..f0209c00725 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1729,6 +1729,7 @@ export enum FeatureFlagKey { IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED', IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED', IS_MARKETPLACE_ENABLED = 'IS_MARKETPLACE_ENABLED', + IS_MARKETPLACE_SETTING_TAB_VISIBLE = 'IS_MARKETPLACE_SETTING_TAB_VISIBLE', IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED', IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED', IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED = 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED', diff --git a/packages/twenty-front/src/pages/settings/applications/SettingsApplications.tsx b/packages/twenty-front/src/pages/settings/applications/SettingsApplications.tsx index 9e2e69ce735..e522f12a696 100644 --- a/packages/twenty-front/src/pages/settings/applications/SettingsApplications.tsx +++ b/packages/twenty-front/src/pages/settings/applications/SettingsApplications.tsx @@ -11,7 +11,7 @@ import { getSettingsPath } from 'twenty-shared/utils'; import { IconApps, IconCode, IconDownload } from 'twenty-ui/display'; import { useQuery } from '@apollo/client/react'; import { - type FeatureFlagKey, + FeatureFlagKey, PermissionFlagType, FindManyApplicationsDocument, } from '~/generated-metadata/graphql'; @@ -33,6 +33,10 @@ export const SettingsApplications = () => { 'IS_MARKETPLACE_ENABLED' as FeatureFlagKey, ); + const isMarketplaceSettingTabVisible = useIsFeatureEnabled( + FeatureFlagKey.IS_MARKETPLACE_SETTING_TAB_VISIBLE, + ); + const activeTabId = useAtomComponentStateValue( activeTabIdComponentState, APPLICATIONS_TAB_LIST_ID, @@ -65,7 +69,9 @@ export const SettingsApplications = () => { } const tabs = [ - { id: 'marketplace', title: t`Marketplace`, Icon: IconDownload }, + ...(isMarketplaceSettingTabVisible + ? [{ id: 'marketplace', title: t`Marketplace`, Icon: IconDownload }] + : []), { id: 'installed', title: t`Installed`, Icon: IconApps }, ...(hasDeveloperAccess ? [{ id: 'developer', title: t`Developer`, Icon: IconCode }] @@ -81,7 +87,11 @@ export const SettingsApplications = () => { case 'developer': return ; default: - return ; + return isMarketplaceSettingTabVisible ? ( + + ) : ( + + ); } }; @@ -97,11 +107,7 @@ export const SettingsApplications = () => { ]} > - + {renderActiveTabContent()} diff --git a/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationsTable.tsx b/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationsTable.tsx index 893318b475b..317731637d1 100644 --- a/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationsTable.tsx +++ b/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationsTable.tsx @@ -22,8 +22,9 @@ const StyledTableContainer = styled.div` margin-top: ${themeCssVariables.spacing[3]}; `; -const StyledTableHeaderRowContainer = styled.div` - margin-bottom: ${themeCssVariables.spacing[2]}; +const StyledTableRowsContainer = styled.div` + border-bottom: 1px solid ${themeCssVariables.border.color.light}; + padding: ${themeCssVariables.spacing[2]} 0; `; const StyledSearchInputContainer = styled.div` @@ -64,47 +65,47 @@ export const SettingsApplicationsTable = ({ - - - {t`Name`} - {t`Description`} - {''} - - - - {filteredApplications.map((application) => { - const isNpmApp = - application.applicationRegistration?.sourceType === - ApplicationRegistrationSourceType.NPM; + + {t`Name`} + {t`Description`} + {''} + + + + {filteredApplications.map((application) => { + const isNpmApp = + application.applicationRegistration?.sourceType === + ApplicationRegistrationSourceType.NPM; - const latestVersion = - application.applicationRegistration?.latestAvailableVersion; + const latestVersion = + application.applicationRegistration?.latestAvailableVersion; - const hasUpdate = - isNpmApp && - isDefined(latestVersion) && - isDefined(application.version) && - isNewerSemver(latestVersion, application.version); + const hasUpdate = + isNpmApp && + isDefined(latestVersion) && + isDefined(application.version) && + isNewerSemver(latestVersion, application.version); - return ( - - } - link={getSettingsPath(SettingsPath.ApplicationDetail, { - applicationId: application.id, - })} - /> - ); - })} + return ( + + } + link={getSettingsPath(SettingsPath.ApplicationDetail, { + applicationId: application.id, + })} + /> + ); + })} +
diff --git a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationsDeveloperTab.tsx b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationsDeveloperTab.tsx index 6528a059bf2..763c5bea551 100644 --- a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationsDeveloperTab.tsx +++ b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationsDeveloperTab.tsx @@ -1,34 +1,68 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { SettingsListCard } from '@/settings/components/SettingsListCard'; +import { + StyledActionTableCell, + StyledNameTableCell, +} from '@/settings/data-model/object-details/components/SettingsObjectItemTableRowStyledComponents'; import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl'; +import { Table } from '@/ui/layout/table/components/Table'; +import { TableCell } from '@/ui/layout/table/components/TableCell'; +import { TableHeader } from '@/ui/layout/table/components/TableHeader'; +import { TableRow } from '@/ui/layout/table/components/TableRow'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { useQuery } from '@apollo/client/react'; import { styled } from '@linaria/react'; import { useLingui } from '@lingui/react/macro'; +import { useContext, useMemo, useState } from 'react'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; import { Tag } from 'twenty-ui/components'; import { + Avatar, CommandBlock, H2Title, IconApps, + IconChevronRight, IconCopy, IconFileInfo, + OverflowingTextWithTooltip, } from 'twenty-ui/display'; -import { Button } from 'twenty-ui/input'; +import { Button, SearchInput } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; -import { themeCssVariables } from 'twenty-ui/theme-constants'; +import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { type ApplicationRegistrationFragmentFragment, ApplicationRegistrationSourceType, + FeatureFlagKey, FindManyApplicationRegistrationsDocument, } from '~/generated-metadata/graphql'; import { useCopyToClipboard } from '~/hooks/useCopyToClipboard'; +import { useMarketplaceApps } from '~/modules/marketplace/hooks/useMarketplaceApps'; const StyledButtonContainer = styled.div` + display: flex; + justify-content: flex-end; margin: ${themeCssVariables.spacing[2]} 0; `; +const StyledSearchInputContainer = styled.div` + padding-bottom: ${themeCssVariables.spacing[2]}; +`; + +const StyledEmptyState = styled.div` + color: ${themeCssVariables.font.color.tertiary}; + padding: ${themeCssVariables.spacing[8]}; + text-align: center; +`; + +const StyledTableRowsContainer = styled.div` + border-bottom: 1px solid ${themeCssVariables.border.color.light}; + padding: ${themeCssVariables.spacing[2]} 0; +`; + +const NPM_PACKAGES_GRID_COLUMNS = '200px 1fr 36px'; + const SOURCE_TYPE_BADGE_CONFIG: Record< ApplicationRegistrationSourceType, { label: string; color: 'gray' | 'blue' | 'green' } @@ -53,12 +87,34 @@ const SOURCE_TYPE_BADGE_CONFIG: Record< export const SettingsApplicationsDeveloperTab = () => { const { t } = useLingui(); + const { theme } = useContext(ThemeContext); const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState); const { copyToClipboard } = useCopyToClipboard(); const { data, loading } = useQuery(FindManyApplicationRegistrationsDocument); + const isMarketplaceSettingTabVisible = useIsFeatureEnabled( + FeatureFlagKey.IS_MARKETPLACE_SETTING_TAB_VISIBLE, + ); + + const [npmSearchTerm, setNpmSearchTerm] = useState(''); + const { data: marketplaceApps } = useMarketplaceApps(); + + const filteredMarketplaceApps = useMemo(() => { + if (!npmSearchTerm) { + return marketplaceApps; + } + + const lowerSearch = npmSearchTerm.toLowerCase(); + + return marketplaceApps.filter( + (application) => + application.name.toLowerCase().includes(lowerSearch) || + application.description.toLowerCase().includes(lowerSearch), + ); + }, [marketplaceApps, npmSearchTerm]); + const registrations: ApplicationRegistrationFragmentFragment[] = data?.findManyApplicationRegistrations ?? []; @@ -123,8 +179,8 @@ export const SettingsApplicationsDeveloperTab = () => { <>
@@ -146,7 +202,7 @@ export const SettingsApplicationsDeveloperTab = () => {
{registrations.length > 0 ? ( @@ -164,6 +220,74 @@ export const SettingsApplicationsDeveloperTab = () => { ) )}
+ + {!isMarketplaceSettingTabVisible && ( +
+ + + + + {filteredMarketplaceApps.length === 0 ? ( + {t`No application found`} + ) : ( + + + {t`Name`} + {t`Description`} + + + + {filteredMarketplaceApps.map((application) => ( + + + + + + + + + + + + + ))} + +
+ )} +
+ )} ); }; 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 aff69507958..46a54090530 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 @@ -234,6 +234,7 @@ describe('WorkspaceEntityManager', () => { IS_JSON_FILTER_ENABLED: false, IS_AI_ENABLED: false, IS_MARKETPLACE_ENABLED: false, + IS_MARKETPLACE_SETTING_TAB_VISIBLE: false, IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: false, IS_PUBLIC_DOMAIN_ENABLED: false, IS_EMAILING_DOMAIN_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 51f3df2b1ab..0746494adbc 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 @@ -50,6 +50,11 @@ export const seedFeatureFlags = async ({ workspaceId: workspaceId, value: true, }, + { + key: FeatureFlagKey.IS_MARKETPLACE_SETTING_TAB_VISIBLE, + workspaceId: workspaceId, + value: true, + }, { key: FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED, workspaceId: workspaceId, diff --git a/packages/twenty-shared/src/types/FeatureFlagKey.ts b/packages/twenty-shared/src/types/FeatureFlagKey.ts index 88406d6167a..635fbf6fc79 100644 --- a/packages/twenty-shared/src/types/FeatureFlagKey.ts +++ b/packages/twenty-shared/src/types/FeatureFlagKey.ts @@ -4,6 +4,7 @@ export enum FeatureFlagKey { IS_AI_ENABLED = 'IS_AI_ENABLED', IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED', IS_MARKETPLACE_ENABLED = 'IS_MARKETPLACE_ENABLED', + IS_MARKETPLACE_SETTING_TAB_VISIBLE = 'IS_MARKETPLACE_SETTING_TAB_VISIBLE', IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED', IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED', IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED', diff --git a/packages/twenty-ui/src/display/command-block/components/CommandBlock.tsx b/packages/twenty-ui/src/display/command-block/components/CommandBlock.tsx index 55882d63fee..547d1710e37 100644 --- a/packages/twenty-ui/src/display/command-block/components/CommandBlock.tsx +++ b/packages/twenty-ui/src/display/command-block/components/CommandBlock.tsx @@ -5,7 +5,7 @@ import { themeCssVariables } from '@ui/theme-constants'; const StyledContainer = styled.div` border-radius: ${themeCssVariables.border.radius.sm}; border: 1px solid ${themeCssVariables.border.color.medium}; - background: ${themeCssVariables.background.transparent.secondary}; + background: ${themeCssVariables.background.secondary}; display: flex; justify-content: space-between; padding: ${themeCssVariables.spacing[3]};