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:
@@ -316,12 +316,6 @@ export type ApiKeyToken = {
|
|||||||
token: Scalars['String'];
|
token: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum AppRegistrationSourceType {
|
|
||||||
LOCAL = 'LOCAL',
|
|
||||||
NPM = 'NPM',
|
|
||||||
TARBALL = 'TARBALL'
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AppToken = {
|
export type AppToken = {
|
||||||
__typename?: 'AppToken';
|
__typename?: 'AppToken';
|
||||||
createdAt: Scalars['DateTime'];
|
createdAt: Scalars['DateTime'];
|
||||||
@@ -362,20 +356,28 @@ export type ApplicationRegistration = {
|
|||||||
description?: Maybe<Scalars['String']>;
|
description?: Maybe<Scalars['String']>;
|
||||||
id: Scalars['UUID'];
|
id: Scalars['UUID'];
|
||||||
isFeatured: Scalars['Boolean'];
|
isFeatured: Scalars['Boolean'];
|
||||||
|
isListed: Scalars['Boolean'];
|
||||||
latestAvailableVersion?: Maybe<Scalars['String']>;
|
latestAvailableVersion?: Maybe<Scalars['String']>;
|
||||||
logoUrl?: Maybe<Scalars['String']>;
|
logoUrl?: Maybe<Scalars['String']>;
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
oAuthClientId: Scalars['String'];
|
oAuthClientId: Scalars['String'];
|
||||||
oAuthRedirectUris: Array<Scalars['String']>;
|
oAuthRedirectUris: Array<Scalars['String']>;
|
||||||
oAuthScopes: Array<Scalars['String']>;
|
oAuthScopes: Array<Scalars['String']>;
|
||||||
|
ownerWorkspaceId?: Maybe<Scalars['UUID']>;
|
||||||
sourcePackage?: Maybe<Scalars['String']>;
|
sourcePackage?: Maybe<Scalars['String']>;
|
||||||
sourceType: AppRegistrationSourceType;
|
sourceType: ApplicationRegistrationSourceType;
|
||||||
termsUrl?: Maybe<Scalars['String']>;
|
termsUrl?: Maybe<Scalars['String']>;
|
||||||
universalIdentifier: Scalars['String'];
|
universalIdentifier: Scalars['String'];
|
||||||
updatedAt: Scalars['DateTime'];
|
updatedAt: Scalars['DateTime'];
|
||||||
websiteUrl?: Maybe<Scalars['String']>;
|
websiteUrl?: Maybe<Scalars['String']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum ApplicationRegistrationSourceType {
|
||||||
|
LOCAL = 'LOCAL',
|
||||||
|
NPM = 'NPM',
|
||||||
|
TARBALL = 'TARBALL'
|
||||||
|
}
|
||||||
|
|
||||||
export type ApplicationRegistrationStats = {
|
export type ApplicationRegistrationStats = {
|
||||||
__typename?: 'ApplicationRegistrationStats';
|
__typename?: 'ApplicationRegistrationStats';
|
||||||
activeInstalls: Scalars['Int'];
|
activeInstalls: Scalars['Int'];
|
||||||
@@ -387,7 +389,7 @@ export type ApplicationRegistrationSummary = {
|
|||||||
__typename?: 'ApplicationRegistrationSummary';
|
__typename?: 'ApplicationRegistrationSummary';
|
||||||
id: Scalars['UUID'];
|
id: Scalars['UUID'];
|
||||||
latestAvailableVersion?: Maybe<Scalars['String']>;
|
latestAvailableVersion?: Maybe<Scalars['String']>;
|
||||||
sourceType: AppRegistrationSourceType;
|
sourceType: ApplicationRegistrationSourceType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApplicationRegistrationVariable = {
|
export type ApplicationRegistrationVariable = {
|
||||||
@@ -1086,15 +1088,6 @@ export type CreateAppTokenInput = {
|
|||||||
expiresAt: Scalars['DateTime'];
|
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 = {
|
export type CreateApplicationRegistration = {
|
||||||
__typename?: 'CreateApplicationRegistration';
|
__typename?: 'CreateApplicationRegistration';
|
||||||
applicationRegistration: ApplicationRegistration;
|
applicationRegistration: ApplicationRegistration;
|
||||||
@@ -2235,6 +2228,7 @@ export type MarketplaceApp = {
|
|||||||
frontComponents: Array<MarketplaceAppFrontComponent>;
|
frontComponents: Array<MarketplaceAppFrontComponent>;
|
||||||
icon: Scalars['String'];
|
icon: Scalars['String'];
|
||||||
id: Scalars['String'];
|
id: Scalars['String'];
|
||||||
|
isFeatured: Scalars['Boolean'];
|
||||||
logicFunctions: Array<MarketplaceAppLogicFunction>;
|
logicFunctions: Array<MarketplaceAppLogicFunction>;
|
||||||
logo?: Maybe<Scalars['String']>;
|
logo?: Maybe<Scalars['String']>;
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
@@ -2383,7 +2377,6 @@ export type Mutation = {
|
|||||||
createObjectEvent: Analytics;
|
createObjectEvent: Analytics;
|
||||||
createOneAgent: Agent;
|
createOneAgent: Agent;
|
||||||
createOneAppToken: AppToken;
|
createOneAppToken: AppToken;
|
||||||
createOneApplication: Application;
|
|
||||||
createOneField: Field;
|
createOneField: Field;
|
||||||
createOneLogicFunction: LogicFunction;
|
createOneLogicFunction: LogicFunction;
|
||||||
createOneObject: Object;
|
createOneObject: Object;
|
||||||
@@ -2456,7 +2449,7 @@ export type Mutation = {
|
|||||||
initiateOTPProvisioningForAuthenticatedUser: InitiateTwoFactorAuthenticationProvisioning;
|
initiateOTPProvisioningForAuthenticatedUser: InitiateTwoFactorAuthenticationProvisioning;
|
||||||
installApplication: Scalars['Boolean'];
|
installApplication: Scalars['Boolean'];
|
||||||
installMarketplaceApp: Scalars['Boolean'];
|
installMarketplaceApp: Scalars['Boolean'];
|
||||||
installNpmApp: Scalars['Boolean'];
|
registerNpmPackage: ApplicationRegistration;
|
||||||
removeQueryFromEventStream: Scalars['Boolean'];
|
removeQueryFromEventStream: Scalars['Boolean'];
|
||||||
removeRoleFromAgent: Scalars['Boolean'];
|
removeRoleFromAgent: Scalars['Boolean'];
|
||||||
renewApplicationToken: ApplicationTokenPair;
|
renewApplicationToken: ApplicationTokenPair;
|
||||||
@@ -2467,6 +2460,7 @@ export type Mutation = {
|
|||||||
revokeApiKey?: Maybe<ApiKey>;
|
revokeApiKey?: Maybe<ApiKey>;
|
||||||
rotateApplicationRegistrationClientSecret: RotateClientSecret;
|
rotateApplicationRegistrationClientSecret: RotateClientSecret;
|
||||||
runEvaluationInput: AgentTurn;
|
runEvaluationInput: AgentTurn;
|
||||||
|
runWorkspaceMigration: Scalars['Boolean'];
|
||||||
saveImapSmtpCaldavAccount: ImapSmtpCaldavConnectionSuccess;
|
saveImapSmtpCaldavAccount: ImapSmtpCaldavConnectionSuccess;
|
||||||
sendInvitations: SendInvitations;
|
sendInvitations: SendInvitations;
|
||||||
setAdminAiModelEnabled: Scalars['Boolean'];
|
setAdminAiModelEnabled: Scalars['Boolean'];
|
||||||
@@ -2482,6 +2476,7 @@ export type Mutation = {
|
|||||||
switchSubscriptionInterval: BillingUpdate;
|
switchSubscriptionInterval: BillingUpdate;
|
||||||
syncApplication: WorkspaceMigration;
|
syncApplication: WorkspaceMigration;
|
||||||
trackAnalytics: Analytics;
|
trackAnalytics: Analytics;
|
||||||
|
transferApplicationRegistrationOwnership: ApplicationRegistration;
|
||||||
uninstallApplication: Scalars['Boolean'];
|
uninstallApplication: Scalars['Boolean'];
|
||||||
updateApiKey?: Maybe<ApiKey>;
|
updateApiKey?: Maybe<ApiKey>;
|
||||||
updateApplicationRegistration: ApplicationRegistration;
|
updateApplicationRegistration: ApplicationRegistration;
|
||||||
@@ -2707,11 +2702,6 @@ export type MutationCreateOneAppTokenArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationCreateOneApplicationArgs = {
|
|
||||||
input: CreateApplicationInput;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export type MutationCreateOneFieldArgs = {
|
export type MutationCreateOneFieldArgs = {
|
||||||
input: CreateOneFieldMetadataInput;
|
input: CreateOneFieldMetadataInput;
|
||||||
};
|
};
|
||||||
@@ -3042,7 +3032,8 @@ export type MutationInitiateOtpProvisioningArgs = {
|
|||||||
|
|
||||||
|
|
||||||
export type MutationInstallApplicationArgs = {
|
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'];
|
packageName: Scalars['String'];
|
||||||
version?: InputMaybe<Scalars['String']>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -3111,6 +3101,11 @@ export type MutationRunEvaluationInputArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationRunWorkspaceMigrationArgs = {
|
||||||
|
workspaceMigration: WorkspaceMigrationInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationSaveImapSmtpCaldavAccountArgs = {
|
export type MutationSaveImapSmtpCaldavAccountArgs = {
|
||||||
accountOwnerId: Scalars['UUID'];
|
accountOwnerId: Scalars['UUID'];
|
||||||
connectionParameters: EmailAccountConnectionParameters;
|
connectionParameters: EmailAccountConnectionParameters;
|
||||||
@@ -3184,6 +3179,12 @@ export type MutationTrackAnalyticsArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationTransferApplicationRegistrationOwnershipArgs = {
|
||||||
|
applicationRegistrationId: Scalars['String'];
|
||||||
|
targetWorkspaceSubdomain: Scalars['String'];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationUninstallApplicationArgs = {
|
export type MutationUninstallApplicationArgs = {
|
||||||
universalIdentifier: Scalars['String'];
|
universalIdentifier: Scalars['String'];
|
||||||
};
|
};
|
||||||
@@ -3932,6 +3933,7 @@ export type Query = {
|
|||||||
agentTurns: Array<AgentTurn>;
|
agentTurns: Array<AgentTurn>;
|
||||||
apiKey?: Maybe<ApiKey>;
|
apiKey?: Maybe<ApiKey>;
|
||||||
apiKeys: Array<ApiKey>;
|
apiKeys: Array<ApiKey>;
|
||||||
|
applicationRegistrationTarballUrl?: Maybe<Scalars['String']>;
|
||||||
barChartData: BarChartData;
|
barChartData: BarChartData;
|
||||||
billingPortalSession: BillingSession;
|
billingPortalSession: BillingSession;
|
||||||
chatMessages: Array<AgentMessage>;
|
chatMessages: Array<AgentMessage>;
|
||||||
@@ -3946,6 +3948,7 @@ export type Query = {
|
|||||||
eventLogs: EventLogQueryResult;
|
eventLogs: EventLogQueryResult;
|
||||||
field: Field;
|
field: Field;
|
||||||
fields: FieldConnection;
|
fields: FieldConnection;
|
||||||
|
findAllApplicationRegistrations: Array<ApplicationRegistration>;
|
||||||
findApplicationRegistrationByClientId?: Maybe<PublicApplicationRegistration>;
|
findApplicationRegistrationByClientId?: Maybe<PublicApplicationRegistration>;
|
||||||
findApplicationRegistrationByUniversalIdentifier?: Maybe<ApplicationRegistration>;
|
findApplicationRegistrationByUniversalIdentifier?: Maybe<ApplicationRegistration>;
|
||||||
findApplicationRegistrationStats: ApplicationRegistrationStats;
|
findApplicationRegistrationStats: ApplicationRegistrationStats;
|
||||||
@@ -3960,6 +3963,7 @@ export type Query = {
|
|||||||
findOneApplication: Application;
|
findOneApplication: Application;
|
||||||
findOneApplicationRegistration: ApplicationRegistration;
|
findOneApplicationRegistration: ApplicationRegistration;
|
||||||
findOneLogicFunction: LogicFunction;
|
findOneLogicFunction: LogicFunction;
|
||||||
|
findOneMarketplaceApp: MarketplaceApp;
|
||||||
findWorkspaceFromInviteHash: Workspace;
|
findWorkspaceFromInviteHash: Workspace;
|
||||||
findWorkspaceInvitations: Array<WorkspaceInvitation>;
|
findWorkspaceInvitations: Array<WorkspaceInvitation>;
|
||||||
frontComponent?: Maybe<FrontComponent>;
|
frontComponent?: Maybe<FrontComponent>;
|
||||||
@@ -4035,6 +4039,11 @@ export type QueryApiKeyArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type QueryApplicationRegistrationTarballUrlArgs = {
|
||||||
|
id: Scalars['String'];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type QueryBarChartDataArgs = {
|
export type QueryBarChartDataArgs = {
|
||||||
input: BarChartDataInput;
|
input: BarChartDataInput;
|
||||||
};
|
};
|
||||||
@@ -4135,6 +4144,11 @@ export type QueryFindOneLogicFunctionArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type QueryFindOneMarketplaceAppArgs = {
|
||||||
|
universalIdentifier: Scalars['String'];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type QueryFindWorkspaceFromInviteHashArgs = {
|
export type QueryFindWorkspaceFromInviteHashArgs = {
|
||||||
inviteHash: Scalars['String'];
|
inviteHash: Scalars['String'];
|
||||||
};
|
};
|
||||||
@@ -4827,6 +4841,7 @@ export type UpdateApplicationRegistrationInput = {
|
|||||||
export type UpdateApplicationRegistrationPayload = {
|
export type UpdateApplicationRegistrationPayload = {
|
||||||
author?: InputMaybe<Scalars['String']>;
|
author?: InputMaybe<Scalars['String']>;
|
||||||
description?: InputMaybe<Scalars['String']>;
|
description?: InputMaybe<Scalars['String']>;
|
||||||
|
isListed?: InputMaybe<Scalars['Boolean']>;
|
||||||
logoUrl?: InputMaybe<Scalars['String']>;
|
logoUrl?: InputMaybe<Scalars['String']>;
|
||||||
name?: InputMaybe<Scalars['String']>;
|
name?: InputMaybe<Scalars['String']>;
|
||||||
oAuthRedirectUris?: InputMaybe<Array<Scalars['String']>>;
|
oAuthRedirectUris?: InputMaybe<Array<Scalars['String']>>;
|
||||||
@@ -5818,19 +5833,26 @@ export type UpdateOneApplicationVariableMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type UpdateOneApplicationVariableMutation = { __typename?: 'Mutation', updateOneApplicationVariable: boolean };
|
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 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<{
|
export type FindOneApplicationQueryVariables = Exact<{
|
||||||
id: Scalars['UUID'];
|
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 };
|
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 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<{
|
export type InstallMarketplaceAppMutationVariables = Exact<{
|
||||||
universalIdentifier: Scalars['String'];
|
universalIdentifier: Scalars['String'];
|
||||||
@@ -6235,13 +6265,12 @@ export type InstallMarketplaceAppMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type InstallMarketplaceAppMutation = { __typename?: 'Mutation', installMarketplaceApp: boolean };
|
export type InstallMarketplaceAppMutation = { __typename?: 'Mutation', installMarketplaceApp: boolean };
|
||||||
|
|
||||||
export type InstallNpmAppMutationVariables = Exact<{
|
export type RegisterNpmPackageMutationVariables = Exact<{
|
||||||
packageName: Scalars['String'];
|
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<{
|
export type UpgradeApplicationMutationVariables = Exact<{
|
||||||
appRegistrationId: Scalars['String'];
|
appRegistrationId: Scalars['String'];
|
||||||
@@ -6262,7 +6291,14 @@ export type UploadAppTarballMutation = { __typename?: 'Mutation', uploadAppTarba
|
|||||||
export type FindManyMarketplaceAppsQueryVariables = Exact<{ [key: string]: never; }>;
|
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 };
|
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 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<{
|
export type CreateDatabaseConfigVariableMutationVariables = Exact<{
|
||||||
key: Scalars['String'];
|
key: Scalars['String'];
|
||||||
value: Scalars['JSON'];
|
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 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<{
|
export type DeleteApplicationRegistrationMutationVariables = Exact<{
|
||||||
id: Scalars['String'];
|
id: Scalars['String'];
|
||||||
@@ -6570,12 +6611,20 @@ export type RotateApplicationRegistrationClientSecretMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type RotateApplicationRegistrationClientSecretMutation = { __typename?: 'Mutation', rotateApplicationRegistrationClientSecret: { __typename?: 'RotateClientSecret', clientSecret: string } };
|
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<{
|
export type UpdateApplicationRegistrationMutationVariables = Exact<{
|
||||||
input: UpdateApplicationRegistrationInput;
|
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<{
|
export type UpdateApplicationRegistrationVariableMutationVariables = Exact<{
|
||||||
input: UpdateApplicationRegistrationVariableInput;
|
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 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<{
|
export type FindApplicationRegistrationByClientIdQueryVariables = Exact<{
|
||||||
clientId: Scalars['String'];
|
clientId: Scalars['String'];
|
||||||
}>;
|
}>;
|
||||||
@@ -6608,14 +6664,14 @@ export type FindApplicationRegistrationVariablesQuery = { __typename?: 'Query',
|
|||||||
export type FindManyApplicationRegistrationsQueryVariables = Exact<{ [key: string]: never; }>;
|
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<{
|
export type FindOneApplicationRegistrationQueryVariables = Exact<{
|
||||||
id: Scalars['String'];
|
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<{
|
export type UninstallApplicationMutationVariables = Exact<{
|
||||||
universalIdentifier: Scalars['String'];
|
universalIdentifier: Scalars['String'];
|
||||||
@@ -7889,6 +7945,7 @@ export const MarketplaceAppFieldsFragmentDoc = gql`
|
|||||||
description
|
description
|
||||||
}
|
}
|
||||||
sourcePackage
|
sourcePackage
|
||||||
|
isFeatured
|
||||||
defaultRole {
|
defaultRole {
|
||||||
id
|
id
|
||||||
label
|
label
|
||||||
@@ -7960,6 +8017,9 @@ export const ApplicationRegistrationFragmentFragmentDoc = gql`
|
|||||||
latestAvailableVersion
|
latestAvailableVersion
|
||||||
websiteUrl
|
websiteUrl
|
||||||
termsUrl
|
termsUrl
|
||||||
|
isListed
|
||||||
|
isFeatured
|
||||||
|
ownerWorkspaceId
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
}
|
}
|
||||||
@@ -9385,6 +9445,7 @@ export const FindManyApplicationsDocument = gql`
|
|||||||
name
|
name
|
||||||
description
|
description
|
||||||
version
|
version
|
||||||
|
universalIdentifier
|
||||||
applicationRegistrationId
|
applicationRegistrationId
|
||||||
applicationRegistration {
|
applicationRegistration {
|
||||||
id
|
id
|
||||||
@@ -9456,6 +9517,41 @@ export function useFindOneApplicationLazyQuery(baseOptions?: Apollo.LazyQueryHoo
|
|||||||
export type FindOneApplicationQueryHookResult = ReturnType<typeof useFindOneApplicationQuery>;
|
export type FindOneApplicationQueryHookResult = ReturnType<typeof useFindOneApplicationQuery>;
|
||||||
export type FindOneApplicationLazyQueryHookResult = ReturnType<typeof useFindOneApplicationLazyQuery>;
|
export type FindOneApplicationLazyQueryHookResult = ReturnType<typeof useFindOneApplicationLazyQuery>;
|
||||||
export type FindOneApplicationQueryResult = Apollo.QueryResult<FindOneApplicationQuery, FindOneApplicationQueryVariables>;
|
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`
|
export const AuthorizeAppDocument = gql`
|
||||||
mutation authorizeApp($clientId: String!, $codeChallenge: String, $redirectUrl: String!) {
|
mutation authorizeApp($clientId: String!, $codeChallenge: String, $redirectUrl: String!) {
|
||||||
authorizeApp(
|
authorizeApp(
|
||||||
@@ -11437,6 +11533,38 @@ export function useGetLogicFunctionSourceCodeLazyQuery(baseOptions?: Apollo.Lazy
|
|||||||
export type GetLogicFunctionSourceCodeQueryHookResult = ReturnType<typeof useGetLogicFunctionSourceCodeQuery>;
|
export type GetLogicFunctionSourceCodeQueryHookResult = ReturnType<typeof useGetLogicFunctionSourceCodeQuery>;
|
||||||
export type GetLogicFunctionSourceCodeLazyQueryHookResult = ReturnType<typeof useGetLogicFunctionSourceCodeLazyQuery>;
|
export type GetLogicFunctionSourceCodeLazyQueryHookResult = ReturnType<typeof useGetLogicFunctionSourceCodeLazyQuery>;
|
||||||
export type GetLogicFunctionSourceCodeQueryResult = Apollo.QueryResult<GetLogicFunctionSourceCodeQuery, GetLogicFunctionSourceCodeQueryVariables>;
|
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`
|
export const InstallMarketplaceAppDocument = gql`
|
||||||
mutation InstallMarketplaceApp($universalIdentifier: String!, $version: String) {
|
mutation InstallMarketplaceApp($universalIdentifier: String!, $version: String) {
|
||||||
installMarketplaceApp(
|
installMarketplaceApp(
|
||||||
@@ -11472,38 +11600,41 @@ export function useInstallMarketplaceAppMutation(baseOptions?: Apollo.MutationHo
|
|||||||
export type InstallMarketplaceAppMutationHookResult = ReturnType<typeof useInstallMarketplaceAppMutation>;
|
export type InstallMarketplaceAppMutationHookResult = ReturnType<typeof useInstallMarketplaceAppMutation>;
|
||||||
export type InstallMarketplaceAppMutationResult = Apollo.MutationResult<InstallMarketplaceAppMutation>;
|
export type InstallMarketplaceAppMutationResult = Apollo.MutationResult<InstallMarketplaceAppMutation>;
|
||||||
export type InstallMarketplaceAppMutationOptions = Apollo.BaseMutationOptions<InstallMarketplaceAppMutation, InstallMarketplaceAppMutationVariables>;
|
export type InstallMarketplaceAppMutationOptions = Apollo.BaseMutationOptions<InstallMarketplaceAppMutation, InstallMarketplaceAppMutationVariables>;
|
||||||
export const InstallNpmAppDocument = gql`
|
export const RegisterNpmPackageDocument = gql`
|
||||||
mutation InstallNpmApp($packageName: String!, $version: String) {
|
mutation RegisterNpmPackage($packageName: String!) {
|
||||||
installNpmApp(packageName: $packageName, version: $version)
|
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.
|
* 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, `useInstallNpmAppMutation` returns a tuple that includes:
|
* When your component renders, `useRegisterNpmPackageMutation` returns a tuple that includes:
|
||||||
* - A mutate function that you can call at any time to execute the mutation
|
* - 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
|
* - 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;
|
* @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
|
* @example
|
||||||
* const [installNpmAppMutation, { data, loading, error }] = useInstallNpmAppMutation({
|
* const [registerNpmPackageMutation, { data, loading, error }] = useRegisterNpmPackageMutation({
|
||||||
* variables: {
|
* variables: {
|
||||||
* packageName: // value for 'packageName'
|
* 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}
|
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 RegisterNpmPackageMutationHookResult = ReturnType<typeof useRegisterNpmPackageMutation>;
|
||||||
export type InstallNpmAppMutationResult = Apollo.MutationResult<InstallNpmAppMutation>;
|
export type RegisterNpmPackageMutationResult = Apollo.MutationResult<RegisterNpmPackageMutation>;
|
||||||
export type InstallNpmAppMutationOptions = Apollo.BaseMutationOptions<InstallNpmAppMutation, InstallNpmAppMutationVariables>;
|
export type RegisterNpmPackageMutationOptions = Apollo.BaseMutationOptions<RegisterNpmPackageMutation, RegisterNpmPackageMutationVariables>;
|
||||||
export const UpgradeApplicationDocument = gql`
|
export const UpgradeApplicationDocument = gql`
|
||||||
mutation UpgradeApplication($appRegistrationId: String!, $targetVersion: String!) {
|
mutation UpgradeApplication($appRegistrationId: String!, $targetVersion: String!) {
|
||||||
upgradeApplication(
|
upgradeApplication(
|
||||||
@@ -11609,6 +11740,41 @@ export function useFindManyMarketplaceAppsLazyQuery(baseOptions?: Apollo.LazyQue
|
|||||||
export type FindManyMarketplaceAppsQueryHookResult = ReturnType<typeof useFindManyMarketplaceAppsQuery>;
|
export type FindManyMarketplaceAppsQueryHookResult = ReturnType<typeof useFindManyMarketplaceAppsQuery>;
|
||||||
export type FindManyMarketplaceAppsLazyQueryHookResult = ReturnType<typeof useFindManyMarketplaceAppsLazyQuery>;
|
export type FindManyMarketplaceAppsLazyQueryHookResult = ReturnType<typeof useFindManyMarketplaceAppsLazyQuery>;
|
||||||
export type FindManyMarketplaceAppsQueryResult = Apollo.QueryResult<FindManyMarketplaceAppsQuery, FindManyMarketplaceAppsQueryVariables>;
|
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`
|
export const CreateNavigationMenuItemDocument = gql`
|
||||||
mutation CreateNavigationMenuItem($input: CreateNavigationMenuItemInput!) {
|
mutation CreateNavigationMenuItem($input: CreateNavigationMenuItemInput!) {
|
||||||
createNavigationMenuItem(input: $input) {
|
createNavigationMenuItem(input: $input) {
|
||||||
@@ -12709,6 +12875,40 @@ export function useGetAdminAiModelsLazyQuery(baseOptions?: Apollo.LazyQueryHookO
|
|||||||
export type GetAdminAiModelsQueryHookResult = ReturnType<typeof useGetAdminAiModelsQuery>;
|
export type GetAdminAiModelsQueryHookResult = ReturnType<typeof useGetAdminAiModelsQuery>;
|
||||||
export type GetAdminAiModelsLazyQueryHookResult = ReturnType<typeof useGetAdminAiModelsLazyQuery>;
|
export type GetAdminAiModelsLazyQueryHookResult = ReturnType<typeof useGetAdminAiModelsLazyQuery>;
|
||||||
export type GetAdminAiModelsQueryResult = Apollo.QueryResult<GetAdminAiModelsQuery, GetAdminAiModelsQueryVariables>;
|
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`
|
export const CreateDatabaseConfigVariableDocument = gql`
|
||||||
mutation CreateDatabaseConfigVariable($key: String!, $value: JSON!) {
|
mutation CreateDatabaseConfigVariable($key: String!, $value: JSON!) {
|
||||||
createDatabaseConfigVariable(key: $key, value: $value)
|
createDatabaseConfigVariable(key: $key, value: $value)
|
||||||
@@ -13368,6 +13568,44 @@ export function useRotateApplicationRegistrationClientSecretMutation(baseOptions
|
|||||||
export type RotateApplicationRegistrationClientSecretMutationHookResult = ReturnType<typeof useRotateApplicationRegistrationClientSecretMutation>;
|
export type RotateApplicationRegistrationClientSecretMutationHookResult = ReturnType<typeof useRotateApplicationRegistrationClientSecretMutation>;
|
||||||
export type RotateApplicationRegistrationClientSecretMutationResult = Apollo.MutationResult<RotateApplicationRegistrationClientSecretMutation>;
|
export type RotateApplicationRegistrationClientSecretMutationResult = Apollo.MutationResult<RotateApplicationRegistrationClientSecretMutation>;
|
||||||
export type RotateApplicationRegistrationClientSecretMutationOptions = Apollo.BaseMutationOptions<RotateApplicationRegistrationClientSecretMutation, RotateApplicationRegistrationClientSecretMutationVariables>;
|
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`
|
export const UpdateApplicationRegistrationDocument = gql`
|
||||||
mutation UpdateApplicationRegistration($input: UpdateApplicationRegistrationInput!) {
|
mutation UpdateApplicationRegistration($input: UpdateApplicationRegistrationInput!) {
|
||||||
updateApplicationRegistration(input: $input) {
|
updateApplicationRegistration(input: $input) {
|
||||||
@@ -13441,6 +13679,39 @@ export function useUpdateApplicationRegistrationVariableMutation(baseOptions?: A
|
|||||||
export type UpdateApplicationRegistrationVariableMutationHookResult = ReturnType<typeof useUpdateApplicationRegistrationVariableMutation>;
|
export type UpdateApplicationRegistrationVariableMutationHookResult = ReturnType<typeof useUpdateApplicationRegistrationVariableMutation>;
|
||||||
export type UpdateApplicationRegistrationVariableMutationResult = Apollo.MutationResult<UpdateApplicationRegistrationVariableMutation>;
|
export type UpdateApplicationRegistrationVariableMutationResult = Apollo.MutationResult<UpdateApplicationRegistrationVariableMutation>;
|
||||||
export type UpdateApplicationRegistrationVariableMutationOptions = Apollo.BaseMutationOptions<UpdateApplicationRegistrationVariableMutation, UpdateApplicationRegistrationVariableMutationVariables>;
|
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`
|
export const FindApplicationRegistrationByClientIdDocument = gql`
|
||||||
query FindApplicationRegistrationByClientId($clientId: String!) {
|
query FindApplicationRegistrationByClientId($clientId: String!) {
|
||||||
findApplicationRegistrationByClientId(clientId: $clientId) {
|
findApplicationRegistrationByClientId(clientId: $clientId) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export const FIND_MANY_APPLICATIONS = gql`
|
|||||||
name
|
name
|
||||||
description
|
description
|
||||||
version
|
version
|
||||||
|
universalIdentifier
|
||||||
applicationRegistrationId
|
applicationRegistrationId
|
||||||
applicationRegistration {
|
applicationRegistration {
|
||||||
id
|
id
|
||||||
|
|||||||
+9
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
+1
@@ -50,6 +50,7 @@ export const MARKETPLACE_APP_FRAGMENT = gql`
|
|||||||
description
|
description
|
||||||
}
|
}
|
||||||
sourcePackage
|
sourcePackage
|
||||||
|
isFeatured
|
||||||
defaultRole {
|
defaultRole {
|
||||||
id
|
id
|
||||||
label
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
+12
@@ -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;
|
return false;
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
const graphqlMessage = error instanceof Error ? error.message : undefined;
|
||||||
|
|
||||||
enqueueErrorSnackBar({
|
enqueueErrorSnackBar({
|
||||||
message: t`Failed to install the application.`,
|
message: graphqlMessage ?? t`Failed to install the application.`,
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
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;
|
return false;
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
const graphqlMessage = error instanceof Error ? error.message : undefined;
|
||||||
|
|
||||||
enqueueErrorSnackBar({
|
enqueueErrorSnackBar({
|
||||||
message: t`Failed to upgrade the application.`,
|
message: graphqlMessage ?? t`Failed to upgrade the application.`,
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { UPLOAD_APP_TARBALL } from '~/modules/marketplace/graphql/mutations/uplo
|
|||||||
type UploadResult =
|
type UploadResult =
|
||||||
| {
|
| {
|
||||||
success: true;
|
success: true;
|
||||||
|
registrationId: string;
|
||||||
universalIdentifier: string;
|
universalIdentifier: string;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
@@ -29,7 +30,10 @@ export const useUploadAppTarball = () => {
|
|||||||
|
|
||||||
const registration = result.data?.uploadAppTarball;
|
const registration = result.data?.uploadAppTarball;
|
||||||
|
|
||||||
if (!isDefined(registration?.universalIdentifier)) {
|
if (
|
||||||
|
!isDefined(registration?.id) ||
|
||||||
|
!isDefined(registration?.universalIdentifier)
|
||||||
|
) {
|
||||||
enqueueErrorSnackBar({ message: t`Upload failed.` });
|
enqueueErrorSnackBar({ message: t`Upload failed.` });
|
||||||
|
|
||||||
return { success: false };
|
return { success: false };
|
||||||
@@ -37,11 +41,14 @@ export const useUploadAppTarball = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
registrationId: registration.id,
|
||||||
universalIdentifier: registration.universalIdentifier,
|
universalIdentifier: registration.universalIdentifier,
|
||||||
};
|
};
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
const graphqlMessage = error instanceof Error ? error.message : undefined;
|
||||||
|
|
||||||
enqueueErrorSnackBar({
|
enqueueErrorSnackBar({
|
||||||
message: t`Failed to upload tarball.`,
|
message: graphqlMessage ?? t`Failed to upload tarball.`,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { success: false };
|
return { success: false };
|
||||||
|
|||||||
+36
-69
@@ -1,27 +1,17 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { styled } from '@linaria/react';
|
|
||||||
|
|
||||||
import { useClientConfig } from '@/client-config/hooks/useClientConfig';
|
import { useClientConfig } from '@/client-config/hooks/useClientConfig';
|
||||||
import { GET_ADMIN_AI_MODELS } from '@/settings/admin-panel/ai/graphql/queries/getAdminAiModels';
|
import { GET_ADMIN_AI_MODELS } from '@/settings/admin-panel/ai/graphql/queries/getAdminAiModels';
|
||||||
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
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 { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import {
|
import { H2Title, IconArchive, IconPlug, IconRobot } from 'twenty-ui/display';
|
||||||
H2Title,
|
import { SearchInput } from 'twenty-ui/input';
|
||||||
IconArchive,
|
|
||||||
IconFilter,
|
|
||||||
IconPlug,
|
|
||||||
IconRobot,
|
|
||||||
IconSearch,
|
|
||||||
} from 'twenty-ui/display';
|
|
||||||
import { Button } from 'twenty-ui/input';
|
|
||||||
import { Card, Section } from 'twenty-ui/layout';
|
import { Card, Section } from 'twenty-ui/layout';
|
||||||
import { MenuItemToggle } from 'twenty-ui/navigation';
|
import { MenuItemToggle } from 'twenty-ui/navigation';
|
||||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
|
||||||
import {
|
import {
|
||||||
useCreateDatabaseConfigVariableMutation,
|
useCreateDatabaseConfigVariableMutation,
|
||||||
useGetAdminAiModelsQuery,
|
useGetAdminAiModelsQuery,
|
||||||
@@ -30,17 +20,6 @@ import {
|
|||||||
import { getModelIcon } from '~/pages/settings/ai/utils/getModelIcon';
|
import { getModelIcon } from '~/pages/settings/ai/utils/getModelIcon';
|
||||||
import { getModelProviderLabel } from '~/pages/settings/ai/utils/getModelProviderLabel';
|
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 = () => {
|
export const SettingsAdminAI = () => {
|
||||||
const { enqueueErrorSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
@@ -162,53 +141,41 @@ export const SettingsAdminAI = () => {
|
|||||||
description={t`Toggle model availability across all workspaces`}
|
description={t`Toggle model availability across all workspaces`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StyledSearchAndFilterContainer>
|
<SearchInput
|
||||||
<StyledSearchInputContainer>
|
placeholder={t`Search a model...`}
|
||||||
<SettingsTextInput
|
value={searchQuery}
|
||||||
instanceId="admin-model-search"
|
onChange={setSearchQuery}
|
||||||
LeftIcon={IconSearch}
|
filterDropdown={(filterButton) => (
|
||||||
placeholder={t`Search a model...`}
|
<Dropdown
|
||||||
value={searchQuery}
|
dropdownId="admin-ai-models-filter-dropdown"
|
||||||
onChange={setSearchQuery}
|
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>
|
<Card rounded>
|
||||||
{filteredModels.map((model, index) => (
|
{filteredModels.map((model, index) => (
|
||||||
|
|||||||
+107
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
+12
@@ -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}
|
||||||
|
`;
|
||||||
+7
@@ -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 { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import {
|
import {
|
||||||
|
IconApps,
|
||||||
IconHeart,
|
IconHeart,
|
||||||
IconSettings2,
|
IconSettings2,
|
||||||
IconSparkles,
|
IconSparkles,
|
||||||
@@ -24,6 +25,12 @@ export const SettingsAdminContent = () => {
|
|||||||
Icon: IconSettings2,
|
Icon: IconSettings2,
|
||||||
disabled: !canAccessFullAdminPanel && !canImpersonate,
|
disabled: !canAccessFullAdminPanel && !canImpersonate,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: SETTINGS_ADMIN_TABS.APPS,
|
||||||
|
title: t`Apps`,
|
||||||
|
Icon: IconApps,
|
||||||
|
disabled: !canAccessFullAdminPanel,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: SETTINGS_ADMIN_TABS.AI,
|
id: SETTINGS_ADMIN_TABS.AI,
|
||||||
title: t`AI`,
|
title: t`AI`,
|
||||||
|
|||||||
+3
@@ -1,4 +1,5 @@
|
|||||||
import { SettingsAdminAI } from '@/settings/admin-panel/ai/components/SettingsAdminAI';
|
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 { SettingsAdminGeneral } from '@/settings/admin-panel/components/SettingsAdminGeneral';
|
||||||
import { SettingsAdminConfigVariables } from '@/settings/admin-panel/config-variables/components/SettingsAdminConfigVariables';
|
import { SettingsAdminConfigVariables } from '@/settings/admin-panel/config-variables/components/SettingsAdminConfigVariables';
|
||||||
import { SETTINGS_ADMIN_TABS } from '@/settings/admin-panel/constants/SettingsAdminTabs';
|
import { SETTINGS_ADMIN_TABS } from '@/settings/admin-panel/constants/SettingsAdminTabs';
|
||||||
@@ -16,6 +17,8 @@ export const SettingsAdminTabContent = () => {
|
|||||||
switch (activeTabId) {
|
switch (activeTabId) {
|
||||||
case SETTINGS_ADMIN_TABS.GENERAL:
|
case SETTINGS_ADMIN_TABS.GENERAL:
|
||||||
return <SettingsAdminGeneral />;
|
return <SettingsAdminGeneral />;
|
||||||
|
case SETTINGS_ADMIN_TABS.APPS:
|
||||||
|
return <SettingsAdminApps />;
|
||||||
case SETTINGS_ADMIN_TABS.AI:
|
case SETTINGS_ADMIN_TABS.AI:
|
||||||
return <SettingsAdminAI />;
|
return <SettingsAdminAI />;
|
||||||
case SETTINGS_ADMIN_TABS.CONFIG_VARIABLES:
|
case SETTINGS_ADMIN_TABS.CONFIG_VARIABLES:
|
||||||
|
|||||||
+4
-3
@@ -45,13 +45,13 @@ export const SettingsAdminTableCard = ({
|
|||||||
<TableRow
|
<TableRow
|
||||||
key={index + item.label}
|
key={index + item.label}
|
||||||
gridAutoColumns={gridAutoColumns}
|
gridAutoColumns={gridAutoColumns}
|
||||||
height={themeCssVariables.spacing[6]}
|
|
||||||
>
|
>
|
||||||
<TableCell
|
<TableCell
|
||||||
align={labelAlign}
|
align={labelAlign}
|
||||||
color={themeCssVariables.font.color.tertiary}
|
color={themeCssVariables.font.color.tertiary}
|
||||||
height={themeCssVariables.spacing[6]}
|
height="auto"
|
||||||
gap={themeCssVariables.spacing[2]}
|
gap={themeCssVariables.spacing[2]}
|
||||||
|
padding={`${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[2]}`}
|
||||||
>
|
>
|
||||||
{item.Icon && <item.Icon size={theme.icon.size.md} />}
|
{item.Icon && <item.Icon size={theme.icon.size.md} />}
|
||||||
<span>{item.label}</span>
|
<span>{item.label}</span>
|
||||||
@@ -59,9 +59,10 @@ export const SettingsAdminTableCard = ({
|
|||||||
<TableCell
|
<TableCell
|
||||||
align={valueAlign}
|
align={valueAlign}
|
||||||
color={themeCssVariables.font.color.primary}
|
color={themeCssVariables.font.color.primary}
|
||||||
height={themeCssVariables.spacing[6]}
|
height="auto"
|
||||||
onClick={item.onClick}
|
onClick={item.onClick}
|
||||||
clickable={isDefined(item.onClick)}
|
clickable={isDefined(item.onClick)}
|
||||||
|
padding={`${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[2]}`}
|
||||||
>
|
>
|
||||||
{item.value}
|
{item.value}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
+6
-17
@@ -1,11 +1,5 @@
|
|||||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
|
||||||
import { styled } from '@linaria/react';
|
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { IconSearch } from 'twenty-ui/display';
|
import { SearchInput } from 'twenty-ui/input';
|
||||||
|
|
||||||
const StyledSearchInputContainer = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
type ConfigVariableSearchInputProps = {
|
type ConfigVariableSearchInputProps = {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -17,15 +11,10 @@ export const ConfigVariableSearchInput = ({
|
|||||||
onChange,
|
onChange,
|
||||||
}: ConfigVariableSearchInputProps) => {
|
}: ConfigVariableSearchInputProps) => {
|
||||||
return (
|
return (
|
||||||
<StyledSearchInputContainer>
|
<SearchInput
|
||||||
<SettingsTextInput
|
placeholder={t`Search config variables`}
|
||||||
instanceId="config-variable-search"
|
value={value}
|
||||||
placeholder={t`Search config variables`}
|
onChange={onChange}
|
||||||
value={value}
|
/>
|
||||||
onChange={onChange}
|
|
||||||
autoFocus={false}
|
|
||||||
LeftIcon={IconSearch}
|
|
||||||
/>
|
|
||||||
</StyledSearchInputContainer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export const SETTINGS_ADMIN_TABS = {
|
export const SETTINGS_ADMIN_TABS = {
|
||||||
GENERAL: 'general',
|
GENERAL: 'general',
|
||||||
|
APPS: 'apps',
|
||||||
AI: 'ai',
|
AI: 'ai',
|
||||||
CONFIG_VARIABLES: 'config-variables',
|
CONFIG_VARIABLES: 'config-variables',
|
||||||
HEALTH_STATUS: 'health-status',
|
HEALTH_STATUS: 'health-status',
|
||||||
|
|||||||
+3
@@ -16,6 +16,9 @@ export const APPLICATION_REGISTRATION_FRAGMENT = gql`
|
|||||||
latestAvailableVersion
|
latestAvailableVersion
|
||||||
websiteUrl
|
websiteUrl
|
||||||
termsUrl
|
termsUrl
|
||||||
|
isListed
|
||||||
|
isFeatured
|
||||||
|
ownerWorkspaceId
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
}
|
}
|
||||||
|
|||||||
+16
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const APPLICATION_REGISTRATION_TARBALL_URL = gql`
|
||||||
|
query ApplicationRegistrationTarballUrl($id: String!) {
|
||||||
|
applicationRegistrationTarballUrl(id: $id)
|
||||||
|
}
|
||||||
|
`;
|
||||||
+336
-11
@@ -1,8 +1,10 @@
|
|||||||
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
|
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
|
||||||
import { DELETE_APPLICATION_REGISTRATION } from '@/settings/application-registrations/graphql/mutations/deleteApplicationRegistration';
|
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 { 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 } from '@/settings/application-registrations/graphql/mutations/updateApplicationRegistration';
|
||||||
import { UPDATE_APPLICATION_REGISTRATION_VARIABLE } from '@/settings/application-registrations/graphql/mutations/updateApplicationRegistrationVariable';
|
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_STATS } from '@/settings/application-registrations/graphql/queries/findApplicationRegistrationStats';
|
||||||
import { FIND_APPLICATION_REGISTRATION_VARIABLES } from '@/settings/application-registrations/graphql/queries/findApplicationRegistrationVariables';
|
import { FIND_APPLICATION_REGISTRATION_VARIABLES } from '@/settings/application-registrations/graphql/queries/findApplicationRegistrationVariables';
|
||||||
import { FIND_MANY_APPLICATION_REGISTRATIONS } from '@/settings/application-registrations/graphql/queries/findManyApplicationRegistrations';
|
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 { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||||
|
import { useInstallMarketplaceApp } from '~/modules/marketplace/hooks/useInstallMarketplaceApp';
|
||||||
import { useMutation, useQuery } from '@apollo/client';
|
import { useMutation, useQuery } from '@apollo/client';
|
||||||
import { styled } from '@linaria/react';
|
import { styled } from '@linaria/react';
|
||||||
import { Trans, useLingui } from '@lingui/react/macro';
|
import { Trans, useLingui } from '@lingui/react/macro';
|
||||||
@@ -25,10 +28,15 @@ import { useParams } from 'react-router-dom';
|
|||||||
import { SettingsPath } from 'twenty-shared/types';
|
import { SettingsPath } from 'twenty-shared/types';
|
||||||
import { getSettingsPath, isDefined, isValidUrl } from 'twenty-shared/utils';
|
import { getSettingsPath, isDefined, isValidUrl } from 'twenty-shared/utils';
|
||||||
import {
|
import {
|
||||||
|
H1Title,
|
||||||
|
H1TitleFontColor,
|
||||||
H2Title,
|
H2Title,
|
||||||
|
IconArrowRight,
|
||||||
IconChartBar,
|
IconChartBar,
|
||||||
IconCheck,
|
IconCheck,
|
||||||
|
IconBox,
|
||||||
IconDownload,
|
IconDownload,
|
||||||
|
IconExternalLink,
|
||||||
IconKey,
|
IconKey,
|
||||||
IconRefresh,
|
IconRefresh,
|
||||||
IconShield,
|
IconShield,
|
||||||
@@ -38,15 +46,33 @@ import {
|
|||||||
IconWorld,
|
IconWorld,
|
||||||
Status,
|
Status,
|
||||||
} from 'twenty-ui/display';
|
} from 'twenty-ui/display';
|
||||||
|
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||||
import { Button } from 'twenty-ui/input';
|
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 { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
|
||||||
|
import {
|
||||||
|
ApplicationRegistrationSourceType,
|
||||||
|
useFindManyApplicationsQuery,
|
||||||
|
} from '~/generated-metadata/graphql';
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
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';
|
import { applicationRegistrationClientSecretFamilyState } from '~/pages/settings/applications/states/applicationRegistrationClientSecretFamilyState';
|
||||||
|
|
||||||
const DELETE_REGISTRATION_MODAL_ID = 'delete-application-registration-modal';
|
const DELETE_REGISTRATION_MODAL_ID = 'delete-application-registration-modal';
|
||||||
const ROTATE_SECRET_MODAL_ID = 'rotate-application-registration-secret-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`
|
const StyledInputContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -99,6 +125,32 @@ const StyledRotateContainer = styled.div`
|
|||||||
padding-top: ${themeCssVariables.spacing[2]};
|
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 = {
|
type ServerVariable = {
|
||||||
id: string;
|
id: string;
|
||||||
key: string;
|
key: string;
|
||||||
@@ -113,7 +165,7 @@ export const SettingsApplicationRegistrationDetails = () => {
|
|||||||
const navigate = useNavigateSettings();
|
const navigate = useNavigateSettings();
|
||||||
const { copyToClipboard } = useCopyToClipboard();
|
const { copyToClipboard } = useCopyToClipboard();
|
||||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||||
const { openModal } = useModal();
|
const { openModal, closeModal } = useModal();
|
||||||
const { applicationRegistrationId = '' } = useParams<{
|
const { applicationRegistrationId = '' } = useParams<{
|
||||||
applicationRegistrationId: string;
|
applicationRegistrationId: string;
|
||||||
}>();
|
}>();
|
||||||
@@ -123,11 +175,14 @@ export const SettingsApplicationRegistrationDetails = () => {
|
|||||||
applicationRegistrationId,
|
applicationRegistrationId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [isInstalling, setIsInstalling] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [formRedirectUris, setFormRedirectUris] = useState<string[]>([]);
|
const [formRedirectUris, setFormRedirectUris] = useState<string[]>([]);
|
||||||
const [newRedirectUri, setNewRedirectUri] = useState('');
|
const [newRedirectUri, setNewRedirectUri] = useState('');
|
||||||
const [hasChanges, setHasChanges] = useState(false);
|
const [hasChanges, setHasChanges] = useState(false);
|
||||||
const [rotatedSecret, setRotatedSecret] = useState<string | null>(null);
|
const [rotatedSecret, setRotatedSecret] = useState<string | null>(null);
|
||||||
|
const [transferSubdomain, setTransferSubdomain] = useState('');
|
||||||
|
const [isTransferring, setIsTransferring] = useState(false);
|
||||||
|
|
||||||
const [variableValues, setVariableValues] = useState<Record<string, string>>(
|
const [variableValues, setVariableValues] = useState<Record<string, string>>(
|
||||||
{},
|
{},
|
||||||
@@ -158,6 +213,14 @@ export const SettingsApplicationRegistrationDetails = () => {
|
|||||||
skip: !applicationRegistrationId,
|
skip: !applicationRegistrationId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: tarballUrlData } = useQuery(
|
||||||
|
APPLICATION_REGISTRATION_TARBALL_URL,
|
||||||
|
{
|
||||||
|
variables: { id: applicationRegistrationId },
|
||||||
|
skip: !applicationRegistrationId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [updateRegistration] = useMutation(UPDATE_APPLICATION_REGISTRATION, {
|
const [updateRegistration] = useMutation(UPDATE_APPLICATION_REGISTRATION, {
|
||||||
refetchQueries: [
|
refetchQueries: [
|
||||||
FIND_ONE_APPLICATION_REGISTRATION,
|
FIND_ONE_APPLICATION_REGISTRATION,
|
||||||
@@ -176,15 +239,120 @@ export const SettingsApplicationRegistrationDetails = () => {
|
|||||||
refetchQueries: [FIND_APPLICATION_REGISTRATION_VARIABLES],
|
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 registration = data?.findOneApplicationRegistration;
|
||||||
const variables: ServerVariable[] =
|
const variables: ServerVariable[] =
|
||||||
variablesData?.findApplicationRegistrationVariables ?? [];
|
variablesData?.findApplicationRegistrationVariables ?? [];
|
||||||
|
|
||||||
|
const isNpmSource =
|
||||||
|
registration?.sourceType === ApplicationRegistrationSourceType.NPM;
|
||||||
|
|
||||||
if (loading || !registration) {
|
if (loading || !registration) {
|
||||||
return null;
|
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 markDirty = () => setHasChanges(true);
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
@@ -364,6 +532,47 @@ export const SettingsApplicationRegistrationDetails = () => {
|
|||||||
t`Universal identifier copied`,
|
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;
|
const stats = statsData?.findApplicationRegistrationStats;
|
||||||
@@ -447,6 +656,61 @@ export const SettingsApplicationRegistrationDetails = () => {
|
|||||||
/>
|
/>
|
||||||
</Section>
|
</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>
|
<Section>
|
||||||
<H2Title
|
<H2Title
|
||||||
title={t`OAuth Credentials`}
|
title={t`OAuth Credentials`}
|
||||||
@@ -574,17 +838,26 @@ export const SettingsApplicationRegistrationDetails = () => {
|
|||||||
description={
|
description={
|
||||||
hasActiveInstalls
|
hasActiveInstalls
|
||||||
? t`Uninstall this app from all workspaces before deleting it`
|
? t`Uninstall this app from all workspaces before deleting it`
|
||||||
: t`Delete this app`
|
: t`Delete or transfer this app registration`
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Button
|
<StyledDangerButtonGroup>
|
||||||
accent="danger"
|
<Button
|
||||||
variant="secondary"
|
accent="danger"
|
||||||
title={t`Delete`}
|
variant="secondary"
|
||||||
Icon={IconTrash}
|
title={t`Delete`}
|
||||||
disabled={hasActiveInstalls}
|
Icon={IconTrash}
|
||||||
onClick={() => openModal(DELETE_REGISTRATION_MODAL_ID)}
|
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>
|
</Section>
|
||||||
</SettingsPageContainer>
|
</SettingsPageContainer>
|
||||||
</SubMenuTopBarContainer>
|
</SubMenuTopBarContainer>
|
||||||
@@ -622,6 +895,58 @@ export const SettingsApplicationRegistrationDetails = () => {
|
|||||||
confirmButtonText={t`Delete`}
|
confirmButtonText={t`Delete`}
|
||||||
loading={isLoading}
|
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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+75
-10
@@ -7,16 +7,18 @@ import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTab
|
|||||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||||
import { styled } from '@linaria/react';
|
import { styled } from '@linaria/react';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { useMemo, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { SettingsPath } from 'twenty-shared/types';
|
import { SettingsPath } from 'twenty-shared/types';
|
||||||
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||||
import {
|
import {
|
||||||
IconApps,
|
IconApps,
|
||||||
IconBox,
|
IconBox,
|
||||||
|
IconCheck,
|
||||||
IconColumns,
|
IconColumns,
|
||||||
IconCommand,
|
IconCommand,
|
||||||
IconDownload,
|
IconDownload,
|
||||||
|
IconEyeOff,
|
||||||
IconFileText,
|
IconFileText,
|
||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
IconLayoutGrid,
|
IconLayoutGrid,
|
||||||
@@ -27,7 +29,11 @@ import {
|
|||||||
import { Button } from 'twenty-ui/input';
|
import { Button } from 'twenty-ui/input';
|
||||||
import { Section } from 'twenty-ui/layout';
|
import { Section } from 'twenty-ui/layout';
|
||||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
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 { useMarketplaceApps } from '~/modules/marketplace/hooks/useMarketplaceApps';
|
||||||
import { SettingsApplicationPermissionsTab } from '~/pages/settings/applications/tabs/SettingsApplicationPermissionsTab';
|
import { SettingsApplicationPermissionsTab } from '~/pages/settings/applications/tabs/SettingsApplicationPermissionsTab';
|
||||||
import { SettingsAvailableApplicationDetailContentTab } from '~/pages/settings/applications/tabs/SettingsAvailableApplicationDetailContentTab';
|
import { SettingsAvailableApplicationDetailContentTab } from '~/pages/settings/applications/tabs/SettingsAvailableApplicationDetailContentTab';
|
||||||
@@ -240,6 +246,19 @@ const StyledProviderItem = styled.li`
|
|||||||
margin-bottom: ${themeCssVariables.spacing[1]};
|
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 = () => {
|
export const SettingsAvailableApplicationDetails = () => {
|
||||||
const { availableApplicationId = '' } = useParams<{
|
const { availableApplicationId = '' } = useParams<{
|
||||||
availableApplicationId: string;
|
availableApplicationId: string;
|
||||||
@@ -252,10 +271,44 @@ export const SettingsAvailableApplicationDetails = () => {
|
|||||||
const canInstallMarketplaceApps = useHasPermissionFlag(
|
const canInstallMarketplaceApps = useHasPermissionFlag(
|
||||||
PermissionFlagType.MARKETPLACE_APPS,
|
PermissionFlagType.MARKETPLACE_APPS,
|
||||||
);
|
);
|
||||||
|
const { data: installedAppData } =
|
||||||
|
useFindOneApplicationByUniversalIdentifierQuery({
|
||||||
|
variables: { universalIdentifier: availableApplicationId },
|
||||||
|
skip: !availableApplicationId,
|
||||||
|
});
|
||||||
|
|
||||||
const application = useMemo(() => {
|
const listedApp = marketplaceApps?.find(
|
||||||
return marketplaceApps?.find((app) => app.id === availableApplicationId);
|
(app) => app.id === availableApplicationId,
|
||||||
}, [availableApplicationId, marketplaceApps]);
|
);
|
||||||
|
|
||||||
|
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 () => {
|
const handleInstall = async () => {
|
||||||
if (isDefined(application)) {
|
if (isDefined(application)) {
|
||||||
@@ -438,6 +491,12 @@ export const SettingsAvailableApplicationDetails = () => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SettingsPageContainer>
|
<SettingsPageContainer>
|
||||||
|
{isUnlisted && (
|
||||||
|
<StyledUnlistedBanner>
|
||||||
|
<IconEyeOff size={16} />
|
||||||
|
{t`This application is not listed on the marketplace. It was shared via a direct link.`}
|
||||||
|
</StyledUnlistedBanner>
|
||||||
|
)}
|
||||||
<StyledHeader>
|
<StyledHeader>
|
||||||
<StyledHeaderLeft>
|
<StyledHeaderLeft>
|
||||||
<StyledLogo>
|
<StyledLogo>
|
||||||
@@ -461,12 +520,18 @@ export const SettingsAvailableApplicationDetails = () => {
|
|||||||
</StyledHeaderLeft>
|
</StyledHeaderLeft>
|
||||||
{canInstallMarketplaceApps && (
|
{canInstallMarketplaceApps && (
|
||||||
<Button
|
<Button
|
||||||
Icon={IconDownload}
|
Icon={isAlreadyInstalled ? IconCheck : IconDownload}
|
||||||
title={isInstalling ? t`Installing...` : t`Install`}
|
title={
|
||||||
variant="primary"
|
isAlreadyInstalled
|
||||||
accent="blue"
|
? t`Installed`
|
||||||
|
: isInstalling
|
||||||
|
? t`Installing...`
|
||||||
|
: t`Install`
|
||||||
|
}
|
||||||
|
variant={isAlreadyInstalled ? 'secondary' : 'primary'}
|
||||||
|
accent={isAlreadyInstalled ? 'default' : 'blue'}
|
||||||
onClick={handleInstall}
|
onClick={handleInstall}
|
||||||
disabled={isInstalling}
|
disabled={isAlreadyInstalled || isInstalling}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</StyledHeader>
|
</StyledHeader>
|
||||||
|
|||||||
+2
-2
@@ -6,7 +6,7 @@ import { isDefined } from 'twenty-shared/utils';
|
|||||||
import { IconCircleDot, IconStatusChange, IconUpload } from 'twenty-ui/display';
|
import { IconCircleDot, IconStatusChange, IconUpload } from 'twenty-ui/display';
|
||||||
import { Button } from 'twenty-ui/input';
|
import { Button } from 'twenty-ui/input';
|
||||||
import {
|
import {
|
||||||
AppRegistrationSourceType,
|
ApplicationRegistrationSourceType,
|
||||||
type Application,
|
type Application,
|
||||||
} from '~/generated-metadata/graphql';
|
} from '~/generated-metadata/graphql';
|
||||||
import { isNewerSemver } from '~/pages/settings/applications/utils/isNewerSemver';
|
import { isNewerSemver } from '~/pages/settings/applications/utils/isNewerSemver';
|
||||||
@@ -26,7 +26,7 @@ export const SettingsApplicationVersionContainer = ({
|
|||||||
const currentVersion = application?.version;
|
const currentVersion = application?.version;
|
||||||
|
|
||||||
const sourceType = application?.applicationRegistration?.sourceType;
|
const sourceType = application?.applicationRegistration?.sourceType;
|
||||||
const isNpmApp = sourceType === AppRegistrationSourceType.NPM;
|
const isNpmApp = sourceType === ApplicationRegistrationSourceType.NPM;
|
||||||
|
|
||||||
const latestVersion = isNpmApp
|
const latestVersion = isNpmApp
|
||||||
? (latestAvailableVersion ?? currentVersion)
|
? (latestAvailableVersion ?? currentVersion)
|
||||||
|
|||||||
+13
-17
@@ -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 { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||||
@@ -10,13 +10,13 @@ import {
|
|||||||
APPLICATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
|
APPLICATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
|
||||||
SettingsApplicationTableRow,
|
SettingsApplicationTableRow,
|
||||||
} from '~/pages/settings/applications/components/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 { type ApplicationWithoutRelation } from '~/pages/settings/applications/types/applicationWithoutRelation';
|
||||||
import { isNewerSemver } from '~/pages/settings/applications/utils/isNewerSemver';
|
import { isNewerSemver } from '~/pages/settings/applications/utils/isNewerSemver';
|
||||||
import { Section } from 'twenty-ui/layout';
|
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 { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||||
import { AppRegistrationSourceType } from '~/generated-metadata/graphql';
|
import { ApplicationRegistrationSourceType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
const StyledTableContainer = styled.div`
|
const StyledTableContainer = styled.div`
|
||||||
margin-top: ${themeCssVariables.spacing[3]};
|
margin-top: ${themeCssVariables.spacing[3]};
|
||||||
@@ -41,15 +41,13 @@ export const SettingsApplicationsTable = ({
|
|||||||
|
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
|
||||||
const filteredApplications = useMemo(() => {
|
const filteredApplications = applications.filter(
|
||||||
return applications.filter(
|
(application) =>
|
||||||
(application) =>
|
application.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
application.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
(application.description ?? '')
|
||||||
(application.description ?? '')
|
.toLowerCase()
|
||||||
.toLowerCase()
|
.includes(searchTerm.toLowerCase()),
|
||||||
.includes(searchTerm.toLowerCase()),
|
);
|
||||||
);
|
|
||||||
}, [applications, searchTerm]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section>
|
<Section>
|
||||||
@@ -58,9 +56,7 @@ export const SettingsApplicationsTable = ({
|
|||||||
description={t`List installed applications. Use filter to search for a specific application`}
|
description={t`List installed applications. Use filter to search for a specific application`}
|
||||||
/>
|
/>
|
||||||
<StyledSearchInputContainer>
|
<StyledSearchInputContainer>
|
||||||
<SettingsTextInput
|
<SearchInput
|
||||||
instanceId="env-var-search"
|
|
||||||
LeftIcon={IconSearch}
|
|
||||||
placeholder={t`Search an application`}
|
placeholder={t`Search an application`}
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={setSearchTerm}
|
onChange={setSearchTerm}
|
||||||
@@ -81,7 +77,7 @@ export const SettingsApplicationsTable = ({
|
|||||||
{filteredApplications.map((application) => {
|
{filteredApplications.map((application) => {
|
||||||
const isNpmApp =
|
const isNpmApp =
|
||||||
application.applicationRegistration?.sourceType ===
|
application.applicationRegistration?.sourceType ===
|
||||||
AppRegistrationSourceType.NPM;
|
ApplicationRegistrationSourceType.NPM;
|
||||||
|
|
||||||
const latestVersion =
|
const latestVersion =
|
||||||
application.applicationRegistration?.latestAvailableVersion;
|
application.applicationRegistration?.latestAvailableVersion;
|
||||||
|
|||||||
+20
-30
@@ -1,14 +1,15 @@
|
|||||||
import { styled } from '@linaria/react';
|
import { styled } from '@linaria/react';
|
||||||
import { useState } from '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 { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||||
|
import { useApolloClient } from '@apollo/client';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { H1Title, H1TitleFontColor } from 'twenty-ui/display';
|
import { H1Title, H1TitleFontColor } from 'twenty-ui/display';
|
||||||
import { Section, SectionAlignment, SectionFontColor } from 'twenty-ui/layout';
|
import { Section, SectionAlignment, SectionFontColor } from 'twenty-ui/layout';
|
||||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||||
import { useInstallNpmApp } from '~/modules/marketplace/hooks/useInstallNpmApp';
|
import { useRegisterNpmPackage } from '~/modules/marketplace/hooks/useRegisterNpmPackage';
|
||||||
import { useFindManyApplicationsQuery } from '~/generated-metadata/graphql';
|
|
||||||
import {
|
import {
|
||||||
StyledAppModal,
|
StyledAppModal,
|
||||||
StyledAppModalButton,
|
StyledAppModalButton,
|
||||||
@@ -16,7 +17,7 @@ import {
|
|||||||
StyledAppModalTitle,
|
StyledAppModalTitle,
|
||||||
} from '~/pages/settings/applications/components/SettingsAppModalLayout';
|
} 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`
|
const StyledInputGroup = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -24,51 +25,49 @@ const StyledInputGroup = styled.div`
|
|||||||
gap: ${themeCssVariables.spacing[3]};
|
gap: ${themeCssVariables.spacing[3]};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SettingsInstallNpmAppModal = () => {
|
export const SettingsRegisterNpmAppModal = () => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const { closeModal } = useModal();
|
const { closeModal } = useModal();
|
||||||
const { install, isInstalling } = useInstallNpmApp();
|
const { register, isRegistering } = useRegisterNpmPackage();
|
||||||
const { refetch } = useFindManyApplicationsQuery();
|
const apolloClient = useApolloClient();
|
||||||
|
|
||||||
const [packageName, setPackageName] = useState('');
|
const [packageName, setPackageName] = useState('');
|
||||||
const [version, setVersion] = useState('');
|
|
||||||
|
|
||||||
const isValid = packageName.trim().length > 0;
|
const isValid = packageName.trim().length > 0;
|
||||||
|
|
||||||
const handleInstall = async () => {
|
const handleRegister = async () => {
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const success = await install({
|
const success = await register({
|
||||||
packageName: packageName.trim(),
|
packageName: packageName.trim(),
|
||||||
version: version.trim() || undefined,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
await refetch();
|
await apolloClient.refetchQueries({
|
||||||
closeModal(INSTALL_NPM_APP_MODAL_ID);
|
include: [FIND_MANY_APPLICATION_REGISTRATIONS],
|
||||||
|
});
|
||||||
|
closeModal(REGISTER_NPM_APP_MODAL_ID);
|
||||||
setPackageName('');
|
setPackageName('');
|
||||||
setVersion('');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
closeModal(INSTALL_NPM_APP_MODAL_ID);
|
closeModal(REGISTER_NPM_APP_MODAL_ID);
|
||||||
setPackageName('');
|
setPackageName('');
|
||||||
setVersion('');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledAppModal
|
<StyledAppModal
|
||||||
modalId={INSTALL_NPM_APP_MODAL_ID}
|
modalId={REGISTER_NPM_APP_MODAL_ID}
|
||||||
isClosable={true}
|
isClosable={true}
|
||||||
padding="large"
|
padding="large"
|
||||||
dataGloballyPreventClickOutside
|
dataGloballyPreventClickOutside
|
||||||
>
|
>
|
||||||
<StyledAppModalTitle>
|
<StyledAppModalTitle>
|
||||||
<H1Title
|
<H1Title
|
||||||
title={t`Install from npm`}
|
title={t`Register from npm`}
|
||||||
fontColor={H1TitleFontColor.Primary}
|
fontColor={H1TitleFontColor.Primary}
|
||||||
/>
|
/>
|
||||||
</StyledAppModalTitle>
|
</StyledAppModalTitle>
|
||||||
@@ -76,7 +75,7 @@ export const SettingsInstallNpmAppModal = () => {
|
|||||||
alignment={SectionAlignment.Center}
|
alignment={SectionAlignment.Center}
|
||||||
fontColor={SectionFontColor.Primary}
|
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>
|
</StyledAppModalSection>
|
||||||
|
|
||||||
<Section>
|
<Section>
|
||||||
@@ -91,15 +90,6 @@ export const SettingsInstallNpmAppModal = () => {
|
|||||||
label={t`Package name`}
|
label={t`Package name`}
|
||||||
autoFocusOnMount
|
autoFocusOnMount
|
||||||
/>
|
/>
|
||||||
<SettingsTextInput
|
|
||||||
instanceId="npm-version-input"
|
|
||||||
value={version}
|
|
||||||
onChange={setVersion}
|
|
||||||
placeholder={t`latest`}
|
|
||||||
fullWidth
|
|
||||||
disableHotkeys
|
|
||||||
label={t`Version (optional)`}
|
|
||||||
/>
|
|
||||||
</StyledInputGroup>
|
</StyledInputGroup>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
@@ -110,11 +100,11 @@ export const SettingsInstallNpmAppModal = () => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<StyledAppModalButton
|
<StyledAppModalButton
|
||||||
onClick={handleInstall}
|
onClick={handleRegister}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
accent="blue"
|
accent="blue"
|
||||||
title={t`Install`}
|
title={t`Register`}
|
||||||
disabled={!isValid || isInstalling}
|
disabled={!isValid || isRegistering}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
</StyledAppModal>
|
</StyledAppModal>
|
||||||
+35
-16
@@ -1,14 +1,17 @@
|
|||||||
import { styled } from '@linaria/react';
|
import { styled } from '@linaria/react';
|
||||||
import { useRef } from '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 { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||||
|
import { useApolloClient, useMutation } from '@apollo/client';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { H1Title, H1TitleFontColor } from 'twenty-ui/display';
|
import { H1Title, H1TitleFontColor } from 'twenty-ui/display';
|
||||||
import { SectionAlignment, SectionFontColor } from 'twenty-ui/layout';
|
import { SectionAlignment, SectionFontColor } from 'twenty-ui/layout';
|
||||||
|
import { INSTALL_APPLICATION } from '~/modules/marketplace/graphql/mutations/installApplication';
|
||||||
import { useUploadAppTarball } from '~/modules/marketplace/hooks/useUploadAppTarball';
|
import { useUploadAppTarball } from '~/modules/marketplace/hooks/useUploadAppTarball';
|
||||||
import { useInstallMarketplaceApp } from '~/modules/marketplace/hooks/useInstallMarketplaceApp';
|
|
||||||
import { useFindManyApplicationsQuery } from '~/generated-metadata/graphql';
|
|
||||||
import {
|
import {
|
||||||
StyledAppModal,
|
StyledAppModal,
|
||||||
StyledAppModalButton,
|
StyledAppModalButton,
|
||||||
@@ -23,11 +26,12 @@ const StyledFileInput = styled.input`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const SettingsUploadTarballModal = () => {
|
export const SettingsUploadTarballModal = () => {
|
||||||
const { t } = useLingui();
|
const { t: tFn } = useLingui();
|
||||||
const { closeModal } = useModal();
|
const { closeModal } = useModal();
|
||||||
const { upload, isUploading } = useUploadAppTarball();
|
const { upload, isUploading } = useUploadAppTarball();
|
||||||
const { install } = useInstallMarketplaceApp();
|
const [installApplication] = useMutation(INSTALL_APPLICATION);
|
||||||
const { refetch } = useFindManyApplicationsQuery();
|
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
const apolloClient = useApolloClient();
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const handleSelectFile = () => {
|
const handleSelectFile = () => {
|
||||||
@@ -46,16 +50,31 @@ export const SettingsUploadTarballModal = () => {
|
|||||||
try {
|
try {
|
||||||
const uploadResult = await upload(file);
|
const uploadResult = await upload(file);
|
||||||
|
|
||||||
if (uploadResult.success) {
|
if (!uploadResult.success) {
|
||||||
const installResult = await install({
|
return;
|
||||||
universalIdentifier: uploadResult.universalIdentifier,
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (installResult) {
|
const registrationId = uploadResult.registrationId;
|
||||||
await refetch();
|
|
||||||
closeModal(UPLOAD_TARBALL_MODAL_ID);
|
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 {
|
} finally {
|
||||||
if (isDefined(fileInputRef.current)) {
|
if (isDefined(fileInputRef.current)) {
|
||||||
fileInputRef.current.value = '';
|
fileInputRef.current.value = '';
|
||||||
@@ -76,7 +95,7 @@ export const SettingsUploadTarballModal = () => {
|
|||||||
>
|
>
|
||||||
<StyledAppModalTitle>
|
<StyledAppModalTitle>
|
||||||
<H1Title
|
<H1Title
|
||||||
title={t`Upload tarball`}
|
title={tFn`Upload tarball`}
|
||||||
fontColor={H1TitleFontColor.Primary}
|
fontColor={H1TitleFontColor.Primary}
|
||||||
/>
|
/>
|
||||||
</StyledAppModalTitle>
|
</StyledAppModalTitle>
|
||||||
@@ -84,7 +103,7 @@ export const SettingsUploadTarballModal = () => {
|
|||||||
alignment={SectionAlignment.Center}
|
alignment={SectionAlignment.Center}
|
||||||
fontColor={SectionFontColor.Primary}
|
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>
|
</StyledAppModalSection>
|
||||||
|
|
||||||
<StyledFileInput
|
<StyledFileInput
|
||||||
@@ -97,14 +116,14 @@ export const SettingsUploadTarballModal = () => {
|
|||||||
<StyledAppModalButton
|
<StyledAppModalButton
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
title={t`Cancel`}
|
title={tFn`Cancel`}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<StyledAppModalButton
|
<StyledAppModalButton
|
||||||
onClick={handleSelectFile}
|
onClick={handleSelectFile}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
accent="blue"
|
accent="blue"
|
||||||
title={t`Choose file`}
|
title={tFn`Choose file`}
|
||||||
disabled={isUploading}
|
disabled={isUploading}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
|||||||
+73
-14
@@ -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 { styled } from '@linaria/react';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
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 { SearchInput } from 'twenty-ui/input';
|
||||||
import { Section } from 'twenty-ui/layout';
|
import { Section } from 'twenty-ui/layout';
|
||||||
|
import { MenuItemToggle } from 'twenty-ui/navigation';
|
||||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||||
import { SettingsAvailableApplicationCard } from '~/pages/settings/applications/components/SettingsAvailableApplicationCard';
|
|
||||||
import { useMarketplaceApps } from '~/modules/marketplace/hooks/useMarketplaceApps';
|
import { useMarketplaceApps } from '~/modules/marketplace/hooks/useMarketplaceApps';
|
||||||
|
import { SettingsAvailableApplicationCard } from '~/pages/settings/applications/components/SettingsAvailableApplicationCard';
|
||||||
|
|
||||||
const StyledSearchInputContainer = styled.div`
|
const StyledSearchInputContainer = styled.div`
|
||||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||||
@@ -27,24 +32,43 @@ const StyledEmptyState = styled.div`
|
|||||||
text-align: center;
|
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 = () => {
|
export const SettingsApplicationsAvailableTab = () => {
|
||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [showFeaturedOnly, setShowFeaturedOnly] = useState(true);
|
||||||
|
|
||||||
const { data: marketplaceApps, isLoading } = useMarketplaceApps();
|
const { data: marketplaceApps, isLoading } = useMarketplaceApps();
|
||||||
|
|
||||||
const applications = marketplaceApps;
|
const textFilteredApplications = searchTerm
|
||||||
|
? marketplaceApps.filter((application) => {
|
||||||
|
const lowerSearch = searchTerm.toLowerCase();
|
||||||
|
|
||||||
const filteredApplications = useMemo(() => {
|
return (
|
||||||
return applications.filter(
|
application.name.toLowerCase().includes(lowerSearch) ||
|
||||||
(application) =>
|
application.description.toLowerCase().includes(lowerSearch) ||
|
||||||
application.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
application.author.toLowerCase().includes(lowerSearch)
|
||||||
application.description
|
);
|
||||||
.toLowerCase()
|
})
|
||||||
.includes(searchTerm.toLowerCase()) ||
|
: marketplaceApps;
|
||||||
application.author.toLowerCase().includes(searchTerm.toLowerCase()),
|
|
||||||
);
|
const filteredApplications = showFeaturedOnly
|
||||||
}, [applications, searchTerm]);
|
? textFilteredApplications.filter((application) => application.isFeatured)
|
||||||
|
: textFilteredApplications;
|
||||||
|
|
||||||
|
const nonFeaturedCount = showFeaturedOnly
|
||||||
|
? textFilteredApplications.filter((application) => !application.isFeatured)
|
||||||
|
.length
|
||||||
|
: 0;
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -54,6 +78,9 @@ export const SettingsApplicationsAvailableTab = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showNonFeaturedHint =
|
||||||
|
filteredApplications.length === 0 && nonFeaturedCount > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section>
|
<Section>
|
||||||
<StyledSearchInputContainer>
|
<StyledSearchInputContainer>
|
||||||
@@ -61,11 +88,43 @@ export const SettingsApplicationsAvailableTab = () => {
|
|||||||
placeholder={t`Search an application`}
|
placeholder={t`Search an application`}
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={setSearchTerm}
|
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>
|
</StyledSearchInputContainer>
|
||||||
|
|
||||||
{filteredApplications.length === 0 ? (
|
{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>
|
<StyledCardsGrid>
|
||||||
{filteredApplications.map((application) => (
|
{filteredApplications.map((application) => (
|
||||||
|
|||||||
+85
-9
@@ -2,6 +2,11 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
|
|||||||
import { FIND_MANY_APPLICATION_REGISTRATIONS } from '@/settings/application-registrations/graphql/queries/findManyApplicationRegistrations';
|
import { FIND_MANY_APPLICATION_REGISTRATIONS } from '@/settings/application-registrations/graphql/queries/findManyApplicationRegistrations';
|
||||||
import { SettingsListCard } from '@/settings/components/SettingsListCard';
|
import { SettingsListCard } from '@/settings/components/SettingsListCard';
|
||||||
import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl';
|
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 { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||||
import { styled } from '@linaria/react';
|
import { styled } from '@linaria/react';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
@@ -13,20 +18,40 @@ import {
|
|||||||
CommandBlock,
|
CommandBlock,
|
||||||
H2Title,
|
H2Title,
|
||||||
IconApps,
|
IconApps,
|
||||||
|
IconChevronDown,
|
||||||
IconChevronRight,
|
IconChevronRight,
|
||||||
IconCopy,
|
IconCopy,
|
||||||
|
IconDownload,
|
||||||
IconFileInfo,
|
IconFileInfo,
|
||||||
|
IconUpload,
|
||||||
} from 'twenty-ui/display';
|
} 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 { Section } from 'twenty-ui/layout';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
import { MenuItem } from 'twenty-ui/navigation';
|
||||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||||
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
|
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`
|
const StyledButtonContainer = styled.div`
|
||||||
margin: ${themeCssVariables.spacing[2]} 0;
|
margin: ${themeCssVariables.spacing[2]} 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledButtonGroupContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: ${themeCssVariables.spacing[4]};
|
||||||
|
`;
|
||||||
|
|
||||||
type ApplicationRegistration = {
|
type ApplicationRegistration = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -38,6 +63,8 @@ export const SettingsApplicationsDeveloperTab = () => {
|
|||||||
const { t } = useLingui();
|
const { t } = useLingui();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState);
|
const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState);
|
||||||
|
const { openModal } = useModal();
|
||||||
|
const { closeDropdown } = useCloseDropdown();
|
||||||
|
|
||||||
const { copyToClipboard } = useCopyToClipboard();
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Section>
|
<Section>
|
||||||
@@ -87,12 +124,12 @@ export const SettingsApplicationsDeveloperTab = () => {
|
|||||||
/>
|
/>
|
||||||
</StyledButtonContainer>
|
</StyledButtonContainer>
|
||||||
</Section>
|
</Section>
|
||||||
{registrations.length > 0 && (
|
<Section>
|
||||||
<Section>
|
<H2Title
|
||||||
<H2Title
|
title={t`My Apps`}
|
||||||
title={t`My Apps`}
|
description={t`Apps you've created, registered, or published`}
|
||||||
description={t`Apps you've created and published`}
|
/>
|
||||||
/>
|
{registrations.length > 0 && (
|
||||||
<SettingsListCard
|
<SettingsListCard
|
||||||
items={registrations}
|
items={registrations}
|
||||||
getItemLabel={(registration) => registration.name}
|
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 />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+4
-89
@@ -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 { useFindManyApplicationsQuery } from '~/generated-metadata/graphql';
|
||||||
import { SettingsApplicationsTable } from '~/pages/settings/applications/components/SettingsApplicationsTable';
|
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 = () => {
|
export const SettingsApplicationsInstalledTab = () => {
|
||||||
const { t } = useLingui();
|
|
||||||
const { data } = useFindManyApplicationsQuery();
|
const { data } = useFindManyApplicationsQuery();
|
||||||
const { openModal } = useModal();
|
|
||||||
const { closeDropdown } = useCloseDropdown();
|
|
||||||
|
|
||||||
const applications = data?.findManyApplications ?? [];
|
const applications = data?.findManyApplications ?? [];
|
||||||
|
|
||||||
const handleInstallFromNpm = () => {
|
if (applications.length === 0) {
|
||||||
closeDropdown(INSTALL_APP_DROPDOWN_ID);
|
return null;
|
||||||
openModal(INSTALL_NPM_APP_MODAL_ID);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const handleUploadTarball = () => {
|
return <SettingsApplicationsTable applications={applications} />;
|
||||||
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 />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,11 +21,11 @@
|
|||||||
"outDir": "dist/assets"
|
"outDir": "dist/assets"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"include": "engine/core-modules/application/constants/seed-dependencies/**",
|
"include": "engine/core-modules/application/application-package/constants/seed-dependencies/**",
|
||||||
"outDir": "dist/assets"
|
"outDir": "dist/assets"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"include": "engine/core-modules/application/constants/yarn-engine/**",
|
"include": "engine/core-modules/application/application-package/constants/yarn-engine/**",
|
||||||
"outDir": "dist/assets"
|
"outDir": "dist/assets"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Logger } from '@nestjs/common';
|
|||||||
|
|
||||||
import { Command, CommandRunner } from 'nest-commander';
|
import { Command, CommandRunner } from 'nest-commander';
|
||||||
|
|
||||||
import { AppVersionCheckCronCommand } from 'src/engine/core-modules/application/application-version-check/crons/commands/app-version-check.cron.command';
|
import { ApplicationVersionCheckCronCommand } from 'src/engine/core-modules/application/application-upgrade/crons/commands/application-version-check.cron.command';
|
||||||
import { MarketplaceCatalogSyncCronCommand } from 'src/engine/core-modules/application/application-marketplace/crons/commands/marketplace-catalog-sync.cron.command';
|
import { MarketplaceCatalogSyncCronCommand } from 'src/engine/core-modules/application/application-marketplace/crons/commands/marketplace-catalog-sync.cron.command';
|
||||||
import { EventLogCleanupCronCommand } from 'src/engine/core-modules/event-logs/cleanup/commands/event-log-cleanup.cron.command';
|
import { EventLogCleanupCronCommand } from 'src/engine/core-modules/event-logs/cleanup/commands/event-log-cleanup.cron.command';
|
||||||
import { CheckPublicDomainsValidRecordsCronCommand } from 'src/engine/core-modules/public-domain/crons/commands/check-public-domains-valid-records.cron.command';
|
import { CheckPublicDomainsValidRecordsCronCommand } from 'src/engine/core-modules/public-domain/crons/commands/check-public-domains-valid-records.cron.command';
|
||||||
@@ -55,7 +55,7 @@ export class CronRegisterAllCommand extends CommandRunner {
|
|||||||
private readonly trashCleanupCronCommand: TrashCleanupCronCommand,
|
private readonly trashCleanupCronCommand: TrashCleanupCronCommand,
|
||||||
private readonly eventLogCleanupCronCommand: EventLogCleanupCronCommand,
|
private readonly eventLogCleanupCronCommand: EventLogCleanupCronCommand,
|
||||||
private readonly marketplaceCatalogSyncCronCommand: MarketplaceCatalogSyncCronCommand,
|
private readonly marketplaceCatalogSyncCronCommand: MarketplaceCatalogSyncCronCommand,
|
||||||
private readonly appVersionCheckCronCommand: AppVersionCheckCronCommand,
|
private readonly applicationVersionCheckCronCommand: ApplicationVersionCheckCronCommand,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@@ -145,8 +145,8 @@ export class CronRegisterAllCommand extends CommandRunner {
|
|||||||
command: this.marketplaceCatalogSyncCronCommand,
|
command: this.marketplaceCatalogSyncCronCommand,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'AppVersionCheck',
|
name: 'ApplicationVersionCheck',
|
||||||
command: this.appVersionCheckCronCommand,
|
command: this.applicationVersionCheckCronCommand,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { UpgradeVersionCommandModule } from 'src/database/commands/upgrade-versi
|
|||||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||||
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
|
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
|
||||||
import { GenerateApiKeyCommand } from 'src/engine/core-modules/api-key/commands/generate-api-key.command';
|
import { GenerateApiKeyCommand } from 'src/engine/core-modules/api-key/commands/generate-api-key.command';
|
||||||
import { AppVersionCheckModule } from 'src/engine/core-modules/application/application-version-check/application-version-check.module';
|
import { ApplicationUpgradeModule } from 'src/engine/core-modules/application/application-upgrade/application-upgrade.module';
|
||||||
import { MarketplaceModule } from 'src/engine/core-modules/application/application-marketplace/marketplace.module';
|
import { MarketplaceModule } from 'src/engine/core-modules/application/application-marketplace/marketplace.module';
|
||||||
import { EventLogCleanupModule } from 'src/engine/core-modules/event-logs/cleanup/event-log-cleanup.module';
|
import { EventLogCleanupModule } from 'src/engine/core-modules/event-logs/cleanup/event-log-cleanup.module';
|
||||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
@@ -58,7 +58,7 @@ import { AutomatedTriggerModule } from 'src/modules/workflow/workflow-trigger/au
|
|||||||
PublicDomainModule,
|
PublicDomainModule,
|
||||||
EventLogCleanupModule,
|
EventLogCleanupModule,
|
||||||
MarketplaceModule,
|
MarketplaceModule,
|
||||||
AppVersionCheckModule,
|
ApplicationUpgradeModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
DataSeedWorkspaceCommand,
|
DataSeedWorkspaceCommand,
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/
|
|||||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||||
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
|
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application/application-package/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
|
||||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||||
import { FileSettings } from 'src/engine/core-modules/file/types/file-settings.types';
|
import { FileSettings } from 'src/engine/core-modules/file/types/file-settings.types';
|
||||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
|||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddIsListedToAppRegistration1772732588833
|
||||||
|
implements MigrationInterface
|
||||||
|
{
|
||||||
|
name = 'AddIsListedToAppRegistration1772732588833';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."applicationRegistration" ADD "isListed" boolean NOT NULL DEFAULT false`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."applicationRegistration" ALTER COLUMN "workspaceId" DROP NOT NULL`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."applicationRegistration" DROP CONSTRAINT IF EXISTS "FK_applicationRegistration_workspaceId"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."applicationRegistration" ADD CONSTRAINT "FK_applicationRegistration_workspaceId" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE SET NULL`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."applicationRegistration" DROP CONSTRAINT "FK_applicationRegistration_workspaceId"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."applicationRegistration" DROP CONSTRAINT "FK_94ab20372e448d45088357f884e"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."applicationRegistration" ALTER COLUMN "workspaceId" DROP NOT NULL`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."applicationRegistration" ADD CONSTRAINT "FK_94ab20372e448d45088357f884e" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE SET NULL ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."applicationRegistration" ADD CONSTRAINT "FK_applicationRegistration_workspaceId" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE SET NULL ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."applicationRegistration" DROP CONSTRAINT "FK_94ab20372e448d45088357f884e"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."applicationRegistration" ALTER COLUMN "workspaceId" SET NOT NULL`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."applicationRegistration" ADD CONSTRAINT "FK_94ab20372e448d45088357f884e" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."applicationRegistration" DROP CONSTRAINT IF EXISTS "FK_applicationRegistration_workspaceId"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."applicationRegistration" ADD CONSTRAINT "FK_applicationRegistration_workspaceId" FOREIGN KEY ("workspaceId") REFERENCES "core"."workspace"("id") ON DELETE CASCADE`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."applicationRegistration" ALTER COLUMN "workspaceId" SET NOT NULL`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "core"."applicationRegistration" DROP COLUMN "isListed"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import { AdminPanelHealthService } from 'src/engine/core-modules/admin-panel/adm
|
|||||||
import { AdminPanelQueueService } from 'src/engine/core-modules/admin-panel/admin-panel-queue.service';
|
import { AdminPanelQueueService } from 'src/engine/core-modules/admin-panel/admin-panel-queue.service';
|
||||||
import { AdminPanelResolver } from 'src/engine/core-modules/admin-panel/admin-panel.resolver';
|
import { AdminPanelResolver } from 'src/engine/core-modules/admin-panel/admin-panel.resolver';
|
||||||
import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
|
import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
|
||||||
|
import { ApplicationRegistrationModule } from 'src/engine/core-modules/application/application-registration/application-registration.module';
|
||||||
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
|
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
|
||||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||||
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
|
||||||
@@ -40,6 +41,7 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
|
|||||||
ImpersonationModule,
|
ImpersonationModule,
|
||||||
PermissionsModule,
|
PermissionsModule,
|
||||||
SecureHttpClientModule,
|
SecureHttpClientModule,
|
||||||
|
ApplicationRegistrationModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
AdminPanelResolver,
|
AdminPanelResolver,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { PermissionFlagType } from 'twenty-shared/constants';
|
|||||||
import { AdminPanelHealthService } from 'src/engine/core-modules/admin-panel/admin-panel-health.service';
|
import { AdminPanelHealthService } from 'src/engine/core-modules/admin-panel/admin-panel-health.service';
|
||||||
import { AdminPanelQueueService } from 'src/engine/core-modules/admin-panel/admin-panel-queue.service';
|
import { AdminPanelQueueService } from 'src/engine/core-modules/admin-panel/admin-panel-queue.service';
|
||||||
import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
|
import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
|
||||||
|
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
||||||
|
import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service';
|
||||||
import { AdminAIModelsDTO } from 'src/engine/core-modules/client-config/client-config.entity';
|
import { AdminAIModelsDTO } from 'src/engine/core-modules/client-config/client-config.entity';
|
||||||
import { ConfigVariableDTO } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto';
|
import { ConfigVariableDTO } from 'src/engine/core-modules/admin-panel/dtos/config-variable.dto';
|
||||||
import { ConfigVariablesDTO } from 'src/engine/core-modules/admin-panel/dtos/config-variables.dto';
|
import { ConfigVariablesDTO } from 'src/engine/core-modules/admin-panel/dtos/config-variables.dto';
|
||||||
@@ -56,8 +58,9 @@ import { QueueMetricsDataDTO } from './dtos/queue-metrics-data.dto';
|
|||||||
)
|
)
|
||||||
export class AdminPanelResolver {
|
export class AdminPanelResolver {
|
||||||
constructor(
|
constructor(
|
||||||
private adminService: AdminPanelService,
|
private readonly adminService: AdminPanelService,
|
||||||
private adminPanelHealthService: AdminPanelHealthService,
|
private readonly adminPanelHealthService: AdminPanelHealthService,
|
||||||
|
private readonly applicationRegistrationService: ApplicationRegistrationService,
|
||||||
private adminPanelQueueService: AdminPanelQueueService,
|
private adminPanelQueueService: AdminPanelQueueService,
|
||||||
private featureFlagService: FeatureFlagService,
|
private featureFlagService: FeatureFlagService,
|
||||||
private readonly twentyConfigService: TwentyConfigService,
|
private readonly twentyConfigService: TwentyConfigService,
|
||||||
@@ -267,4 +270,12 @@ export class AdminPanelResolver {
|
|||||||
jobIds,
|
jobIds,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(AdminPanelGuard)
|
||||||
|
@Query(() => [ApplicationRegistrationEntity])
|
||||||
|
async findAllApplicationRegistrations(): Promise<
|
||||||
|
ApplicationRegistrationEntity[]
|
||||||
|
> {
|
||||||
|
return this.applicationRegistrationService.findAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { ApplicationRegistrationModule } from 'src/engine/core-modules/application/application-registration/application-registration.module';
|
import { ApplicationRegistrationModule } from 'src/engine/core-modules/application/application-registration/application-registration.module';
|
||||||
import { ApplicationInstallModule } from 'src/engine/core-modules/application/application-install/application-install.module';
|
import { ApplicationManifestModule } from 'src/engine/core-modules/application/application-manifest/application-manifest.module';
|
||||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||||
import { ApplicationDevelopmentResolver } from 'src/engine/core-modules/application/application-development/application-development.resolver';
|
import { ApplicationDevelopmentResolver } from 'src/engine/core-modules/application/application-development/application-development.resolver';
|
||||||
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
||||||
@@ -12,9 +12,9 @@ import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/wor
|
|||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ApplicationInstallModule,
|
|
||||||
ApplicationRegistrationModule,
|
|
||||||
ApplicationModule,
|
ApplicationModule,
|
||||||
|
ApplicationManifestModule,
|
||||||
|
ApplicationRegistrationModule,
|
||||||
FeatureFlagModule,
|
FeatureFlagModule,
|
||||||
TokenModule,
|
TokenModule,
|
||||||
FileStorageModule,
|
FileStorageModule,
|
||||||
|
|||||||
+61
-72
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
Logger,
|
|
||||||
UseFilters,
|
UseFilters,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
UseInterceptors,
|
UseInterceptors,
|
||||||
@@ -13,24 +12,22 @@ import { FileFolder, FeatureFlagKey } from 'twenty-shared/types';
|
|||||||
|
|
||||||
import type { FileUpload } from 'graphql-upload/processRequest.mjs';
|
import type { FileUpload } from 'graphql-upload/processRequest.mjs';
|
||||||
|
|
||||||
import { ApplicationRegistrationVariableService } from 'src/engine/core-modules/application/application-registration/application-registration-variable.service';
|
import { ApplicationRegistrationVariableService } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.service';
|
||||||
import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service';
|
import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service';
|
||||||
import { AppRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/app-registration-source-type.enum';
|
|
||||||
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
||||||
import { ApplicationExceptionFilter } from 'src/engine/core-modules/application/application-exception-filter';
|
import { ApplicationExceptionFilter } from 'src/engine/core-modules/application/application-exception-filter';
|
||||||
import {
|
import {
|
||||||
ApplicationException,
|
ApplicationException,
|
||||||
ApplicationExceptionCode,
|
ApplicationExceptionCode,
|
||||||
} from 'src/engine/core-modules/application/application.exception';
|
} from 'src/engine/core-modules/application/application.exception';
|
||||||
import { ApplicationDTO } from 'src/engine/core-modules/application/dtos/application.dto';
|
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
|
||||||
import { ApplicationInput } from 'src/engine/core-modules/application/dtos/application.input';
|
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-manifest/application-sync.service';
|
||||||
import { CreateApplicationInput } from 'src/engine/core-modules/application/dtos/create-application.input';
|
|
||||||
import { GenerateApplicationTokenInput } from 'src/engine/core-modules/application/dtos/generate-application-token.input';
|
|
||||||
import { UploadApplicationFileInput } from 'src/engine/core-modules/application/dtos/uploadApplicationFileInput';
|
|
||||||
import { WorkspaceMigrationDTO } from 'src/engine/core-modules/application/dtos/workspace-migration.dto';
|
|
||||||
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-install/application-sync.service';
|
|
||||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||||
import { ApplicationTokenPairDTO } from 'src/engine/core-modules/application/dtos/application-token-pair.dto';
|
import { ApplicationInput } from 'src/engine/core-modules/application/application-development/dtos/application.input';
|
||||||
|
import { GenerateApplicationTokenInput } from 'src/engine/core-modules/application/application-development/dtos/generate-application-token.input';
|
||||||
|
import { UploadApplicationFileInput } from 'src/engine/core-modules/application/application-development/dtos/upload-application-file.input';
|
||||||
|
import { WorkspaceMigrationDTO } from 'src/engine/core-modules/application/application-development/dtos/workspace-migration.dto';
|
||||||
|
import { ApplicationTokenPairDTO } from 'src/engine/core-modules/application/application-oauth/dtos/application-token-pair.dto';
|
||||||
import { ApplicationTokenService } from 'src/engine/core-modules/auth/token/services/application-token.service';
|
import { ApplicationTokenService } from 'src/engine/core-modules/auth/token/services/application-token.service';
|
||||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||||
import { FileDTO } from 'src/engine/core-modules/file/dtos/file.dto';
|
import { FileDTO } from 'src/engine/core-modules/file/dtos/file.dto';
|
||||||
@@ -58,12 +55,10 @@ import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
|||||||
SettingsPermissionGuard(PermissionFlagType.APPLICATIONS),
|
SettingsPermissionGuard(PermissionFlagType.APPLICATIONS),
|
||||||
)
|
)
|
||||||
export class ApplicationDevelopmentResolver {
|
export class ApplicationDevelopmentResolver {
|
||||||
private readonly logger = new Logger(ApplicationDevelopmentResolver.name);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly applicationTokenService: ApplicationTokenService,
|
private readonly applicationTokenService: ApplicationTokenService,
|
||||||
private readonly applicationSyncService: ApplicationSyncService,
|
|
||||||
private readonly applicationService: ApplicationService,
|
private readonly applicationService: ApplicationService,
|
||||||
|
private readonly applicationSyncService: ApplicationSyncService,
|
||||||
private readonly applicationRegistrationService: ApplicationRegistrationService,
|
private readonly applicationRegistrationService: ApplicationRegistrationService,
|
||||||
private readonly applicationRegistrationVariableService: ApplicationRegistrationVariableService,
|
private readonly applicationRegistrationVariableService: ApplicationRegistrationVariableService,
|
||||||
private readonly fileStorageService: FileStorageService,
|
private readonly fileStorageService: FileStorageService,
|
||||||
@@ -87,19 +82,17 @@ export class ApplicationDevelopmentResolver {
|
|||||||
@Args() { manifest }: ApplicationInput,
|
@Args() { manifest }: ApplicationInput,
|
||||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||||
): Promise<WorkspaceMigrationDTO> {
|
): Promise<WorkspaceMigrationDTO> {
|
||||||
const applicationRegistrationId =
|
const applicationRegistrationId = await this.findApplicationRegistrationId(
|
||||||
await this.resolveApplicationRegistrationId(
|
manifest.application.universalIdentifier,
|
||||||
manifest.application.universalIdentifier,
|
workspaceId,
|
||||||
{
|
);
|
||||||
name: manifest.application.displayName,
|
|
||||||
description: manifest.application.description,
|
await this.ensureApplicationExists({
|
||||||
logoUrl: manifest.application.logoUrl,
|
universalIdentifier: manifest.application.universalIdentifier,
|
||||||
author: manifest.application.author,
|
name: manifest.application.displayName,
|
||||||
websiteUrl: manifest.application.websiteUrl,
|
workspaceId,
|
||||||
termsUrl: manifest.application.termsUrl,
|
applicationRegistrationId,
|
||||||
},
|
});
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const workspaceMigration =
|
const workspaceMigration =
|
||||||
await this.applicationSyncService.synchronizeFromManifest({
|
await this.applicationSyncService.synchronizeFromManifest({
|
||||||
@@ -121,19 +114,6 @@ export class ApplicationDevelopmentResolver {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => ApplicationDTO)
|
|
||||||
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
|
|
||||||
async createOneApplication(
|
|
||||||
@Args('input') input: CreateApplicationInput,
|
|
||||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
|
||||||
) {
|
|
||||||
return await this.applicationService.create({
|
|
||||||
...input,
|
|
||||||
sourceType: AppRegistrationSourceType.LOCAL,
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Mutation(() => FileDTO)
|
@Mutation(() => FileDTO)
|
||||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.UPLOAD_FILE))
|
@UseGuards(SettingsPermissionGuard(PermissionFlagType.UPLOAD_FILE))
|
||||||
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
|
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
|
||||||
@@ -176,16 +156,8 @@ export class ApplicationDevelopmentResolver {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async resolveApplicationRegistrationId(
|
private async findApplicationRegistrationId(
|
||||||
universalIdentifier: string,
|
universalIdentifier: string,
|
||||||
metadata: {
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
logoUrl?: string;
|
|
||||||
author?: string;
|
|
||||||
websiteUrl?: string;
|
|
||||||
termsUrl?: string;
|
|
||||||
},
|
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const existingRegistration =
|
const existingRegistration =
|
||||||
@@ -193,35 +165,27 @@ export class ApplicationDevelopmentResolver {
|
|||||||
universalIdentifier,
|
universalIdentifier,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (existingRegistration) {
|
if (!existingRegistration) {
|
||||||
const isOwner =
|
throw new ApplicationException(
|
||||||
await this.applicationRegistrationService.isOwnedByWorkspace(
|
`No registration found for "${universalIdentifier}". Create one first with createApplicationRegistration.`,
|
||||||
existingRegistration.id,
|
ApplicationExceptionCode.APPLICATION_NOT_FOUND,
|
||||||
workspaceId,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
if (!isOwner) {
|
|
||||||
throw new ApplicationException(
|
|
||||||
'Cannot sync application: registration is owned by another workspace',
|
|
||||||
ApplicationExceptionCode.FORBIDDEN,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return existingRegistration.id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { applicationRegistration: newRegistration } =
|
const isOwner =
|
||||||
await this.applicationRegistrationService.create(
|
await this.applicationRegistrationService.isOwnedByWorkspace(
|
||||||
{ ...metadata, universalIdentifier },
|
existingRegistration.id,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
null,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.logger.log(
|
if (!isOwner) {
|
||||||
`Created app registration for ${metadata.name} (${universalIdentifier})`,
|
throw new ApplicationException(
|
||||||
);
|
'Cannot sync application: registration is owned by another workspace',
|
||||||
|
ApplicationExceptionCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return newRegistration.id;
|
return existingRegistration.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async syncRegistrationMetadata(
|
private async syncRegistrationMetadata(
|
||||||
@@ -259,4 +223,29 @@ export class ApplicationDevelopmentResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async ensureApplicationExists(params: {
|
||||||
|
universalIdentifier: string;
|
||||||
|
name: string;
|
||||||
|
workspaceId: string;
|
||||||
|
applicationRegistrationId: string;
|
||||||
|
}): Promise<void> {
|
||||||
|
const existing = await this.applicationService.findByUniversalIdentifier({
|
||||||
|
universalIdentifier: params.universalIdentifier,
|
||||||
|
workspaceId: params.workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.applicationService.create({
|
||||||
|
universalIdentifier: params.universalIdentifier,
|
||||||
|
name: params.name,
|
||||||
|
sourcePath: params.universalIdentifier,
|
||||||
|
sourceType: ApplicationRegistrationSourceType.LOCAL,
|
||||||
|
applicationRegistrationId: params.applicationRegistrationId,
|
||||||
|
workspaceId: params.workspaceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -21,6 +21,7 @@ export class ApplicationExceptionFilter implements ExceptionFilter {
|
|||||||
case ApplicationExceptionCode.FIELD_NOT_FOUND:
|
case ApplicationExceptionCode.FIELD_NOT_FOUND:
|
||||||
case ApplicationExceptionCode.ENTITY_NOT_FOUND:
|
case ApplicationExceptionCode.ENTITY_NOT_FOUND:
|
||||||
case ApplicationExceptionCode.APPLICATION_NOT_FOUND:
|
case ApplicationExceptionCode.APPLICATION_NOT_FOUND:
|
||||||
|
case ApplicationExceptionCode.APP_NOT_INSTALLED:
|
||||||
case ApplicationExceptionCode.LOGIC_FUNCTION_NOT_FOUND:
|
case ApplicationExceptionCode.LOGIC_FUNCTION_NOT_FOUND:
|
||||||
case ApplicationExceptionCode.FRONT_COMPONENT_NOT_FOUND:
|
case ApplicationExceptionCode.FRONT_COMPONENT_NOT_FOUND:
|
||||||
throw new NotFoundError(exception);
|
throw new NotFoundError(exception);
|
||||||
|
|||||||
+7
-47
@@ -4,66 +4,26 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
|||||||
import { CacheLockModule } from 'src/engine/core-modules/cache-lock/cache-lock.module';
|
import { CacheLockModule } from 'src/engine/core-modules/cache-lock/cache-lock.module';
|
||||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
||||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
|
||||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||||
|
import { ApplicationManifestModule } from 'src/engine/core-modules/application/application-manifest/application-manifest.module';
|
||||||
|
import { ApplicationPackageModule } from 'src/engine/core-modules/application/application-package/application-package.module';
|
||||||
import { ApplicationInstallResolver } from 'src/engine/core-modules/application/application-install/application-install.resolver';
|
import { ApplicationInstallResolver } from 'src/engine/core-modules/application/application-install/application-install.resolver';
|
||||||
import { AppPackageFetcherService } from 'src/engine/core-modules/application/application-install/app-package-fetcher.service';
|
|
||||||
import { ApplicationInstallService } from 'src/engine/core-modules/application/application-install/application-install.service';
|
import { ApplicationInstallService } from 'src/engine/core-modules/application/application-install/application-install.service';
|
||||||
import { ApplicationManifestMigrationService } from 'src/engine/core-modules/application/application-install/application-manifest-migration.service';
|
|
||||||
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-install/application-sync.service';
|
|
||||||
import { AppUpgradeService } from 'src/engine/core-modules/application/application-install/app-upgrade.service';
|
|
||||||
import { ApplicationVariableEntityModule } from 'src/engine/core-modules/application/application-variable/application-variable.module';
|
|
||||||
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
|
||||||
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
|
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
|
||||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
|
||||||
import { ObjectPermissionModule } from 'src/engine/metadata-modules/object-permission/object-permission.module';
|
|
||||||
import { PermissionFlagModule } from 'src/engine/metadata-modules/permission-flag/permission-flag.module';
|
|
||||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
|
|
||||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
|
||||||
import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor';
|
|
||||||
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/workspace-migration-runner.module';
|
|
||||||
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
|
|
||||||
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
|
|
||||||
import { CodeStepBuildModule } from 'src/modules/workflow/workflow-builder/workflow-version-step/code-step/code-step-build.module';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([
|
TypeOrmModule.forFeature([ApplicationRegistrationEntity]),
|
||||||
FileEntity,
|
|
||||||
ApplicationRegistrationEntity,
|
|
||||||
ApplicationEntity,
|
|
||||||
]),
|
|
||||||
ApplicationModule,
|
ApplicationModule,
|
||||||
|
ApplicationManifestModule,
|
||||||
|
ApplicationPackageModule,
|
||||||
CacheLockModule,
|
CacheLockModule,
|
||||||
FeatureFlagModule,
|
FeatureFlagModule,
|
||||||
ApplicationVariableEntityModule,
|
|
||||||
TokenModule,
|
|
||||||
WorkspaceMigrationModule,
|
|
||||||
PermissionsModule,
|
PermissionsModule,
|
||||||
ObjectPermissionModule,
|
|
||||||
PermissionFlagModule,
|
|
||||||
WorkflowCommonModule,
|
|
||||||
CodeStepBuildModule,
|
|
||||||
FileStorageModule,
|
FileStorageModule,
|
||||||
WorkspaceCacheModule,
|
|
||||||
WorkspaceMigrationRunnerModule,
|
|
||||||
TwentyConfigModule,
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
ApplicationInstallResolver,
|
|
||||||
ApplicationManifestMigrationService,
|
|
||||||
ApplicationSyncService,
|
|
||||||
AppPackageFetcherService,
|
|
||||||
ApplicationInstallService,
|
|
||||||
AppUpgradeService,
|
|
||||||
WorkspaceMigrationGraphqlApiExceptionInterceptor,
|
|
||||||
],
|
|
||||||
exports: [
|
|
||||||
ApplicationSyncService,
|
|
||||||
AppPackageFetcherService,
|
|
||||||
ApplicationInstallService,
|
|
||||||
AppUpgradeService,
|
|
||||||
],
|
],
|
||||||
|
providers: [ApplicationInstallResolver, ApplicationInstallService],
|
||||||
|
exports: [ApplicationInstallService],
|
||||||
})
|
})
|
||||||
export class ApplicationInstallModule {}
|
export class ApplicationInstallModule {}
|
||||||
|
|||||||
+10
-96
@@ -1,9 +1,4 @@
|
|||||||
import {
|
import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
|
||||||
UseFilters,
|
|
||||||
UseGuards,
|
|
||||||
UseInterceptors,
|
|
||||||
UsePipes,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { Args, Mutation, Query } from '@nestjs/graphql';
|
import { Args, Mutation, Query } from '@nestjs/graphql';
|
||||||
|
|
||||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||||
@@ -12,18 +7,10 @@ import { FeatureFlagKey } from 'twenty-shared/types';
|
|||||||
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
||||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||||
import { ApplicationExceptionFilter } from 'src/engine/core-modules/application/application-exception-filter';
|
import { ApplicationExceptionFilter } from 'src/engine/core-modules/application/application-exception-filter';
|
||||||
import {
|
|
||||||
ApplicationException,
|
|
||||||
ApplicationExceptionCode,
|
|
||||||
} from 'src/engine/core-modules/application/application.exception';
|
|
||||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||||
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-install/application-sync.service';
|
import { ApplicationInstallService } from 'src/engine/core-modules/application/application-install/application-install.service';
|
||||||
import { ApplicationTokenPairDTO } from 'src/engine/core-modules/application/dtos/application-token-pair.dto';
|
|
||||||
import { ApplicationDTO } from 'src/engine/core-modules/application/dtos/application.dto';
|
import { ApplicationDTO } from 'src/engine/core-modules/application/dtos/application.dto';
|
||||||
import { InstallApplicationInput } from 'src/engine/core-modules/application/dtos/install-application.input';
|
|
||||||
import { UninstallApplicationInput } from 'src/engine/core-modules/application/dtos/uninstallApplicationInput';
|
|
||||||
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
||||||
import { ApplicationTokenService } from 'src/engine/core-modules/auth/token/services/application-token.service';
|
|
||||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||||
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
@@ -31,25 +18,17 @@ import {
|
|||||||
FeatureFlagGuard,
|
FeatureFlagGuard,
|
||||||
RequireFeatureFlag,
|
RequireFeatureFlag,
|
||||||
} from 'src/engine/guards/feature-flag.guard';
|
} from 'src/engine/guards/feature-flag.guard';
|
||||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
|
||||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
|
||||||
import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor';
|
|
||||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service';
|
|
||||||
|
|
||||||
@UsePipes(ResolverValidationPipe)
|
@UsePipes(ResolverValidationPipe)
|
||||||
@MetadataResolver()
|
@MetadataResolver()
|
||||||
@UseInterceptors(WorkspaceMigrationGraphqlApiExceptionInterceptor)
|
|
||||||
@UseFilters(ApplicationExceptionFilter, AuthGraphqlApiExceptionFilter)
|
@UseFilters(ApplicationExceptionFilter, AuthGraphqlApiExceptionFilter)
|
||||||
@UseGuards(WorkspaceAuthGuard, FeatureFlagGuard)
|
@UseGuards(WorkspaceAuthGuard, FeatureFlagGuard)
|
||||||
export class ApplicationInstallResolver {
|
export class ApplicationInstallResolver {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly applicationService: ApplicationService,
|
private readonly applicationService: ApplicationService,
|
||||||
private readonly applicationTokenService: ApplicationTokenService,
|
private readonly applicationInstallService: ApplicationInstallService,
|
||||||
private readonly applicationSyncService: ApplicationSyncService,
|
|
||||||
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
|
||||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Query(() => [ApplicationDTO])
|
@Query(() => [ApplicationDTO])
|
||||||
@@ -78,84 +57,19 @@ export class ApplicationInstallResolver {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => ApplicationTokenPairDTO)
|
|
||||||
@UseGuards(NoPermissionGuard)
|
|
||||||
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
|
|
||||||
async renewApplicationToken(
|
|
||||||
@Args('applicationRefreshToken') applicationRefreshToken: string,
|
|
||||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
|
||||||
): Promise<ApplicationTokenPairDTO> {
|
|
||||||
const applicationRefreshTokenPayload =
|
|
||||||
this.applicationTokenService.validateApplicationRefreshToken(
|
|
||||||
applicationRefreshToken,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (applicationRefreshTokenPayload.workspaceId !== workspaceId) {
|
|
||||||
throw new ApplicationException(
|
|
||||||
'Refresh token workspace does not match authenticated workspace',
|
|
||||||
ApplicationExceptionCode.FORBIDDEN,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.applicationTokenService.renewApplicationTokens(
|
|
||||||
applicationRefreshTokenPayload,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.APPLICATIONS))
|
@UseGuards(SettingsPermissionGuard(PermissionFlagType.APPLICATIONS))
|
||||||
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
|
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
|
||||||
async installApplication(
|
async installApplication(
|
||||||
@Args() { workspaceMigration: { actions } }: InstallApplicationInput,
|
@Args('appRegistrationId') appRegistrationId: string,
|
||||||
|
@Args('version', { type: () => String, nullable: true })
|
||||||
|
version: string | undefined,
|
||||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||||
) {
|
): Promise<boolean> {
|
||||||
const { featureFlagsMap } = await this.workspaceCacheService.getOrRecompute(
|
return this.applicationInstallService.installApplication({
|
||||||
workspaceId,
|
appRegistrationId,
|
||||||
['featureFlagsMap'],
|
version,
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
featureFlagsMap[
|
|
||||||
FeatureFlagKey.IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED
|
|
||||||
] !== true
|
|
||||||
) {
|
|
||||||
throw new ApplicationException(
|
|
||||||
'Application installation from tarball is not enabled',
|
|
||||||
ApplicationExceptionCode.FORBIDDEN,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { workspaceCustomFlatApplication } =
|
|
||||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
|
||||||
{
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.workspaceMigrationRunnerService.run({
|
|
||||||
workspaceMigration: {
|
|
||||||
actions,
|
|
||||||
applicationUniversalIdentifier:
|
|
||||||
workspaceCustomFlatApplication.universalIdentifier,
|
|
||||||
},
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Mutation(() => Boolean)
|
|
||||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.APPLICATIONS))
|
|
||||||
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
|
|
||||||
async uninstallApplication(
|
|
||||||
@Args() { universalIdentifier }: UninstallApplicationInput,
|
|
||||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
|
||||||
) {
|
|
||||||
await this.applicationSyncService.uninstallApplication({
|
|
||||||
applicationUniversalIdentifier: universalIdentifier,
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+66
-33
@@ -8,14 +8,19 @@ import { FileFolder } from 'twenty-shared/types';
|
|||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
|
||||||
import { AppRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/app-registration-source-type.enum';
|
|
||||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
|
||||||
import {
|
import {
|
||||||
AppPackageFetcherService,
|
ApplicationException,
|
||||||
|
ApplicationExceptionCode,
|
||||||
|
} from 'src/engine/core-modules/application/application.exception';
|
||||||
|
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
||||||
|
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
|
||||||
|
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||||
|
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||||
|
import {
|
||||||
|
ApplicationPackageFetcherService,
|
||||||
type ResolvedPackage,
|
type ResolvedPackage,
|
||||||
} from 'src/engine/core-modules/application/application-install/app-package-fetcher.service';
|
} from 'src/engine/core-modules/application/application-package/application-package-fetcher.service';
|
||||||
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-install/application-sync.service';
|
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-manifest/application-sync.service';
|
||||||
import { CacheLockService } from 'src/engine/core-modules/cache-lock/cache-lock.service';
|
import { CacheLockService } from 'src/engine/core-modules/cache-lock/cache-lock.service';
|
||||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||||
|
|
||||||
@@ -43,9 +48,8 @@ export class ApplicationInstallService {
|
|||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(ApplicationRegistrationEntity)
|
@InjectRepository(ApplicationRegistrationEntity)
|
||||||
private readonly appRegistrationRepository: Repository<ApplicationRegistrationEntity>,
|
private readonly appRegistrationRepository: Repository<ApplicationRegistrationEntity>,
|
||||||
@InjectRepository(ApplicationEntity)
|
private readonly applicationService: ApplicationService,
|
||||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
private readonly applicationPackageFetcherService: ApplicationPackageFetcherService,
|
||||||
private readonly appPackageFetcherService: AppPackageFetcherService,
|
|
||||||
private readonly applicationSyncService: ApplicationSyncService,
|
private readonly applicationSyncService: ApplicationSyncService,
|
||||||
private readonly fileStorageService: FileStorageService,
|
private readonly fileStorageService: FileStorageService,
|
||||||
private readonly cacheLockService: CacheLockService,
|
private readonly cacheLockService: CacheLockService,
|
||||||
@@ -56,11 +60,20 @@ export class ApplicationInstallService {
|
|||||||
version?: string;
|
version?: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
}): Promise<boolean> {
|
}): Promise<boolean> {
|
||||||
const appRegistration = await this.appRegistrationRepository.findOneOrFail({
|
const appRegistration = await this.appRegistrationRepository.findOne({
|
||||||
where: { id: params.appRegistrationId },
|
where: { id: params.appRegistrationId },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (appRegistration.sourceType === AppRegistrationSourceType.LOCAL) {
|
if (!appRegistration) {
|
||||||
|
throw new ApplicationException(
|
||||||
|
`Application registration with id ${params.appRegistrationId} not found`,
|
||||||
|
ApplicationExceptionCode.APPLICATION_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
appRegistration.sourceType === ApplicationRegistrationSourceType.LOCAL
|
||||||
|
) {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Skipping install for LOCAL app ${appRegistration.universalIdentifier} (files synced by CLI watcher in dev mode)`,
|
`Skipping install for LOCAL app ${appRegistration.universalIdentifier} (files synced by CLI watcher in dev mode)`,
|
||||||
);
|
);
|
||||||
@@ -88,18 +101,29 @@ export class ApplicationInstallService {
|
|||||||
let resolvedPackage: ResolvedPackage | null = null;
|
let resolvedPackage: ResolvedPackage | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
resolvedPackage = await this.appPackageFetcherService.resolvePackage(
|
resolvedPackage =
|
||||||
appRegistration,
|
await this.applicationPackageFetcherService.resolvePackage(
|
||||||
{ targetVersion: params.version },
|
appRegistration,
|
||||||
);
|
{ targetVersion: params.version },
|
||||||
|
);
|
||||||
|
|
||||||
if (!resolvedPackage) {
|
if (!resolvedPackage) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const universalIdentifier = appRegistration.universalIdentifier;
|
||||||
|
|
||||||
|
await this.ensureApplicationExists({
|
||||||
|
universalIdentifier,
|
||||||
|
name: resolvedPackage.manifest.application.displayName,
|
||||||
|
workspaceId: params.workspaceId,
|
||||||
|
applicationRegistrationId: appRegistration.id,
|
||||||
|
sourceType: appRegistration.sourceType,
|
||||||
|
});
|
||||||
|
|
||||||
await this.writeFilesToStorage(
|
await this.writeFilesToStorage(
|
||||||
resolvedPackage.extractedDir,
|
resolvedPackage.extractedDir,
|
||||||
appRegistration.universalIdentifier,
|
universalIdentifier,
|
||||||
params.workspaceId,
|
params.workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -109,14 +133,8 @@ export class ApplicationInstallService {
|
|||||||
applicationRegistrationId: appRegistration.id,
|
applicationRegistrationId: appRegistration.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.updateApplicationSourceType(
|
|
||||||
appRegistration.universalIdentifier,
|
|
||||||
params.workspaceId,
|
|
||||||
appRegistration.sourceType,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Successfully installed app ${appRegistration.universalIdentifier} v${resolvedPackage.packageJson.version ?? 'unknown'}`,
|
`Successfully installed app ${universalIdentifier} v${resolvedPackage.packageJson.version ?? 'unknown'}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -128,7 +146,7 @@ export class ApplicationInstallService {
|
|||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
if (resolvedPackage) {
|
if (resolvedPackage) {
|
||||||
await this.appPackageFetcherService.cleanupExtractedDir(
|
await this.applicationPackageFetcherService.cleanupExtractedDir(
|
||||||
resolvedPackage.cleanupDir,
|
resolvedPackage.cleanupDir,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -202,14 +220,29 @@ export class ApplicationInstallService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateApplicationSourceType(
|
private async ensureApplicationExists(params: {
|
||||||
universalIdentifier: string,
|
universalIdentifier: string;
|
||||||
workspaceId: string,
|
name: string;
|
||||||
sourceType: AppRegistrationSourceType,
|
workspaceId: string;
|
||||||
): Promise<void> {
|
applicationRegistrationId: string;
|
||||||
await this.applicationRepository.update(
|
sourceType: ApplicationRegistrationSourceType;
|
||||||
{ universalIdentifier, workspaceId },
|
}): Promise<ApplicationEntity> {
|
||||||
{ sourceType },
|
const existing = await this.applicationService.findByUniversalIdentifier({
|
||||||
);
|
universalIdentifier: params.universalIdentifier,
|
||||||
|
workspaceId: params.workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isDefined(existing)) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.applicationService.create({
|
||||||
|
universalIdentifier: params.universalIdentifier,
|
||||||
|
name: params.name,
|
||||||
|
sourcePath: params.universalIdentifier,
|
||||||
|
sourceType: params.sourceType,
|
||||||
|
applicationRegistrationId: params.applicationRegistrationId,
|
||||||
|
workspaceId: params.workspaceId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-8
@@ -5,14 +5,14 @@ import axios from 'axios';
|
|||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { ApplicationInstallService } from 'src/engine/core-modules/application/application-install/application-install.service';
|
||||||
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
||||||
import { AppRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/app-registration-source-type.enum';
|
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
|
||||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||||
import {
|
import {
|
||||||
ApplicationException,
|
ApplicationException,
|
||||||
ApplicationExceptionCode,
|
ApplicationExceptionCode,
|
||||||
} from 'src/engine/core-modules/application/application.exception';
|
} from 'src/engine/core-modules/application/application.exception';
|
||||||
import { ApplicationInstallService } from 'src/engine/core-modules/application/application-install/application-install.service';
|
|
||||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||||
|
|
||||||
const npmPackageMetadataSchema = z.object({
|
const npmPackageMetadataSchema = z.object({
|
||||||
@@ -20,8 +20,8 @@ const npmPackageMetadataSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppUpgradeService {
|
export class ApplicationUpgradeService {
|
||||||
private readonly logger = new Logger(AppUpgradeService.name);
|
private readonly logger = new Logger(ApplicationUpgradeService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(ApplicationRegistrationEntity)
|
@InjectRepository(ApplicationRegistrationEntity)
|
||||||
@@ -35,7 +35,7 @@ export class AppUpgradeService {
|
|||||||
async checkForUpdates(
|
async checkForUpdates(
|
||||||
appRegistration: ApplicationRegistrationEntity,
|
appRegistration: ApplicationRegistrationEntity,
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
if (appRegistration.sourceType !== AppRegistrationSourceType.NPM) {
|
if (appRegistration.sourceType !== ApplicationRegistrationSourceType.NPM) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ export class AppUpgradeService {
|
|||||||
|
|
||||||
async checkAllForUpdates(): Promise<void> {
|
async checkAllForUpdates(): Promise<void> {
|
||||||
const npmRegistrations = await this.appRegistrationRepository.find({
|
const npmRegistrations = await this.appRegistrationRepository.find({
|
||||||
where: { sourceType: AppRegistrationSourceType.NPM },
|
where: { sourceType: ApplicationRegistrationSourceType.NPM },
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const registration of npmRegistrations) {
|
for (const registration of npmRegistrations) {
|
||||||
@@ -100,8 +100,8 @@ export class AppUpgradeService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
appRegistration.sourceType === AppRegistrationSourceType.LOCAL ||
|
appRegistration.sourceType === ApplicationRegistrationSourceType.LOCAL ||
|
||||||
appRegistration.sourceType === AppRegistrationSourceType.TARBALL
|
appRegistration.sourceType === ApplicationRegistrationSourceType.TARBALL
|
||||||
) {
|
) {
|
||||||
throw new ApplicationException(
|
throw new ApplicationException(
|
||||||
'Cannot upgrade an app installed from a tarball or local source',
|
'Cannot upgrade an app installed from a tarball or local source',
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
||||||
|
import { APPLICATION_VERSION_CHECK_CRON_PATTERN } from 'src/engine/core-modules/application/application-upgrade/crons/constants/application-version-check-cron-pattern.constant';
|
||||||
|
import { ApplicationUpgradeService } from 'src/engine/core-modules/application/application-upgrade/application-upgrade.service';
|
||||||
|
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
||||||
|
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||||
|
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
@Processor(MessageQueue.cronQueue)
|
||||||
|
export class ApplicationVersionCheckCronJob {
|
||||||
|
private readonly logger = new Logger(ApplicationVersionCheckCronJob.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly applicationUpgradeService: ApplicationUpgradeService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Process(ApplicationVersionCheckCronJob.name)
|
||||||
|
@SentryCronMonitor(
|
||||||
|
ApplicationVersionCheckCronJob.name,
|
||||||
|
APPLICATION_VERSION_CHECK_CRON_PATTERN,
|
||||||
|
)
|
||||||
|
async handle(): Promise<void> {
|
||||||
|
this.logger.log('Starting application version check...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.applicationUpgradeService.checkAllForUpdates();
|
||||||
|
this.logger.log('Application version check completed successfully');
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(
|
||||||
|
`Application version check failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-5
@@ -1,7 +1,7 @@
|
|||||||
import { Command, CommandRunner } from 'nest-commander';
|
import { Command, CommandRunner } from 'nest-commander';
|
||||||
|
|
||||||
import { APP_VERSION_CHECK_CRON_PATTERN } from 'src/engine/core-modules/application/application-version-check/crons/constants/app-version-check-cron-pattern.constant';
|
import { APPLICATION_VERSION_CHECK_CRON_PATTERN } from 'src/engine/core-modules/application/application-upgrade/crons/constants/application-version-check-cron-pattern.constant';
|
||||||
import { AppVersionCheckCronJob } from 'src/engine/core-modules/application/application-version-check/crons/app-version-check.cron.job';
|
import { ApplicationVersionCheckCronJob } from 'src/engine/core-modules/application/application-upgrade/crons/application-version-check.cron.job';
|
||||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||||
@@ -11,7 +11,7 @@ import { MessageQueueService } from 'src/engine/core-modules/message-queue/servi
|
|||||||
description:
|
description:
|
||||||
'Starts a cron job to check for app version updates on npm registries',
|
'Starts a cron job to check for app version updates on npm registries',
|
||||||
})
|
})
|
||||||
export class AppVersionCheckCronCommand extends CommandRunner {
|
export class ApplicationVersionCheckCronCommand extends CommandRunner {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectMessageQueue(MessageQueue.cronQueue)
|
@InjectMessageQueue(MessageQueue.cronQueue)
|
||||||
private readonly messageQueueService: MessageQueueService,
|
private readonly messageQueueService: MessageQueueService,
|
||||||
@@ -21,11 +21,11 @@ export class AppVersionCheckCronCommand extends CommandRunner {
|
|||||||
|
|
||||||
async run(): Promise<void> {
|
async run(): Promise<void> {
|
||||||
await this.messageQueueService.addCron<undefined>({
|
await this.messageQueueService.addCron<undefined>({
|
||||||
jobName: AppVersionCheckCronJob.name,
|
jobName: ApplicationVersionCheckCronJob.name,
|
||||||
data: undefined,
|
data: undefined,
|
||||||
options: {
|
options: {
|
||||||
repeat: {
|
repeat: {
|
||||||
pattern: APP_VERSION_CHECK_CRON_PATTERN,
|
pattern: APPLICATION_VERSION_CHECK_CRON_PATTERN,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
// Every 6 hours
|
||||||
|
export const APPLICATION_VERSION_CHECK_CRON_PATTERN = '0 */6 * * *';
|
||||||
+3
-3
@@ -10,9 +10,9 @@ import {
|
|||||||
} from 'src/engine/core-modules/application/application.exception';
|
} from 'src/engine/core-modules/application/application.exception';
|
||||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||||
import { buildFromToAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/utils/build-from-to-all-universal-flat-entity-maps.util';
|
import { buildFromToAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/application-manifest/utils/build-from-to-all-universal-flat-entity-maps.util';
|
||||||
import { computeApplicationManifestAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/utils/compute-application-manifest-all-universal-flat-entity-maps.util';
|
import { computeApplicationManifestAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/application-manifest/utils/compute-application-manifest-all-universal-flat-entity-maps.util';
|
||||||
import { getApplicationSubAllFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-application-sub-all-flat-entity-maps.util';
|
import { getApplicationSubAllFlatEntityMaps } from 'src/engine/core-modules/application/application-manifest/utils/get-application-sub-all-flat-entity-maps.util';
|
||||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||||
import { FieldPermissionService } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.service';
|
import { FieldPermissionService } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.service';
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||||
|
import { ApplicationManifestMigrationService } from 'src/engine/core-modules/application/application-manifest/application-manifest-migration.service';
|
||||||
|
import { ApplicationManifestResolver } from 'src/engine/core-modules/application/application-manifest/application-manifest.resolver';
|
||||||
|
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-manifest/application-sync.service';
|
||||||
|
import { ApplicationVariableEntityModule } from 'src/engine/core-modules/application/application-variable/application-variable.module';
|
||||||
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
|
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
|
||||||
|
import { ObjectPermissionModule } from 'src/engine/metadata-modules/object-permission/object-permission.module';
|
||||||
|
import { PermissionFlagModule } from 'src/engine/metadata-modules/permission-flag/permission-flag.module';
|
||||||
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
|
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||||
|
import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor';
|
||||||
|
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/workspace-migration-runner.module';
|
||||||
|
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ApplicationModule,
|
||||||
|
ApplicationVariableEntityModule,
|
||||||
|
FeatureFlagModule,
|
||||||
|
FileStorageModule,
|
||||||
|
ObjectPermissionModule,
|
||||||
|
PermissionFlagModule,
|
||||||
|
PermissionsModule,
|
||||||
|
WorkspaceCacheModule,
|
||||||
|
WorkspaceMigrationModule,
|
||||||
|
WorkspaceMigrationRunnerModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
ApplicationManifestMigrationService,
|
||||||
|
ApplicationManifestResolver,
|
||||||
|
ApplicationSyncService,
|
||||||
|
WorkspaceMigrationGraphqlApiExceptionInterceptor,
|
||||||
|
],
|
||||||
|
exports: [ApplicationManifestMigrationService, ApplicationSyncService],
|
||||||
|
})
|
||||||
|
export class ApplicationManifestModule {}
|
||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
import {
|
||||||
|
UseFilters,
|
||||||
|
UseGuards,
|
||||||
|
UseInterceptors,
|
||||||
|
UsePipes,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { Args, Mutation } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||||
|
import { FeatureFlagKey } from 'twenty-shared/types';
|
||||||
|
|
||||||
|
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
||||||
|
import { ApplicationExceptionFilter } from 'src/engine/core-modules/application/application-exception-filter';
|
||||||
|
import {
|
||||||
|
ApplicationException,
|
||||||
|
ApplicationExceptionCode,
|
||||||
|
} from 'src/engine/core-modules/application/application.exception';
|
||||||
|
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||||
|
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-manifest/application-sync.service';
|
||||||
|
import { RunWorkspaceMigrationInput } from 'src/engine/core-modules/application/application-manifest/dtos/run-workspace-migration.input';
|
||||||
|
import { UninstallApplicationInput } from 'src/engine/core-modules/application/application-manifest/dtos/uninstall-application.input';
|
||||||
|
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
||||||
|
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||||
|
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
|
import {
|
||||||
|
FeatureFlagGuard,
|
||||||
|
RequireFeatureFlag,
|
||||||
|
} from 'src/engine/guards/feature-flag.guard';
|
||||||
|
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||||
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
|
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||||
|
import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor';
|
||||||
|
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/services/workspace-migration-runner.service';
|
||||||
|
|
||||||
|
@UsePipes(ResolverValidationPipe)
|
||||||
|
@MetadataResolver()
|
||||||
|
@UseInterceptors(WorkspaceMigrationGraphqlApiExceptionInterceptor)
|
||||||
|
@UseFilters(ApplicationExceptionFilter, AuthGraphqlApiExceptionFilter)
|
||||||
|
@UseGuards(WorkspaceAuthGuard, FeatureFlagGuard)
|
||||||
|
export class ApplicationManifestResolver {
|
||||||
|
constructor(
|
||||||
|
private readonly applicationService: ApplicationService,
|
||||||
|
private readonly applicationSyncService: ApplicationSyncService,
|
||||||
|
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
||||||
|
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Mutation(() => Boolean)
|
||||||
|
@UseGuards(SettingsPermissionGuard(PermissionFlagType.APPLICATIONS))
|
||||||
|
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
|
||||||
|
async runWorkspaceMigration(
|
||||||
|
@Args() { workspaceMigration: { actions } }: RunWorkspaceMigrationInput,
|
||||||
|
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||||
|
) {
|
||||||
|
const { featureFlagsMap } = await this.workspaceCacheService.getOrRecompute(
|
||||||
|
workspaceId,
|
||||||
|
['featureFlagsMap'],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
featureFlagsMap[
|
||||||
|
FeatureFlagKey.IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED
|
||||||
|
] !== true
|
||||||
|
) {
|
||||||
|
throw new ApplicationException(
|
||||||
|
'Application installation from tarball is not enabled',
|
||||||
|
ApplicationExceptionCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { workspaceCustomFlatApplication } =
|
||||||
|
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||||
|
{
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.workspaceMigrationRunnerService.run({
|
||||||
|
workspaceMigration: {
|
||||||
|
actions,
|
||||||
|
applicationUniversalIdentifier:
|
||||||
|
workspaceCustomFlatApplication.universalIdentifier,
|
||||||
|
},
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation(() => Boolean)
|
||||||
|
@UseGuards(SettingsPermissionGuard(PermissionFlagType.APPLICATIONS))
|
||||||
|
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
|
||||||
|
async uninstallApplication(
|
||||||
|
@Args() { universalIdentifier }: UninstallApplicationInput,
|
||||||
|
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||||
|
) {
|
||||||
|
await this.applicationSyncService.uninstallApplication({
|
||||||
|
applicationUniversalIdentifier: universalIdentifier,
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
+13
-24
@@ -11,12 +11,11 @@ import {
|
|||||||
ApplicationException,
|
ApplicationException,
|
||||||
ApplicationExceptionCode,
|
ApplicationExceptionCode,
|
||||||
} from 'src/engine/core-modules/application/application.exception';
|
} from 'src/engine/core-modules/application/application.exception';
|
||||||
import { ApplicationManifestMigrationService } from 'src/engine/core-modules/application/application-install/application-manifest-migration.service';
|
import { ApplicationManifestMigrationService } from 'src/engine/core-modules/application/application-manifest/application-manifest-migration.service';
|
||||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||||
import { buildFromToAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/utils/build-from-to-all-universal-flat-entity-maps.util';
|
import { buildFromToAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/application-manifest/utils/build-from-to-all-universal-flat-entity-maps.util';
|
||||||
import { getApplicationSubAllFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-application-sub-all-flat-entity-maps.util';
|
import { getApplicationSubAllFlatEntityMaps } from 'src/engine/core-modules/application/application-manifest/utils/get-application-sub-all-flat-entity-maps.util';
|
||||||
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application/utils/get-default-application-package-fields.util';
|
|
||||||
import { ApplicationVariableEntityService } from 'src/engine/core-modules/application/application-variable/application-variable.service';
|
import { ApplicationVariableEntityService } from 'src/engine/core-modules/application/application-variable/application-variable.service';
|
||||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||||
import { createEmptyAllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-all-flat-entity-maps.constant';
|
import { createEmptyAllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-all-flat-entity-maps.constant';
|
||||||
@@ -93,28 +92,18 @@ export class ApplicationSyncService {
|
|||||||
).toString('utf-8'),
|
).toString('utf-8'),
|
||||||
) as PackageJson;
|
) as PackageJson;
|
||||||
|
|
||||||
const defaultPackageFields = await getDefaultApplicationPackageFields();
|
const application = await this.applicationService.findByUniversalIdentifier(
|
||||||
|
{
|
||||||
let application = await this.applicationService.findByUniversalIdentifier({
|
universalIdentifier: manifest.application.universalIdentifier,
|
||||||
universalIdentifier: manifest.application.universalIdentifier,
|
workspaceId,
|
||||||
workspaceId,
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
if (!application) {
|
if (!application) {
|
||||||
application = await this.applicationService.create({
|
throw new ApplicationException(
|
||||||
universalIdentifier: manifest.application.universalIdentifier,
|
`Application "${manifest.application.universalIdentifier}" is not installed in workspace "${workspaceId}". Install it first.`,
|
||||||
name,
|
ApplicationExceptionCode.APP_NOT_INSTALLED,
|
||||||
description: manifest.application.description,
|
);
|
||||||
version: packageJson.version,
|
|
||||||
sourcePath: 'cli-sync',
|
|
||||||
defaultRoleId: null,
|
|
||||||
workspaceId,
|
|
||||||
packageJsonChecksum: defaultPackageFields.packageJsonChecksum,
|
|
||||||
packageJsonFileId: null,
|
|
||||||
yarnLockChecksum: defaultPackageFields.yarnLockChecksum,
|
|
||||||
yarnLockFileId: null,
|
|
||||||
availablePackages: defaultPackageFields.availablePackages,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.applicationVariableService.upsertManyApplicationVariableEntities(
|
await this.applicationVariableService.upsertManyApplicationVariableEntities(
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem } from 'src/engine/core-modules/application/utils/from-navigation-menu-item-manifest-to-universal-flat-navigation-menu-item.util';
|
import { fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem } from 'src/engine/core-modules/application/application-manifest/converters/from-navigation-menu-item-manifest-to-universal-flat-navigation-menu-item.util';
|
||||||
|
|
||||||
describe('fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem', () => {
|
describe('fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem', () => {
|
||||||
const now = '2026-01-01T00:00:00.000Z';
|
const now = '2026-01-01T00:00:00.000Z';
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||||
import { fromPageLayoutManifestToUniversalFlatPageLayout } from 'src/engine/core-modules/application/utils/from-page-layout-manifest-to-universal-flat-page-layout.util';
|
import { fromPageLayoutManifestToUniversalFlatPageLayout } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-manifest-to-universal-flat-page-layout.util';
|
||||||
|
|
||||||
describe('fromPageLayoutManifestToUniversalFlatPageLayout', () => {
|
describe('fromPageLayoutManifestToUniversalFlatPageLayout', () => {
|
||||||
const now = '2026-01-01T00:00:00.000Z';
|
const now = '2026-01-01T00:00:00.000Z';
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { PageLayoutTabLayoutMode } from 'twenty-shared/types';
|
import { PageLayoutTabLayoutMode } from 'twenty-shared/types';
|
||||||
|
|
||||||
import { fromPageLayoutTabManifestToUniversalFlatPageLayoutTab } from 'src/engine/core-modules/application/utils/from-page-layout-tab-manifest-to-universal-flat-page-layout-tab.util';
|
import { fromPageLayoutTabManifestToUniversalFlatPageLayoutTab } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-tab-manifest-to-universal-flat-page-layout-tab.util';
|
||||||
|
|
||||||
describe('fromPageLayoutTabManifestToUniversalFlatPageLayoutTab', () => {
|
describe('fromPageLayoutTabManifestToUniversalFlatPageLayoutTab', () => {
|
||||||
const now = '2026-01-01T00:00:00.000Z';
|
const now = '2026-01-01T00:00:00.000Z';
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||||
import { fromPageLayoutWidgetManifestToUniversalFlatPageLayoutWidget } from 'src/engine/core-modules/application/utils/from-page-layout-widget-manifest-to-universal-flat-page-layout-widget.util';
|
import { fromPageLayoutWidgetManifestToUniversalFlatPageLayoutWidget } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-widget-manifest-to-universal-flat-page-layout-widget.util';
|
||||||
|
|
||||||
describe('fromPageLayoutWidgetManifestToUniversalFlatPageLayoutWidget', () => {
|
describe('fromPageLayoutWidgetManifestToUniversalFlatPageLayoutWidget', () => {
|
||||||
const now = '2026-01-01T00:00:00.000Z';
|
const now = '2026-01-01T00:00:00.000Z';
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { AggregateOperations } from 'twenty-shared/types';
|
import { AggregateOperations } from 'twenty-shared/types';
|
||||||
|
|
||||||
import { fromViewFieldManifestToUniversalFlatViewField } from 'src/engine/core-modules/application/utils/from-view-field-manifest-to-universal-flat-view-field.util';
|
import { fromViewFieldManifestToUniversalFlatViewField } from 'src/engine/core-modules/application/application-manifest/converters/from-view-field-manifest-to-universal-flat-view-field.util';
|
||||||
|
|
||||||
describe('fromViewFieldManifestToUniversalFlatViewField', () => {
|
describe('fromViewFieldManifestToUniversalFlatViewField', () => {
|
||||||
const now = '2026-01-01T00:00:00.000Z';
|
const now = '2026-01-01T00:00:00.000Z';
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { ViewFilterOperand } from 'twenty-shared/types';
|
import { ViewFilterOperand } from 'twenty-shared/types';
|
||||||
|
|
||||||
import { fromViewFilterManifestToUniversalFlatViewFilter } from 'src/engine/core-modules/application/utils/from-view-filter-manifest-to-universal-flat-view-filter.util';
|
import { fromViewFilterManifestToUniversalFlatViewFilter } from 'src/engine/core-modules/application/application-manifest/converters/from-view-filter-manifest-to-universal-flat-view-filter.util';
|
||||||
|
|
||||||
describe('fromViewFilterManifestToUniversalFlatViewFilter', () => {
|
describe('fromViewFilterManifestToUniversalFlatViewFilter', () => {
|
||||||
const now = '2026-01-01T00:00:00.000Z';
|
const now = '2026-01-01T00:00:00.000Z';
|
||||||
+1
-1
@@ -4,7 +4,7 @@ import {
|
|||||||
ViewVisibility,
|
ViewVisibility,
|
||||||
} from 'twenty-shared/types';
|
} from 'twenty-shared/types';
|
||||||
|
|
||||||
import { fromViewManifestToUniversalFlatView } from 'src/engine/core-modules/application/utils/from-view-manifest-to-universal-flat-view.util';
|
import { fromViewManifestToUniversalFlatView } from 'src/engine/core-modules/application/application-manifest/converters/from-view-manifest-to-universal-flat-view.util';
|
||||||
|
|
||||||
describe('fromViewManifestToUniversalFlatView', () => {
|
describe('fromViewManifestToUniversalFlatView', () => {
|
||||||
const now = '2026-01-01T00:00:00.000Z';
|
const now = '2026-01-01T00:00:00.000Z';
|
||||||
+1
-1
@@ -46,7 +46,7 @@ export class WorkspaceMigrationInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ArgsType()
|
@ArgsType()
|
||||||
export class InstallApplicationInput {
|
export class RunWorkspaceMigrationInput {
|
||||||
@Field(() => WorkspaceMigrationInput)
|
@Field(() => WorkspaceMigrationInput)
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => WorkspaceMigrationInput)
|
@Type(() => WorkspaceMigrationInput)
|
||||||
+17
-17
@@ -1,23 +1,23 @@
|
|||||||
import { type Manifest } from 'twenty-shared/application';
|
import { type Manifest } from 'twenty-shared/application';
|
||||||
|
|
||||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||||
import { fromCommandMenuItemManifestToUniversalFlatCommandMenuItem } from 'src/engine/core-modules/application/utils/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util';
|
import { fromCommandMenuItemManifestToUniversalFlatCommandMenuItem } from 'src/engine/core-modules/application/application-manifest/converters/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util';
|
||||||
import { fromFieldManifestToUniversalFlatFieldMetadata } from 'src/engine/core-modules/application/utils/from-field-manifest-to-universal-flat-field-metadata.util';
|
import { fromFieldManifestToUniversalFlatFieldMetadata } from 'src/engine/core-modules/application/application-manifest/converters/from-field-manifest-to-universal-flat-field-metadata.util';
|
||||||
import { fromFrontComponentManifestToUniversalFlatFrontComponent } from 'src/engine/core-modules/application/utils/from-front-component-manifest-to-universal-flat-front-component.util';
|
import { fromFrontComponentManifestToUniversalFlatFrontComponent } from 'src/engine/core-modules/application/application-manifest/converters/from-front-component-manifest-to-universal-flat-front-component.util';
|
||||||
import { fromLogicFunctionManifestToUniversalFlatLogicFunction } from 'src/engine/core-modules/application/utils/from-logic-function-manifest-to-universal-flat-logic-function.util';
|
import { fromLogicFunctionManifestToUniversalFlatLogicFunction } from 'src/engine/core-modules/application/application-manifest/converters/from-logic-function-manifest-to-universal-flat-logic-function.util';
|
||||||
import { fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem } from 'src/engine/core-modules/application/utils/from-navigation-menu-item-manifest-to-universal-flat-navigation-menu-item.util';
|
import { fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem } from 'src/engine/core-modules/application/application-manifest/converters/from-navigation-menu-item-manifest-to-universal-flat-navigation-menu-item.util';
|
||||||
import { fromObjectManifestToUniversalFlatObjectMetadata } from 'src/engine/core-modules/application/utils/from-object-manifest-to-universal-flat-object-metadata.util';
|
import { fromObjectManifestToUniversalFlatObjectMetadata } from 'src/engine/core-modules/application/application-manifest/converters/from-object-manifest-to-universal-flat-object-metadata.util';
|
||||||
import { fromPageLayoutManifestToUniversalFlatPageLayout } from 'src/engine/core-modules/application/utils/from-page-layout-manifest-to-universal-flat-page-layout.util';
|
import { fromPageLayoutManifestToUniversalFlatPageLayout } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-manifest-to-universal-flat-page-layout.util';
|
||||||
import { fromPageLayoutTabManifestToUniversalFlatPageLayoutTab } from 'src/engine/core-modules/application/utils/from-page-layout-tab-manifest-to-universal-flat-page-layout-tab.util';
|
import { fromPageLayoutTabManifestToUniversalFlatPageLayoutTab } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-tab-manifest-to-universal-flat-page-layout-tab.util';
|
||||||
import { fromPageLayoutWidgetManifestToUniversalFlatPageLayoutWidget } from 'src/engine/core-modules/application/utils/from-page-layout-widget-manifest-to-universal-flat-page-layout-widget.util';
|
import { fromPageLayoutWidgetManifestToUniversalFlatPageLayoutWidget } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-widget-manifest-to-universal-flat-page-layout-widget.util';
|
||||||
import { fromRoleManifestToUniversalFlatRole } from 'src/engine/core-modules/application/utils/from-role-manifest-to-universal-flat-role.util';
|
import { fromRoleManifestToUniversalFlatRole } from 'src/engine/core-modules/application/application-manifest/converters/from-role-manifest-to-universal-flat-role.util';
|
||||||
import { fromSkillManifestToUniversalFlatSkill } from 'src/engine/core-modules/application/utils/from-skill-manifest-to-universal-flat-skill.util';
|
import { fromSkillManifestToUniversalFlatSkill } from 'src/engine/core-modules/application/application-manifest/converters/from-skill-manifest-to-universal-flat-skill.util';
|
||||||
import { fromViewFieldGroupManifestToUniversalFlatViewFieldGroup } from 'src/engine/core-modules/application/utils/from-view-field-group-manifest-to-universal-flat-view-field-group.util';
|
import { fromViewFieldGroupManifestToUniversalFlatViewFieldGroup } from 'src/engine/core-modules/application/application-manifest/converters/from-view-field-group-manifest-to-universal-flat-view-field-group.util';
|
||||||
import { fromViewFieldManifestToUniversalFlatViewField } from 'src/engine/core-modules/application/utils/from-view-field-manifest-to-universal-flat-view-field.util';
|
import { fromViewFieldManifestToUniversalFlatViewField } from 'src/engine/core-modules/application/application-manifest/converters/from-view-field-manifest-to-universal-flat-view-field.util';
|
||||||
import { fromViewFilterGroupManifestToUniversalFlatViewFilterGroup } from 'src/engine/core-modules/application/utils/from-view-filter-group-manifest-to-universal-flat-view-filter-group.util';
|
import { fromViewFilterGroupManifestToUniversalFlatViewFilterGroup } from 'src/engine/core-modules/application/application-manifest/converters/from-view-filter-group-manifest-to-universal-flat-view-filter-group.util';
|
||||||
import { fromViewFilterManifestToUniversalFlatViewFilter } from 'src/engine/core-modules/application/utils/from-view-filter-manifest-to-universal-flat-view-filter.util';
|
import { fromViewFilterManifestToUniversalFlatViewFilter } from 'src/engine/core-modules/application/application-manifest/converters/from-view-filter-manifest-to-universal-flat-view-filter.util';
|
||||||
import { fromViewGroupManifestToUniversalFlatViewGroup } from 'src/engine/core-modules/application/utils/from-view-group-manifest-to-universal-flat-view-group.util';
|
import { fromViewGroupManifestToUniversalFlatViewGroup } from 'src/engine/core-modules/application/application-manifest/converters/from-view-group-manifest-to-universal-flat-view-group.util';
|
||||||
import { fromViewManifestToUniversalFlatView } from 'src/engine/core-modules/application/utils/from-view-manifest-to-universal-flat-view.util';
|
import { fromViewManifestToUniversalFlatView } from 'src/engine/core-modules/application/application-manifest/converters/from-view-manifest-to-universal-flat-view.util';
|
||||||
import { createEmptyAllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-all-flat-entity-maps.constant';
|
import { createEmptyAllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-all-flat-entity-maps.constant';
|
||||||
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
|
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
|
||||||
import { addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/add-universal-flat-entity-to-universal-flat-entity-maps-through-mutation-or-throw.util';
|
import { addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/add-universal-flat-entity-to-universal-flat-entity-maps-through-mutation-or-throw.util';
|
||||||
+1
-1
@@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
|||||||
|
|
||||||
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
||||||
import { MARKETPLACE_CATALOG_SYNC_CRON_PATTERN } from 'src/engine/core-modules/application/application-marketplace/crons/constants/marketplace-catalog-sync-cron-pattern.constant';
|
import { MARKETPLACE_CATALOG_SYNC_CRON_PATTERN } from 'src/engine/core-modules/application/application-marketplace/crons/constants/marketplace-catalog-sync-cron-pattern.constant';
|
||||||
import { MarketplaceCatalogSyncService } from 'src/engine/core-modules/application/application-marketplace/services/marketplace-catalog-sync.service';
|
import { MarketplaceCatalogSyncService } from 'src/engine/core-modules/application/application-marketplace/marketplace-catalog-sync.service';
|
||||||
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
||||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||||
|
|||||||
+5
@@ -2,6 +2,7 @@ import { Field, Int, ObjectType } from '@nestjs/graphql';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
IsArray,
|
IsArray,
|
||||||
|
IsBoolean,
|
||||||
IsNotEmpty,
|
IsNotEmpty,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
@@ -285,4 +286,8 @@ export class MarketplaceAppDTO {
|
|||||||
@IsString()
|
@IsString()
|
||||||
@Field({ nullable: true })
|
@Field({ nullable: true })
|
||||||
sourcePackage?: string;
|
sourcePackage?: string;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@Field(() => Boolean)
|
||||||
|
isFeatured: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service';
|
||||||
|
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
|
||||||
|
import { MARKETPLACE_CATALOG_INDEX } from 'src/engine/core-modules/application/application-marketplace/constants/marketplace-catalog-index.constant';
|
||||||
|
import { MarketplaceService } from 'src/engine/core-modules/application/application-marketplace/marketplace.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MarketplaceCatalogSyncService {
|
||||||
|
private readonly logger = new Logger(MarketplaceCatalogSyncService.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly applicationRegistrationService: ApplicationRegistrationService,
|
||||||
|
private readonly marketplaceService: MarketplaceService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async syncCatalog(): Promise<void> {
|
||||||
|
await this.syncCuratedApps();
|
||||||
|
await this.syncNpmApps();
|
||||||
|
|
||||||
|
this.logger.log('Marketplace catalog sync completed');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async syncCuratedApps(): Promise<void> {
|
||||||
|
for (const entry of MARKETPLACE_CATALOG_INDEX) {
|
||||||
|
try {
|
||||||
|
await this.applicationRegistrationService.upsertFromCatalog({
|
||||||
|
universalIdentifier: entry.universalIdentifier,
|
||||||
|
name: entry.name,
|
||||||
|
description:
|
||||||
|
entry.richDisplayData.aboutDescription ?? entry.description,
|
||||||
|
author: entry.author,
|
||||||
|
sourceType: ApplicationRegistrationSourceType.NPM,
|
||||||
|
sourcePackage: entry.sourcePackage,
|
||||||
|
logoUrl: entry.logoUrl ?? null,
|
||||||
|
websiteUrl: entry.websiteUrl ?? null,
|
||||||
|
termsUrl: entry.termsUrl ?? null,
|
||||||
|
latestAvailableVersion: entry.richDisplayData.version ?? null,
|
||||||
|
isListed: true,
|
||||||
|
isFeatured: entry.isFeatured,
|
||||||
|
marketplaceDisplayData: entry.richDisplayData,
|
||||||
|
ownerWorkspaceId: null,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to sync curated app "${entry.name}": ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async syncNpmApps(): Promise<void> {
|
||||||
|
const npmApps = await this.marketplaceService.fetchAppsFromNpmRegistry();
|
||||||
|
|
||||||
|
const curatedIdentifiers = new Set(
|
||||||
|
MARKETPLACE_CATALOG_INDEX.map((entry) => entry.universalIdentifier),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const app of npmApps) {
|
||||||
|
if (curatedIdentifiers.has(app.id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.applicationRegistrationService.upsertFromCatalog({
|
||||||
|
universalIdentifier: app.id,
|
||||||
|
name: app.name,
|
||||||
|
description: app.description,
|
||||||
|
author: app.author,
|
||||||
|
sourceType: ApplicationRegistrationSourceType.NPM,
|
||||||
|
sourcePackage: app.sourcePackage ?? app.name,
|
||||||
|
logoUrl: null,
|
||||||
|
websiteUrl: app.websiteUrl ?? null,
|
||||||
|
termsUrl: null,
|
||||||
|
latestAvailableVersion: app.version ?? null,
|
||||||
|
isListed: true,
|
||||||
|
isFeatured: false,
|
||||||
|
marketplaceDisplayData: null,
|
||||||
|
ownerWorkspaceId: null,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(
|
||||||
|
`Failed to sync npm app "${app.name}": ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-61
@@ -1,17 +1,13 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
import { type ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
||||||
import {
|
import {
|
||||||
ApplicationRegistrationException,
|
ApplicationRegistrationException,
|
||||||
ApplicationRegistrationExceptionCode,
|
ApplicationRegistrationExceptionCode,
|
||||||
} from 'src/engine/core-modules/application/application-registration/application-registration.exception';
|
} from 'src/engine/core-modules/application/application-registration/application-registration.exception';
|
||||||
import { assertValidNpmPackageName } from 'src/engine/core-modules/application/utils/assert-valid-npm-package-name.util';
|
import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service';
|
||||||
import { AppRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/app-registration-source-type.enum';
|
|
||||||
import { MarketplaceCatalogSyncCronJob } from 'src/engine/core-modules/application/application-marketplace/crons/marketplace-catalog-sync.cron.job';
|
import { MarketplaceCatalogSyncCronJob } from 'src/engine/core-modules/application/application-marketplace/crons/marketplace-catalog-sync.cron.job';
|
||||||
import { MarketplaceAppDTO } from 'src/engine/core-modules/application/application-marketplace/dtos/marketplace-app.dto';
|
import { MarketplaceAppDTO } from 'src/engine/core-modules/application/application-marketplace/dtos/marketplace-app.dto';
|
||||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||||
@@ -28,8 +24,7 @@ export class MarketplaceQueryService {
|
|||||||
private hasSyncBeenEnqueued = false;
|
private hasSyncBeenEnqueued = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(ApplicationRegistrationEntity)
|
private readonly applicationRegistrationService: ApplicationRegistrationService,
|
||||||
private readonly appRegistrationRepository: Repository<ApplicationRegistrationEntity>,
|
|
||||||
@InjectMessageQueue(MessageQueue.cronQueue)
|
@InjectMessageQueue(MessageQueue.cronQueue)
|
||||||
private readonly messageQueueService: MessageQueueService,
|
private readonly messageQueueService: MessageQueueService,
|
||||||
) {}
|
) {}
|
||||||
@@ -39,9 +34,8 @@ export class MarketplaceQueryService {
|
|||||||
return this.cachedApps;
|
return this.cachedApps;
|
||||||
}
|
}
|
||||||
|
|
||||||
const registrations = await this.appRegistrationRepository.find({
|
const registrations =
|
||||||
where: { sourceType: AppRegistrationSourceType.NPM },
|
await this.applicationRegistrationService.findManyListed();
|
||||||
});
|
|
||||||
|
|
||||||
if (registrations.length === 0) {
|
if (registrations.length === 0) {
|
||||||
if (!this.hasSyncBeenEnqueued) {
|
if (!this.hasSyncBeenEnqueued) {
|
||||||
@@ -66,12 +60,22 @@ export class MarketplaceQueryService {
|
|||||||
return this.cachedApps;
|
return this.cachedApps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findOneMarketplaceApp(
|
||||||
|
universalIdentifier: string,
|
||||||
|
): Promise<MarketplaceAppDTO> {
|
||||||
|
const registration =
|
||||||
|
await this.findRegistrationByUniversalIdentifier(universalIdentifier);
|
||||||
|
|
||||||
|
return this.toMarketplaceAppDTO(registration);
|
||||||
|
}
|
||||||
|
|
||||||
async findRegistrationByUniversalIdentifier(
|
async findRegistrationByUniversalIdentifier(
|
||||||
universalIdentifier: string,
|
universalIdentifier: string,
|
||||||
): Promise<ApplicationRegistrationEntity> {
|
): Promise<ApplicationRegistrationEntity> {
|
||||||
const registration = await this.appRegistrationRepository.findOne({
|
const registration =
|
||||||
where: { universalIdentifier },
|
await this.applicationRegistrationService.findOneByUniversalIdentifier(
|
||||||
});
|
universalIdentifier,
|
||||||
|
);
|
||||||
|
|
||||||
if (!isDefined(registration)) {
|
if (!isDefined(registration)) {
|
||||||
throw new ApplicationRegistrationException(
|
throw new ApplicationRegistrationException(
|
||||||
@@ -83,53 +87,6 @@ export class MarketplaceQueryService {
|
|||||||
return registration;
|
return registration;
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOrCreateNpmRegistration(params: {
|
|
||||||
packageName: string;
|
|
||||||
ownerWorkspaceId: string;
|
|
||||||
}): Promise<ApplicationRegistrationEntity> {
|
|
||||||
assertValidNpmPackageName(params.packageName);
|
|
||||||
|
|
||||||
const existing = await this.appRegistrationRepository.findOne({
|
|
||||||
where: { sourcePackage: params.packageName },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isDefined(existing)) {
|
|
||||||
return existing;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.log(
|
|
||||||
`Creating new registration for npm package "${params.packageName}"`,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const registration = this.appRegistrationRepository.create({
|
|
||||||
universalIdentifier: v4(),
|
|
||||||
name: params.packageName,
|
|
||||||
sourceType: AppRegistrationSourceType.NPM,
|
|
||||||
sourcePackage: params.packageName,
|
|
||||||
oAuthClientId: v4(),
|
|
||||||
oAuthRedirectUris: [],
|
|
||||||
oAuthScopes: [],
|
|
||||||
ownerWorkspaceId: params.ownerWorkspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return await this.appRegistrationRepository.save(registration);
|
|
||||||
} catch {
|
|
||||||
const concurrentlyCreated = await this.appRegistrationRepository.findOne({
|
|
||||||
where: { sourcePackage: params.packageName },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isDefined(concurrentlyCreated)) {
|
|
||||||
return concurrentlyCreated;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ApplicationRegistrationException(
|
|
||||||
`Failed to create registration for package "${params.packageName}"`,
|
|
||||||
ApplicationRegistrationExceptionCode.APPLICATION_REGISTRATION_NOT_FOUND,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toMarketplaceAppDTO(
|
toMarketplaceAppDTO(
|
||||||
registration: ApplicationRegistrationEntity,
|
registration: ApplicationRegistrationEntity,
|
||||||
): MarketplaceAppDTO {
|
): MarketplaceAppDTO {
|
||||||
@@ -157,6 +114,7 @@ export class MarketplaceQueryService {
|
|||||||
frontComponents: displayData?.frontComponents ?? [],
|
frontComponents: displayData?.frontComponents ?? [],
|
||||||
sourcePackage: registration.sourcePackage ?? undefined,
|
sourcePackage: registration.sourcePackage ?? undefined,
|
||||||
defaultRole: displayData?.defaultRole,
|
defaultRole: displayData?.defaultRole,
|
||||||
|
isFeatured: registration.isFeatured,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+6
-7
@@ -1,21 +1,20 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
import { ApplicationRegistrationModule } from 'src/engine/core-modules/application/application-registration/application-registration.module';
|
||||||
import { ApplicationInstallModule } from 'src/engine/core-modules/application/application-install/application-install.module';
|
import { ApplicationInstallModule } from 'src/engine/core-modules/application/application-install/application-install.module';
|
||||||
import { MarketplaceCatalogSyncCronCommand } from 'src/engine/core-modules/application/application-marketplace/crons/commands/marketplace-catalog-sync.cron.command';
|
import { MarketplaceCatalogSyncCronCommand } from 'src/engine/core-modules/application/application-marketplace/crons/commands/marketplace-catalog-sync.cron.command';
|
||||||
import { MarketplaceCatalogSyncCronJob } from 'src/engine/core-modules/application/application-marketplace/crons/marketplace-catalog-sync.cron.job';
|
import { MarketplaceCatalogSyncCronJob } from 'src/engine/core-modules/application/application-marketplace/crons/marketplace-catalog-sync.cron.job';
|
||||||
import { MarketplaceCatalogSyncService } from 'src/engine/core-modules/application/application-marketplace/services/marketplace-catalog-sync.service';
|
import { MarketplaceCatalogSyncService } from 'src/engine/core-modules/application/application-marketplace/marketplace-catalog-sync.service';
|
||||||
import { MarketplaceQueryService } from 'src/engine/core-modules/application/application-marketplace/services/marketplace-query.service';
|
import { MarketplaceQueryService } from 'src/engine/core-modules/application/application-marketplace/marketplace-query.service';
|
||||||
import { MarketplaceResolver } from 'src/engine/core-modules/application/application-marketplace/resolvers/marketplace.resolver';
|
import { MarketplaceResolver } from 'src/engine/core-modules/application/application-marketplace/marketplace.resolver';
|
||||||
import { MarketplaceService } from 'src/engine/core-modules/application/application-marketplace/services/marketplace.service';
|
import { MarketplaceService } from 'src/engine/core-modules/application/application-marketplace/marketplace.service';
|
||||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
|
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
|
||||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([ApplicationRegistrationEntity]),
|
ApplicationRegistrationModule,
|
||||||
ApplicationInstallModule,
|
ApplicationInstallModule,
|
||||||
FeatureFlagModule,
|
FeatureFlagModule,
|
||||||
PermissionsModule,
|
PermissionsModule,
|
||||||
|
|||||||
+11
-52
@@ -6,15 +6,9 @@ import { FeatureFlagKey } from 'twenty-shared/types';
|
|||||||
|
|
||||||
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
||||||
import { ApplicationRegistrationExceptionFilter } from 'src/engine/core-modules/application/application-registration/application-registration-exception-filter';
|
import { ApplicationRegistrationExceptionFilter } from 'src/engine/core-modules/application/application-registration/application-registration-exception-filter';
|
||||||
import {
|
|
||||||
ApplicationRegistrationException,
|
|
||||||
ApplicationRegistrationExceptionCode,
|
|
||||||
} from 'src/engine/core-modules/application/application-registration/application-registration.exception';
|
|
||||||
import { AppRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/app-registration-source-type.enum';
|
|
||||||
import { ApplicationInstallService } from 'src/engine/core-modules/application/application-install/application-install.service';
|
import { ApplicationInstallService } from 'src/engine/core-modules/application/application-install/application-install.service';
|
||||||
import { AppUpgradeService } from 'src/engine/core-modules/application/application-install/app-upgrade.service';
|
|
||||||
import { MarketplaceAppDTO } from 'src/engine/core-modules/application/application-marketplace/dtos/marketplace-app.dto';
|
import { MarketplaceAppDTO } from 'src/engine/core-modules/application/application-marketplace/dtos/marketplace-app.dto';
|
||||||
import { MarketplaceQueryService } from 'src/engine/core-modules/application/application-marketplace/services/marketplace-query.service';
|
import { MarketplaceQueryService } from 'src/engine/core-modules/application/application-marketplace/marketplace-query.service';
|
||||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
import {
|
import {
|
||||||
@@ -38,7 +32,6 @@ export class MarketplaceResolver {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly marketplaceQueryService: MarketplaceQueryService,
|
private readonly marketplaceQueryService: MarketplaceQueryService,
|
||||||
private readonly applicationInstallService: ApplicationInstallService,
|
private readonly applicationInstallService: ApplicationInstallService,
|
||||||
private readonly appUpgradeService: AppUpgradeService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Query(() => [MarketplaceAppDTO])
|
@Query(() => [MarketplaceAppDTO])
|
||||||
@@ -47,6 +40,16 @@ export class MarketplaceResolver {
|
|||||||
return this.marketplaceQueryService.findManyMarketplaceApps();
|
return this.marketplaceQueryService.findManyMarketplaceApps();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Query(() => MarketplaceAppDTO)
|
||||||
|
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
|
||||||
|
async findOneMarketplaceApp(
|
||||||
|
@Args('universalIdentifier') universalIdentifier: string,
|
||||||
|
): Promise<MarketplaceAppDTO> {
|
||||||
|
return this.marketplaceQueryService.findOneMarketplaceApp(
|
||||||
|
universalIdentifier,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Mutation(() => Boolean)
|
@Mutation(() => Boolean)
|
||||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.MARKETPLACE_APPS))
|
@UseGuards(SettingsPermissionGuard(PermissionFlagType.MARKETPLACE_APPS))
|
||||||
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
|
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
|
||||||
@@ -61,54 +64,10 @@ export class MarketplaceResolver {
|
|||||||
universalIdentifier,
|
universalIdentifier,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (registration.sourceType !== AppRegistrationSourceType.NPM) {
|
|
||||||
throw new ApplicationRegistrationException(
|
|
||||||
`Only NPM apps can be installed via the marketplace`,
|
|
||||||
ApplicationRegistrationExceptionCode.SOURCE_CHANNEL_MISMATCH,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.applicationInstallService.installApplication({
|
return this.applicationInstallService.installApplication({
|
||||||
appRegistrationId: registration.id,
|
appRegistrationId: registration.id,
|
||||||
version,
|
version,
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => Boolean)
|
|
||||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.MARKETPLACE_APPS))
|
|
||||||
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
|
|
||||||
async installNpmApp(
|
|
||||||
@Args('packageName') packageName: string,
|
|
||||||
@Args('version', { type: () => String, nullable: true })
|
|
||||||
version: string | undefined,
|
|
||||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
|
||||||
): Promise<boolean> {
|
|
||||||
const registration =
|
|
||||||
await this.marketplaceQueryService.findOrCreateNpmRegistration({
|
|
||||||
packageName,
|
|
||||||
ownerWorkspaceId: workspace.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.applicationInstallService.installApplication({
|
|
||||||
appRegistrationId: registration.id,
|
|
||||||
version,
|
|
||||||
workspaceId: workspace.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Mutation(() => Boolean)
|
|
||||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.MARKETPLACE_APPS))
|
|
||||||
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
|
|
||||||
async upgradeApplication(
|
|
||||||
@Args('appRegistrationId') appRegistrationId: string,
|
|
||||||
@Args('targetVersion') targetVersion: string,
|
|
||||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
|
||||||
): Promise<boolean> {
|
|
||||||
return this.appUpgradeService.upgradeApplication({
|
|
||||||
appRegistrationId,
|
|
||||||
targetVersion,
|
|
||||||
workspaceId: workspace.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+1
@@ -80,6 +80,7 @@ export class MarketplaceService {
|
|||||||
logicFunctions: [],
|
logicFunctions: [],
|
||||||
frontComponents: [],
|
frontComponents: [],
|
||||||
sourcePackage: name,
|
sourcePackage: name,
|
||||||
|
isFeatured: false,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter(isDefined);
|
.filter(isDefined);
|
||||||
-171
@@ -1,171 +0,0 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
|
||||||
import { AppRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/app-registration-source-type.enum';
|
|
||||||
import { MARKETPLACE_CATALOG_INDEX } from 'src/engine/core-modules/application/application-marketplace/constants/marketplace-catalog-index.constant';
|
|
||||||
import { MarketplaceService } from 'src/engine/core-modules/application/application-marketplace/services/marketplace.service';
|
|
||||||
import { getAdminWorkspaceId } from 'src/engine/core-modules/application/application-marketplace/utils/get-admin-workspace-id.util';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class MarketplaceCatalogSyncService {
|
|
||||||
private readonly logger = new Logger(MarketplaceCatalogSyncService.name);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(ApplicationRegistrationEntity)
|
|
||||||
private readonly appRegistrationRepository: Repository<ApplicationRegistrationEntity>,
|
|
||||||
private readonly marketplaceService: MarketplaceService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async syncCatalog(): Promise<void> {
|
|
||||||
const dataSource = this.appRegistrationRepository.manager.connection;
|
|
||||||
const adminWorkspaceId = await getAdminWorkspaceId(dataSource);
|
|
||||||
|
|
||||||
if (!isDefined(adminWorkspaceId)) {
|
|
||||||
this.logger.warn(
|
|
||||||
'No admin workspace found. Skipping marketplace catalog sync.',
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.syncCuratedApps(adminWorkspaceId);
|
|
||||||
await this.syncNpmApps(adminWorkspaceId);
|
|
||||||
|
|
||||||
this.logger.log('Marketplace catalog sync completed');
|
|
||||||
}
|
|
||||||
|
|
||||||
private async syncCuratedApps(ownerWorkspaceId: string): Promise<void> {
|
|
||||||
for (const entry of MARKETPLACE_CATALOG_INDEX) {
|
|
||||||
try {
|
|
||||||
await this.upsertRegistration({
|
|
||||||
universalIdentifier: entry.universalIdentifier,
|
|
||||||
name: entry.name,
|
|
||||||
description:
|
|
||||||
entry.richDisplayData.aboutDescription ?? entry.description,
|
|
||||||
author: entry.author,
|
|
||||||
sourceType: AppRegistrationSourceType.NPM,
|
|
||||||
sourcePackage: entry.sourcePackage,
|
|
||||||
logoUrl: entry.logoUrl ?? null,
|
|
||||||
websiteUrl: entry.websiteUrl ?? null,
|
|
||||||
termsUrl: entry.termsUrl ?? null,
|
|
||||||
latestAvailableVersion: entry.richDisplayData.version ?? null,
|
|
||||||
isFeatured: entry.isFeatured,
|
|
||||||
marketplaceDisplayData: entry.richDisplayData,
|
|
||||||
ownerWorkspaceId,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(
|
|
||||||
`Failed to sync curated app "${entry.name}": ${error instanceof Error ? error.message : String(error)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async syncNpmApps(ownerWorkspaceId: string): Promise<void> {
|
|
||||||
const npmApps = await this.marketplaceService.fetchAppsFromNpmRegistry();
|
|
||||||
|
|
||||||
const curatedIdentifiers = new Set(
|
|
||||||
MARKETPLACE_CATALOG_INDEX.map((entry) => entry.universalIdentifier),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const app of npmApps) {
|
|
||||||
if (curatedIdentifiers.has(app.id)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.upsertRegistration({
|
|
||||||
universalIdentifier: app.id,
|
|
||||||
name: app.name,
|
|
||||||
description: app.description,
|
|
||||||
author: app.author,
|
|
||||||
sourceType: AppRegistrationSourceType.NPM,
|
|
||||||
sourcePackage: app.sourcePackage ?? app.name,
|
|
||||||
logoUrl: null,
|
|
||||||
websiteUrl: app.websiteUrl ?? null,
|
|
||||||
termsUrl: null,
|
|
||||||
latestAvailableVersion: app.version ?? null,
|
|
||||||
isFeatured: false,
|
|
||||||
marketplaceDisplayData: null,
|
|
||||||
ownerWorkspaceId,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(
|
|
||||||
`Failed to sync npm app "${app.name}": ${error instanceof Error ? error.message : String(error)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup by universalIdentifier only (matches the unique constraint).
|
|
||||||
// ownerWorkspaceId is only set on insert.
|
|
||||||
private async upsertRegistration(
|
|
||||||
params: Pick<
|
|
||||||
ApplicationRegistrationEntity,
|
|
||||||
| 'universalIdentifier'
|
|
||||||
| 'name'
|
|
||||||
| 'description'
|
|
||||||
| 'author'
|
|
||||||
| 'sourceType'
|
|
||||||
| 'sourcePackage'
|
|
||||||
| 'logoUrl'
|
|
||||||
| 'websiteUrl'
|
|
||||||
| 'termsUrl'
|
|
||||||
| 'latestAvailableVersion'
|
|
||||||
| 'isFeatured'
|
|
||||||
| 'marketplaceDisplayData'
|
|
||||||
| 'ownerWorkspaceId'
|
|
||||||
>,
|
|
||||||
): Promise<void> {
|
|
||||||
const existing = await this.appRegistrationRepository.findOne({
|
|
||||||
where: {
|
|
||||||
universalIdentifier: params.universalIdentifier,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isDefined(existing)) {
|
|
||||||
await this.appRegistrationRepository.save({
|
|
||||||
...existing,
|
|
||||||
name: params.name,
|
|
||||||
description: params.description,
|
|
||||||
author: params.author,
|
|
||||||
sourceType: params.sourceType,
|
|
||||||
sourcePackage: params.sourcePackage,
|
|
||||||
logoUrl: params.logoUrl,
|
|
||||||
websiteUrl: params.websiteUrl,
|
|
||||||
termsUrl: params.termsUrl,
|
|
||||||
latestAvailableVersion: params.latestAvailableVersion,
|
|
||||||
isFeatured: params.isFeatured,
|
|
||||||
marketplaceDisplayData: params.marketplaceDisplayData,
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const registration = this.appRegistrationRepository.create({
|
|
||||||
universalIdentifier: params.universalIdentifier,
|
|
||||||
name: params.name,
|
|
||||||
description: params.description,
|
|
||||||
author: params.author,
|
|
||||||
sourceType: params.sourceType,
|
|
||||||
sourcePackage: params.sourcePackage,
|
|
||||||
logoUrl: params.logoUrl,
|
|
||||||
websiteUrl: params.websiteUrl,
|
|
||||||
termsUrl: params.termsUrl,
|
|
||||||
latestAvailableVersion: params.latestAvailableVersion,
|
|
||||||
isFeatured: params.isFeatured,
|
|
||||||
marketplaceDisplayData: params.marketplaceDisplayData,
|
|
||||||
oAuthClientId: v4(),
|
|
||||||
oAuthRedirectUris: [],
|
|
||||||
oAuthScopes: [],
|
|
||||||
ownerWorkspaceId: params.ownerWorkspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.appRegistrationRepository.save(registration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-36
@@ -1,36 +0,0 @@
|
|||||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
|
||||||
import { type DataSource } from 'typeorm';
|
|
||||||
|
|
||||||
// Every ApplicationRegistration must be owned by a workspace (ownerWorkspaceId
|
|
||||||
// represents ownership / write-access, not visibility scoping — marketplace
|
|
||||||
// registrations are readable by all workspaces). When the catalog sync creates
|
|
||||||
// registrations for marketplace apps that no developer has explicitly claimed,
|
|
||||||
// we assign them to the "admin" workspace: the oldest active workspace whose
|
|
||||||
// owner has admin privileges.
|
|
||||||
//
|
|
||||||
// TODO: This heuristic is fragile — on fresh instances with no admin users the
|
|
||||||
// catalog sync is silently skipped, and if the admin workspace is later deleted
|
|
||||||
// all marketplace registrations become orphaned. Consider introducing a
|
|
||||||
// dedicated "platform" workspace or making ownerWorkspaceId nullable instead.
|
|
||||||
export const getAdminWorkspaceId = async (
|
|
||||||
dataSource: DataSource,
|
|
||||||
): Promise<string | null> => {
|
|
||||||
const result = await dataSource.query<Array<{ workspaceId: string }>>(
|
|
||||||
`SELECT uw."workspaceId"
|
|
||||||
FROM core."userWorkspace" uw
|
|
||||||
JOIN core."user" u ON u.id = uw."userId" AND u."deletedAt" IS NULL
|
|
||||||
JOIN core."workspace" w ON w.id = uw."workspaceId" AND w."deletedAt" IS NULL
|
|
||||||
WHERE (u."canAccessFullAdminPanel" = true OR u."canImpersonate" = true)
|
|
||||||
AND w."activationStatus" = $1
|
|
||||||
AND uw."deletedAt" IS NULL
|
|
||||||
ORDER BY w."createdAt" ASC
|
|
||||||
LIMIT 1`,
|
|
||||||
[WorkspaceActivationStatus.ACTIVE],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result[0].workspaceId;
|
|
||||||
};
|
|
||||||
+1
-1
@@ -2,7 +2,7 @@ import {
|
|||||||
ALL_OAUTH_SCOPES,
|
ALL_OAUTH_SCOPES,
|
||||||
OAUTH_SCOPE_DESCRIPTIONS,
|
OAUTH_SCOPE_DESCRIPTIONS,
|
||||||
OAUTH_SCOPES,
|
OAUTH_SCOPES,
|
||||||
} from 'src/engine/core-modules/application/application-registration/constants/oauth-scopes';
|
} from 'src/engine/core-modules/application/application-oauth/constants/oauth-scopes';
|
||||||
|
|
||||||
describe('OAuth Scopes', () => {
|
describe('OAuth Scopes', () => {
|
||||||
it('should have all scopes defined', () => {
|
it('should have all scopes defined', () => {
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||||
|
import { ApplicationInstallModule } from 'src/engine/core-modules/application/application-install/application-install.module';
|
||||||
|
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||||
|
import { ApplicationModule as ApplicationCoreModule } from 'src/engine/core-modules/application/application.module';
|
||||||
|
import { ApplicationOAuthResolver } from 'src/engine/core-modules/application/application-oauth/application-oauth.resolver';
|
||||||
|
import { OAuthDiscoveryController } from 'src/engine/core-modules/application/application-oauth/controllers/oauth-discovery.controller';
|
||||||
|
import { OAuthTokenController } from 'src/engine/core-modules/application/application-oauth/controllers/oauth-token.controller';
|
||||||
|
import { OAuthService } from 'src/engine/core-modules/application/application-oauth/oauth.service';
|
||||||
|
import { ApplicationRegistrationModule } from 'src/engine/core-modules/application/application-registration/application-registration.module';
|
||||||
|
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
||||||
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
|
import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module';
|
||||||
|
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
|
||||||
|
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||||
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
|
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([
|
||||||
|
AppTokenEntity,
|
||||||
|
ApplicationEntity,
|
||||||
|
UserWorkspaceEntity,
|
||||||
|
]),
|
||||||
|
ApplicationRegistrationModule,
|
||||||
|
ApplicationCoreModule,
|
||||||
|
ApplicationInstallModule,
|
||||||
|
TokenModule,
|
||||||
|
FeatureFlagModule,
|
||||||
|
PermissionsModule,
|
||||||
|
ThrottlerModule,
|
||||||
|
TwentyConfigModule,
|
||||||
|
WorkspaceCacheStorageModule,
|
||||||
|
],
|
||||||
|
controllers: [OAuthTokenController, OAuthDiscoveryController],
|
||||||
|
providers: [OAuthService, ApplicationOAuthResolver],
|
||||||
|
exports: [OAuthService],
|
||||||
|
})
|
||||||
|
export class ApplicationOAuthModule {}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
|
||||||
|
import { Args, Mutation } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
import { FeatureFlagKey } from 'twenty-shared/types';
|
||||||
|
|
||||||
|
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
|
||||||
|
import {
|
||||||
|
ApplicationException,
|
||||||
|
ApplicationExceptionCode,
|
||||||
|
} from 'src/engine/core-modules/application/application.exception';
|
||||||
|
import { ApplicationTokenPairDTO } from 'src/engine/core-modules/application/application-oauth/dtos/application-token-pair.dto';
|
||||||
|
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
||||||
|
import { ApplicationTokenService } from 'src/engine/core-modules/auth/token/services/application-token.service';
|
||||||
|
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||||
|
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
|
import {
|
||||||
|
FeatureFlagGuard,
|
||||||
|
RequireFeatureFlag,
|
||||||
|
} from 'src/engine/guards/feature-flag.guard';
|
||||||
|
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||||
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
|
|
||||||
|
@UsePipes(ResolverValidationPipe)
|
||||||
|
@MetadataResolver()
|
||||||
|
@UseFilters(AuthGraphqlApiExceptionFilter)
|
||||||
|
@UseGuards(WorkspaceAuthGuard, FeatureFlagGuard)
|
||||||
|
export class ApplicationOAuthResolver {
|
||||||
|
constructor(
|
||||||
|
private readonly applicationTokenService: ApplicationTokenService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Mutation(() => ApplicationTokenPairDTO)
|
||||||
|
@UseGuards(NoPermissionGuard)
|
||||||
|
@RequireFeatureFlag(FeatureFlagKey.IS_APPLICATION_ENABLED)
|
||||||
|
async renewApplicationToken(
|
||||||
|
@Args('applicationRefreshToken') applicationRefreshToken: string,
|
||||||
|
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||||
|
): Promise<ApplicationTokenPairDTO> {
|
||||||
|
const applicationRefreshTokenPayload =
|
||||||
|
this.applicationTokenService.validateApplicationRefreshToken(
|
||||||
|
applicationRefreshToken,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (applicationRefreshTokenPayload.workspaceId !== workspaceId) {
|
||||||
|
throw new ApplicationException(
|
||||||
|
'Refresh token workspace does not match authenticated workspace',
|
||||||
|
ApplicationExceptionCode.FORBIDDEN,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.applicationTokenService.renewApplicationTokens(
|
||||||
|
applicationRefreshTokenPayload,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user