Refactor application module architecture for clarity and explicitness (#18432)

## Summary

- **Module reorganization**: Moved `ApplicationUpgradeService` and cron
jobs to `application-upgrade/`, `ApplicationSyncService` to
`application-manifest/`, and
`runWorkspaceMigration`/`uninstallApplication` mutations to the manifest
resolver — each module now has a single clear responsibility.
- **Explicit install flow**: Removed implicit `ApplicationEntity`
creation from `ApplicationSyncService`. The install service and dev
resolver now explicitly create the `ApplicationEntity` before syncing.
npm packages are resolved at registration time to extract manifest
metadata (universalIdentifier, name, description, etc.), eliminating the
`reconcileUniversalIdentifier` hack.
- **Better error handling**: Frontend hooks now surface actual server
error messages in snackbars instead of swallowing them. Replaced the
ugly `ConfirmationModal` for transfer ownership with a proper form
modal. Fixed `SettingsAdminTableCard` row height overflow and corrected
the `yarn-engine` asset path.

## Test plan
- [ ] Register an npm package — verify manifest metadata (name,
description, universalIdentifier) is extracted correctly
- [ ] Install a registered npm app on a workspace — verify
ApplicationEntity is created and sync succeeds
- [ ] Test `app:dev` CLI flow — verify local app registration and sync
work
- [ ] Upload a tarball — verify registration and install flow
- [ ] Transfer ownership — verify the new modal UX works
- [ ] Verify error messages appear correctly in snackbars when
operations fail


Made with [Cursor](https://cursor.com)
This commit is contained in:
Félix Malfait
2026-03-06 08:45:08 +01:00
committed by GitHub
parent 90cced0e74
commit 514d0017ea
184 changed files with 3179 additions and 1382 deletions
@@ -316,12 +316,6 @@ export type ApiKeyToken = {
token: Scalars['String'];
};
export enum AppRegistrationSourceType {
LOCAL = 'LOCAL',
NPM = 'NPM',
TARBALL = 'TARBALL'
}
export type AppToken = {
__typename?: 'AppToken';
createdAt: Scalars['DateTime'];
@@ -362,20 +356,28 @@ export type ApplicationRegistration = {
description?: Maybe<Scalars['String']>;
id: Scalars['UUID'];
isFeatured: Scalars['Boolean'];
isListed: Scalars['Boolean'];
latestAvailableVersion?: Maybe<Scalars['String']>;
logoUrl?: Maybe<Scalars['String']>;
name: Scalars['String'];
oAuthClientId: Scalars['String'];
oAuthRedirectUris: Array<Scalars['String']>;
oAuthScopes: Array<Scalars['String']>;
ownerWorkspaceId?: Maybe<Scalars['UUID']>;
sourcePackage?: Maybe<Scalars['String']>;
sourceType: AppRegistrationSourceType;
sourceType: ApplicationRegistrationSourceType;
termsUrl?: Maybe<Scalars['String']>;
universalIdentifier: Scalars['String'];
updatedAt: Scalars['DateTime'];
websiteUrl?: Maybe<Scalars['String']>;
};
export enum ApplicationRegistrationSourceType {
LOCAL = 'LOCAL',
NPM = 'NPM',
TARBALL = 'TARBALL'
}
export type ApplicationRegistrationStats = {
__typename?: 'ApplicationRegistrationStats';
activeInstalls: Scalars['Int'];
@@ -387,7 +389,7 @@ export type ApplicationRegistrationSummary = {
__typename?: 'ApplicationRegistrationSummary';
id: Scalars['UUID'];
latestAvailableVersion?: Maybe<Scalars['String']>;
sourceType: AppRegistrationSourceType;
sourceType: ApplicationRegistrationSourceType;
};
export type ApplicationRegistrationVariable = {
@@ -1086,15 +1088,6 @@ export type CreateAppTokenInput = {
expiresAt: Scalars['DateTime'];
};
export type CreateApplicationInput = {
applicationRegistrationId?: InputMaybe<Scalars['String']>;
description?: InputMaybe<Scalars['String']>;
name: Scalars['String'];
sourcePath: Scalars['String'];
universalIdentifier: Scalars['String'];
version: Scalars['String'];
};
export type CreateApplicationRegistration = {
__typename?: 'CreateApplicationRegistration';
applicationRegistration: ApplicationRegistration;
@@ -2235,6 +2228,7 @@ export type MarketplaceApp = {
frontComponents: Array<MarketplaceAppFrontComponent>;
icon: Scalars['String'];
id: Scalars['String'];
isFeatured: Scalars['Boolean'];
logicFunctions: Array<MarketplaceAppLogicFunction>;
logo?: Maybe<Scalars['String']>;
name: Scalars['String'];
@@ -2383,7 +2377,6 @@ export type Mutation = {
createObjectEvent: Analytics;
createOneAgent: Agent;
createOneAppToken: AppToken;
createOneApplication: Application;
createOneField: Field;
createOneLogicFunction: LogicFunction;
createOneObject: Object;
@@ -2456,7 +2449,7 @@ export type Mutation = {
initiateOTPProvisioningForAuthenticatedUser: InitiateTwoFactorAuthenticationProvisioning;
installApplication: Scalars['Boolean'];
installMarketplaceApp: Scalars['Boolean'];
installNpmApp: Scalars['Boolean'];
registerNpmPackage: ApplicationRegistration;
removeQueryFromEventStream: Scalars['Boolean'];
removeRoleFromAgent: Scalars['Boolean'];
renewApplicationToken: ApplicationTokenPair;
@@ -2467,6 +2460,7 @@ export type Mutation = {
revokeApiKey?: Maybe<ApiKey>;
rotateApplicationRegistrationClientSecret: RotateClientSecret;
runEvaluationInput: AgentTurn;
runWorkspaceMigration: Scalars['Boolean'];
saveImapSmtpCaldavAccount: ImapSmtpCaldavConnectionSuccess;
sendInvitations: SendInvitations;
setAdminAiModelEnabled: Scalars['Boolean'];
@@ -2482,6 +2476,7 @@ export type Mutation = {
switchSubscriptionInterval: BillingUpdate;
syncApplication: WorkspaceMigration;
trackAnalytics: Analytics;
transferApplicationRegistrationOwnership: ApplicationRegistration;
uninstallApplication: Scalars['Boolean'];
updateApiKey?: Maybe<ApiKey>;
updateApplicationRegistration: ApplicationRegistration;
@@ -2707,11 +2702,6 @@ export type MutationCreateOneAppTokenArgs = {
};
export type MutationCreateOneApplicationArgs = {
input: CreateApplicationInput;
};
export type MutationCreateOneFieldArgs = {
input: CreateOneFieldMetadataInput;
};
@@ -3042,7 +3032,8 @@ export type MutationInitiateOtpProvisioningArgs = {
export type MutationInstallApplicationArgs = {
workspaceMigration: WorkspaceMigrationInput;
appRegistrationId: Scalars['String'];
version?: InputMaybe<Scalars['String']>;
};
@@ -3052,9 +3043,8 @@ export type MutationInstallMarketplaceAppArgs = {
};
export type MutationInstallNpmAppArgs = {
export type MutationRegisterNpmPackageArgs = {
packageName: Scalars['String'];
version?: InputMaybe<Scalars['String']>;
};
@@ -3111,6 +3101,11 @@ export type MutationRunEvaluationInputArgs = {
};
export type MutationRunWorkspaceMigrationArgs = {
workspaceMigration: WorkspaceMigrationInput;
};
export type MutationSaveImapSmtpCaldavAccountArgs = {
accountOwnerId: Scalars['UUID'];
connectionParameters: EmailAccountConnectionParameters;
@@ -3184,6 +3179,12 @@ export type MutationTrackAnalyticsArgs = {
};
export type MutationTransferApplicationRegistrationOwnershipArgs = {
applicationRegistrationId: Scalars['String'];
targetWorkspaceSubdomain: Scalars['String'];
};
export type MutationUninstallApplicationArgs = {
universalIdentifier: Scalars['String'];
};
@@ -3932,6 +3933,7 @@ export type Query = {
agentTurns: Array<AgentTurn>;
apiKey?: Maybe<ApiKey>;
apiKeys: Array<ApiKey>;
applicationRegistrationTarballUrl?: Maybe<Scalars['String']>;
barChartData: BarChartData;
billingPortalSession: BillingSession;
chatMessages: Array<AgentMessage>;
@@ -3946,6 +3948,7 @@ export type Query = {
eventLogs: EventLogQueryResult;
field: Field;
fields: FieldConnection;
findAllApplicationRegistrations: Array<ApplicationRegistration>;
findApplicationRegistrationByClientId?: Maybe<PublicApplicationRegistration>;
findApplicationRegistrationByUniversalIdentifier?: Maybe<ApplicationRegistration>;
findApplicationRegistrationStats: ApplicationRegistrationStats;
@@ -3960,6 +3963,7 @@ export type Query = {
findOneApplication: Application;
findOneApplicationRegistration: ApplicationRegistration;
findOneLogicFunction: LogicFunction;
findOneMarketplaceApp: MarketplaceApp;
findWorkspaceFromInviteHash: Workspace;
findWorkspaceInvitations: Array<WorkspaceInvitation>;
frontComponent?: Maybe<FrontComponent>;
@@ -4035,6 +4039,11 @@ export type QueryApiKeyArgs = {
};
export type QueryApplicationRegistrationTarballUrlArgs = {
id: Scalars['String'];
};
export type QueryBarChartDataArgs = {
input: BarChartDataInput;
};
@@ -4135,6 +4144,11 @@ export type QueryFindOneLogicFunctionArgs = {
};
export type QueryFindOneMarketplaceAppArgs = {
universalIdentifier: Scalars['String'];
};
export type QueryFindWorkspaceFromInviteHashArgs = {
inviteHash: Scalars['String'];
};
@@ -4827,6 +4841,7 @@ export type UpdateApplicationRegistrationInput = {
export type UpdateApplicationRegistrationPayload = {
author?: InputMaybe<Scalars['String']>;
description?: InputMaybe<Scalars['String']>;
isListed?: InputMaybe<Scalars['Boolean']>;
logoUrl?: InputMaybe<Scalars['String']>;
name?: InputMaybe<Scalars['String']>;
oAuthRedirectUris?: InputMaybe<Array<Scalars['String']>>;
@@ -5818,19 +5833,26 @@ export type UpdateOneApplicationVariableMutationVariables = Exact<{
export type UpdateOneApplicationVariableMutation = { __typename?: 'Mutation', updateOneApplicationVariable: boolean };
export type ApplicationFieldsFragment = { __typename?: 'Application', id: string, name: string, description?: string | null, version?: string | null, universalIdentifier: string, applicationRegistrationId?: string | null, canBeUninstalled: boolean, defaultRoleId?: string | null, settingsCustomTabFrontComponentId?: string | null, availablePackages: any, applicationRegistration?: { __typename?: 'ApplicationRegistrationSummary', id: string, latestAvailableVersion?: string | null, sourceType: AppRegistrationSourceType } | null, applicationVariables: Array<{ __typename?: 'ApplicationVariable', id: string, key: string, value: string, description: string, isSecret: boolean }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array<string>, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, universalIdentifier: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, logicFunctions: Array<{ __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, cronTriggerSettings?: any | null, databaseEventTriggerSettings?: any | null, httpRouteTriggerSettings?: any | null, applicationId?: string | null, createdAt: string, updatedAt: string }> };
export type ApplicationFieldsFragment = { __typename?: 'Application', id: string, name: string, description?: string | null, version?: string | null, universalIdentifier: string, applicationRegistrationId?: string | null, canBeUninstalled: boolean, defaultRoleId?: string | null, settingsCustomTabFrontComponentId?: string | null, availablePackages: any, applicationRegistration?: { __typename?: 'ApplicationRegistrationSummary', id: string, latestAvailableVersion?: string | null, sourceType: ApplicationRegistrationSourceType } | null, applicationVariables: Array<{ __typename?: 'ApplicationVariable', id: string, key: string, value: string, description: string, isSecret: boolean }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array<string>, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, universalIdentifier: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, logicFunctions: Array<{ __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, cronTriggerSettings?: any | null, databaseEventTriggerSettings?: any | null, httpRouteTriggerSettings?: any | null, applicationId?: string | null, createdAt: string, updatedAt: string }> };
export type FindManyApplicationsQueryVariables = Exact<{ [key: string]: never; }>;
export type FindManyApplicationsQuery = { __typename?: 'Query', findManyApplications: Array<{ __typename?: 'Application', id: string, name: string, description?: string | null, version?: string | null, applicationRegistrationId?: string | null, applicationRegistration?: { __typename?: 'ApplicationRegistrationSummary', id: string, latestAvailableVersion?: string | null, sourceType: AppRegistrationSourceType } | null }> };
export type FindManyApplicationsQuery = { __typename?: 'Query', findManyApplications: Array<{ __typename?: 'Application', id: string, name: string, description?: string | null, version?: string | null, universalIdentifier: string, applicationRegistrationId?: string | null, applicationRegistration?: { __typename?: 'ApplicationRegistrationSummary', id: string, latestAvailableVersion?: string | null, sourceType: ApplicationRegistrationSourceType } | null }> };
export type FindOneApplicationQueryVariables = Exact<{
id: Scalars['UUID'];
}>;
export type FindOneApplicationQuery = { __typename?: 'Query', findOneApplication: { __typename?: 'Application', id: string, name: string, description?: string | null, version?: string | null, universalIdentifier: string, applicationRegistrationId?: string | null, canBeUninstalled: boolean, defaultRoleId?: string | null, settingsCustomTabFrontComponentId?: string | null, availablePackages: any, applicationRegistration?: { __typename?: 'ApplicationRegistrationSummary', id: string, latestAvailableVersion?: string | null, sourceType: AppRegistrationSourceType } | null, applicationVariables: Array<{ __typename?: 'ApplicationVariable', id: string, key: string, value: string, description: string, isSecret: boolean }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array<string>, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, universalIdentifier: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, logicFunctions: Array<{ __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, cronTriggerSettings?: any | null, databaseEventTriggerSettings?: any | null, httpRouteTriggerSettings?: any | null, applicationId?: string | null, createdAt: string, updatedAt: string }> } };
export type FindOneApplicationQuery = { __typename?: 'Query', findOneApplication: { __typename?: 'Application', id: string, name: string, description?: string | null, version?: string | null, universalIdentifier: string, applicationRegistrationId?: string | null, canBeUninstalled: boolean, defaultRoleId?: string | null, settingsCustomTabFrontComponentId?: string | null, availablePackages: any, applicationRegistration?: { __typename?: 'ApplicationRegistrationSummary', id: string, latestAvailableVersion?: string | null, sourceType: ApplicationRegistrationSourceType } | null, applicationVariables: Array<{ __typename?: 'ApplicationVariable', id: string, key: string, value: string, description: string, isSecret: boolean }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array<string>, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, universalIdentifier: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, logicFunctions: Array<{ __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, cronTriggerSettings?: any | null, databaseEventTriggerSettings?: any | null, httpRouteTriggerSettings?: any | null, applicationId?: string | null, createdAt: string, updatedAt: string }> } };
export type FindOneApplicationByUniversalIdentifierQueryVariables = Exact<{
universalIdentifier: Scalars['UUID'];
}>;
export type FindOneApplicationByUniversalIdentifierQuery = { __typename?: 'Query', findOneApplication: { __typename?: 'Application', id: string } };
export type AuthTokenFragmentFragment = { __typename?: 'AuthToken', token: string, expiresAt: string };
@@ -6225,7 +6247,15 @@ export type GetLogicFunctionSourceCodeQueryVariables = Exact<{
export type GetLogicFunctionSourceCodeQuery = { __typename?: 'Query', getLogicFunctionSourceCode?: string | null };
export type MarketplaceAppFieldsFragment = { __typename?: 'MarketplaceApp', id: string, name: string, description: string, icon: string, version: string, author: string, category: string, logo?: string | null, screenshots: Array<string>, aboutDescription: string, providers: Array<string>, websiteUrl?: string | null, termsUrl?: string | null, sourcePackage?: string | null, objects: Array<{ __typename?: 'MarketplaceAppObject', universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, fields: Array<{ __typename?: 'MarketplaceAppField', universalIdentifier?: string | null, name: string, type: string, label: string, description?: string | null, icon?: string | null }> }>, fields: Array<{ __typename?: 'MarketplaceAppField', name: string, type: string, label: string, description?: string | null, icon?: string | null, objectUniversalIdentifier?: string | null }>, logicFunctions: Array<{ __typename?: 'MarketplaceAppLogicFunction', name: string, description?: string | null, timeoutSeconds?: number | null }>, frontComponents: Array<{ __typename?: 'MarketplaceAppFrontComponent', name: string, description?: string | null }>, defaultRole?: { __typename?: 'MarketplaceAppDefaultRole', id: string, label: string, description?: string | null, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canUpdateAllSettings: boolean, canAccessAllTools: boolean, permissionFlags: Array<string>, objectPermissions: Array<{ __typename?: 'MarketplaceAppRoleObjectPermission', objectUniversalIdentifier: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }>, fieldPermissions: Array<{ __typename?: 'MarketplaceAppRoleFieldPermission', objectUniversalIdentifier: string, fieldUniversalIdentifier: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null }> } | null };
export type MarketplaceAppFieldsFragment = { __typename?: 'MarketplaceApp', id: string, name: string, description: string, icon: string, version: string, author: string, category: string, logo?: string | null, screenshots: Array<string>, aboutDescription: string, providers: Array<string>, websiteUrl?: string | null, termsUrl?: string | null, sourcePackage?: string | null, isFeatured: boolean, objects: Array<{ __typename?: 'MarketplaceAppObject', universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, fields: Array<{ __typename?: 'MarketplaceAppField', universalIdentifier?: string | null, name: string, type: string, label: string, description?: string | null, icon?: string | null }> }>, fields: Array<{ __typename?: 'MarketplaceAppField', name: string, type: string, label: string, description?: string | null, icon?: string | null, objectUniversalIdentifier?: string | null }>, logicFunctions: Array<{ __typename?: 'MarketplaceAppLogicFunction', name: string, description?: string | null, timeoutSeconds?: number | null }>, frontComponents: Array<{ __typename?: 'MarketplaceAppFrontComponent', name: string, description?: string | null }>, defaultRole?: { __typename?: 'MarketplaceAppDefaultRole', id: string, label: string, description?: string | null, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canUpdateAllSettings: boolean, canAccessAllTools: boolean, permissionFlags: Array<string>, objectPermissions: Array<{ __typename?: 'MarketplaceAppRoleObjectPermission', objectUniversalIdentifier: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }>, fieldPermissions: Array<{ __typename?: 'MarketplaceAppRoleFieldPermission', objectUniversalIdentifier: string, fieldUniversalIdentifier: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null }> } | null };
export type InstallApplicationMutationVariables = Exact<{
appRegistrationId: Scalars['String'];
version?: InputMaybe<Scalars['String']>;
}>;
export type InstallApplicationMutation = { __typename?: 'Mutation', installApplication: boolean };
export type InstallMarketplaceAppMutationVariables = Exact<{
universalIdentifier: Scalars['String'];
@@ -6235,13 +6265,12 @@ export type InstallMarketplaceAppMutationVariables = Exact<{
export type InstallMarketplaceAppMutation = { __typename?: 'Mutation', installMarketplaceApp: boolean };
export type InstallNpmAppMutationVariables = Exact<{
export type RegisterNpmPackageMutationVariables = Exact<{
packageName: Scalars['String'];
version?: InputMaybe<Scalars['String']>;
}>;
export type InstallNpmAppMutation = { __typename?: 'Mutation', installNpmApp: boolean };
export type RegisterNpmPackageMutation = { __typename?: 'Mutation', registerNpmPackage: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string } };
export type UpgradeApplicationMutationVariables = Exact<{
appRegistrationId: Scalars['String'];
@@ -6262,7 +6291,14 @@ export type UploadAppTarballMutation = { __typename?: 'Mutation', uploadAppTarba
export type FindManyMarketplaceAppsQueryVariables = Exact<{ [key: string]: never; }>;
export type FindManyMarketplaceAppsQuery = { __typename?: 'Query', findManyMarketplaceApps: Array<{ __typename?: 'MarketplaceApp', id: string, name: string, description: string, icon: string, version: string, author: string, category: string, logo?: string | null, screenshots: Array<string>, aboutDescription: string, providers: Array<string>, websiteUrl?: string | null, termsUrl?: string | null, sourcePackage?: string | null, objects: Array<{ __typename?: 'MarketplaceAppObject', universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, fields: Array<{ __typename?: 'MarketplaceAppField', universalIdentifier?: string | null, name: string, type: string, label: string, description?: string | null, icon?: string | null }> }>, fields: Array<{ __typename?: 'MarketplaceAppField', name: string, type: string, label: string, description?: string | null, icon?: string | null, objectUniversalIdentifier?: string | null }>, logicFunctions: Array<{ __typename?: 'MarketplaceAppLogicFunction', name: string, description?: string | null, timeoutSeconds?: number | null }>, frontComponents: Array<{ __typename?: 'MarketplaceAppFrontComponent', name: string, description?: string | null }>, defaultRole?: { __typename?: 'MarketplaceAppDefaultRole', id: string, label: string, description?: string | null, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canUpdateAllSettings: boolean, canAccessAllTools: boolean, permissionFlags: Array<string>, objectPermissions: Array<{ __typename?: 'MarketplaceAppRoleObjectPermission', objectUniversalIdentifier: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }>, fieldPermissions: Array<{ __typename?: 'MarketplaceAppRoleFieldPermission', objectUniversalIdentifier: string, fieldUniversalIdentifier: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null }> } | null }> };
export type FindManyMarketplaceAppsQuery = { __typename?: 'Query', findManyMarketplaceApps: Array<{ __typename?: 'MarketplaceApp', id: string, name: string, description: string, icon: string, version: string, author: string, category: string, logo?: string | null, screenshots: Array<string>, aboutDescription: string, providers: Array<string>, websiteUrl?: string | null, termsUrl?: string | null, sourcePackage?: string | null, isFeatured: boolean, objects: Array<{ __typename?: 'MarketplaceAppObject', universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, fields: Array<{ __typename?: 'MarketplaceAppField', universalIdentifier?: string | null, name: string, type: string, label: string, description?: string | null, icon?: string | null }> }>, fields: Array<{ __typename?: 'MarketplaceAppField', name: string, type: string, label: string, description?: string | null, icon?: string | null, objectUniversalIdentifier?: string | null }>, logicFunctions: Array<{ __typename?: 'MarketplaceAppLogicFunction', name: string, description?: string | null, timeoutSeconds?: number | null }>, frontComponents: Array<{ __typename?: 'MarketplaceAppFrontComponent', name: string, description?: string | null }>, defaultRole?: { __typename?: 'MarketplaceAppDefaultRole', id: string, label: string, description?: string | null, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canUpdateAllSettings: boolean, canAccessAllTools: boolean, permissionFlags: Array<string>, objectPermissions: Array<{ __typename?: 'MarketplaceAppRoleObjectPermission', objectUniversalIdentifier: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }>, fieldPermissions: Array<{ __typename?: 'MarketplaceAppRoleFieldPermission', objectUniversalIdentifier: string, fieldUniversalIdentifier: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null }> } | null }> };
export type FindOneMarketplaceAppQueryVariables = Exact<{
universalIdentifier: Scalars['String'];
}>;
export type FindOneMarketplaceAppQuery = { __typename?: 'Query', findOneMarketplaceApp: { __typename?: 'MarketplaceApp', id: string, name: string, description: string, icon: string, version: string, author: string, category: string, logo?: string | null, screenshots: Array<string>, aboutDescription: string, providers: Array<string>, websiteUrl?: string | null, termsUrl?: string | null, sourcePackage?: string | null, isFeatured: boolean, objects: Array<{ __typename?: 'MarketplaceAppObject', universalIdentifier: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, fields: Array<{ __typename?: 'MarketplaceAppField', universalIdentifier?: string | null, name: string, type: string, label: string, description?: string | null, icon?: string | null }> }>, fields: Array<{ __typename?: 'MarketplaceAppField', name: string, type: string, label: string, description?: string | null, icon?: string | null, objectUniversalIdentifier?: string | null }>, logicFunctions: Array<{ __typename?: 'MarketplaceAppLogicFunction', name: string, description?: string | null, timeoutSeconds?: number | null }>, frontComponents: Array<{ __typename?: 'MarketplaceAppFrontComponent', name: string, description?: string | null }>, defaultRole?: { __typename?: 'MarketplaceAppDefaultRole', id: string, label: string, description?: string | null, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canUpdateAllSettings: boolean, canAccessAllTools: boolean, permissionFlags: Array<string>, objectPermissions: Array<{ __typename?: 'MarketplaceAppRoleObjectPermission', objectUniversalIdentifier: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }>, fieldPermissions: Array<{ __typename?: 'MarketplaceAppRoleFieldPermission', objectUniversalIdentifier: string, fieldUniversalIdentifier: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null }> } | null } };
export type NavigationMenuItemFieldsFragment = { __typename?: 'NavigationMenuItem', id: string, userWorkspaceId?: string | null, targetRecordId?: string | null, targetObjectMetadataId?: string | null, viewId?: string | null, folderId?: string | null, name?: string | null, link?: string | null, icon?: string | null, color?: string | null, position: number, applicationId?: string | null, createdAt: string, updatedAt: string };
@@ -6452,6 +6488,11 @@ export type GetAdminAiModelsQueryVariables = Exact<{ [key: string]: never; }>;
export type GetAdminAiModelsQuery = { __typename?: 'Query', getAdminAiModels: { __typename?: 'AdminAIModels', autoEnableNewModels: boolean, models: Array<{ __typename?: 'AdminAIModelConfig', modelId: string, label: string, modelFamily?: ModelFamily | null, inferenceProvider: InferenceProvider, isAvailable: boolean, isAdminEnabled: boolean, deprecated?: boolean | null, isRecommended?: boolean | null }> } };
export type FindAllApplicationRegistrationsQueryVariables = Exact<{ [key: string]: never; }>;
export type FindAllApplicationRegistrationsQuery = { __typename?: 'Query', findAllApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> };
export type CreateDatabaseConfigVariableMutationVariables = Exact<{
key: Scalars['String'];
value: Scalars['JSON'];
@@ -6554,7 +6595,7 @@ export type GetSystemHealthStatusQueryVariables = Exact<{ [key: string]: never;
export type GetSystemHealthStatusQuery = { __typename?: 'Query', getSystemHealthStatus: { __typename?: 'SystemHealth', services: Array<{ __typename?: 'SystemHealthService', id: HealthIndicatorId, label: string, status: AdminPanelHealthServiceStatus }> } };
export type ApplicationRegistrationFragmentFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: AppRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, createdAt: string, updatedAt: string };
export type ApplicationRegistrationFragmentFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string };
export type DeleteApplicationRegistrationMutationVariables = Exact<{
id: Scalars['String'];
@@ -6570,12 +6611,20 @@ export type RotateApplicationRegistrationClientSecretMutationVariables = Exact<{
export type RotateApplicationRegistrationClientSecretMutation = { __typename?: 'Mutation', rotateApplicationRegistrationClientSecret: { __typename?: 'RotateClientSecret', clientSecret: string } };
export type TransferApplicationRegistrationOwnershipMutationVariables = Exact<{
applicationRegistrationId: Scalars['String'];
targetWorkspaceSubdomain: Scalars['String'];
}>;
export type TransferApplicationRegistrationOwnershipMutation = { __typename?: 'Mutation', transferApplicationRegistrationOwnership: { __typename?: 'ApplicationRegistration', id: string, name: string } };
export type UpdateApplicationRegistrationMutationVariables = Exact<{
input: UpdateApplicationRegistrationInput;
}>;
export type UpdateApplicationRegistrationMutation = { __typename?: 'Mutation', updateApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: AppRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, createdAt: string, updatedAt: string } };
export type UpdateApplicationRegistrationMutation = { __typename?: 'Mutation', updateApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } };
export type UpdateApplicationRegistrationVariableMutationVariables = Exact<{
input: UpdateApplicationRegistrationVariableInput;
@@ -6584,6 +6633,13 @@ export type UpdateApplicationRegistrationVariableMutationVariables = Exact<{
export type UpdateApplicationRegistrationVariableMutation = { __typename?: 'Mutation', updateApplicationRegistrationVariable: { __typename?: 'ApplicationRegistrationVariable', id: string, key: string, description: string, isSecret: boolean, isRequired: boolean, isFilled: boolean, createdAt: string, updatedAt: string } };
export type ApplicationRegistrationTarballUrlQueryVariables = Exact<{
id: Scalars['String'];
}>;
export type ApplicationRegistrationTarballUrlQuery = { __typename?: 'Query', applicationRegistrationTarballUrl?: string | null };
export type FindApplicationRegistrationByClientIdQueryVariables = Exact<{
clientId: Scalars['String'];
}>;
@@ -6608,14 +6664,14 @@ export type FindApplicationRegistrationVariablesQuery = { __typename?: 'Query',
export type FindManyApplicationRegistrationsQueryVariables = Exact<{ [key: string]: never; }>;
export type FindManyApplicationRegistrationsQuery = { __typename?: 'Query', findManyApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: AppRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, createdAt: string, updatedAt: string }> };
export type FindManyApplicationRegistrationsQuery = { __typename?: 'Query', findManyApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> };
export type FindOneApplicationRegistrationQueryVariables = Exact<{
id: Scalars['String'];
}>;
export type FindOneApplicationRegistrationQuery = { __typename?: 'Query', findOneApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: AppRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, createdAt: string, updatedAt: string } };
export type FindOneApplicationRegistrationQuery = { __typename?: 'Query', findOneApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, description?: string | null, logoUrl?: string | null, author?: string | null, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, websiteUrl?: string | null, termsUrl?: string | null, isListed: boolean, isFeatured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } };
export type UninstallApplicationMutationVariables = Exact<{
universalIdentifier: Scalars['String'];
@@ -7889,6 +7945,7 @@ export const MarketplaceAppFieldsFragmentDoc = gql`
description
}
sourcePackage
isFeatured
defaultRole {
id
label
@@ -7960,6 +8017,9 @@ export const ApplicationRegistrationFragmentFragmentDoc = gql`
latestAvailableVersion
websiteUrl
termsUrl
isListed
isFeatured
ownerWorkspaceId
createdAt
updatedAt
}
@@ -9385,6 +9445,7 @@ export const FindManyApplicationsDocument = gql`
name
description
version
universalIdentifier
applicationRegistrationId
applicationRegistration {
id
@@ -9456,6 +9517,41 @@ export function useFindOneApplicationLazyQuery(baseOptions?: Apollo.LazyQueryHoo
export type FindOneApplicationQueryHookResult = ReturnType<typeof useFindOneApplicationQuery>;
export type FindOneApplicationLazyQueryHookResult = ReturnType<typeof useFindOneApplicationLazyQuery>;
export type FindOneApplicationQueryResult = Apollo.QueryResult<FindOneApplicationQuery, FindOneApplicationQueryVariables>;
export const FindOneApplicationByUniversalIdentifierDocument = gql`
query FindOneApplicationByUniversalIdentifier($universalIdentifier: UUID!) {
findOneApplication(universalIdentifier: $universalIdentifier) {
id
}
}
`;
/**
* __useFindOneApplicationByUniversalIdentifierQuery__
*
* To run a query within a React component, call `useFindOneApplicationByUniversalIdentifierQuery` and pass it any options that fit your needs.
* When your component renders, `useFindOneApplicationByUniversalIdentifierQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useFindOneApplicationByUniversalIdentifierQuery({
* variables: {
* universalIdentifier: // value for 'universalIdentifier'
* },
* });
*/
export function useFindOneApplicationByUniversalIdentifierQuery(baseOptions: Apollo.QueryHookOptions<FindOneApplicationByUniversalIdentifierQuery, FindOneApplicationByUniversalIdentifierQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<FindOneApplicationByUniversalIdentifierQuery, FindOneApplicationByUniversalIdentifierQueryVariables>(FindOneApplicationByUniversalIdentifierDocument, options);
}
export function useFindOneApplicationByUniversalIdentifierLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindOneApplicationByUniversalIdentifierQuery, FindOneApplicationByUniversalIdentifierQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<FindOneApplicationByUniversalIdentifierQuery, FindOneApplicationByUniversalIdentifierQueryVariables>(FindOneApplicationByUniversalIdentifierDocument, options);
}
export type FindOneApplicationByUniversalIdentifierQueryHookResult = ReturnType<typeof useFindOneApplicationByUniversalIdentifierQuery>;
export type FindOneApplicationByUniversalIdentifierLazyQueryHookResult = ReturnType<typeof useFindOneApplicationByUniversalIdentifierLazyQuery>;
export type FindOneApplicationByUniversalIdentifierQueryResult = Apollo.QueryResult<FindOneApplicationByUniversalIdentifierQuery, FindOneApplicationByUniversalIdentifierQueryVariables>;
export const AuthorizeAppDocument = gql`
mutation authorizeApp($clientId: String!, $codeChallenge: String, $redirectUrl: String!) {
authorizeApp(
@@ -11437,6 +11533,38 @@ export function useGetLogicFunctionSourceCodeLazyQuery(baseOptions?: Apollo.Lazy
export type GetLogicFunctionSourceCodeQueryHookResult = ReturnType<typeof useGetLogicFunctionSourceCodeQuery>;
export type GetLogicFunctionSourceCodeLazyQueryHookResult = ReturnType<typeof useGetLogicFunctionSourceCodeLazyQuery>;
export type GetLogicFunctionSourceCodeQueryResult = Apollo.QueryResult<GetLogicFunctionSourceCodeQuery, GetLogicFunctionSourceCodeQueryVariables>;
export const InstallApplicationDocument = gql`
mutation InstallApplication($appRegistrationId: String!, $version: String) {
installApplication(appRegistrationId: $appRegistrationId, version: $version)
}
`;
export type InstallApplicationMutationFn = Apollo.MutationFunction<InstallApplicationMutation, InstallApplicationMutationVariables>;
/**
* __useInstallApplicationMutation__
*
* To run a mutation, you first call `useInstallApplicationMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useInstallApplicationMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [installApplicationMutation, { data, loading, error }] = useInstallApplicationMutation({
* variables: {
* appRegistrationId: // value for 'appRegistrationId'
* version: // value for 'version'
* },
* });
*/
export function useInstallApplicationMutation(baseOptions?: Apollo.MutationHookOptions<InstallApplicationMutation, InstallApplicationMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<InstallApplicationMutation, InstallApplicationMutationVariables>(InstallApplicationDocument, options);
}
export type InstallApplicationMutationHookResult = ReturnType<typeof useInstallApplicationMutation>;
export type InstallApplicationMutationResult = Apollo.MutationResult<InstallApplicationMutation>;
export type InstallApplicationMutationOptions = Apollo.BaseMutationOptions<InstallApplicationMutation, InstallApplicationMutationVariables>;
export const InstallMarketplaceAppDocument = gql`
mutation InstallMarketplaceApp($universalIdentifier: String!, $version: String) {
installMarketplaceApp(
@@ -11472,38 +11600,41 @@ export function useInstallMarketplaceAppMutation(baseOptions?: Apollo.MutationHo
export type InstallMarketplaceAppMutationHookResult = ReturnType<typeof useInstallMarketplaceAppMutation>;
export type InstallMarketplaceAppMutationResult = Apollo.MutationResult<InstallMarketplaceAppMutation>;
export type InstallMarketplaceAppMutationOptions = Apollo.BaseMutationOptions<InstallMarketplaceAppMutation, InstallMarketplaceAppMutationVariables>;
export const InstallNpmAppDocument = gql`
mutation InstallNpmApp($packageName: String!, $version: String) {
installNpmApp(packageName: $packageName, version: $version)
export const RegisterNpmPackageDocument = gql`
mutation RegisterNpmPackage($packageName: String!) {
registerNpmPackage(packageName: $packageName) {
id
universalIdentifier
name
}
}
`;
export type InstallNpmAppMutationFn = Apollo.MutationFunction<InstallNpmAppMutation, InstallNpmAppMutationVariables>;
export type RegisterNpmPackageMutationFn = Apollo.MutationFunction<RegisterNpmPackageMutation, RegisterNpmPackageMutationVariables>;
/**
* __useInstallNpmAppMutation__
* __useRegisterNpmPackageMutation__
*
* To run a mutation, you first call `useInstallNpmAppMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useInstallNpmAppMutation` returns a tuple that includes:
* To run a mutation, you first call `useRegisterNpmPackageMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useRegisterNpmPackageMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [installNpmAppMutation, { data, loading, error }] = useInstallNpmAppMutation({
* const [registerNpmPackageMutation, { data, loading, error }] = useRegisterNpmPackageMutation({
* variables: {
* packageName: // value for 'packageName'
* version: // value for 'version'
* },
* });
*/
export function useInstallNpmAppMutation(baseOptions?: Apollo.MutationHookOptions<InstallNpmAppMutation, InstallNpmAppMutationVariables>) {
export function useRegisterNpmPackageMutation(baseOptions?: Apollo.MutationHookOptions<RegisterNpmPackageMutation, RegisterNpmPackageMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<InstallNpmAppMutation, InstallNpmAppMutationVariables>(InstallNpmAppDocument, options);
return Apollo.useMutation<RegisterNpmPackageMutation, RegisterNpmPackageMutationVariables>(RegisterNpmPackageDocument, options);
}
export type InstallNpmAppMutationHookResult = ReturnType<typeof useInstallNpmAppMutation>;
export type InstallNpmAppMutationResult = Apollo.MutationResult<InstallNpmAppMutation>;
export type InstallNpmAppMutationOptions = Apollo.BaseMutationOptions<InstallNpmAppMutation, InstallNpmAppMutationVariables>;
export type RegisterNpmPackageMutationHookResult = ReturnType<typeof useRegisterNpmPackageMutation>;
export type RegisterNpmPackageMutationResult = Apollo.MutationResult<RegisterNpmPackageMutation>;
export type RegisterNpmPackageMutationOptions = Apollo.BaseMutationOptions<RegisterNpmPackageMutation, RegisterNpmPackageMutationVariables>;
export const UpgradeApplicationDocument = gql`
mutation UpgradeApplication($appRegistrationId: String!, $targetVersion: String!) {
upgradeApplication(
@@ -11609,6 +11740,41 @@ export function useFindManyMarketplaceAppsLazyQuery(baseOptions?: Apollo.LazyQue
export type FindManyMarketplaceAppsQueryHookResult = ReturnType<typeof useFindManyMarketplaceAppsQuery>;
export type FindManyMarketplaceAppsLazyQueryHookResult = ReturnType<typeof useFindManyMarketplaceAppsLazyQuery>;
export type FindManyMarketplaceAppsQueryResult = Apollo.QueryResult<FindManyMarketplaceAppsQuery, FindManyMarketplaceAppsQueryVariables>;
export const FindOneMarketplaceAppDocument = gql`
query FindOneMarketplaceApp($universalIdentifier: String!) {
findOneMarketplaceApp(universalIdentifier: $universalIdentifier) {
...MarketplaceAppFields
}
}
${MarketplaceAppFieldsFragmentDoc}`;
/**
* __useFindOneMarketplaceAppQuery__
*
* To run a query within a React component, call `useFindOneMarketplaceAppQuery` and pass it any options that fit your needs.
* When your component renders, `useFindOneMarketplaceAppQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useFindOneMarketplaceAppQuery({
* variables: {
* universalIdentifier: // value for 'universalIdentifier'
* },
* });
*/
export function useFindOneMarketplaceAppQuery(baseOptions: Apollo.QueryHookOptions<FindOneMarketplaceAppQuery, FindOneMarketplaceAppQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<FindOneMarketplaceAppQuery, FindOneMarketplaceAppQueryVariables>(FindOneMarketplaceAppDocument, options);
}
export function useFindOneMarketplaceAppLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindOneMarketplaceAppQuery, FindOneMarketplaceAppQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<FindOneMarketplaceAppQuery, FindOneMarketplaceAppQueryVariables>(FindOneMarketplaceAppDocument, options);
}
export type FindOneMarketplaceAppQueryHookResult = ReturnType<typeof useFindOneMarketplaceAppQuery>;
export type FindOneMarketplaceAppLazyQueryHookResult = ReturnType<typeof useFindOneMarketplaceAppLazyQuery>;
export type FindOneMarketplaceAppQueryResult = Apollo.QueryResult<FindOneMarketplaceAppQuery, FindOneMarketplaceAppQueryVariables>;
export const CreateNavigationMenuItemDocument = gql`
mutation CreateNavigationMenuItem($input: CreateNavigationMenuItemInput!) {
createNavigationMenuItem(input: $input) {
@@ -12709,6 +12875,40 @@ export function useGetAdminAiModelsLazyQuery(baseOptions?: Apollo.LazyQueryHookO
export type GetAdminAiModelsQueryHookResult = ReturnType<typeof useGetAdminAiModelsQuery>;
export type GetAdminAiModelsLazyQueryHookResult = ReturnType<typeof useGetAdminAiModelsLazyQuery>;
export type GetAdminAiModelsQueryResult = Apollo.QueryResult<GetAdminAiModelsQuery, GetAdminAiModelsQueryVariables>;
export const FindAllApplicationRegistrationsDocument = gql`
query FindAllApplicationRegistrations {
findAllApplicationRegistrations {
...ApplicationRegistrationFragment
}
}
${ApplicationRegistrationFragmentFragmentDoc}`;
/**
* __useFindAllApplicationRegistrationsQuery__
*
* To run a query within a React component, call `useFindAllApplicationRegistrationsQuery` and pass it any options that fit your needs.
* When your component renders, `useFindAllApplicationRegistrationsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useFindAllApplicationRegistrationsQuery({
* variables: {
* },
* });
*/
export function useFindAllApplicationRegistrationsQuery(baseOptions?: Apollo.QueryHookOptions<FindAllApplicationRegistrationsQuery, FindAllApplicationRegistrationsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<FindAllApplicationRegistrationsQuery, FindAllApplicationRegistrationsQueryVariables>(FindAllApplicationRegistrationsDocument, options);
}
export function useFindAllApplicationRegistrationsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindAllApplicationRegistrationsQuery, FindAllApplicationRegistrationsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<FindAllApplicationRegistrationsQuery, FindAllApplicationRegistrationsQueryVariables>(FindAllApplicationRegistrationsDocument, options);
}
export type FindAllApplicationRegistrationsQueryHookResult = ReturnType<typeof useFindAllApplicationRegistrationsQuery>;
export type FindAllApplicationRegistrationsLazyQueryHookResult = ReturnType<typeof useFindAllApplicationRegistrationsLazyQuery>;
export type FindAllApplicationRegistrationsQueryResult = Apollo.QueryResult<FindAllApplicationRegistrationsQuery, FindAllApplicationRegistrationsQueryVariables>;
export const CreateDatabaseConfigVariableDocument = gql`
mutation CreateDatabaseConfigVariable($key: String!, $value: JSON!) {
createDatabaseConfigVariable(key: $key, value: $value)
@@ -13368,6 +13568,44 @@ export function useRotateApplicationRegistrationClientSecretMutation(baseOptions
export type RotateApplicationRegistrationClientSecretMutationHookResult = ReturnType<typeof useRotateApplicationRegistrationClientSecretMutation>;
export type RotateApplicationRegistrationClientSecretMutationResult = Apollo.MutationResult<RotateApplicationRegistrationClientSecretMutation>;
export type RotateApplicationRegistrationClientSecretMutationOptions = Apollo.BaseMutationOptions<RotateApplicationRegistrationClientSecretMutation, RotateApplicationRegistrationClientSecretMutationVariables>;
export const TransferApplicationRegistrationOwnershipDocument = gql`
mutation TransferApplicationRegistrationOwnership($applicationRegistrationId: String!, $targetWorkspaceSubdomain: String!) {
transferApplicationRegistrationOwnership(
applicationRegistrationId: $applicationRegistrationId
targetWorkspaceSubdomain: $targetWorkspaceSubdomain
) {
id
name
}
}
`;
export type TransferApplicationRegistrationOwnershipMutationFn = Apollo.MutationFunction<TransferApplicationRegistrationOwnershipMutation, TransferApplicationRegistrationOwnershipMutationVariables>;
/**
* __useTransferApplicationRegistrationOwnershipMutation__
*
* To run a mutation, you first call `useTransferApplicationRegistrationOwnershipMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useTransferApplicationRegistrationOwnershipMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [transferApplicationRegistrationOwnershipMutation, { data, loading, error }] = useTransferApplicationRegistrationOwnershipMutation({
* variables: {
* applicationRegistrationId: // value for 'applicationRegistrationId'
* targetWorkspaceSubdomain: // value for 'targetWorkspaceSubdomain'
* },
* });
*/
export function useTransferApplicationRegistrationOwnershipMutation(baseOptions?: Apollo.MutationHookOptions<TransferApplicationRegistrationOwnershipMutation, TransferApplicationRegistrationOwnershipMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<TransferApplicationRegistrationOwnershipMutation, TransferApplicationRegistrationOwnershipMutationVariables>(TransferApplicationRegistrationOwnershipDocument, options);
}
export type TransferApplicationRegistrationOwnershipMutationHookResult = ReturnType<typeof useTransferApplicationRegistrationOwnershipMutation>;
export type TransferApplicationRegistrationOwnershipMutationResult = Apollo.MutationResult<TransferApplicationRegistrationOwnershipMutation>;
export type TransferApplicationRegistrationOwnershipMutationOptions = Apollo.BaseMutationOptions<TransferApplicationRegistrationOwnershipMutation, TransferApplicationRegistrationOwnershipMutationVariables>;
export const UpdateApplicationRegistrationDocument = gql`
mutation UpdateApplicationRegistration($input: UpdateApplicationRegistrationInput!) {
updateApplicationRegistration(input: $input) {
@@ -13441,6 +13679,39 @@ export function useUpdateApplicationRegistrationVariableMutation(baseOptions?: A
export type UpdateApplicationRegistrationVariableMutationHookResult = ReturnType<typeof useUpdateApplicationRegistrationVariableMutation>;
export type UpdateApplicationRegistrationVariableMutationResult = Apollo.MutationResult<UpdateApplicationRegistrationVariableMutation>;
export type UpdateApplicationRegistrationVariableMutationOptions = Apollo.BaseMutationOptions<UpdateApplicationRegistrationVariableMutation, UpdateApplicationRegistrationVariableMutationVariables>;
export const ApplicationRegistrationTarballUrlDocument = gql`
query ApplicationRegistrationTarballUrl($id: String!) {
applicationRegistrationTarballUrl(id: $id)
}
`;
/**
* __useApplicationRegistrationTarballUrlQuery__
*
* To run a query within a React component, call `useApplicationRegistrationTarballUrlQuery` and pass it any options that fit your needs.
* When your component renders, `useApplicationRegistrationTarballUrlQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useApplicationRegistrationTarballUrlQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useApplicationRegistrationTarballUrlQuery(baseOptions: Apollo.QueryHookOptions<ApplicationRegistrationTarballUrlQuery, ApplicationRegistrationTarballUrlQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ApplicationRegistrationTarballUrlQuery, ApplicationRegistrationTarballUrlQueryVariables>(ApplicationRegistrationTarballUrlDocument, options);
}
export function useApplicationRegistrationTarballUrlLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ApplicationRegistrationTarballUrlQuery, ApplicationRegistrationTarballUrlQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ApplicationRegistrationTarballUrlQuery, ApplicationRegistrationTarballUrlQueryVariables>(ApplicationRegistrationTarballUrlDocument, options);
}
export type ApplicationRegistrationTarballUrlQueryHookResult = ReturnType<typeof useApplicationRegistrationTarballUrlQuery>;
export type ApplicationRegistrationTarballUrlLazyQueryHookResult = ReturnType<typeof useApplicationRegistrationTarballUrlLazyQuery>;
export type ApplicationRegistrationTarballUrlQueryResult = Apollo.QueryResult<ApplicationRegistrationTarballUrlQuery, ApplicationRegistrationTarballUrlQueryVariables>;
export const FindApplicationRegistrationByClientIdDocument = gql`
query FindApplicationRegistrationByClientId($clientId: String!) {
findApplicationRegistrationByClientId(clientId: $clientId) {
@@ -7,6 +7,7 @@ export const FIND_MANY_APPLICATIONS = gql`
name
description
version
universalIdentifier
applicationRegistrationId
applicationRegistration {
id
@@ -0,0 +1,9 @@
import { gql } from '@apollo/client';
export const FIND_ONE_APPLICATION_BY_UNIVERSAL_IDENTIFIER = gql`
query FindOneApplicationByUniversalIdentifier($universalIdentifier: UUID!) {
findOneApplication(universalIdentifier: $universalIdentifier) {
id
}
}
`;
@@ -50,6 +50,7 @@ export const MARKETPLACE_APP_FRAGMENT = gql`
description
}
sourcePackage
isFeatured
defaultRole {
id
label
@@ -0,0 +1,7 @@
import gql from 'graphql-tag';
export const INSTALL_APPLICATION = gql`
mutation InstallApplication($appRegistrationId: String!, $version: String) {
installApplication(appRegistrationId: $appRegistrationId, version: $version)
}
`;
@@ -1,7 +0,0 @@
import gql from 'graphql-tag';
export const INSTALL_NPM_APP = gql`
mutation InstallNpmApp($packageName: String!, $version: String) {
installNpmApp(packageName: $packageName, version: $version)
}
`;
@@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
export const REGISTER_NPM_PACKAGE = gql`
mutation RegisterNpmPackage($packageName: String!) {
registerNpmPackage(packageName: $packageName) {
id
universalIdentifier
name
}
}
`;
@@ -0,0 +1,12 @@
import gql from 'graphql-tag';
import { MARKETPLACE_APP_FRAGMENT } from '@/marketplace/graphql/fragments/marketplaceAppFragment';
export const FIND_ONE_MARKETPLACE_APP = gql`
${MARKETPLACE_APP_FRAGMENT}
query FindOneMarketplaceApp($universalIdentifier: String!) {
findOneMarketplaceApp(universalIdentifier: $universalIdentifier) {
...MarketplaceAppFields
}
}
`;
@@ -26,9 +26,11 @@ export const useInstallApp = <TVariables extends Record<string, unknown>>(
}
return false;
} catch {
} catch (error) {
const graphqlMessage = error instanceof Error ? error.message : undefined;
enqueueErrorSnackBar({
message: t`Failed to install the application.`,
message: graphqlMessage ?? t`Failed to install the application.`,
});
return false;
@@ -1,13 +0,0 @@
import { useMutation } from '@apollo/client';
import { useInstallApp } from '~/modules/marketplace/hooks/useInstallApp';
import { INSTALL_NPM_APP } from '~/modules/marketplace/graphql/mutations/installNpmApp';
export const useInstallNpmApp = () => {
const [installNpmAppMutation] = useMutation(INSTALL_NPM_APP);
return useInstallApp<{
packageName: string;
version?: string;
}>(installNpmAppMutation);
};
@@ -0,0 +1,50 @@
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useMutation } from '@apollo/client';
import { t } from '@lingui/core/macro';
import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { REGISTER_NPM_PACKAGE } from '~/modules/marketplace/graphql/mutations/registerNpmPackage';
export const useRegisterNpmPackage = () => {
const [registerNpmPackageMutation] = useMutation(REGISTER_NPM_PACKAGE);
const { enqueueErrorSnackBar, enqueueSuccessSnackBar } = useSnackBar();
const [isRegistering, setIsRegistering] = useState(false);
const register = async (params: {
packageName: string;
}): Promise<boolean> => {
setIsRegistering(true);
try {
const result = await registerNpmPackageMutation({
variables: { packageName: params.packageName },
});
const registration = result.data?.registerNpmPackage;
if (!isDefined(registration)) {
enqueueErrorSnackBar({ message: t`Registration failed.` });
return false;
}
enqueueSuccessSnackBar({
message: t`Package registered successfully.`,
});
return true;
} catch (error) {
const graphqlMessage = error instanceof Error ? error.message : undefined;
enqueueErrorSnackBar({
message: graphqlMessage ?? t`Failed to register npm package.`,
});
return false;
} finally {
setIsRegistering(false);
}
};
return { register, isRegistering };
};
@@ -29,9 +29,11 @@ export const useUpgradeApplication = () => {
}
return false;
} catch {
} catch (error) {
const graphqlMessage = error instanceof Error ? error.message : undefined;
enqueueErrorSnackBar({
message: t`Failed to upgrade the application.`,
message: graphqlMessage ?? t`Failed to upgrade the application.`,
});
return false;
@@ -8,6 +8,7 @@ import { UPLOAD_APP_TARBALL } from '~/modules/marketplace/graphql/mutations/uplo
type UploadResult =
| {
success: true;
registrationId: string;
universalIdentifier: string;
}
| {
@@ -29,7 +30,10 @@ export const useUploadAppTarball = () => {
const registration = result.data?.uploadAppTarball;
if (!isDefined(registration?.universalIdentifier)) {
if (
!isDefined(registration?.id) ||
!isDefined(registration?.universalIdentifier)
) {
enqueueErrorSnackBar({ message: t`Upload failed.` });
return { success: false };
@@ -37,11 +41,14 @@ export const useUploadAppTarball = () => {
return {
success: true,
registrationId: registration.id,
universalIdentifier: registration.universalIdentifier,
};
} catch {
} catch (error) {
const graphqlMessage = error instanceof Error ? error.message : undefined;
enqueueErrorSnackBar({
message: t`Failed to upload tarball.`,
message: graphqlMessage ?? t`Failed to upload tarball.`,
});
return { success: false };
@@ -1,27 +1,17 @@
import { useState } from 'react';
import { styled } from '@linaria/react';
import { useClientConfig } from '@/client-config/hooks/useClientConfig';
import { GET_ADMIN_AI_MODELS } from '@/settings/admin-panel/ai/graphql/queries/getAdminAiModels';
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { t } from '@lingui/core/macro';
import {
H2Title,
IconArchive,
IconFilter,
IconPlug,
IconRobot,
IconSearch,
} from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { H2Title, IconArchive, IconPlug, IconRobot } from 'twenty-ui/display';
import { SearchInput } from 'twenty-ui/input';
import { Card, Section } from 'twenty-ui/layout';
import { MenuItemToggle } from 'twenty-ui/navigation';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import {
useCreateDatabaseConfigVariableMutation,
useGetAdminAiModelsQuery,
@@ -30,17 +20,6 @@ import {
import { getModelIcon } from '~/pages/settings/ai/utils/getModelIcon';
import { getModelProviderLabel } from '~/pages/settings/ai/utils/getModelProviderLabel';
const StyledSearchAndFilterContainer = styled.div`
display: flex;
gap: ${themeCssVariables.spacing[2]};
margin-bottom: ${themeCssVariables.spacing[2]};
width: 100%;
`;
const StyledSearchInputContainer = styled.div`
flex: 1;
`;
export const SettingsAdminAI = () => {
const { enqueueErrorSnackBar } = useSnackBar();
const [searchQuery, setSearchQuery] = useState('');
@@ -162,53 +141,41 @@ export const SettingsAdminAI = () => {
description={t`Toggle model availability across all workspaces`}
/>
<StyledSearchAndFilterContainer>
<StyledSearchInputContainer>
<SettingsTextInput
instanceId="admin-model-search"
LeftIcon={IconSearch}
placeholder={t`Search a model...`}
value={searchQuery}
onChange={setSearchQuery}
<SearchInput
placeholder={t`Search a model...`}
value={searchQuery}
onChange={setSearchQuery}
filterDropdown={(filterButton) => (
<Dropdown
dropdownId="admin-ai-models-filter-dropdown"
dropdownPlacement="bottom-end"
dropdownOffset={{ x: 0, y: 8 }}
clickableComponent={filterButton}
dropdownComponents={
<DropdownContent>
<DropdownMenuItemsContainer>
<MenuItemToggle
LeftIcon={IconPlug}
onToggleChange={() =>
setShowUnconfigured(!showUnconfigured)
}
toggled={showUnconfigured}
text={t`Unconfigured models`}
toggleSize="small"
/>
<MenuItemToggle
LeftIcon={IconArchive}
onToggleChange={() => setShowDeprecated(!showDeprecated)}
toggled={showDeprecated}
text={t`Deprecated models`}
toggleSize="small"
/>
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
</StyledSearchInputContainer>
<Dropdown
dropdownId="admin-ai-models-filter-dropdown"
dropdownPlacement="bottom-end"
dropdownOffset={{ x: 0, y: 8 }}
clickableComponent={
<Button
Icon={IconFilter}
size="medium"
variant="secondary"
accent="default"
ariaLabel={t`Filter`}
/>
}
dropdownComponents={
<DropdownContent>
<DropdownMenuItemsContainer>
<MenuItemToggle
LeftIcon={IconPlug}
onToggleChange={() =>
setShowUnconfigured(!showUnconfigured)
}
toggled={showUnconfigured}
text={t`Unconfigured models`}
toggleSize="small"
/>
<MenuItemToggle
LeftIcon={IconArchive}
onToggleChange={() => setShowDeprecated(!showDeprecated)}
toggled={showDeprecated}
text={t`Deprecated models`}
toggleSize="small"
/>
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
</StyledSearchAndFilterContainer>
)}
/>
<Card rounded>
{filteredModels.map((model, index) => (
@@ -0,0 +1,107 @@
import { FIND_ALL_APPLICATION_REGISTRATIONS } from '@/settings/admin-panel/apps/graphql/queries/findAllApplicationRegistrations';
import { Table } from '@/ui/layout/table/components/Table';
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';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { useState } from 'react';
import { getSettingsPath } from 'twenty-shared/utils';
import { SettingsPath } from 'twenty-shared/types';
import { H2Title, Status } from 'twenty-ui/display';
import { SearchInput } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { UndecoratedLink } from 'twenty-ui/navigation';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { type ApplicationRegistrationFragmentFragment } from '~/generated-metadata/graphql';
const StyledTableContainer = styled.div`
margin-top: ${themeCssVariables.spacing[3]};
`;
const StyledTableHeaderRowContainer = styled.div`
margin-bottom: ${themeCssVariables.spacing[2]};
`;
const TABLE_GRID = '1fr 1fr 100px 80px';
export const SettingsAdminApps = () => {
const [searchQuery, setSearchQuery] = useState('');
const { data } = useQuery(FIND_ALL_APPLICATION_REGISTRATIONS);
const registrations: ApplicationRegistrationFragmentFragment[] =
data?.findAllApplicationRegistrations ?? [];
const filtered =
searchQuery.trim().length === 0
? registrations
: registrations.filter((registration) => {
const query = searchQuery.toLowerCase();
return (
registration.name.toLowerCase().includes(query) ||
(registration.sourcePackage ?? '').toLowerCase().includes(query) ||
registration.universalIdentifier.toLowerCase().includes(query)
);
});
return (
<Section>
<H2Title
title={t`All App Registrations`}
description={t`All application registrations across the platform, including orphaned marketplace apps`}
/>
<SearchInput
placeholder={t`Search registrations...`}
value={searchQuery}
onChange={setSearchQuery}
/>
<StyledTableContainer>
<Table>
<StyledTableHeaderRowContainer>
<TableRow gridTemplateColumns={TABLE_GRID}>
<TableHeader>{t`Name`}</TableHeader>
<TableHeader>{t`Source`}</TableHeader>
<TableHeader>{t`Listed`}</TableHeader>
<TableHeader>{t`Featured`}</TableHeader>
</TableRow>
</StyledTableHeaderRowContainer>
<TableBody>
{filtered.map((registration) => (
<UndecoratedLink
key={registration.id}
to={getSettingsPath(
SettingsPath.ApplicationRegistrationDetail,
{ applicationRegistrationId: registration.id },
)}
fullWidth
>
<TableRow gridTemplateColumns={TABLE_GRID} isClickable>
<TableCell>{registration.name}</TableCell>
<TableCell>
{registration.sourcePackage ?? registration.sourceType}
</TableCell>
<TableCell>
<Status
color={registration.isListed ? 'green' : 'gray'}
text={registration.isListed ? t`Yes` : t`No`}
/>
</TableCell>
<TableCell>
<Status
color={registration.isFeatured ? 'yellow' : 'gray'}
text={registration.isFeatured ? t`Yes` : t`No`}
/>
</TableCell>
</TableRow>
</UndecoratedLink>
))}
</TableBody>
</Table>
</StyledTableContainer>
</Section>
);
};
@@ -0,0 +1,12 @@
import { gql } from '@apollo/client';
import { APPLICATION_REGISTRATION_FRAGMENT } from '@/settings/application-registrations/graphql/fragments/applicationRegistrationFragment';
export const FIND_ALL_APPLICATION_REGISTRATIONS = gql`
query FindAllApplicationRegistrations {
findAllApplicationRegistrations {
...ApplicationRegistrationFragment
}
}
${APPLICATION_REGISTRATION_FRAGMENT}
`;
@@ -5,6 +5,7 @@ import { SETTINGS_ADMIN_TABS_ID } from '@/settings/admin-panel/constants/Setting
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { t } from '@lingui/core/macro';
import {
IconApps,
IconHeart,
IconSettings2,
IconSparkles,
@@ -24,6 +25,12 @@ export const SettingsAdminContent = () => {
Icon: IconSettings2,
disabled: !canAccessFullAdminPanel && !canImpersonate,
},
{
id: SETTINGS_ADMIN_TABS.APPS,
title: t`Apps`,
Icon: IconApps,
disabled: !canAccessFullAdminPanel,
},
{
id: SETTINGS_ADMIN_TABS.AI,
title: t`AI`,
@@ -1,4 +1,5 @@
import { SettingsAdminAI } from '@/settings/admin-panel/ai/components/SettingsAdminAI';
import { SettingsAdminApps } from '@/settings/admin-panel/apps/components/SettingsAdminApps';
import { SettingsAdminGeneral } from '@/settings/admin-panel/components/SettingsAdminGeneral';
import { SettingsAdminConfigVariables } from '@/settings/admin-panel/config-variables/components/SettingsAdminConfigVariables';
import { SETTINGS_ADMIN_TABS } from '@/settings/admin-panel/constants/SettingsAdminTabs';
@@ -16,6 +17,8 @@ export const SettingsAdminTabContent = () => {
switch (activeTabId) {
case SETTINGS_ADMIN_TABS.GENERAL:
return <SettingsAdminGeneral />;
case SETTINGS_ADMIN_TABS.APPS:
return <SettingsAdminApps />;
case SETTINGS_ADMIN_TABS.AI:
return <SettingsAdminAI />;
case SETTINGS_ADMIN_TABS.CONFIG_VARIABLES:
@@ -45,13 +45,13 @@ export const SettingsAdminTableCard = ({
<TableRow
key={index + item.label}
gridAutoColumns={gridAutoColumns}
height={themeCssVariables.spacing[6]}
>
<TableCell
align={labelAlign}
color={themeCssVariables.font.color.tertiary}
height={themeCssVariables.spacing[6]}
height="auto"
gap={themeCssVariables.spacing[2]}
padding={`${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[2]}`}
>
{item.Icon && <item.Icon size={theme.icon.size.md} />}
<span>{item.label}</span>
@@ -59,9 +59,10 @@ export const SettingsAdminTableCard = ({
<TableCell
align={valueAlign}
color={themeCssVariables.font.color.primary}
height={themeCssVariables.spacing[6]}
height="auto"
onClick={item.onClick}
clickable={isDefined(item.onClick)}
padding={`${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[2]}`}
>
{item.value}
</TableCell>
@@ -1,11 +1,5 @@
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { IconSearch } from 'twenty-ui/display';
const StyledSearchInputContainer = styled.div`
width: 100%;
`;
import { SearchInput } from 'twenty-ui/input';
type ConfigVariableSearchInputProps = {
value: string;
@@ -17,15 +11,10 @@ export const ConfigVariableSearchInput = ({
onChange,
}: ConfigVariableSearchInputProps) => {
return (
<StyledSearchInputContainer>
<SettingsTextInput
instanceId="config-variable-search"
placeholder={t`Search config variables`}
value={value}
onChange={onChange}
autoFocus={false}
LeftIcon={IconSearch}
/>
</StyledSearchInputContainer>
<SearchInput
placeholder={t`Search config variables`}
value={value}
onChange={onChange}
/>
);
};
@@ -1,5 +1,6 @@
export const SETTINGS_ADMIN_TABS = {
GENERAL: 'general',
APPS: 'apps',
AI: 'ai',
CONFIG_VARIABLES: 'config-variables',
HEALTH_STATUS: 'health-status',
@@ -16,6 +16,9 @@ export const APPLICATION_REGISTRATION_FRAGMENT = gql`
latestAvailableVersion
websiteUrl
termsUrl
isListed
isFeatured
ownerWorkspaceId
createdAt
updatedAt
}
@@ -0,0 +1,16 @@
import { gql } from '@apollo/client';
export const TRANSFER_APPLICATION_REGISTRATION_OWNERSHIP = gql`
mutation TransferApplicationRegistrationOwnership(
$applicationRegistrationId: String!
$targetWorkspaceSubdomain: String!
) {
transferApplicationRegistrationOwnership(
applicationRegistrationId: $applicationRegistrationId
targetWorkspaceSubdomain: $targetWorkspaceSubdomain
) {
id
name
}
}
`;
@@ -0,0 +1,7 @@
import { gql } from '@apollo/client';
export const APPLICATION_REGISTRATION_TARBALL_URL = gql`
query ApplicationRegistrationTarballUrl($id: String!) {
applicationRegistrationTarballUrl(id: $id)
}
`;
@@ -1,8 +1,10 @@
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
import { DELETE_APPLICATION_REGISTRATION } from '@/settings/application-registrations/graphql/mutations/deleteApplicationRegistration';
import { ROTATE_APPLICATION_REGISTRATION_CLIENT_SECRET } from '@/settings/application-registrations/graphql/mutations/rotateApplicationRegistrationClientSecret';
import { TRANSFER_APPLICATION_REGISTRATION_OWNERSHIP } from '@/settings/application-registrations/graphql/mutations/transferApplicationRegistrationOwnership';
import { UPDATE_APPLICATION_REGISTRATION } from '@/settings/application-registrations/graphql/mutations/updateApplicationRegistration';
import { UPDATE_APPLICATION_REGISTRATION_VARIABLE } from '@/settings/application-registrations/graphql/mutations/updateApplicationRegistrationVariable';
import { APPLICATION_REGISTRATION_TARBALL_URL } from '@/settings/application-registrations/graphql/queries/applicationRegistrationTarballUrl';
import { FIND_APPLICATION_REGISTRATION_STATS } from '@/settings/application-registrations/graphql/queries/findApplicationRegistrationStats';
import { FIND_APPLICATION_REGISTRATION_VARIABLES } from '@/settings/application-registrations/graphql/queries/findApplicationRegistrationVariables';
import { FIND_MANY_APPLICATION_REGISTRATIONS } from '@/settings/application-registrations/graphql/queries/findManyApplicationRegistrations';
@@ -16,6 +18,7 @@ import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModa
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useInstallMarketplaceApp } from '~/modules/marketplace/hooks/useInstallMarketplaceApp';
import { useMutation, useQuery } from '@apollo/client';
import { styled } from '@linaria/react';
import { Trans, useLingui } from '@lingui/react/macro';
@@ -25,10 +28,15 @@ import { useParams } from 'react-router-dom';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined, isValidUrl } from 'twenty-shared/utils';
import {
H1Title,
H1TitleFontColor,
H2Title,
IconArrowRight,
IconChartBar,
IconCheck,
IconBox,
IconDownload,
IconExternalLink,
IconKey,
IconRefresh,
IconShield,
@@ -38,15 +46,33 @@ import {
IconWorld,
Status,
} from 'twenty-ui/display';
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import {
Card,
Section,
SectionAlignment,
SectionFontColor,
} from 'twenty-ui/layout';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
import {
ApplicationRegistrationSourceType,
useFindManyApplicationsQuery,
} from '~/generated-metadata/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import {
StyledAppModal,
StyledAppModalButton,
StyledAppModalSection,
StyledAppModalTitle,
} from '~/pages/settings/applications/components/SettingsAppModalLayout';
import { applicationRegistrationClientSecretFamilyState } from '~/pages/settings/applications/states/applicationRegistrationClientSecretFamilyState';
const DELETE_REGISTRATION_MODAL_ID = 'delete-application-registration-modal';
const ROTATE_SECRET_MODAL_ID = 'rotate-application-registration-secret-modal';
const TRANSFER_OWNERSHIP_MODAL_ID =
'transfer-application-registration-ownership-modal';
const StyledInputContainer = styled.div`
align-items: center;
@@ -99,6 +125,32 @@ const StyledRotateContainer = styled.div`
padding-top: ${themeCssVariables.spacing[2]};
`;
const StyledDangerButtonGroup = styled.div`
display: flex;
gap: ${themeCssVariables.spacing[2]};
`;
const StyledSourceRow = styled.div`
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[2]};
`;
const StyledDownloadLink = styled.a`
color: ${themeCssVariables.font.color.secondary};
cursor: pointer;
text-decoration: underline;
&:hover {
color: ${themeCssVariables.font.color.primary};
}
`;
const StyledMarketplaceActions = styled.div`
display: flex;
gap: ${themeCssVariables.spacing[2]};
padding-top: ${themeCssVariables.spacing[2]};
`;
type ServerVariable = {
id: string;
key: string;
@@ -113,7 +165,7 @@ export const SettingsApplicationRegistrationDetails = () => {
const navigate = useNavigateSettings();
const { copyToClipboard } = useCopyToClipboard();
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
const { openModal } = useModal();
const { openModal, closeModal } = useModal();
const { applicationRegistrationId = '' } = useParams<{
applicationRegistrationId: string;
}>();
@@ -123,11 +175,14 @@ export const SettingsApplicationRegistrationDetails = () => {
applicationRegistrationId,
);
const [isInstalling, setIsInstalling] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [formRedirectUris, setFormRedirectUris] = useState<string[]>([]);
const [newRedirectUri, setNewRedirectUri] = useState('');
const [hasChanges, setHasChanges] = useState(false);
const [rotatedSecret, setRotatedSecret] = useState<string | null>(null);
const [transferSubdomain, setTransferSubdomain] = useState('');
const [isTransferring, setIsTransferring] = useState(false);
const [variableValues, setVariableValues] = useState<Record<string, string>>(
{},
@@ -158,6 +213,14 @@ export const SettingsApplicationRegistrationDetails = () => {
skip: !applicationRegistrationId,
});
const { data: tarballUrlData } = useQuery(
APPLICATION_REGISTRATION_TARBALL_URL,
{
variables: { id: applicationRegistrationId },
skip: !applicationRegistrationId,
},
);
const [updateRegistration] = useMutation(UPDATE_APPLICATION_REGISTRATION, {
refetchQueries: [
FIND_ONE_APPLICATION_REGISTRATION,
@@ -176,15 +239,120 @@ export const SettingsApplicationRegistrationDetails = () => {
refetchQueries: [FIND_APPLICATION_REGISTRATION_VARIABLES],
},
);
const [transferOwnership] = useMutation(
TRANSFER_APPLICATION_REGISTRATION_OWNERSHIP,
{
refetchQueries: [
FIND_ONE_APPLICATION_REGISTRATION,
FIND_MANY_APPLICATION_REGISTRATIONS,
],
},
);
const { install } = useInstallMarketplaceApp();
const { data: applicationsData, refetch: refetchApplications } =
useFindManyApplicationsQuery();
const registration = data?.findOneApplicationRegistration;
const variables: ServerVariable[] =
variablesData?.findApplicationRegistrationVariables ?? [];
const isNpmSource =
registration?.sourceType === ApplicationRegistrationSourceType.NPM;
if (loading || !registration) {
return null;
}
const isInstalledOnWorkspace = (
applicationsData?.findManyApplications ?? []
).some(
(application) =>
application.universalIdentifier === registration.universalIdentifier,
);
const handleToggleListed = async () => {
try {
await updateRegistration({
variables: {
input: {
id: applicationRegistrationId,
update: {
isListed: !registration.isListed,
},
},
},
});
enqueueSuccessSnackBar({
message: registration.isListed
? t`App removed from marketplace`
: t`App listed on marketplace`,
});
} catch {
enqueueErrorSnackBar({
message: t`Error updating marketplace listing`,
});
}
};
const marketplacePageUrl = getSettingsPath(
SettingsPath.AvailableApplicationDetail,
{
availableApplicationId: registration.universalIdentifier,
},
);
const handleInstallOnWorkspace = async () => {
setIsInstalling(true);
try {
const success = await install({
universalIdentifier: registration.universalIdentifier,
});
if (success) {
await refetchApplications();
enqueueSuccessSnackBar({
message: t`App installed on this workspace`,
});
}
} catch {
enqueueErrorSnackBar({
message: t`Error installing app`,
});
} finally {
setIsInstalling(false);
}
};
const handleTransferOwnership = async () => {
const trimmed = transferSubdomain.trim();
if (!isNonEmptyString(trimmed)) {
return;
}
setIsTransferring(true);
try {
await transferOwnership({
variables: {
applicationRegistrationId,
targetWorkspaceSubdomain: trimmed,
},
});
enqueueSuccessSnackBar({
message: t`Ownership transferred successfully`,
});
setTransferSubdomain('');
navigate(SettingsPath.Applications);
} catch {
enqueueErrorSnackBar({
message: t`Failed to transfer ownership. Check that the subdomain is correct.`,
});
} finally {
setIsTransferring(false);
}
};
const markDirty = () => setHasChanges(true);
const handleSave = async () => {
@@ -364,6 +532,47 @@ export const SettingsApplicationRegistrationDetails = () => {
t`Universal identifier copied`,
),
},
...(isNpmSource && isNonEmptyString(registration.sourcePackage)
? [
{
Icon: IconBox,
label: t`Package`,
value: registration.sourcePackage,
},
]
: registration.sourceType === ApplicationRegistrationSourceType.TARBALL
? [
{
Icon: IconBox,
label: t`Source`,
value: isNonEmptyString(
tarballUrlData?.applicationRegistrationTarballUrl,
) ? (
<StyledSourceRow>
<span>
<Trans>Tarball upload</Trans>
</span>
<StyledDownloadLink
href={tarballUrlData.applicationRegistrationTarballUrl}
download
>
<Trans>Download</Trans>
</StyledDownloadLink>
</StyledSourceRow>
) : (
t`Tarball upload`
),
},
]
: registration.sourceType === ApplicationRegistrationSourceType.LOCAL
? [
{
Icon: IconBox,
label: t`Source`,
value: t`Local development`,
},
]
: []),
];
const stats = statsData?.findApplicationRegistrationStats;
@@ -447,6 +656,61 @@ export const SettingsApplicationRegistrationDetails = () => {
/>
</Section>
<Section>
<H2Title
title={t`Marketplace Listing`}
description={t`Control visibility on the marketplace. Unlisted apps are still accessible via direct link.`}
/>
<Card rounded>
<SettingsOptionCardContentToggle
title={t`Listed on marketplace`}
description={
isNpmSource
? t`Managed by the marketplace catalog sync for npm packages`
: t`When enabled, this app appears in the marketplace browse page`
}
checked={registration.isListed}
onChange={handleToggleListed}
disabled={isNpmSource}
divider
/>
<SettingsOptionCardContentToggle
title={t`Featured`}
description={t`Featured apps are curated. Open a PR to request featured status.`}
checked={registration.isFeatured}
onChange={() => {}}
disabled
/>
</Card>
<StyledMarketplaceActions>
<Button
Icon={IconExternalLink}
title={t`View marketplace page`}
variant="secondary"
to={marketplacePageUrl}
/>
</StyledMarketplaceActions>
</Section>
<Section>
<H2Title
title={t`Installation`}
description={t`Install this app on the current workspace`}
/>
<Button
Icon={isInstalledOnWorkspace ? IconCheck : IconDownload}
title={
isInstalledOnWorkspace
? t`Installed`
: t`Install on this workspace`
}
variant="secondary"
accent="blue"
disabled={isInstalledOnWorkspace || isInstalling}
onClick={handleInstallOnWorkspace}
/>
</Section>
<Section>
<H2Title
title={t`OAuth Credentials`}
@@ -574,17 +838,26 @@ export const SettingsApplicationRegistrationDetails = () => {
description={
hasActiveInstalls
? t`Uninstall this app from all workspaces before deleting it`
: t`Delete this app`
: t`Delete or transfer this app registration`
}
/>
<Button
accent="danger"
variant="secondary"
title={t`Delete`}
Icon={IconTrash}
disabled={hasActiveInstalls}
onClick={() => openModal(DELETE_REGISTRATION_MODAL_ID)}
/>
<StyledDangerButtonGroup>
<Button
accent="danger"
variant="secondary"
title={t`Delete`}
Icon={IconTrash}
disabled={hasActiveInstalls}
onClick={() => openModal(DELETE_REGISTRATION_MODAL_ID)}
/>
<Button
accent="default"
variant="secondary"
title={t`Transfer ownership`}
Icon={IconArrowRight}
onClick={() => openModal(TRANSFER_OWNERSHIP_MODAL_ID)}
/>
</StyledDangerButtonGroup>
</Section>
</SettingsPageContainer>
</SubMenuTopBarContainer>
@@ -622,6 +895,58 @@ export const SettingsApplicationRegistrationDetails = () => {
confirmButtonText={t`Delete`}
loading={isLoading}
/>
<StyledAppModal
modalId={TRANSFER_OWNERSHIP_MODAL_ID}
isClosable
onClose={() => setTransferSubdomain('')}
padding="large"
dataGloballyPreventClickOutside
>
<StyledAppModalTitle>
<H1Title
title={t`Transfer ownership`}
fontColor={H1TitleFontColor.Primary}
/>
</StyledAppModalTitle>
<StyledAppModalSection
alignment={SectionAlignment.Center}
fontColor={SectionFontColor.Primary}
>
{t`Enter the workspace subdomain to transfer this app to. You will lose access to manage it.`}
</StyledAppModalSection>
<Section>
<SettingsTextInput
instanceId="transfer-ownership-subdomain"
value={transferSubdomain}
onChange={setTransferSubdomain}
placeholder={t`e.g. my-workspace`}
fullWidth
disableHotkeys
label={t`Target workspace subdomain`}
autoFocusOnMount
/>
</Section>
<StyledAppModalButton
onClick={() => {
closeModal(TRANSFER_OWNERSHIP_MODAL_ID);
setTransferSubdomain('');
}}
variant="secondary"
title={t`Cancel`}
fullWidth
/>
<StyledAppModalButton
onClick={handleTransferOwnership}
variant="secondary"
accent="danger"
title={t`Transfer`}
disabled={
!isNonEmptyString(transferSubdomain.trim()) || isTransferring
}
fullWidth
/>
</StyledAppModal>
</>
);
};
@@ -7,16 +7,18 @@ import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTab
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { useMemo, useState } from 'react';
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
import {
IconApps,
IconBox,
IconCheck,
IconColumns,
IconCommand,
IconDownload,
IconEyeOff,
IconFileText,
IconInfoCircle,
IconLayoutGrid,
@@ -27,7 +29,11 @@ import {
import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { PermissionFlagType } from '~/generated-metadata/graphql';
import {
PermissionFlagType,
useFindOneApplicationByUniversalIdentifierQuery,
useFindOneMarketplaceAppQuery,
} from '~/generated-metadata/graphql';
import { useMarketplaceApps } from '~/modules/marketplace/hooks/useMarketplaceApps';
import { SettingsApplicationPermissionsTab } from '~/pages/settings/applications/tabs/SettingsApplicationPermissionsTab';
import { SettingsAvailableApplicationDetailContentTab } from '~/pages/settings/applications/tabs/SettingsAvailableApplicationDetailContentTab';
@@ -240,6 +246,19 @@ const StyledProviderItem = styled.li`
margin-bottom: ${themeCssVariables.spacing[1]};
`;
const StyledUnlistedBanner = styled.div`
align-items: center;
background-color: ${themeCssVariables.background.transparent.lighter};
border: 1px solid ${themeCssVariables.border.color.medium};
border-radius: ${themeCssVariables.border.radius.sm};
color: ${themeCssVariables.font.color.secondary};
display: flex;
font-size: ${themeCssVariables.font.size.md};
gap: ${themeCssVariables.spacing[2]};
margin-bottom: ${themeCssVariables.spacing[4]};
padding: ${themeCssVariables.spacing[3]} ${themeCssVariables.spacing[4]};
`;
export const SettingsAvailableApplicationDetails = () => {
const { availableApplicationId = '' } = useParams<{
availableApplicationId: string;
@@ -252,10 +271,44 @@ export const SettingsAvailableApplicationDetails = () => {
const canInstallMarketplaceApps = useHasPermissionFlag(
PermissionFlagType.MARKETPLACE_APPS,
);
const { data: installedAppData } =
useFindOneApplicationByUniversalIdentifierQuery({
variables: { universalIdentifier: availableApplicationId },
skip: !availableApplicationId,
});
const application = useMemo(() => {
return marketplaceApps?.find((app) => app.id === availableApplicationId);
}, [availableApplicationId, marketplaceApps]);
const listedApp = marketplaceApps?.find(
(app) => app.id === availableApplicationId,
);
const { data: singleAppData } = useFindOneMarketplaceAppQuery({
variables: { universalIdentifier: availableApplicationId },
skip: isDefined(listedApp) || !availableApplicationId,
});
const singleApp = singleAppData?.findOneMarketplaceApp;
const application = isDefined(listedApp)
? listedApp
: isDefined(singleApp)
? {
...singleApp,
content: {
objects: (singleApp.objects ?? []).length,
fields:
(singleApp.objects ?? []).reduce(
(count, appObject) => count + appObject.fields.length,
0,
) + (singleApp.fields ?? []).length,
functions: (singleApp.logicFunctions ?? []).length,
frontComponents: (singleApp.frontComponents ?? []).length,
},
}
: undefined;
const isUnlisted = !isDefined(listedApp) && isDefined(application);
const isAlreadyInstalled = isDefined(installedAppData?.findOneApplication);
const handleInstall = async () => {
if (isDefined(application)) {
@@ -438,6 +491,12 @@ export const SettingsAvailableApplicationDetails = () => {
]}
>
<SettingsPageContainer>
{isUnlisted && (
<StyledUnlistedBanner>
<IconEyeOff size={16} />
{t`This application is not listed on the marketplace. It was shared via a direct link.`}
</StyledUnlistedBanner>
)}
<StyledHeader>
<StyledHeaderLeft>
<StyledLogo>
@@ -461,12 +520,18 @@ export const SettingsAvailableApplicationDetails = () => {
</StyledHeaderLeft>
{canInstallMarketplaceApps && (
<Button
Icon={IconDownload}
title={isInstalling ? t`Installing...` : t`Install`}
variant="primary"
accent="blue"
Icon={isAlreadyInstalled ? IconCheck : IconDownload}
title={
isAlreadyInstalled
? t`Installed`
: isInstalling
? t`Installing...`
: t`Install`
}
variant={isAlreadyInstalled ? 'secondary' : 'primary'}
accent={isAlreadyInstalled ? 'default' : 'blue'}
onClick={handleInstall}
disabled={isInstalling}
disabled={isAlreadyInstalled || isInstalling}
/>
)}
</StyledHeader>
@@ -6,7 +6,7 @@ import { isDefined } from 'twenty-shared/utils';
import { IconCircleDot, IconStatusChange, IconUpload } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import {
AppRegistrationSourceType,
ApplicationRegistrationSourceType,
type Application,
} from '~/generated-metadata/graphql';
import { isNewerSemver } from '~/pages/settings/applications/utils/isNewerSemver';
@@ -26,7 +26,7 @@ export const SettingsApplicationVersionContainer = ({
const currentVersion = application?.version;
const sourceType = application?.applicationRegistration?.sourceType;
const isNpmApp = sourceType === AppRegistrationSourceType.NPM;
const isNpmApp = sourceType === ApplicationRegistrationSourceType.NPM;
const latestVersion = isNpmApp
? (latestAvailableVersion ?? currentVersion)
@@ -1,4 +1,4 @@
import { H2Title, IconChevronRight, IconSearch } from 'twenty-ui/display';
import { H2Title, IconChevronRight } from 'twenty-ui/display';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
@@ -10,13 +10,13 @@ import {
APPLICATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
SettingsApplicationTableRow,
} from '~/pages/settings/applications/components/SettingsApplicationTableRow';
import { useContext, useMemo, useState } from 'react';
import { useContext, useState } from 'react';
import { type ApplicationWithoutRelation } from '~/pages/settings/applications/types/applicationWithoutRelation';
import { isNewerSemver } from '~/pages/settings/applications/utils/isNewerSemver';
import { Section } from 'twenty-ui/layout';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { SearchInput } from 'twenty-ui/input';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { AppRegistrationSourceType } from '~/generated-metadata/graphql';
import { ApplicationRegistrationSourceType } from '~/generated-metadata/graphql';
const StyledTableContainer = styled.div`
margin-top: ${themeCssVariables.spacing[3]};
@@ -41,15 +41,13 @@ export const SettingsApplicationsTable = ({
const [searchTerm, setSearchTerm] = useState('');
const filteredApplications = useMemo(() => {
return applications.filter(
(application) =>
application.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(application.description ?? '')
.toLowerCase()
.includes(searchTerm.toLowerCase()),
);
}, [applications, searchTerm]);
const filteredApplications = applications.filter(
(application) =>
application.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(application.description ?? '')
.toLowerCase()
.includes(searchTerm.toLowerCase()),
);
return (
<Section>
@@ -58,9 +56,7 @@ export const SettingsApplicationsTable = ({
description={t`List installed applications. Use filter to search for a specific application`}
/>
<StyledSearchInputContainer>
<SettingsTextInput
instanceId="env-var-search"
LeftIcon={IconSearch}
<SearchInput
placeholder={t`Search an application`}
value={searchTerm}
onChange={setSearchTerm}
@@ -81,7 +77,7 @@ export const SettingsApplicationsTable = ({
{filteredApplications.map((application) => {
const isNpmApp =
application.applicationRegistration?.sourceType ===
AppRegistrationSourceType.NPM;
ApplicationRegistrationSourceType.NPM;
const latestVersion =
application.applicationRegistration?.latestAvailableVersion;
@@ -1,14 +1,15 @@
import { styled } from '@linaria/react';
import { useState } from 'react';
import { FIND_MANY_APPLICATION_REGISTRATIONS } from '@/settings/application-registrations/graphql/queries/findManyApplicationRegistrations';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { useApolloClient } from '@apollo/client';
import { useLingui } from '@lingui/react/macro';
import { H1Title, H1TitleFontColor } from 'twenty-ui/display';
import { Section, SectionAlignment, SectionFontColor } from 'twenty-ui/layout';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useInstallNpmApp } from '~/modules/marketplace/hooks/useInstallNpmApp';
import { useFindManyApplicationsQuery } from '~/generated-metadata/graphql';
import { useRegisterNpmPackage } from '~/modules/marketplace/hooks/useRegisterNpmPackage';
import {
StyledAppModal,
StyledAppModalButton,
@@ -16,7 +17,7 @@ import {
StyledAppModalTitle,
} from '~/pages/settings/applications/components/SettingsAppModalLayout';
export const INSTALL_NPM_APP_MODAL_ID = 'install-npm-app-modal';
export const REGISTER_NPM_APP_MODAL_ID = 'register-npm-app-modal';
const StyledInputGroup = styled.div`
display: flex;
@@ -24,51 +25,49 @@ const StyledInputGroup = styled.div`
gap: ${themeCssVariables.spacing[3]};
`;
export const SettingsInstallNpmAppModal = () => {
export const SettingsRegisterNpmAppModal = () => {
const { t } = useLingui();
const { closeModal } = useModal();
const { install, isInstalling } = useInstallNpmApp();
const { refetch } = useFindManyApplicationsQuery();
const { register, isRegistering } = useRegisterNpmPackage();
const apolloClient = useApolloClient();
const [packageName, setPackageName] = useState('');
const [version, setVersion] = useState('');
const isValid = packageName.trim().length > 0;
const handleInstall = async () => {
const handleRegister = async () => {
if (!isValid) {
return;
}
const success = await install({
const success = await register({
packageName: packageName.trim(),
version: version.trim() || undefined,
});
if (success) {
await refetch();
closeModal(INSTALL_NPM_APP_MODAL_ID);
await apolloClient.refetchQueries({
include: [FIND_MANY_APPLICATION_REGISTRATIONS],
});
closeModal(REGISTER_NPM_APP_MODAL_ID);
setPackageName('');
setVersion('');
}
};
const handleCancel = () => {
closeModal(INSTALL_NPM_APP_MODAL_ID);
closeModal(REGISTER_NPM_APP_MODAL_ID);
setPackageName('');
setVersion('');
};
return (
<StyledAppModal
modalId={INSTALL_NPM_APP_MODAL_ID}
modalId={REGISTER_NPM_APP_MODAL_ID}
isClosable={true}
padding="large"
dataGloballyPreventClickOutside
>
<StyledAppModalTitle>
<H1Title
title={t`Install from npm`}
title={t`Register from npm`}
fontColor={H1TitleFontColor.Primary}
/>
</StyledAppModalTitle>
@@ -76,7 +75,7 @@ export const SettingsInstallNpmAppModal = () => {
alignment={SectionAlignment.Center}
fontColor={SectionFontColor.Primary}
>
{t`Enter the npm package name of the application you want to install.`}
{t`Enter the npm package name to register as an application.`}
</StyledAppModalSection>
<Section>
@@ -91,15 +90,6 @@ export const SettingsInstallNpmAppModal = () => {
label={t`Package name`}
autoFocusOnMount
/>
<SettingsTextInput
instanceId="npm-version-input"
value={version}
onChange={setVersion}
placeholder={t`latest`}
fullWidth
disableHotkeys
label={t`Version (optional)`}
/>
</StyledInputGroup>
</Section>
@@ -110,11 +100,11 @@ export const SettingsInstallNpmAppModal = () => {
fullWidth
/>
<StyledAppModalButton
onClick={handleInstall}
onClick={handleRegister}
variant="secondary"
accent="blue"
title={t`Install`}
disabled={!isValid || isInstalling}
title={t`Register`}
disabled={!isValid || isRegistering}
fullWidth
/>
</StyledAppModal>
@@ -1,14 +1,17 @@
import { styled } from '@linaria/react';
import { useRef } from 'react';
import { FIND_MANY_APPLICATION_REGISTRATIONS } from '@/settings/application-registrations/graphql/queries/findManyApplicationRegistrations';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { useApolloClient, useMutation } from '@apollo/client';
import { useLingui } from '@lingui/react/macro';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { H1Title, H1TitleFontColor } from 'twenty-ui/display';
import { SectionAlignment, SectionFontColor } from 'twenty-ui/layout';
import { INSTALL_APPLICATION } from '~/modules/marketplace/graphql/mutations/installApplication';
import { useUploadAppTarball } from '~/modules/marketplace/hooks/useUploadAppTarball';
import { useInstallMarketplaceApp } from '~/modules/marketplace/hooks/useInstallMarketplaceApp';
import { useFindManyApplicationsQuery } from '~/generated-metadata/graphql';
import {
StyledAppModal,
StyledAppModalButton,
@@ -23,11 +26,12 @@ const StyledFileInput = styled.input`
`;
export const SettingsUploadTarballModal = () => {
const { t } = useLingui();
const { t: tFn } = useLingui();
const { closeModal } = useModal();
const { upload, isUploading } = useUploadAppTarball();
const { install } = useInstallMarketplaceApp();
const { refetch } = useFindManyApplicationsQuery();
const [installApplication] = useMutation(INSTALL_APPLICATION);
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
const apolloClient = useApolloClient();
const fileInputRef = useRef<HTMLInputElement>(null);
const handleSelectFile = () => {
@@ -46,16 +50,31 @@ export const SettingsUploadTarballModal = () => {
try {
const uploadResult = await upload(file);
if (uploadResult.success) {
const installResult = await install({
universalIdentifier: uploadResult.universalIdentifier,
});
if (!uploadResult.success) {
return;
}
if (installResult) {
await refetch();
closeModal(UPLOAD_TARBALL_MODAL_ID);
const registrationId = uploadResult.registrationId;
if (isDefined(registrationId)) {
try {
await installApplication({
variables: { appRegistrationId: registrationId },
});
enqueueSuccessSnackBar({
message: t`Application installed successfully.`,
});
} catch {
enqueueErrorSnackBar({
message: t`Tarball uploaded but installation failed.`,
});
}
}
await apolloClient.refetchQueries({
include: [FIND_MANY_APPLICATION_REGISTRATIONS],
});
closeModal(UPLOAD_TARBALL_MODAL_ID);
} finally {
if (isDefined(fileInputRef.current)) {
fileInputRef.current.value = '';
@@ -76,7 +95,7 @@ export const SettingsUploadTarballModal = () => {
>
<StyledAppModalTitle>
<H1Title
title={t`Upload tarball`}
title={tFn`Upload tarball`}
fontColor={H1TitleFontColor.Primary}
/>
</StyledAppModalTitle>
@@ -84,7 +103,7 @@ export const SettingsUploadTarballModal = () => {
alignment={SectionAlignment.Center}
fontColor={SectionFontColor.Primary}
>
{t`Select a .tar.gz application package to upload and install.`}
{tFn`Select a .tar.gz application package to upload and install.`}
</StyledAppModalSection>
<StyledFileInput
@@ -97,14 +116,14 @@ export const SettingsUploadTarballModal = () => {
<StyledAppModalButton
onClick={handleCancel}
variant="secondary"
title={t`Cancel`}
title={tFn`Cancel`}
fullWidth
/>
<StyledAppModalButton
onClick={handleSelectFile}
variant="secondary"
accent="blue"
title={t`Choose file`}
title={tFn`Choose file`}
disabled={isUploading}
fullWidth
/>
@@ -1,11 +1,16 @@
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { useMemo, useState } from 'react';
import { type ReactNode, useState } from 'react';
import { IconSparkles } from 'twenty-ui/display';
import { SearchInput } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { MenuItemToggle } from 'twenty-ui/navigation';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { SettingsAvailableApplicationCard } from '~/pages/settings/applications/components/SettingsAvailableApplicationCard';
import { useMarketplaceApps } from '~/modules/marketplace/hooks/useMarketplaceApps';
import { SettingsAvailableApplicationCard } from '~/pages/settings/applications/components/SettingsAvailableApplicationCard';
const StyledSearchInputContainer = styled.div`
padding-bottom: ${themeCssVariables.spacing[2]};
@@ -27,24 +32,43 @@ const StyledEmptyState = styled.div`
text-align: center;
`;
const StyledHintLink = styled.button`
background: none;
border: none;
color: ${themeCssVariables.color.blue};
cursor: pointer;
font-size: inherit;
padding: 0;
text-decoration: underline;
`;
export const SettingsApplicationsAvailableTab = () => {
const { t } = useLingui();
const [searchTerm, setSearchTerm] = useState('');
const [showFeaturedOnly, setShowFeaturedOnly] = useState(true);
const { data: marketplaceApps, isLoading } = useMarketplaceApps();
const applications = marketplaceApps;
const textFilteredApplications = searchTerm
? marketplaceApps.filter((application) => {
const lowerSearch = searchTerm.toLowerCase();
const filteredApplications = useMemo(() => {
return applications.filter(
(application) =>
application.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
application.description
.toLowerCase()
.includes(searchTerm.toLowerCase()) ||
application.author.toLowerCase().includes(searchTerm.toLowerCase()),
);
}, [applications, searchTerm]);
return (
application.name.toLowerCase().includes(lowerSearch) ||
application.description.toLowerCase().includes(lowerSearch) ||
application.author.toLowerCase().includes(lowerSearch)
);
})
: marketplaceApps;
const filteredApplications = showFeaturedOnly
? textFilteredApplications.filter((application) => application.isFeatured)
: textFilteredApplications;
const nonFeaturedCount = showFeaturedOnly
? textFilteredApplications.filter((application) => !application.isFeatured)
.length
: 0;
if (isLoading) {
return (
@@ -54,6 +78,9 @@ export const SettingsApplicationsAvailableTab = () => {
);
}
const showNonFeaturedHint =
filteredApplications.length === 0 && nonFeaturedCount > 0;
return (
<Section>
<StyledSearchInputContainer>
@@ -61,11 +88,43 @@ export const SettingsApplicationsAvailableTab = () => {
placeholder={t`Search an application`}
value={searchTerm}
onChange={setSearchTerm}
filterDropdown={(filterButton: ReactNode) => (
<Dropdown
dropdownId="marketplace-filter-dropdown"
dropdownPlacement="bottom-end"
dropdownOffset={{ x: 0, y: 8 }}
clickableComponent={filterButton}
dropdownComponents={
<DropdownContent>
<DropdownMenuItemsContainer>
<MenuItemToggle
LeftIcon={IconSparkles}
onToggleChange={() =>
setShowFeaturedOnly(!showFeaturedOnly)
}
toggled={showFeaturedOnly}
text={t`Featured only`}
toggleSize="small"
/>
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
)}
/>
</StyledSearchInputContainer>
{filteredApplications.length === 0 ? (
<StyledEmptyState>{t`No applications available`}</StyledEmptyState>
<StyledEmptyState>
{showNonFeaturedHint
? t`No featured applications found. ${nonFeaturedCount} non-featured result(s) available — `
: t`No applications available`}
{showNonFeaturedHint && (
<StyledHintLink onClick={() => setShowFeaturedOnly(false)}>
{t`show all`}
</StyledHintLink>
)}
</StyledEmptyState>
) : (
<StyledCardsGrid>
{filteredApplications.map((application) => (
@@ -2,6 +2,11 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
import { FIND_MANY_APPLICATION_REGISTRATIONS } from '@/settings/application-registrations/graphql/queries/findManyApplicationRegistrations';
import { SettingsListCard } from '@/settings/components/SettingsListCard';
import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
@@ -13,20 +18,40 @@ import {
CommandBlock,
H2Title,
IconApps,
IconChevronDown,
IconChevronRight,
IconCopy,
IconDownload,
IconFileInfo,
IconUpload,
} from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { Button, ButtonGroup, IconButton } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { useContext } from 'react';
import { MenuItem } from 'twenty-ui/navigation';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
import {
REGISTER_NPM_APP_MODAL_ID,
SettingsRegisterNpmAppModal,
} from '~/pages/settings/applications/components/SettingsRegisterNpmAppModal';
import {
SettingsUploadTarballModal,
UPLOAD_TARBALL_MODAL_ID,
} from '~/pages/settings/applications/components/SettingsUploadTarballModal';
const REGISTER_APP_DROPDOWN_ID = 'register-app-dropdown';
const StyledButtonContainer = styled.div`
margin: ${themeCssVariables.spacing[2]} 0;
`;
const StyledButtonGroupContainer = styled.div`
display: flex;
justify-content: flex-end;
margin-bottom: ${themeCssVariables.spacing[4]};
`;
type ApplicationRegistration = {
id: string;
name: string;
@@ -38,6 +63,8 @@ export const SettingsApplicationsDeveloperTab = () => {
const { t } = useLingui();
const navigate = useNavigate();
const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState);
const { openModal } = useModal();
const { closeDropdown } = useCloseDropdown();
const { copyToClipboard } = useCopyToClipboard();
@@ -63,6 +90,16 @@ export const SettingsApplicationsDeveloperTab = () => {
/>
);
const handleRegisterFromNpm = () => {
closeDropdown(REGISTER_APP_DROPDOWN_ID);
openModal(REGISTER_NPM_APP_MODAL_ID);
};
const handleUploadTarball = () => {
closeDropdown(REGISTER_APP_DROPDOWN_ID);
openModal(UPLOAD_TARBALL_MODAL_ID);
};
return (
<>
<Section>
@@ -87,12 +124,12 @@ export const SettingsApplicationsDeveloperTab = () => {
/>
</StyledButtonContainer>
</Section>
{registrations.length > 0 && (
<Section>
<H2Title
title={t`My Apps`}
description={t`Apps you've created and published`}
/>
<Section>
<H2Title
title={t`My Apps`}
description={t`Apps you've created, registered, or published`}
/>
{registrations.length > 0 && (
<SettingsListCard
items={registrations}
getItemLabel={(registration) => registration.name}
@@ -112,8 +149,47 @@ export const SettingsApplicationsDeveloperTab = () => {
/>
)}
/>
</Section>
)}
)}
</Section>
<StyledButtonGroupContainer>
<ButtonGroup size="small" variant="secondary">
<Button
Icon={IconDownload}
title={t`Register from npm`}
onClick={handleRegisterFromNpm}
/>
<Dropdown
dropdownId={REGISTER_APP_DROPDOWN_ID}
clickableComponent={
<IconButton
size="small"
variant="secondary"
Icon={IconChevronDown}
position="right"
/>
}
dropdownComponents={
<DropdownContent>
<DropdownMenuItemsContainer>
<MenuItem
LeftIcon={IconDownload}
text={t`Register from npm`}
onClick={handleRegisterFromNpm}
/>
<MenuItem
LeftIcon={IconUpload}
text={t`Upload tarball`}
onClick={handleUploadTarball}
/>
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
</ButtonGroup>
</StyledButtonGroupContainer>
<SettingsRegisterNpmAppModal />
<SettingsUploadTarballModal />
</>
);
};
@@ -1,99 +1,14 @@
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { IconChevronDown, IconDownload, IconUpload } from 'twenty-ui/display';
import { Button, ButtonGroup, IconButton } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { MenuItem } from 'twenty-ui/navigation';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useFindManyApplicationsQuery } from '~/generated-metadata/graphql';
import { SettingsApplicationsTable } from '~/pages/settings/applications/components/SettingsApplicationsTable';
import {
INSTALL_NPM_APP_MODAL_ID,
SettingsInstallNpmAppModal,
} from '~/pages/settings/applications/components/SettingsInstallNpmAppModal';
import {
SettingsUploadTarballModal,
UPLOAD_TARBALL_MODAL_ID,
} from '~/pages/settings/applications/components/SettingsUploadTarballModal';
const INSTALL_APP_DROPDOWN_ID = 'install-app-dropdown';
const StyledButtonGroupContainer = styled.div`
display: flex;
justify-content: flex-end;
margin-bottom: ${themeCssVariables.spacing[4]};
`;
export const SettingsApplicationsInstalledTab = () => {
const { t } = useLingui();
const { data } = useFindManyApplicationsQuery();
const { openModal } = useModal();
const { closeDropdown } = useCloseDropdown();
const applications = data?.findManyApplications ?? [];
const handleInstallFromNpm = () => {
closeDropdown(INSTALL_APP_DROPDOWN_ID);
openModal(INSTALL_NPM_APP_MODAL_ID);
};
if (applications.length === 0) {
return null;
}
const handleUploadTarball = () => {
closeDropdown(INSTALL_APP_DROPDOWN_ID);
openModal(UPLOAD_TARBALL_MODAL_ID);
};
return (
<>
{applications.length > 0 && (
<SettingsApplicationsTable applications={applications} />
)}
<Section>
<StyledButtonGroupContainer>
<ButtonGroup size="small" variant="secondary">
<Button
Icon={IconDownload}
title={t`Install app`}
onClick={handleInstallFromNpm}
/>
<Dropdown
dropdownId={INSTALL_APP_DROPDOWN_ID}
clickableComponent={
<IconButton
size="small"
variant="secondary"
Icon={IconChevronDown}
position="right"
/>
}
dropdownComponents={
<DropdownContent>
<DropdownMenuItemsContainer>
<MenuItem
LeftIcon={IconDownload}
text={t`Install from npm`}
onClick={handleInstallFromNpm}
/>
<MenuItem
LeftIcon={IconUpload}
text={t`Upload tarball`}
onClick={handleUploadTarball}
/>
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
</ButtonGroup>
</StyledButtonGroupContainer>
</Section>
<SettingsInstallNpmAppModal />
<SettingsUploadTarballModal />
</>
);
return <SettingsApplicationsTable applications={applications} />;
};