diff --git a/packages/twenty-front/src/generated-admin/graphql.ts b/packages/twenty-front/src/generated-admin/graphql.ts index 504f58ee17..7d9cf00b9d 100644 --- a/packages/twenty-front/src/generated-admin/graphql.ts +++ b/packages/twenty-front/src/generated-admin/graphql.ts @@ -325,7 +325,8 @@ export enum FeatureFlagKey { export type FindApplicationRegistrationInstalledWorkspacesInput = { id: Scalars['String']['input']; - page?: Scalars['Int']['input']; + limit?: Scalars['Int']['input']; + offset?: Scalars['Int']['input']; searchTerm?: InputMaybe; }; @@ -576,6 +577,13 @@ export type MutationUpdateWorkspaceFeatureFlagArgs = { workspaceId: Scalars['UUID']['input']; }; +export type PaginatedApplicationRegistrations = { + __typename?: 'PaginatedApplicationRegistrations'; + hasMore: Scalars['Boolean']['output']; + registrations: Array; + totalCount: Scalars['Int']['output']; +}; + export type Query = { __typename?: 'Query'; adminPanelRecentUsers: Array; @@ -583,7 +591,7 @@ export type Query = { findAdminApplicationRegistrationInstalledWorkspaces: ApplicationRegistrationInstalledWorkspaces; findAdminApplicationRegistrationStats: ApplicationRegistrationStats; findAdminApplicationRegistrationVariables: Array; - findAllApplicationRegistrations: Array; + findAllApplicationRegistrations: PaginatedApplicationRegistrations; findOneAdminApplicationRegistration: ApplicationRegistration; getAdminAiModels: AdminAiModels; getAdminAiUsageByWorkspace: Array; @@ -635,6 +643,14 @@ export type QueryFindAdminApplicationRegistrationVariablesArgs = { }; +export type QueryFindAllApplicationRegistrationsArgs = { + isPreInstalledOnly?: InputMaybe; + limit?: InputMaybe; + offset?: InputMaybe; + searchTerm?: InputMaybe; +}; + + export type QueryFindOneAdminApplicationRegistrationArgs = { id: Scalars['String']['input']; }; @@ -1096,10 +1112,15 @@ export type FindAdminApplicationRegistrationVariablesQueryVariables = Exact<{ export type FindAdminApplicationRegistrationVariablesQuery = { __typename?: 'Query', findAdminApplicationRegistrationVariables: Array<{ __typename?: 'ApplicationRegistrationVariableDTO', id: string, key: string, value?: string | null, description: string, isSecret: boolean, isRequired: boolean, isFilled: boolean, type: string, options?: any | null, createdAt: string, updatedAt: string }> }; -export type FindAllApplicationRegistrationsQueryVariables = Exact<{ [key: string]: never; }>; +export type FindAllApplicationRegistrationsQueryVariables = Exact<{ + limit?: InputMaybe; + offset?: InputMaybe; + searchTerm?: InputMaybe; + isPreInstalledOnly?: InputMaybe; +}>; -export type FindAllApplicationRegistrationsQuery = { __typename?: 'Query', findAllApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, oAuthClientId: string, oAuthRedirectUris: Array, oAuthScopes: Array, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isVetted: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> }; +export type FindAllApplicationRegistrationsQuery = { __typename?: 'Query', findAllApplicationRegistrations: { __typename?: 'PaginatedApplicationRegistrations', hasMore: boolean, registrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, oAuthClientId: string, oAuthRedirectUris: Array, oAuthScopes: Array, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isVetted: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> } }; export type CreateDatabaseConfigVariableMutationVariables = Exact<{ key: Scalars['String']['input']; @@ -1342,7 +1363,7 @@ export const UpdateAdminApplicationRegistrationVariableDocument = {"kind":"Docum export const FindAdminApplicationRegistrationInstalledWorkspacesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindAdminApplicationRegistrationInstalledWorkspaces"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"FindApplicationRegistrationInstalledWorkspacesInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAdminApplicationRegistrationInstalledWorkspaces"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"hasMore"}},{"kind":"Field","name":{"kind":"Name","value":"workspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"version"}}]}}]}}]}}]} as unknown as DocumentNode; export const FindAdminApplicationRegistrationStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindAdminApplicationRegistrationStats"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAdminApplicationRegistrationStats"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeInstalls"}},{"kind":"Field","name":{"kind":"Name","value":"mostInstalledVersion"}},{"kind":"Field","name":{"kind":"Name","value":"versionDistribution"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]} as unknown as DocumentNode; export const FindAdminApplicationRegistrationVariablesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindAdminApplicationRegistrationVariables"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAdminApplicationRegistrationVariables"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"applicationRegistrationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"isSecret"}},{"kind":"Field","name":{"kind":"Name","value":"isRequired"}},{"kind":"Field","name":{"kind":"Name","value":"isFilled"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"options"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; -export const FindAllApplicationRegistrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindAllApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAllApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; +export const FindAllApplicationRegistrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindAllApplicationRegistrations"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isPreInstalledOnly"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAllApplicationRegistrations"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}},{"kind":"Argument","name":{"kind":"Name","value":"searchTerm"},"value":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}}},{"kind":"Argument","name":{"kind":"Name","value":"isPreInstalledOnly"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isPreInstalledOnly"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"registrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"hasMore"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const CreateDatabaseConfigVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateDatabaseConfigVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"value"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createDatabaseConfigVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}},{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"value"}}}]}]}}]} as unknown as DocumentNode; export const DeleteDatabaseConfigVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteDatabaseConfigVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteDatabaseConfigVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}}]}]}}]} as unknown as DocumentNode; export const UpdateDatabaseConfigVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateDatabaseConfigVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"value"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateDatabaseConfigVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}},{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"value"}}}]}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/modules/settings/admin-panel/apps/components/SettingsAdminApps.tsx b/packages/twenty-front/src/modules/settings/admin-panel/apps/components/SettingsAdminApps.tsx index 3de05adbd0..f7a78e9877 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/apps/components/SettingsAdminApps.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/apps/components/SettingsAdminApps.tsx @@ -1,5 +1,6 @@ import { ApplicationDisplay } from '@/applications/components/ApplicationDisplay'; import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient'; +import { SettingsEmptyPlaceholder } from '@/settings/components/SettingsEmptyPlaceholder'; import { StyledNameTableCell } from '@/settings/data-model/object-details/components/SettingsObjectItemTableRowStyledComponents'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; @@ -14,9 +15,19 @@ import { useMutation, useQuery } from '@apollo/client/react'; import { styled } from '@linaria/react'; import { t } from '@lingui/core/macro'; import { type ReactNode, useContext, useState } from 'react'; -import { assertUnreachable, getSettingsPath } from 'twenty-shared/utils'; +import { useDebounce } from 'use-debounce'; +import { + assertUnreachable, + getSettingsPath, + isDefined, +} from 'twenty-shared/utils'; import { SettingsPath } from 'twenty-shared/types'; -import { IconChevronRight, IconPinned, IconRefresh } from 'twenty-ui/icon'; +import { + IconChevronRight, + IconDotsVertical, + IconPinned, + IconRefresh, +} from 'twenty-ui/icon'; import { H2Title } from 'twenty-ui/typography'; import { Button, SearchInput } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; @@ -35,18 +46,36 @@ const StyledTableContainer = styled.div` margin-top: ${themeCssVariables.spacing[3]}; `; +const StyledShowMoreContainer = styled.div` + display: flex; + justify-content: center; + margin-top: ${themeCssVariables.spacing[2]}; +`; + const TABLE_GRID = '1fr 100px 100px 100px 40px'; const TABLE_GRID_MOBILE = '3fr 3fr 1fr 1fr 40px'; +const PAGE_SIZE = 25; export const SettingsAdminApps = () => { const apolloAdminClient = useApolloAdminClient(); const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar(); const [searchQuery, setSearchQuery] = useState(''); + const [debouncedSearchQuery] = useDebounce(searchQuery, 300); const [showPreInstalledOnly, setShowPreInstalledOnly] = useState(false); - const { data } = useQuery(FindAllApplicationRegistrationsDocument, { - client: apolloAdminClient, - }); + const { data, loading, fetchMore } = useQuery( + FindAllApplicationRegistrationsDocument, + { + client: apolloAdminClient, + notifyOnNetworkStatusChange: true, + variables: { + limit: PAGE_SIZE, + offset: 0, + searchTerm: debouncedSearchQuery, + isPreInstalledOnly: showPreInstalledOnly, + }, + }, + ); const [syncMarketplaceCatalog, { loading: isSyncing }] = useMutation( SyncMarketplaceCatalogDocument, @@ -66,27 +95,35 @@ export const SettingsAdminApps = () => { } }; - const registrations = data?.findAllApplicationRegistrations ?? []; + const registrations = [ + ...(data?.findAllApplicationRegistrations.registrations ?? []), + ].sort((a, b) => Number(a.isConfigured) - Number(b.isConfigured)); - const query = searchQuery.trim().toLowerCase(); + const hasMore = data?.findAllApplicationRegistrations.hasMore ?? false; - const filtered = registrations - .filter((registration) => { - if (showPreInstalledOnly && !registration.isPreInstalled) { - return false; - } + const handleShowMore = () => { + fetchMore({ + variables: { + limit: PAGE_SIZE, + offset: registrations.length, + }, + updateQuery: (previousData, { fetchMoreResult }) => { + if (!isDefined(fetchMoreResult)) { + return previousData; + } - if (query.length === 0) { - return true; - } - - return ( - registration.name.toLowerCase().includes(query) || - (registration.sourcePackage ?? '').toLowerCase().includes(query) || - registration.universalIdentifier.toLowerCase().includes(query) - ); - }) - .toSorted((a, b) => Number(a.isConfigured) - Number(b.isConfigured)); + return { + findAllApplicationRegistrations: { + ...fetchMoreResult.findAllApplicationRegistrations, + registrations: [ + ...previousData.findAllApplicationRegistrations.registrations, + ...fetchMoreResult.findAllApplicationRegistrations.registrations, + ], + }, + }; + }, + }); + }; const getFormattedSource = ( registration: ApplicationRegistrationFragmentFragment, @@ -159,29 +196,47 @@ export const SettingsAdminApps = () => { /> )} /> - - - - {t`Name`} - {t`Source`} - {t`Listed`} - {t`Configured`} - - - - {filtered.map((registration) => ( - - ))} - -
-
+ {loading && registrations.length === 0 ? ( + {t`Loading apps...`} + ) : registrations.length === 0 ? ( + {t`No apps found`} + ) : ( + + + + {t`Name`} + {t`Source`} + {t`Listed`} + {t`Configured`} + + + + {registrations.map((registration) => ( + + ))} + +
+
+ )} + {hasMore && ( + +