diff --git a/packages/twenty-front/src/generated-admin/graphql.ts b/packages/twenty-front/src/generated-admin/graphql.ts index 5d747ee6b6..bd4ae1a954 100644 --- a/packages/twenty-front/src/generated-admin/graphql.ts +++ b/packages/twenty-front/src/generated-admin/graphql.ts @@ -415,6 +415,7 @@ export type Mutation = { setAdminAiModelsRecommended: Scalars['Boolean']['output']; setAdminDefaultAiModel: Scalars['Boolean']['output']; setMaintenanceMode: Scalars['Boolean']['output']; + syncMarketplaceCatalog: Scalars['Boolean']['output']; updateAdminApplicationRegistration: ApplicationRegistration; updateAdminApplicationRegistrationVariable: ApplicationRegistrationVariableDto; updateDatabaseConfigVariable: Scalars['Boolean']['output']; @@ -1007,6 +1008,11 @@ export type BackfillApplicationInstallationMutationVariables = Exact<{ export type BackfillApplicationInstallationMutation = { __typename?: 'Mutation', backfillApplicationInstallation: boolean }; +export type SyncMarketplaceCatalogMutationVariables = Exact<{ [key: string]: never; }>; + + +export type SyncMarketplaceCatalogMutation = { __typename?: 'Mutation', syncMarketplaceCatalog: boolean }; + export type UpdateAdminApplicationRegistrationMutationVariables = Exact<{ input: UpdateApplicationRegistrationInput; }>; @@ -1268,6 +1274,7 @@ export const GetAiProvidersDocument = {"kind":"Document","definitions":[{"kind": export const GetModelsDevProvidersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetModelsDevProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getModelsDevProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"modelCount"}},{"kind":"Field","name":{"kind":"Name","value":"npm"}}]}}]}}]} as unknown as DocumentNode; export const GetModelsDevSuggestionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetModelsDevSuggestions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerType"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getModelsDevSuggestions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"providerType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerType"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"modelId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"inputCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"outputCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"cachedInputCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"cacheCreationCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"contextWindowTokens"}},{"kind":"Field","name":{"kind":"Name","value":"maxOutputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"modalities"}},{"kind":"Field","name":{"kind":"Name","value":"supportsReasoning"}}]}}]}}]} as unknown as DocumentNode; export const BackfillApplicationInstallationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"BackfillApplicationInstallation"},"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":"backfillApplicationInstallation"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"applicationRegistrationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}}}]}]}}]} as unknown as DocumentNode; +export const SyncMarketplaceCatalogDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SyncMarketplaceCatalog"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"syncMarketplaceCatalog"}}]}}]} as unknown as DocumentNode; export const UpdateAdminApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateAdminApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateApplicationRegistrationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateAdminApplicationRegistration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"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":"isFeatured"}},{"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 UpdateAdminApplicationRegistrationVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateAdminApplicationRegistrationVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateApplicationRegistrationVariableInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateAdminApplicationRegistrationVariable"},"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":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"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":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} 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":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} 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 62ff0e4018..64471e2791 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 { AppChip } from '@/applications/components/AppChip'; import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient'; +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; @@ -8,15 +9,15 @@ import { TableBody } from '@/ui/layout/table/components/TableBody'; 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 { useQuery } from '@apollo/client/react'; +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 { SettingsPath } from 'twenty-shared/types'; -import { IconChevronRight, IconPinned } from 'twenty-ui/icon'; +import { IconChevronRight, IconPinned, IconRefresh } from 'twenty-ui/icon'; import { H2Title } from 'twenty-ui/typography'; -import { SearchInput } from 'twenty-ui/input'; +import { Button, SearchInput } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; import { MenuItemToggle } from 'twenty-ui/navigation'; import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants'; @@ -25,6 +26,7 @@ import { type ApplicationRegistrationFragmentFragment, ApplicationRegistrationSourceType, FindAllApplicationRegistrationsDocument, + SyncMarketplaceCatalogDocument, } from '~/generated-admin/graphql'; const StyledTableContainer = styled.div` @@ -37,6 +39,7 @@ const TABLE_GRID_MOBILE = '3fr 3fr 1fr 1fr 40px'; export const SettingsAdminApps = () => { const apolloAdminClient = useApolloAdminClient(); + const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar(); const [searchQuery, setSearchQuery] = useState(''); const [showPreInstalledOnly, setShowPreInstalledOnly] = useState(false); @@ -44,6 +47,24 @@ export const SettingsAdminApps = () => { client: apolloAdminClient, }); + const [syncMarketplaceCatalog, { loading: isSyncing }] = useMutation( + SyncMarketplaceCatalogDocument, + { client: apolloAdminClient }, + ); + + const handleSyncCatalog = async () => { + try { + await syncMarketplaceCatalog(); + enqueueSuccessSnackBar({ + message: t`Marketplace catalog synchronization started.`, + }); + } catch { + enqueueErrorSnackBar({ + message: t`Failed to synchronize the marketplace catalog.`, + }); + } + }; + const registrations = data?.findAllApplicationRegistrations ?? []; const query = searchQuery.trim().toLowerCase(); @@ -88,63 +109,80 @@ export const SettingsAdminApps = () => { }; return ( -
- - ( - - - - setShowPreInstalledOnly(!showPreInstalledOnly) - } - toggled={showPreInstalledOnly} - text={t`Pre-installed only`} - toggleSize="small" - /> - - - } - /> - )} - /> - - - - {t`Name`} - {t`Source`} - {t`Listed`} - {t`Configured`} - - - - {filtered.map((registration) => ( - - ))} - -
-
-
+ <> +
+ +
+
+ + ( + + + + setShowPreInstalledOnly(!showPreInstalledOnly) + } + toggled={showPreInstalledOnly} + text={t`Pre-installed only`} + toggleSize="small" + /> + + + } + /> + )} + /> + + + + {t`Name`} + {t`Source`} + {t`Listed`} + {t`Configured`} + + + + {filtered.map((registration) => ( + + ))} + +
+
+
+ ); }; diff --git a/packages/twenty-front/src/modules/settings/admin-panel/apps/graphql/mutations/syncMarketplaceCatalog.ts b/packages/twenty-front/src/modules/settings/admin-panel/apps/graphql/mutations/syncMarketplaceCatalog.ts new file mode 100644 index 0000000000..071334e41b --- /dev/null +++ b/packages/twenty-front/src/modules/settings/admin-panel/apps/graphql/mutations/syncMarketplaceCatalog.ts @@ -0,0 +1,7 @@ +import { gql } from '@apollo/client'; + +export const SYNC_MARKETPLACE_CATALOG = gql` + mutation SyncMarketplaceCatalog { + syncMarketplaceCatalog + } +`; diff --git a/packages/twenty-server/src/engine/core-modules/actor/utils/build-created-by-from-full-name-metadata.util.ts b/packages/twenty-server/src/engine/core-modules/actor/utils/build-created-by-from-full-name-metadata.util.ts index 5fbcd5a5db..f12433622e 100644 --- a/packages/twenty-server/src/engine/core-modules/actor/utils/build-created-by-from-full-name-metadata.util.ts +++ b/packages/twenty-server/src/engine/core-modules/actor/utils/build-created-by-from-full-name-metadata.util.ts @@ -3,7 +3,6 @@ import { FieldActorSource, type FullNameMetadata, } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; type BuildCreatedByFromFullNameMetadataArgs = { workspaceMemberId: string; diff --git a/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel.resolver.ts b/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel.resolver.ts index 4edd52812e..2e198d5d85 100644 --- a/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel.resolver.ts @@ -65,6 +65,7 @@ import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/service import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter'; import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe'; import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { MarketplaceCatalogSyncCronJob } from 'src/engine/core-modules/application/application-marketplace/crons/marketplace-catalog-sync.cron.job'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; @@ -139,8 +140,10 @@ export class AdminPanelResolver { private readonly upgradeStatusService: UpgradeStatusService, @InjectRepository(WorkspaceEntity) private readonly workspaceRepository: Repository, + @InjectMessageQueue(MessageQueue.cronQueue) + private readonly cronQueueService: MessageQueueService, @InjectMessageQueue(MessageQueue.workspaceQueue) - private readonly messageQueueService: MessageQueueService, + private readonly workspaceQueueService: MessageQueueService, ) {} @UseGuards(AdminPanelOrImpersonateGuard) @@ -479,6 +482,18 @@ export class AdminPanelResolver { return this.applicationRegistrationService.findAll(); } + @UseGuards(AdminPanelGuard) + @Mutation(() => Boolean) + async syncMarketplaceCatalog(): Promise { + await this.cronQueueService.add( + MarketplaceCatalogSyncCronJob.name, + {}, + { id: 'marketplace-catalog-sync' }, // Avoids triggering multiple pending jobs + ); + + return true; + } + @UseGuards(AdminPanelGuard) @Mutation(() => ApplicationRegistrationEntity) async updateAdminApplicationRegistration( @@ -503,7 +518,7 @@ export class AdminPanelResolver { ); } - await this.messageQueueService.add( + await this.workspaceQueueService.add( BACKFILL_APPLICATION_INSTALLATION_JOB_NAME, { applicationRegistrationId }, {