Api keys and webhook migration to core (#13011)
TODO: check Zapier trigger records work as expected --------- Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
@ -87,6 +87,18 @@ export type ApiConfig = {
|
||||
mutationMaximumAffectedRecords: Scalars['Float'];
|
||||
};
|
||||
|
||||
export type ApiKey = {
|
||||
__typename?: 'ApiKey';
|
||||
createdAt: Scalars['DateTime'];
|
||||
expiresAt: Scalars['DateTime'];
|
||||
id: Scalars['UUID'];
|
||||
name: Scalars['String'];
|
||||
revokedAt?: Maybe<Scalars['DateTime']>;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
workspace: Workspace;
|
||||
workspaceId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ApiKeyToken = {
|
||||
__typename?: 'ApiKeyToken';
|
||||
token: Scalars['String'];
|
||||
@ -435,6 +447,12 @@ export type ConnectionParametersOutput = {
|
||||
username: Scalars['String'];
|
||||
};
|
||||
|
||||
export type CreateApiKeyDto = {
|
||||
expiresAt: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
revokedAt?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type CreateAppTokenInput = {
|
||||
expiresAt: Scalars['DateTime'];
|
||||
};
|
||||
@ -527,6 +545,13 @@ export type CreateServerlessFunctionInput = {
|
||||
timeoutSeconds?: InputMaybe<Scalars['Float']>;
|
||||
};
|
||||
|
||||
export type CreateWebhookDto = {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
operations: Array<Scalars['String']>;
|
||||
secret?: InputMaybe<Scalars['String']>;
|
||||
targetUrl: Scalars['String'];
|
||||
};
|
||||
|
||||
export type CreateWorkflowVersionStepInput = {
|
||||
/** Next step ID */
|
||||
nextStepId?: InputMaybe<Scalars['String']>;
|
||||
@ -608,6 +633,10 @@ export type DeleteSsoOutput = {
|
||||
identityProviderId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type DeleteWebhookDto = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type DeleteWorkflowVersionStepInput = {
|
||||
/** Step to delete ID */
|
||||
stepId: Scalars['String'];
|
||||
@ -683,9 +712,11 @@ export enum FeatureFlagKey {
|
||||
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
|
||||
IS_MORPH_RELATION_ENABLED = 'IS_MORPH_RELATION_ENABLED',
|
||||
IS_POSTGRESQL_INTEGRATION_ENABLED = 'IS_POSTGRESQL_INTEGRATION_ENABLED',
|
||||
IS_RELATION_CONNECT_ENABLED = 'IS_RELATION_CONNECT_ENABLED',
|
||||
IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED',
|
||||
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED',
|
||||
IS_WORKFLOW_FILTERING_ENABLED = 'IS_WORKFLOW_FILTERING_ENABLED'
|
||||
IS_WORKFLOW_FILTERING_ENABLED = 'IS_WORKFLOW_FILTERING_ENABLED',
|
||||
IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED = 'IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED'
|
||||
}
|
||||
|
||||
export type Field = {
|
||||
@ -819,6 +850,10 @@ export type FullName = {
|
||||
lastName: Scalars['String'];
|
||||
};
|
||||
|
||||
export type GetApiKeyDto = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type GetAuthorizationUrlForSsoInput = {
|
||||
identityProviderId: Scalars['String'];
|
||||
workspaceInviteHash?: InputMaybe<Scalars['String']>;
|
||||
@ -844,6 +879,10 @@ export type GetServerlessFunctionSourceCodeInput = {
|
||||
version?: Scalars['String'];
|
||||
};
|
||||
|
||||
export type GetWebhookDto = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export enum HealthIndicatorId {
|
||||
app = 'app',
|
||||
connectedAccount = 'connectedAccount',
|
||||
@ -1015,6 +1054,7 @@ export type Mutation = {
|
||||
checkCustomDomainValidRecords?: Maybe<CustomDomainValidRecords>;
|
||||
checkoutSession: BillingSessionOutput;
|
||||
computeStepOutputSchema: Scalars['JSON'];
|
||||
createApiKey: ApiKey;
|
||||
createApprovedAccessDomain: ApprovedAccessDomain;
|
||||
createDatabaseConfigVariable: Scalars['Boolean'];
|
||||
createDraftFromWorkflowVersion: WorkflowVersion;
|
||||
@ -1027,6 +1067,7 @@ export type Mutation = {
|
||||
createOneRole: Role;
|
||||
createOneServerlessFunction: ServerlessFunction;
|
||||
createSAMLIdentityProvider: SetupSsoOutput;
|
||||
createWebhook: Webhook;
|
||||
createWorkflowVersionStep: WorkflowAction;
|
||||
deactivateWorkflowVersion: Scalars['Boolean'];
|
||||
deleteApprovedAccessDomain: Scalars['Boolean'];
|
||||
@ -1039,6 +1080,7 @@ export type Mutation = {
|
||||
deleteOneServerlessFunction: ServerlessFunction;
|
||||
deleteSSOIdentityProvider: DeleteSsoOutput;
|
||||
deleteUser: User;
|
||||
deleteWebhook: Scalars['Boolean'];
|
||||
deleteWorkflowVersionStep: WorkflowAction;
|
||||
deleteWorkspaceInvitation: Scalars['String'];
|
||||
disablePostgresProxy: PostgresCredentials;
|
||||
@ -1059,6 +1101,7 @@ export type Mutation = {
|
||||
renewToken: AuthTokens;
|
||||
resendEmailVerificationToken: ResendEmailVerificationTokenOutput;
|
||||
resendWorkspaceInvitation: SendInvitationsOutput;
|
||||
revokeApiKey?: Maybe<ApiKey>;
|
||||
runWorkflowVersion: WorkflowRun;
|
||||
saveImapSmtpCaldav: ImapSmtpCaldavConnectionSuccess;
|
||||
sendInvitations: SendInvitationsOutput;
|
||||
@ -1075,6 +1118,7 @@ export type Mutation = {
|
||||
syncRemoteTableSchemaChanges: RemoteTable;
|
||||
trackAnalytics: Analytics;
|
||||
unsyncRemoteTable: RemoteTable;
|
||||
updateApiKey?: Maybe<ApiKey>;
|
||||
updateDatabaseConfigVariable: Scalars['Boolean'];
|
||||
updateLabPublicFeatureFlag: FeatureFlagDto;
|
||||
updateOneAgent: Agent;
|
||||
@ -1084,6 +1128,7 @@ export type Mutation = {
|
||||
updateOneRole: Role;
|
||||
updateOneServerlessFunction: ServerlessFunction;
|
||||
updatePasswordViaResetToken: InvalidatePassword;
|
||||
updateWebhook?: Maybe<Webhook>;
|
||||
updateWorkflowRunStep: WorkflowAction;
|
||||
updateWorkflowVersionStep: WorkflowAction;
|
||||
updateWorkspace: Workspace;
|
||||
@ -1137,6 +1182,11 @@ export type MutationComputeStepOutputSchemaArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateApiKeyArgs = {
|
||||
input: CreateApiKeyDto;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateApprovedAccessDomainArgs = {
|
||||
input: CreateApprovedAccessDomainInput;
|
||||
};
|
||||
@ -1201,6 +1251,11 @@ export type MutationCreateSamlIdentityProviderArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateWebhookArgs = {
|
||||
input: CreateWebhookDto;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateWorkflowVersionStepArgs = {
|
||||
input: CreateWorkflowVersionStepInput;
|
||||
};
|
||||
@ -1251,6 +1306,11 @@ export type MutationDeleteSsoIdentityProviderArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteWebhookArgs = {
|
||||
input: DeleteWebhookDto;
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteWorkflowVersionStepArgs = {
|
||||
input: DeleteWorkflowVersionStepInput;
|
||||
};
|
||||
@ -1342,6 +1402,11 @@ export type MutationResendWorkspaceInvitationArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationRevokeApiKeyArgs = {
|
||||
input: RevokeApiKeyDto;
|
||||
};
|
||||
|
||||
|
||||
export type MutationRunWorkflowVersionArgs = {
|
||||
input: RunWorkflowVersionInput;
|
||||
};
|
||||
@ -1415,6 +1480,11 @@ export type MutationUnsyncRemoteTableArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateApiKeyArgs = {
|
||||
input: UpdateApiKeyDto;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateDatabaseConfigVariableArgs = {
|
||||
key: Scalars['String'];
|
||||
value: Scalars['JSON'];
|
||||
@ -1462,6 +1532,11 @@ export type MutationUpdatePasswordViaResetTokenArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateWebhookArgs = {
|
||||
input: UpdateWebhookDto;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateWorkflowRunStepArgs = {
|
||||
input: UpdateWorkflowRunStepInput;
|
||||
};
|
||||
@ -1744,6 +1819,8 @@ export type PublishServerlessFunctionInput = {
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
apiKey?: Maybe<ApiKey>;
|
||||
apiKeys: Array<ApiKey>;
|
||||
billingPortalSession: BillingSessionOutput;
|
||||
checkUserExists: CheckUserExistOutput;
|
||||
checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValid;
|
||||
@ -1785,6 +1862,13 @@ export type Query = {
|
||||
search: SearchResultConnection;
|
||||
validatePasswordResetToken: ValidatePasswordResetToken;
|
||||
versionInfo: VersionInfo;
|
||||
webhook?: Maybe<Webhook>;
|
||||
webhooks: Array<Webhook>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryApiKeyArgs = {
|
||||
input: GetApiKeyDto;
|
||||
};
|
||||
|
||||
|
||||
@ -1945,6 +2029,11 @@ export type QueryValidatePasswordResetTokenArgs = {
|
||||
passwordResetToken: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryWebhookArgs = {
|
||||
input: GetWebhookDto;
|
||||
};
|
||||
|
||||
export type QueueMetricsData = {
|
||||
__typename?: 'QueueMetricsData';
|
||||
data: Array<QueueMetricsSeries>;
|
||||
@ -2036,6 +2125,10 @@ export type ResendEmailVerificationTokenOutput = {
|
||||
success: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export type RevokeApiKeyDto = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Role = {
|
||||
__typename?: 'Role';
|
||||
canDestroyAllObjectRecords: Scalars['Boolean'];
|
||||
@ -2398,6 +2491,13 @@ export type UpdateAgentInput = {
|
||||
responseFormat?: InputMaybe<Scalars['JSON']>;
|
||||
};
|
||||
|
||||
export type UpdateApiKeyDto = {
|
||||
expiresAt?: InputMaybe<Scalars['String']>;
|
||||
id: Scalars['String'];
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
revokedAt?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type UpdateFieldInput = {
|
||||
defaultValue?: InputMaybe<Scalars['JSON']>;
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
@ -2479,6 +2579,14 @@ export type UpdateServerlessFunctionInput = {
|
||||
timeoutSeconds?: InputMaybe<Scalars['Float']>;
|
||||
};
|
||||
|
||||
export type UpdateWebhookDto = {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
id: Scalars['String'];
|
||||
operations?: InputMaybe<Array<Scalars['String']>>;
|
||||
secret?: InputMaybe<Scalars['String']>;
|
||||
targetUrl?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type UpdateWorkflowRunStepInput = {
|
||||
/** Step to update in JSON format */
|
||||
step: Scalars['JSON'];
|
||||
@ -2621,6 +2729,20 @@ export type VersionInfo = {
|
||||
latestVersion: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Webhook = {
|
||||
__typename?: 'Webhook';
|
||||
createdAt: Scalars['DateTime'];
|
||||
deletedAt?: Maybe<Scalars['DateTime']>;
|
||||
description?: Maybe<Scalars['String']>;
|
||||
id: Scalars['UUID'];
|
||||
operations: Array<Scalars['String']>;
|
||||
secret: Scalars['String'];
|
||||
targetUrl: Scalars['String'];
|
||||
updatedAt: Scalars['DateTime'];
|
||||
workspace: Workspace;
|
||||
workspaceId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type WorkerQueueMetrics = {
|
||||
__typename?: 'WorkerQueueMetrics';
|
||||
active: Scalars['Float'];
|
||||
@ -3224,6 +3346,76 @@ 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 ApiKeyFragmentFragment = { __typename?: 'ApiKey', id: any, name: string, expiresAt: string, revokedAt?: string | null };
|
||||
|
||||
export type WebhookFragmentFragment = { __typename?: 'Webhook', id: any, targetUrl: string, operations: Array<string>, description?: string | null, secret: string };
|
||||
|
||||
export type CreateApiKeyMutationVariables = Exact<{
|
||||
input: CreateApiKeyDto;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateApiKeyMutation = { __typename?: 'Mutation', createApiKey: { __typename?: 'ApiKey', id: any, name: string, expiresAt: string, revokedAt?: string | null } };
|
||||
|
||||
export type CreateWebhookMutationVariables = Exact<{
|
||||
input: CreateWebhookDto;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateWebhookMutation = { __typename?: 'Mutation', createWebhook: { __typename?: 'Webhook', id: any, targetUrl: string, operations: Array<string>, description?: string | null, secret: string } };
|
||||
|
||||
export type DeleteWebhookMutationVariables = Exact<{
|
||||
input: DeleteWebhookDto;
|
||||
}>;
|
||||
|
||||
|
||||
export type DeleteWebhookMutation = { __typename?: 'Mutation', deleteWebhook: boolean };
|
||||
|
||||
export type RevokeApiKeyMutationVariables = Exact<{
|
||||
input: RevokeApiKeyDto;
|
||||
}>;
|
||||
|
||||
|
||||
export type RevokeApiKeyMutation = { __typename?: 'Mutation', revokeApiKey?: { __typename?: 'ApiKey', id: any } | null };
|
||||
|
||||
export type UpdateApiKeyMutationVariables = Exact<{
|
||||
input: UpdateApiKeyDto;
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateApiKeyMutation = { __typename?: 'Mutation', updateApiKey?: { __typename?: 'ApiKey', id: any, name: string, expiresAt: string, revokedAt?: string | null } | null };
|
||||
|
||||
export type UpdateWebhookMutationVariables = Exact<{
|
||||
input: UpdateWebhookDto;
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateWebhookMutation = { __typename?: 'Mutation', updateWebhook?: { __typename?: 'Webhook', id: any, targetUrl: string, operations: Array<string>, description?: string | null, secret: string } | null };
|
||||
|
||||
export type GetApiKeyQueryVariables = Exact<{
|
||||
input: GetApiKeyDto;
|
||||
}>;
|
||||
|
||||
|
||||
export type GetApiKeyQuery = { __typename?: 'Query', apiKey?: { __typename?: 'ApiKey', createdAt: string, id: any, name: string, expiresAt: string, revokedAt?: string | null } | null };
|
||||
|
||||
export type GetApiKeysQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetApiKeysQuery = { __typename?: 'Query', apiKeys: Array<{ __typename?: 'ApiKey', id: any, name: string, expiresAt: string, revokedAt?: string | null }> };
|
||||
|
||||
export type GetWebhookQueryVariables = Exact<{
|
||||
input: GetWebhookDto;
|
||||
}>;
|
||||
|
||||
|
||||
export type GetWebhookQuery = { __typename?: 'Query', webhook?: { __typename?: 'Webhook', id: any, targetUrl: string, operations: Array<string>, description?: string | null, secret: string } | null };
|
||||
|
||||
export type GetWebhooksQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetWebhooksQuery = { __typename?: 'Query', webhooks: Array<{ __typename?: 'Webhook', id: any, targetUrl: string, operations: Array<string>, description?: string | null, secret: string }> };
|
||||
|
||||
export type UpdateLabPublicFeatureFlagMutationVariables = Exact<{
|
||||
input: UpdateLabPublicFeatureFlagInput;
|
||||
}>;
|
||||
@ -3647,6 +3839,23 @@ export const RemoteTableFieldsFragmentDoc = gql`
|
||||
schemaPendingUpdates
|
||||
}
|
||||
`;
|
||||
export const ApiKeyFragmentFragmentDoc = gql`
|
||||
fragment ApiKeyFragment on ApiKey {
|
||||
id
|
||||
name
|
||||
expiresAt
|
||||
revokedAt
|
||||
}
|
||||
`;
|
||||
export const WebhookFragmentFragmentDoc = gql`
|
||||
fragment WebhookFragment on Webhook {
|
||||
id
|
||||
targetUrl
|
||||
operations
|
||||
description
|
||||
secret
|
||||
}
|
||||
`;
|
||||
export const SettingPermissionFragmentFragmentDoc = gql`
|
||||
fragment SettingPermissionFragment on SettingPermission {
|
||||
id
|
||||
@ -6286,6 +6495,341 @@ export function useGetSystemHealthStatusLazyQuery(baseOptions?: Apollo.LazyQuery
|
||||
export type GetSystemHealthStatusQueryHookResult = ReturnType<typeof useGetSystemHealthStatusQuery>;
|
||||
export type GetSystemHealthStatusLazyQueryHookResult = ReturnType<typeof useGetSystemHealthStatusLazyQuery>;
|
||||
export type GetSystemHealthStatusQueryResult = Apollo.QueryResult<GetSystemHealthStatusQuery, GetSystemHealthStatusQueryVariables>;
|
||||
export const CreateApiKeyDocument = gql`
|
||||
mutation CreateApiKey($input: CreateApiKeyDTO!) {
|
||||
createApiKey(input: $input) {
|
||||
...ApiKeyFragment
|
||||
}
|
||||
}
|
||||
${ApiKeyFragmentFragmentDoc}`;
|
||||
export type CreateApiKeyMutationFn = Apollo.MutationFunction<CreateApiKeyMutation, CreateApiKeyMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useCreateApiKeyMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useCreateApiKeyMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useCreateApiKeyMutation` 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 [createApiKeyMutation, { data, loading, error }] = useCreateApiKeyMutation({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useCreateApiKeyMutation(baseOptions?: Apollo.MutationHookOptions<CreateApiKeyMutation, CreateApiKeyMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<CreateApiKeyMutation, CreateApiKeyMutationVariables>(CreateApiKeyDocument, options);
|
||||
}
|
||||
export type CreateApiKeyMutationHookResult = ReturnType<typeof useCreateApiKeyMutation>;
|
||||
export type CreateApiKeyMutationResult = Apollo.MutationResult<CreateApiKeyMutation>;
|
||||
export type CreateApiKeyMutationOptions = Apollo.BaseMutationOptions<CreateApiKeyMutation, CreateApiKeyMutationVariables>;
|
||||
export const CreateWebhookDocument = gql`
|
||||
mutation CreateWebhook($input: CreateWebhookDTO!) {
|
||||
createWebhook(input: $input) {
|
||||
...WebhookFragment
|
||||
}
|
||||
}
|
||||
${WebhookFragmentFragmentDoc}`;
|
||||
export type CreateWebhookMutationFn = Apollo.MutationFunction<CreateWebhookMutation, CreateWebhookMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useCreateWebhookMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useCreateWebhookMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useCreateWebhookMutation` 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 [createWebhookMutation, { data, loading, error }] = useCreateWebhookMutation({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useCreateWebhookMutation(baseOptions?: Apollo.MutationHookOptions<CreateWebhookMutation, CreateWebhookMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<CreateWebhookMutation, CreateWebhookMutationVariables>(CreateWebhookDocument, options);
|
||||
}
|
||||
export type CreateWebhookMutationHookResult = ReturnType<typeof useCreateWebhookMutation>;
|
||||
export type CreateWebhookMutationResult = Apollo.MutationResult<CreateWebhookMutation>;
|
||||
export type CreateWebhookMutationOptions = Apollo.BaseMutationOptions<CreateWebhookMutation, CreateWebhookMutationVariables>;
|
||||
export const DeleteWebhookDocument = gql`
|
||||
mutation DeleteWebhook($input: DeleteWebhookDTO!) {
|
||||
deleteWebhook(input: $input)
|
||||
}
|
||||
`;
|
||||
export type DeleteWebhookMutationFn = Apollo.MutationFunction<DeleteWebhookMutation, DeleteWebhookMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useDeleteWebhookMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useDeleteWebhookMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useDeleteWebhookMutation` 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 [deleteWebhookMutation, { data, loading, error }] = useDeleteWebhookMutation({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useDeleteWebhookMutation(baseOptions?: Apollo.MutationHookOptions<DeleteWebhookMutation, DeleteWebhookMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<DeleteWebhookMutation, DeleteWebhookMutationVariables>(DeleteWebhookDocument, options);
|
||||
}
|
||||
export type DeleteWebhookMutationHookResult = ReturnType<typeof useDeleteWebhookMutation>;
|
||||
export type DeleteWebhookMutationResult = Apollo.MutationResult<DeleteWebhookMutation>;
|
||||
export type DeleteWebhookMutationOptions = Apollo.BaseMutationOptions<DeleteWebhookMutation, DeleteWebhookMutationVariables>;
|
||||
export const RevokeApiKeyDocument = gql`
|
||||
mutation RevokeApiKey($input: RevokeApiKeyDTO!) {
|
||||
revokeApiKey(input: $input) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type RevokeApiKeyMutationFn = Apollo.MutationFunction<RevokeApiKeyMutation, RevokeApiKeyMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useRevokeApiKeyMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useRevokeApiKeyMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useRevokeApiKeyMutation` 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 [revokeApiKeyMutation, { data, loading, error }] = useRevokeApiKeyMutation({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useRevokeApiKeyMutation(baseOptions?: Apollo.MutationHookOptions<RevokeApiKeyMutation, RevokeApiKeyMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<RevokeApiKeyMutation, RevokeApiKeyMutationVariables>(RevokeApiKeyDocument, options);
|
||||
}
|
||||
export type RevokeApiKeyMutationHookResult = ReturnType<typeof useRevokeApiKeyMutation>;
|
||||
export type RevokeApiKeyMutationResult = Apollo.MutationResult<RevokeApiKeyMutation>;
|
||||
export type RevokeApiKeyMutationOptions = Apollo.BaseMutationOptions<RevokeApiKeyMutation, RevokeApiKeyMutationVariables>;
|
||||
export const UpdateApiKeyDocument = gql`
|
||||
mutation UpdateApiKey($input: UpdateApiKeyDTO!) {
|
||||
updateApiKey(input: $input) {
|
||||
...ApiKeyFragment
|
||||
}
|
||||
}
|
||||
${ApiKeyFragmentFragmentDoc}`;
|
||||
export type UpdateApiKeyMutationFn = Apollo.MutationFunction<UpdateApiKeyMutation, UpdateApiKeyMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useUpdateApiKeyMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useUpdateApiKeyMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useUpdateApiKeyMutation` 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 [updateApiKeyMutation, { data, loading, error }] = useUpdateApiKeyMutation({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useUpdateApiKeyMutation(baseOptions?: Apollo.MutationHookOptions<UpdateApiKeyMutation, UpdateApiKeyMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<UpdateApiKeyMutation, UpdateApiKeyMutationVariables>(UpdateApiKeyDocument, options);
|
||||
}
|
||||
export type UpdateApiKeyMutationHookResult = ReturnType<typeof useUpdateApiKeyMutation>;
|
||||
export type UpdateApiKeyMutationResult = Apollo.MutationResult<UpdateApiKeyMutation>;
|
||||
export type UpdateApiKeyMutationOptions = Apollo.BaseMutationOptions<UpdateApiKeyMutation, UpdateApiKeyMutationVariables>;
|
||||
export const UpdateWebhookDocument = gql`
|
||||
mutation UpdateWebhook($input: UpdateWebhookDTO!) {
|
||||
updateWebhook(input: $input) {
|
||||
...WebhookFragment
|
||||
}
|
||||
}
|
||||
${WebhookFragmentFragmentDoc}`;
|
||||
export type UpdateWebhookMutationFn = Apollo.MutationFunction<UpdateWebhookMutation, UpdateWebhookMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useUpdateWebhookMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useUpdateWebhookMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useUpdateWebhookMutation` 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 [updateWebhookMutation, { data, loading, error }] = useUpdateWebhookMutation({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useUpdateWebhookMutation(baseOptions?: Apollo.MutationHookOptions<UpdateWebhookMutation, UpdateWebhookMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<UpdateWebhookMutation, UpdateWebhookMutationVariables>(UpdateWebhookDocument, options);
|
||||
}
|
||||
export type UpdateWebhookMutationHookResult = ReturnType<typeof useUpdateWebhookMutation>;
|
||||
export type UpdateWebhookMutationResult = Apollo.MutationResult<UpdateWebhookMutation>;
|
||||
export type UpdateWebhookMutationOptions = Apollo.BaseMutationOptions<UpdateWebhookMutation, UpdateWebhookMutationVariables>;
|
||||
export const GetApiKeyDocument = gql`
|
||||
query GetApiKey($input: GetApiKeyDTO!) {
|
||||
apiKey(input: $input) {
|
||||
...ApiKeyFragment
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
${ApiKeyFragmentFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useGetApiKeyQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetApiKeyQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetApiKeyQuery` 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 } = useGetApiKeyQuery({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetApiKeyQuery(baseOptions: Apollo.QueryHookOptions<GetApiKeyQuery, GetApiKeyQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetApiKeyQuery, GetApiKeyQueryVariables>(GetApiKeyDocument, options);
|
||||
}
|
||||
export function useGetApiKeyLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetApiKeyQuery, GetApiKeyQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetApiKeyQuery, GetApiKeyQueryVariables>(GetApiKeyDocument, options);
|
||||
}
|
||||
export type GetApiKeyQueryHookResult = ReturnType<typeof useGetApiKeyQuery>;
|
||||
export type GetApiKeyLazyQueryHookResult = ReturnType<typeof useGetApiKeyLazyQuery>;
|
||||
export type GetApiKeyQueryResult = Apollo.QueryResult<GetApiKeyQuery, GetApiKeyQueryVariables>;
|
||||
export const GetApiKeysDocument = gql`
|
||||
query GetApiKeys {
|
||||
apiKeys {
|
||||
...ApiKeyFragment
|
||||
}
|
||||
}
|
||||
${ApiKeyFragmentFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useGetApiKeysQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetApiKeysQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetApiKeysQuery` 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 } = useGetApiKeysQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetApiKeysQuery(baseOptions?: Apollo.QueryHookOptions<GetApiKeysQuery, GetApiKeysQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetApiKeysQuery, GetApiKeysQueryVariables>(GetApiKeysDocument, options);
|
||||
}
|
||||
export function useGetApiKeysLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetApiKeysQuery, GetApiKeysQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetApiKeysQuery, GetApiKeysQueryVariables>(GetApiKeysDocument, options);
|
||||
}
|
||||
export type GetApiKeysQueryHookResult = ReturnType<typeof useGetApiKeysQuery>;
|
||||
export type GetApiKeysLazyQueryHookResult = ReturnType<typeof useGetApiKeysLazyQuery>;
|
||||
export type GetApiKeysQueryResult = Apollo.QueryResult<GetApiKeysQuery, GetApiKeysQueryVariables>;
|
||||
export const GetWebhookDocument = gql`
|
||||
query GetWebhook($input: GetWebhookDTO!) {
|
||||
webhook(input: $input) {
|
||||
...WebhookFragment
|
||||
}
|
||||
}
|
||||
${WebhookFragmentFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useGetWebhookQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetWebhookQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetWebhookQuery` 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 } = useGetWebhookQuery({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetWebhookQuery(baseOptions: Apollo.QueryHookOptions<GetWebhookQuery, GetWebhookQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetWebhookQuery, GetWebhookQueryVariables>(GetWebhookDocument, options);
|
||||
}
|
||||
export function useGetWebhookLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetWebhookQuery, GetWebhookQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetWebhookQuery, GetWebhookQueryVariables>(GetWebhookDocument, options);
|
||||
}
|
||||
export type GetWebhookQueryHookResult = ReturnType<typeof useGetWebhookQuery>;
|
||||
export type GetWebhookLazyQueryHookResult = ReturnType<typeof useGetWebhookLazyQuery>;
|
||||
export type GetWebhookQueryResult = Apollo.QueryResult<GetWebhookQuery, GetWebhookQueryVariables>;
|
||||
export const GetWebhooksDocument = gql`
|
||||
query GetWebhooks {
|
||||
webhooks {
|
||||
...WebhookFragment
|
||||
}
|
||||
}
|
||||
${WebhookFragmentFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useGetWebhooksQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetWebhooksQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetWebhooksQuery` 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 } = useGetWebhooksQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetWebhooksQuery(baseOptions?: Apollo.QueryHookOptions<GetWebhooksQuery, GetWebhooksQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetWebhooksQuery, GetWebhooksQueryVariables>(GetWebhooksDocument, options);
|
||||
}
|
||||
export function useGetWebhooksLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetWebhooksQuery, GetWebhooksQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetWebhooksQuery, GetWebhooksQueryVariables>(GetWebhooksDocument, options);
|
||||
}
|
||||
export type GetWebhooksQueryHookResult = ReturnType<typeof useGetWebhooksQuery>;
|
||||
export type GetWebhooksLazyQueryHookResult = ReturnType<typeof useGetWebhooksLazyQuery>;
|
||||
export type GetWebhooksQueryResult = Apollo.QueryResult<GetWebhooksQuery, GetWebhooksQueryVariables>;
|
||||
export const UpdateLabPublicFeatureFlagDocument = gql`
|
||||
mutation UpdateLabPublicFeatureFlag($input: UpdateLabPublicFeatureFlagInput!) {
|
||||
updateLabPublicFeatureFlag(input: $input) {
|
||||
|
||||
@ -87,6 +87,18 @@ export type ApiConfig = {
|
||||
mutationMaximumAffectedRecords: Scalars['Float'];
|
||||
};
|
||||
|
||||
export type ApiKey = {
|
||||
__typename?: 'ApiKey';
|
||||
createdAt: Scalars['DateTime'];
|
||||
expiresAt: Scalars['DateTime'];
|
||||
id: Scalars['UUID'];
|
||||
name: Scalars['String'];
|
||||
revokedAt?: Maybe<Scalars['DateTime']>;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
workspace: Workspace;
|
||||
workspaceId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ApiKeyToken = {
|
||||
__typename?: 'ApiKeyToken';
|
||||
token: Scalars['String'];
|
||||
@ -435,6 +447,12 @@ export type ConnectionParametersOutput = {
|
||||
username: Scalars['String'];
|
||||
};
|
||||
|
||||
export type CreateApiKeyDto = {
|
||||
expiresAt: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
revokedAt?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type CreateApprovedAccessDomainInput = {
|
||||
domain: Scalars['String'];
|
||||
email: Scalars['String'];
|
||||
@ -491,6 +509,13 @@ export type CreateServerlessFunctionInput = {
|
||||
timeoutSeconds?: InputMaybe<Scalars['Float']>;
|
||||
};
|
||||
|
||||
export type CreateWebhookDto = {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
operations: Array<Scalars['String']>;
|
||||
secret?: InputMaybe<Scalars['String']>;
|
||||
targetUrl: Scalars['String'];
|
||||
};
|
||||
|
||||
export type CreateWorkflowVersionStepInput = {
|
||||
/** Next step ID */
|
||||
nextStepId?: InputMaybe<Scalars['String']>;
|
||||
@ -572,6 +597,10 @@ export type DeleteSsoOutput = {
|
||||
identityProviderId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type DeleteWebhookDto = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type DeleteWorkflowVersionStepInput = {
|
||||
/** Step to delete ID */
|
||||
stepId: Scalars['String'];
|
||||
@ -647,9 +676,11 @@ export enum FeatureFlagKey {
|
||||
IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
|
||||
IS_MORPH_RELATION_ENABLED = 'IS_MORPH_RELATION_ENABLED',
|
||||
IS_POSTGRESQL_INTEGRATION_ENABLED = 'IS_POSTGRESQL_INTEGRATION_ENABLED',
|
||||
IS_RELATION_CONNECT_ENABLED = 'IS_RELATION_CONNECT_ENABLED',
|
||||
IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED',
|
||||
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED',
|
||||
IS_WORKFLOW_FILTERING_ENABLED = 'IS_WORKFLOW_FILTERING_ENABLED'
|
||||
IS_WORKFLOW_FILTERING_ENABLED = 'IS_WORKFLOW_FILTERING_ENABLED',
|
||||
IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED = 'IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED'
|
||||
}
|
||||
|
||||
export type Field = {
|
||||
@ -776,6 +807,10 @@ export type FullName = {
|
||||
lastName: Scalars['String'];
|
||||
};
|
||||
|
||||
export type GetApiKeyDto = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type GetAuthorizationUrlForSsoInput = {
|
||||
identityProviderId: Scalars['String'];
|
||||
workspaceInviteHash?: InputMaybe<Scalars['String']>;
|
||||
@ -801,6 +836,10 @@ export type GetServerlessFunctionSourceCodeInput = {
|
||||
version?: Scalars['String'];
|
||||
};
|
||||
|
||||
export type GetWebhookDto = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export enum HealthIndicatorId {
|
||||
app = 'app',
|
||||
connectedAccount = 'connectedAccount',
|
||||
@ -972,6 +1011,7 @@ export type Mutation = {
|
||||
checkCustomDomainValidRecords?: Maybe<CustomDomainValidRecords>;
|
||||
checkoutSession: BillingSessionOutput;
|
||||
computeStepOutputSchema: Scalars['JSON'];
|
||||
createApiKey: ApiKey;
|
||||
createApprovedAccessDomain: ApprovedAccessDomain;
|
||||
createDatabaseConfigVariable: Scalars['Boolean'];
|
||||
createDraftFromWorkflowVersion: WorkflowVersion;
|
||||
@ -983,6 +1023,7 @@ export type Mutation = {
|
||||
createOneRole: Role;
|
||||
createOneServerlessFunction: ServerlessFunction;
|
||||
createSAMLIdentityProvider: SetupSsoOutput;
|
||||
createWebhook: Webhook;
|
||||
createWorkflowVersionStep: WorkflowAction;
|
||||
deactivateWorkflowVersion: Scalars['Boolean'];
|
||||
deleteApprovedAccessDomain: Scalars['Boolean'];
|
||||
@ -994,6 +1035,7 @@ export type Mutation = {
|
||||
deleteOneServerlessFunction: ServerlessFunction;
|
||||
deleteSSOIdentityProvider: DeleteSsoOutput;
|
||||
deleteUser: User;
|
||||
deleteWebhook: Scalars['Boolean'];
|
||||
deleteWorkflowVersionStep: WorkflowAction;
|
||||
deleteWorkspaceInvitation: Scalars['String'];
|
||||
disablePostgresProxy: PostgresCredentials;
|
||||
@ -1014,6 +1056,7 @@ export type Mutation = {
|
||||
renewToken: AuthTokens;
|
||||
resendEmailVerificationToken: ResendEmailVerificationTokenOutput;
|
||||
resendWorkspaceInvitation: SendInvitationsOutput;
|
||||
revokeApiKey?: Maybe<ApiKey>;
|
||||
runWorkflowVersion: WorkflowRun;
|
||||
saveImapSmtpCaldav: ImapSmtpCaldavConnectionSuccess;
|
||||
sendInvitations: SendInvitationsOutput;
|
||||
@ -1027,6 +1070,7 @@ export type Mutation = {
|
||||
switchToEnterprisePlan: BillingUpdateOutput;
|
||||
switchToYearlyInterval: BillingUpdateOutput;
|
||||
trackAnalytics: Analytics;
|
||||
updateApiKey?: Maybe<ApiKey>;
|
||||
updateDatabaseConfigVariable: Scalars['Boolean'];
|
||||
updateLabPublicFeatureFlag: FeatureFlagDto;
|
||||
updateOneAgent: Agent;
|
||||
@ -1035,6 +1079,7 @@ export type Mutation = {
|
||||
updateOneRole: Role;
|
||||
updateOneServerlessFunction: ServerlessFunction;
|
||||
updatePasswordViaResetToken: InvalidatePassword;
|
||||
updateWebhook?: Maybe<Webhook>;
|
||||
updateWorkflowRunStep: WorkflowAction;
|
||||
updateWorkflowVersionStep: WorkflowAction;
|
||||
updateWorkspace: Workspace;
|
||||
@ -1088,6 +1133,11 @@ export type MutationComputeStepOutputSchemaArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateApiKeyArgs = {
|
||||
input: CreateApiKeyDto;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateApprovedAccessDomainArgs = {
|
||||
input: CreateApprovedAccessDomainInput;
|
||||
};
|
||||
@ -1137,6 +1187,11 @@ export type MutationCreateSamlIdentityProviderArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateWebhookArgs = {
|
||||
input: CreateWebhookDto;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateWorkflowVersionStepArgs = {
|
||||
input: CreateWorkflowVersionStepInput;
|
||||
};
|
||||
@ -1182,6 +1237,11 @@ export type MutationDeleteSsoIdentityProviderArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteWebhookArgs = {
|
||||
input: DeleteWebhookDto;
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteWorkflowVersionStepArgs = {
|
||||
input: DeleteWorkflowVersionStepInput;
|
||||
};
|
||||
@ -1273,6 +1333,11 @@ export type MutationResendWorkspaceInvitationArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationRevokeApiKeyArgs = {
|
||||
input: RevokeApiKeyDto;
|
||||
};
|
||||
|
||||
|
||||
export type MutationRunWorkflowVersionArgs = {
|
||||
input: RunWorkflowVersionInput;
|
||||
};
|
||||
@ -1331,6 +1396,11 @@ export type MutationTrackAnalyticsArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateApiKeyArgs = {
|
||||
input: UpdateApiKeyDto;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateDatabaseConfigVariableArgs = {
|
||||
key: Scalars['String'];
|
||||
value: Scalars['JSON'];
|
||||
@ -1373,6 +1443,11 @@ export type MutationUpdatePasswordViaResetTokenArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateWebhookArgs = {
|
||||
input: UpdateWebhookDto;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateWorkflowRunStepArgs = {
|
||||
input: UpdateWorkflowRunStepInput;
|
||||
};
|
||||
@ -1655,6 +1730,8 @@ export type PublishServerlessFunctionInput = {
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
apiKey?: Maybe<ApiKey>;
|
||||
apiKeys: Array<ApiKey>;
|
||||
billingPortalSession: BillingSessionOutput;
|
||||
checkUserExists: CheckUserExistOutput;
|
||||
checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValid;
|
||||
@ -1693,6 +1770,13 @@ export type Query = {
|
||||
search: SearchResultConnection;
|
||||
validatePasswordResetToken: ValidatePasswordResetToken;
|
||||
versionInfo: VersionInfo;
|
||||
webhook?: Maybe<Webhook>;
|
||||
webhooks: Array<Webhook>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryApiKeyArgs = {
|
||||
input: GetApiKeyDto;
|
||||
};
|
||||
|
||||
|
||||
@ -1805,6 +1889,11 @@ export type QueryValidatePasswordResetTokenArgs = {
|
||||
passwordResetToken: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryWebhookArgs = {
|
||||
input: GetWebhookDto;
|
||||
};
|
||||
|
||||
export type QueueMetricsData = {
|
||||
__typename?: 'QueueMetricsData';
|
||||
data: Array<QueueMetricsSeries>;
|
||||
@ -1882,6 +1971,10 @@ export type ResendEmailVerificationTokenOutput = {
|
||||
success: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export type RevokeApiKeyDto = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Role = {
|
||||
__typename?: 'Role';
|
||||
canDestroyAllObjectRecords: Scalars['Boolean'];
|
||||
@ -2244,6 +2337,13 @@ export type UpdateAgentInput = {
|
||||
responseFormat?: InputMaybe<Scalars['JSON']>;
|
||||
};
|
||||
|
||||
export type UpdateApiKeyDto = {
|
||||
expiresAt?: InputMaybe<Scalars['String']>;
|
||||
id: Scalars['String'];
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
revokedAt?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type UpdateFieldInput = {
|
||||
defaultValue?: InputMaybe<Scalars['JSON']>;
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
@ -2317,6 +2417,14 @@ export type UpdateServerlessFunctionInput = {
|
||||
timeoutSeconds?: InputMaybe<Scalars['Float']>;
|
||||
};
|
||||
|
||||
export type UpdateWebhookDto = {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
id: Scalars['String'];
|
||||
operations?: InputMaybe<Array<Scalars['String']>>;
|
||||
secret?: InputMaybe<Scalars['String']>;
|
||||
targetUrl?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type UpdateWorkflowRunStepInput = {
|
||||
/** Step to update in JSON format */
|
||||
step: Scalars['JSON'];
|
||||
@ -2449,6 +2557,20 @@ export type VersionInfo = {
|
||||
latestVersion: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Webhook = {
|
||||
__typename?: 'Webhook';
|
||||
createdAt: Scalars['DateTime'];
|
||||
deletedAt?: Maybe<Scalars['DateTime']>;
|
||||
description?: Maybe<Scalars['String']>;
|
||||
id: Scalars['UUID'];
|
||||
operations: Array<Scalars['String']>;
|
||||
secret: Scalars['String'];
|
||||
targetUrl: Scalars['String'];
|
||||
updatedAt: Scalars['DateTime'];
|
||||
workspace: Workspace;
|
||||
workspaceId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type WorkerQueueMetrics = {
|
||||
__typename?: 'WorkerQueueMetrics';
|
||||
active: Scalars['Float'];
|
||||
|
||||
@ -2,11 +2,9 @@ import styled from '@emotion/styled';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useUpdateApiKeyMutation } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledComboInputContainer = styled.div`
|
||||
display: flex;
|
||||
@ -29,9 +27,7 @@ export const ApiKeyNameInput = ({
|
||||
disabled,
|
||||
onNameUpdate,
|
||||
}: ApiKeyNameInputProps) => {
|
||||
const { updateOneRecord: updateApiKey } = useUpdateOneRecord<ApiKey>({
|
||||
objectNameSingular: CoreObjectNameSingular.ApiKey,
|
||||
});
|
||||
const [updateApiKey] = useUpdateApiKeyMutation();
|
||||
|
||||
// TODO: Enhance this with react-web-hook-form (https://www.react-hook-form.com)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -43,10 +39,18 @@ export const ApiKeyNameInput = ({
|
||||
if (!apiKeyName) {
|
||||
return;
|
||||
}
|
||||
await updateApiKey({
|
||||
idToUpdate: apiKeyId,
|
||||
updateOneRecordInput: { name },
|
||||
const { data: updatedApiKeyData } = await updateApiKey({
|
||||
variables: {
|
||||
input: {
|
||||
id: apiKeyId,
|
||||
name,
|
||||
},
|
||||
},
|
||||
});
|
||||
const updatedApiKey = updatedApiKeyData?.updateApiKey;
|
||||
if (isDefined(updatedApiKey)) {
|
||||
onNameUpdate?.(updatedApiKey.name);
|
||||
}
|
||||
}, 500),
|
||||
[updateApiKey, onNameUpdate],
|
||||
);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { ApiFieldItem } from '@/settings/developers/types/api-key/ApiFieldItem';
|
||||
import { formatExpiration } from '@/settings/developers/utils/formatExpiration';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import {
|
||||
@ -9,6 +9,7 @@ import {
|
||||
OverflowingTextWithTooltip,
|
||||
} from 'twenty-ui/display';
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
|
||||
import { ApiKey } from '~/generated-metadata/graphql';
|
||||
|
||||
export const StyledApisFieldTableRow = styled(TableRow)`
|
||||
grid-template-columns: 312px auto 28px;
|
||||
@ -34,27 +35,28 @@ const StyledIconChevronRight = styled(IconChevronRight)`
|
||||
`;
|
||||
|
||||
export const SettingsApiKeysFieldItemTableRow = ({
|
||||
fieldItem,
|
||||
apiKey,
|
||||
to,
|
||||
}: {
|
||||
fieldItem: ApiFieldItem;
|
||||
apiKey: Pick<ApiKey, 'id' | 'name' | 'expiresAt' | 'revokedAt'>;
|
||||
to: string;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const formattedExpiration = formatExpiration(apiKey.expiresAt || null);
|
||||
|
||||
return (
|
||||
<StyledApisFieldTableRow to={to}>
|
||||
<StyledNameTableCell>
|
||||
<OverflowingTextWithTooltip text={fieldItem.name} />
|
||||
<OverflowingTextWithTooltip text={apiKey.name} />
|
||||
</StyledNameTableCell>
|
||||
<TableCell
|
||||
color={
|
||||
fieldItem.expiration === 'Expired'
|
||||
formattedExpiration === 'Expired'
|
||||
? theme.font.color.danger
|
||||
: theme.font.color.tertiary
|
||||
}
|
||||
>
|
||||
{fieldItem.expiration}
|
||||
{formattedExpiration}
|
||||
</TableCell>
|
||||
<StyledIconTableCell>
|
||||
<StyledIconChevronRight
|
||||
|
||||
@ -1,9 +1,4 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { SettingsApiKeysFieldItemTableRow } from '@/settings/developers/components/SettingsApiKeysFieldItemTableRow';
|
||||
import { ApiFieldItem } from '@/settings/developers/types/api-key/ApiFieldItem';
|
||||
import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
|
||||
import { formatExpirations } from '@/settings/developers/utils/formatExpiration';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableBody } from '@/ui/layout/table/components/TableBody';
|
||||
@ -12,6 +7,7 @@ import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import styled from '@emotion/styled';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
|
||||
import { useGetApiKeysQuery } from '~/generated-metadata/graphql';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
const StyledTableBody = styled(TableBody)`
|
||||
@ -33,10 +29,9 @@ const StyledTableRow = styled(TableRow)`
|
||||
`;
|
||||
|
||||
export const SettingsApiKeysTable = () => {
|
||||
const { records: apiKeys } = useFindManyRecords<ApiKey>({
|
||||
objectNameSingular: CoreObjectNameSingular.ApiKey,
|
||||
filter: { revokedAt: { is: 'NULL' } },
|
||||
});
|
||||
const { data: apiKeysData } = useGetApiKeysQuery();
|
||||
|
||||
const apiKeys = apiKeysData?.apiKeys;
|
||||
|
||||
return (
|
||||
<Table>
|
||||
@ -49,14 +44,14 @@ export const SettingsApiKeysTable = () => {
|
||||
</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
</StyledTableRow>
|
||||
{!!apiKeys.length && (
|
||||
{!!apiKeys?.length && (
|
||||
<StyledTableBody>
|
||||
{formatExpirations(apiKeys).map((fieldItem) => (
|
||||
{apiKeys.map((apiKey) => (
|
||||
<SettingsApiKeysFieldItemTableRow
|
||||
key={fieldItem.id}
|
||||
fieldItem={fieldItem as ApiFieldItem}
|
||||
key={apiKey.id}
|
||||
apiKey={apiKey}
|
||||
to={getSettingsPath(SettingsPath.ApiKeyDetail, {
|
||||
apiKeyId: fieldItem.id,
|
||||
apiKeyId: apiKey.id,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
|
||||
@ -79,7 +79,7 @@ export const SettingsDevelopersWebhookForm = ({
|
||||
handleSave,
|
||||
updateOperation,
|
||||
removeOperation,
|
||||
deleteWebhook,
|
||||
handleDelete,
|
||||
isCreationMode,
|
||||
error,
|
||||
} = useWebhookForm({ webhookId, mode });
|
||||
@ -285,7 +285,7 @@ export const SettingsDevelopersWebhookForm = ({
|
||||
Please type "yes" to confirm you want to delete this webhook.
|
||||
</Trans>
|
||||
}
|
||||
onConfirmClick={deleteWebhook}
|
||||
onConfirmClick={handleDelete}
|
||||
confirmButtonText={t`Delete`}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Webhook } from '@/settings/developers/types/webhook/Webhook';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { getUrlHostnameOrThrow, isValidUrl } from 'twenty-shared/utils';
|
||||
import { IconChevronRight } from 'twenty-ui/display';
|
||||
import { Webhook } from '~/generated-metadata/graphql';
|
||||
|
||||
export const StyledApisFieldTableRow = styled(TableRow)`
|
||||
grid-template-columns: 1fr 28px;
|
||||
@ -28,10 +28,13 @@ const StyledIconChevronRight = styled(IconChevronRight)`
|
||||
`;
|
||||
|
||||
export const SettingsDevelopersWebhookTableRow = ({
|
||||
fieldItem,
|
||||
webhook,
|
||||
to,
|
||||
}: {
|
||||
fieldItem: Webhook;
|
||||
webhook: Pick<
|
||||
Webhook,
|
||||
'id' | 'targetUrl' | 'operations' | 'description' | 'secret'
|
||||
>;
|
||||
to: string;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
@ -39,9 +42,9 @@ export const SettingsDevelopersWebhookTableRow = ({
|
||||
return (
|
||||
<StyledApisFieldTableRow to={to}>
|
||||
<StyledUrlTableCell>
|
||||
{isValidUrl(fieldItem.targetUrl)
|
||||
? getUrlHostnameOrThrow(fieldItem.targetUrl)
|
||||
: fieldItem.targetUrl}
|
||||
{isValidUrl(webhook.targetUrl)
|
||||
? getUrlHostnameOrThrow(webhook.targetUrl)
|
||||
: webhook.targetUrl}
|
||||
</StyledUrlTableCell>
|
||||
<StyledIconTableCell>
|
||||
<StyledIconChevronRight
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { SettingsDevelopersWebhookTableRow } from '@/settings/developers/components/SettingsDevelopersWebhookTableRow';
|
||||
import { Webhook } from '@/settings/developers/types/webhook/Webhook';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableBody } from '@/ui/layout/table/components/TableBody';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useGetWebhooksQuery } from '~/generated-metadata/graphql';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
const StyledTableBody = styled(TableBody)`
|
||||
@ -22,9 +20,9 @@ const StyledTableRow = styled(TableRow)`
|
||||
`;
|
||||
|
||||
export const SettingsWebhooksTable = () => {
|
||||
const { records: webhooks } = useFindManyRecords<Webhook>({
|
||||
objectNameSingular: CoreObjectNameSingular.Webhook,
|
||||
});
|
||||
const { data: webhooksData } = useGetWebhooksQuery();
|
||||
|
||||
const webhooks = webhooksData?.webhooks;
|
||||
|
||||
return (
|
||||
<Table>
|
||||
@ -32,12 +30,12 @@ export const SettingsWebhooksTable = () => {
|
||||
<TableHeader>URL</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
</StyledTableRow>
|
||||
{!!webhooks.length && (
|
||||
{!!webhooks?.length && (
|
||||
<StyledTableBody>
|
||||
{webhooks.map((webhookFieldItem) => (
|
||||
<SettingsDevelopersWebhookTableRow
|
||||
key={webhookFieldItem.id}
|
||||
fieldItem={webhookFieldItem}
|
||||
webhook={webhookFieldItem}
|
||||
to={getSettingsPath(SettingsPath.WebhookDetail, {
|
||||
webhookId: webhookFieldItem.id,
|
||||
})}
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { SettingsApiKeysFieldItemTableRow } from '@/settings/developers/components/SettingsApiKeysFieldItemTableRow';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
|
||||
|
||||
const meta: Meta<typeof SettingsApiKeysFieldItemTableRow> = {
|
||||
title: 'Modules/Settings/Developers/ApiKeys/SettingsApiKeysFieldItemTableRow',
|
||||
component: SettingsApiKeysFieldItemTableRow,
|
||||
decorators: [ComponentDecorator],
|
||||
decorators: [ComponentDecorator, RouterDecorator],
|
||||
args: {
|
||||
fieldItem: {
|
||||
apiKey: {
|
||||
id: '3f4a42e8-b81f-4f8c-9c20-1602e6b34791',
|
||||
name: 'Zapier Api Key',
|
||||
type: 'internal',
|
||||
expiration: 'In 3 days',
|
||||
expiresAt: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000).toISOString(), // 3 days from now
|
||||
revokedAt: null,
|
||||
},
|
||||
to: '/settings/developers/api-keys/3f4a42e8-b81f-4f8c-9c20-1602e6b34791',
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ export const CreateMode: Story = {
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await canvas.findByText('New Webhook', undefined, { timeout: 10000 });
|
||||
await canvas.findByText('New Webhook', undefined, { timeout: 3000 });
|
||||
await canvas.findByPlaceholderText('https://example.com/webhook');
|
||||
await canvas.findByPlaceholderText('Write a description');
|
||||
|
||||
@ -48,15 +48,21 @@ export const EditMode: Story = {
|
||||
mode: WebhookFormMode.Edit,
|
||||
webhookId: '1234',
|
||||
},
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await canvas.findByDisplayValue('https://example.com/webhook', undefined, {
|
||||
timeout: 10000,
|
||||
});
|
||||
await canvas.findByDisplayValue('A Sample Description');
|
||||
await canvas.findByDisplayValue(
|
||||
'https://api.slackbot.io/webhooks/twenty',
|
||||
undefined,
|
||||
{
|
||||
timeout: 3000,
|
||||
},
|
||||
);
|
||||
await canvas.findByDisplayValue('Slack notifications for lead updates');
|
||||
|
||||
const allObjectsLabels = await canvas.findAllByText('All Objects');
|
||||
expect(allObjectsLabels).toHaveLength(2);
|
||||
await canvas.findByText('Created');
|
||||
await canvas.findByText('Updated');
|
||||
|
||||
await canvas.findByText('Danger zone');
|
||||
await canvas.findByText('Delete this webhook');
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const API_KEY_FRAGMENT = gql`
|
||||
fragment ApiKeyFragment on ApiKey {
|
||||
id
|
||||
name
|
||||
expiresAt
|
||||
revokedAt
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,11 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const WEBHOOK_FRAGMENT = gql`
|
||||
fragment WebhookFragment on Webhook {
|
||||
id
|
||||
targetUrl
|
||||
operations
|
||||
description
|
||||
secret
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,11 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { API_KEY_FRAGMENT } from '../fragments/apiKeyFragment';
|
||||
|
||||
export const CREATE_API_KEY = gql`
|
||||
mutation CreateApiKey($input: CreateApiKeyDTO!) {
|
||||
createApiKey(input: $input) {
|
||||
...ApiKeyFragment
|
||||
}
|
||||
}
|
||||
${API_KEY_FRAGMENT}
|
||||
`;
|
||||
@ -0,0 +1,11 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { WEBHOOK_FRAGMENT } from '../fragments/webhookFragment';
|
||||
|
||||
export const CREATE_WEBHOOK = gql`
|
||||
mutation CreateWebhook($input: CreateWebhookDTO!) {
|
||||
createWebhook(input: $input) {
|
||||
...WebhookFragment
|
||||
}
|
||||
}
|
||||
${WEBHOOK_FRAGMENT}
|
||||
`;
|
||||
@ -0,0 +1,7 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const DELETE_WEBHOOK = gql`
|
||||
mutation DeleteWebhook($input: DeleteWebhookDTO!) {
|
||||
deleteWebhook(input: $input)
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,9 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export const REVOKE_API_KEY = gql`
|
||||
mutation RevokeApiKey($input: RevokeApiKeyDTO!) {
|
||||
revokeApiKey(input: $input) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,11 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { API_KEY_FRAGMENT } from '../fragments/apiKeyFragment';
|
||||
|
||||
export const UPDATE_API_KEY = gql`
|
||||
mutation UpdateApiKey($input: UpdateApiKeyDTO!) {
|
||||
updateApiKey(input: $input) {
|
||||
...ApiKeyFragment
|
||||
}
|
||||
}
|
||||
${API_KEY_FRAGMENT}
|
||||
`;
|
||||
@ -0,0 +1,11 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { WEBHOOK_FRAGMENT } from '../fragments/webhookFragment';
|
||||
|
||||
export const UPDATE_WEBHOOK = gql`
|
||||
mutation UpdateWebhook($input: UpdateWebhookDTO!) {
|
||||
updateWebhook(input: $input) {
|
||||
...WebhookFragment
|
||||
}
|
||||
}
|
||||
${WEBHOOK_FRAGMENT}
|
||||
`;
|
||||
@ -0,0 +1,12 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { API_KEY_FRAGMENT } from '../fragments/apiKeyFragment';
|
||||
|
||||
export const GET_API_KEY = gql`
|
||||
query GetApiKey($input: GetApiKeyDTO!) {
|
||||
apiKey(input: $input) {
|
||||
...ApiKeyFragment
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
${API_KEY_FRAGMENT}
|
||||
`;
|
||||
@ -0,0 +1,11 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { API_KEY_FRAGMENT } from '../fragments/apiKeyFragment';
|
||||
|
||||
export const GET_API_KEYS = gql`
|
||||
query GetApiKeys {
|
||||
apiKeys {
|
||||
...ApiKeyFragment
|
||||
}
|
||||
}
|
||||
${API_KEY_FRAGMENT}
|
||||
`;
|
||||
@ -0,0 +1,11 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { WEBHOOK_FRAGMENT } from '../fragments/webhookFragment';
|
||||
|
||||
export const GET_WEBHOOK = gql`
|
||||
query GetWebhook($input: GetWebhookDTO!) {
|
||||
webhook(input: $input) {
|
||||
...WebhookFragment
|
||||
}
|
||||
}
|
||||
${WEBHOOK_FRAGMENT}
|
||||
`;
|
||||
@ -0,0 +1,11 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { WEBHOOK_FRAGMENT } from '../fragments/webhookFragment';
|
||||
|
||||
export const GET_WEBHOOKS = gql`
|
||||
query GetWebhooks {
|
||||
webhooks {
|
||||
...WebhookFragment
|
||||
}
|
||||
}
|
||||
${WEBHOOK_FRAGMENT}
|
||||
`;
|
||||
@ -5,16 +5,15 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { WebhookFormMode } from '@/settings/developers/constants/WebhookFormMode';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { CREATE_WEBHOOK } from '@/settings/developers/graphql/mutations/createWebhook';
|
||||
import { DELETE_WEBHOOK } from '@/settings/developers/graphql/mutations/deleteWebhook';
|
||||
import { UPDATE_WEBHOOK } from '@/settings/developers/graphql/mutations/updateWebhook';
|
||||
import { GET_WEBHOOK } from '@/settings/developers/graphql/queries/getWebhook';
|
||||
import { useWebhookForm } from '../useWebhookForm';
|
||||
|
||||
// Mock dependencies
|
||||
const mockNavigateSettings = jest.fn();
|
||||
const mockEnqueueSuccessSnackBar = jest.fn();
|
||||
const mockEnqueueErrorSnackBar = jest.fn();
|
||||
const mockCreateOneRecord = jest.fn();
|
||||
const mockUpdateOneRecord = jest.fn();
|
||||
const mockDeleteOneRecord = jest.fn();
|
||||
|
||||
jest.mock('~/hooks/useNavigateSettings', () => ({
|
||||
useNavigateSettings: () => mockNavigateSettings,
|
||||
@ -27,32 +26,108 @@ jest.mock('@/ui/feedback/snack-bar-manager/hooks/useSnackBar', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('@/object-record/hooks/useCreateOneRecord', () => ({
|
||||
useCreateOneRecord: () => ({
|
||||
createOneRecord: mockCreateOneRecord,
|
||||
}),
|
||||
}));
|
||||
const createMockWebhookData = (overrides = {}) => ({
|
||||
id: 'test-webhook-id',
|
||||
targetUrl: 'https://test.com/webhook',
|
||||
operations: ['person.created'],
|
||||
description: 'Test webhook',
|
||||
secret: 'test-secret',
|
||||
...overrides,
|
||||
});
|
||||
|
||||
jest.mock('@/object-record/hooks/useUpdateOneRecord', () => ({
|
||||
useUpdateOneRecord: () => ({
|
||||
updateOneRecord: mockUpdateOneRecord,
|
||||
}),
|
||||
}));
|
||||
const createSuccessfulCreateMock = (webhookData = {}) => ({
|
||||
request: {
|
||||
query: CREATE_WEBHOOK,
|
||||
variables: {
|
||||
input: {
|
||||
targetUrl: 'https://test.com/webhook',
|
||||
operations: ['person.created'],
|
||||
description: 'Test webhook',
|
||||
secret: 'test-secret',
|
||||
...webhookData,
|
||||
},
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
createWebhook: createMockWebhookData(webhookData),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
jest.mock('@/object-record/hooks/useDeleteOneRecord', () => ({
|
||||
useDeleteOneRecord: () => ({
|
||||
deleteOneRecord: mockDeleteOneRecord,
|
||||
}),
|
||||
}));
|
||||
const createSuccessfulUpdateMock = (webhookId: string, webhookData = {}) => ({
|
||||
request: {
|
||||
query: UPDATE_WEBHOOK,
|
||||
variables: {
|
||||
input: {
|
||||
id: webhookId,
|
||||
targetUrl: 'https://updated.com/webhook',
|
||||
operations: ['person.updated'],
|
||||
description: 'Updated webhook',
|
||||
secret: 'updated-secret',
|
||||
...webhookData,
|
||||
},
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
updateWebhook: createMockWebhookData({
|
||||
id: webhookId,
|
||||
targetUrl: 'https://updated.com/webhook',
|
||||
operations: ['person.updated'],
|
||||
description: 'Updated webhook',
|
||||
secret: 'updated-secret',
|
||||
...webhookData,
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
jest.mock('@/object-record/hooks/useFindOneRecord', () => ({
|
||||
useFindOneRecord: () => ({
|
||||
loading: false,
|
||||
}),
|
||||
}));
|
||||
const createSuccessfulDeleteMock = (webhookId: string) => ({
|
||||
request: {
|
||||
query: DELETE_WEBHOOK,
|
||||
variables: {
|
||||
input: {
|
||||
id: webhookId,
|
||||
},
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
deleteWebhook: {
|
||||
id: webhookId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<MockedProvider addTypename={false}>
|
||||
const createGetWebhookMock = (webhookId: string, webhookData = {}) => ({
|
||||
request: {
|
||||
query: GET_WEBHOOK,
|
||||
variables: {
|
||||
input: {
|
||||
id: webhookId,
|
||||
},
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
webhook: createMockWebhookData({
|
||||
id: webhookId,
|
||||
...webhookData,
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const Wrapper = ({
|
||||
children,
|
||||
mocks = [],
|
||||
}: {
|
||||
children: ReactNode;
|
||||
mocks?: any[];
|
||||
}) => (
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
<RecoilRoot>
|
||||
<MemoryRouter>{children}</MemoryRouter>
|
||||
</RecoilRoot>
|
||||
@ -68,7 +143,7 @@ describe('useWebhookForm', () => {
|
||||
it('should initialize with default values in create mode', () => {
|
||||
const { result } = renderHook(
|
||||
() => useWebhookForm({ mode: WebhookFormMode.Create }),
|
||||
{ wrapper: Wrapper },
|
||||
{ wrapper: ({ children }) => <Wrapper>{children}</Wrapper> },
|
||||
);
|
||||
|
||||
expect(result.current.isCreationMode).toBe(true);
|
||||
@ -81,15 +156,15 @@ describe('useWebhookForm', () => {
|
||||
});
|
||||
|
||||
it('should handle webhook creation successfully', async () => {
|
||||
const mockCreatedWebhook = {
|
||||
id: 'new-webhook-id',
|
||||
targetUrl: 'https://test.com/webhook',
|
||||
};
|
||||
mockCreateOneRecord.mockResolvedValue(mockCreatedWebhook);
|
||||
const mocks = [createSuccessfulCreateMock()];
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useWebhookForm({ mode: WebhookFormMode.Create }),
|
||||
{ wrapper: Wrapper },
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<Wrapper mocks={mocks}>{children}</Wrapper>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
const formData = {
|
||||
@ -103,28 +178,36 @@ describe('useWebhookForm', () => {
|
||||
await result.current.handleSave(formData);
|
||||
});
|
||||
|
||||
expect(mockCreateOneRecord).toHaveBeenCalledWith({
|
||||
id: expect.any(String),
|
||||
targetUrl: 'https://test.com/webhook',
|
||||
description: 'Test webhook',
|
||||
operations: ['person.created'],
|
||||
secret: 'test-secret',
|
||||
});
|
||||
|
||||
expect(mockEnqueueSuccessSnackBar).toHaveBeenCalledWith({
|
||||
message: 'Webhook https://test.com/webhook created successfully',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle creation errors', async () => {
|
||||
const error = new ApolloError({
|
||||
graphQLErrors: [{ message: 'Creation failed' }],
|
||||
});
|
||||
mockCreateOneRecord.mockRejectedValue(error);
|
||||
const errorMock = {
|
||||
request: {
|
||||
query: CREATE_WEBHOOK,
|
||||
variables: {
|
||||
input: {
|
||||
targetUrl: 'https://test.com/webhook',
|
||||
operations: ['person.created'],
|
||||
description: 'Test webhook',
|
||||
secret: 'test-secret',
|
||||
},
|
||||
},
|
||||
},
|
||||
error: new Error('Creation failed'),
|
||||
};
|
||||
|
||||
const mocks = [errorMock];
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useWebhookForm({ mode: WebhookFormMode.Create }),
|
||||
{ wrapper: Wrapper },
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<Wrapper mocks={mocks}>{children}</Wrapper>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
const formData = {
|
||||
@ -139,16 +222,24 @@ describe('useWebhookForm', () => {
|
||||
});
|
||||
|
||||
expect(mockEnqueueErrorSnackBar).toHaveBeenCalledWith({
|
||||
apolloError: error,
|
||||
apolloError: expect.any(Error),
|
||||
});
|
||||
});
|
||||
|
||||
it('should clean and format operations correctly', async () => {
|
||||
mockCreateOneRecord.mockResolvedValue({ id: 'test-id' });
|
||||
const mocks = [
|
||||
createSuccessfulCreateMock({
|
||||
operations: ['person.created', 'company.updated'],
|
||||
}),
|
||||
];
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useWebhookForm({ mode: WebhookFormMode.Create }),
|
||||
{ wrapper: Wrapper },
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<Wrapper mocks={mocks}>{children}</Wrapper>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
const formData = {
|
||||
@ -167,12 +258,8 @@ describe('useWebhookForm', () => {
|
||||
await result.current.handleSave(formData);
|
||||
});
|
||||
|
||||
expect(mockCreateOneRecord).toHaveBeenCalledWith({
|
||||
id: expect.any(String),
|
||||
targetUrl: 'https://test.com/webhook',
|
||||
description: 'Test webhook',
|
||||
operations: ['person.created', 'company.updated'],
|
||||
secret: 'test-secret',
|
||||
expect(mockEnqueueSuccessSnackBar).toHaveBeenCalledWith({
|
||||
message: 'Webhook https://test.com/webhook created successfully',
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -181,20 +268,29 @@ describe('useWebhookForm', () => {
|
||||
const webhookId = 'test-webhook-id';
|
||||
|
||||
it('should initialize correctly in edit mode', () => {
|
||||
const mocks = [createGetWebhookMock(webhookId)];
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useWebhookForm({
|
||||
mode: WebhookFormMode.Edit,
|
||||
webhookId,
|
||||
}),
|
||||
{ wrapper: Wrapper },
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<Wrapper mocks={mocks}>{children}</Wrapper>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.isCreationMode).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle webhook update successfully', async () => {
|
||||
mockUpdateOneRecord.mockResolvedValue({});
|
||||
const mocks = [
|
||||
createGetWebhookMock(webhookId),
|
||||
createSuccessfulUpdateMock(webhookId),
|
||||
];
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
@ -202,7 +298,11 @@ describe('useWebhookForm', () => {
|
||||
mode: WebhookFormMode.Edit,
|
||||
webhookId,
|
||||
}),
|
||||
{ wrapper: Wrapper },
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<Wrapper mocks={mocks}>{children}</Wrapper>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
const formData = {
|
||||
@ -216,22 +316,30 @@ describe('useWebhookForm', () => {
|
||||
await result.current.handleSave(formData);
|
||||
});
|
||||
|
||||
expect(mockUpdateOneRecord).toHaveBeenCalledWith({
|
||||
idToUpdate: webhookId,
|
||||
updateOneRecordInput: {
|
||||
targetUrl: 'https://updated.com/webhook',
|
||||
description: 'Updated webhook',
|
||||
operations: ['person.updated'],
|
||||
secret: 'updated-secret',
|
||||
},
|
||||
expect(mockEnqueueSuccessSnackBar).toHaveBeenCalledWith({
|
||||
message: 'Webhook https://updated.com/webhook updated successfully',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle update errors', async () => {
|
||||
const error = new ApolloError({
|
||||
graphQLErrors: [{ message: 'Update failed' }],
|
||||
});
|
||||
mockUpdateOneRecord.mockRejectedValue(error);
|
||||
const getWebhookMock = createGetWebhookMock(webhookId);
|
||||
const updateErrorMock = {
|
||||
request: {
|
||||
query: UPDATE_WEBHOOK,
|
||||
variables: {
|
||||
input: {
|
||||
id: webhookId,
|
||||
targetUrl: 'https://test.com/webhook',
|
||||
operations: ['person.created'],
|
||||
description: 'Test webhook',
|
||||
secret: 'test-secret',
|
||||
},
|
||||
},
|
||||
},
|
||||
error: new Error('Update failed'),
|
||||
};
|
||||
|
||||
const mocks = [getWebhookMock, updateErrorMock];
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
@ -239,7 +347,11 @@ describe('useWebhookForm', () => {
|
||||
mode: WebhookFormMode.Edit,
|
||||
webhookId,
|
||||
}),
|
||||
{ wrapper: Wrapper },
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<Wrapper mocks={mocks}>{children}</Wrapper>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
const formData = {
|
||||
@ -254,7 +366,7 @@ describe('useWebhookForm', () => {
|
||||
});
|
||||
|
||||
expect(mockEnqueueErrorSnackBar).toHaveBeenCalledWith({
|
||||
apolloError: error,
|
||||
apolloError: expect.any(Error),
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -263,7 +375,7 @@ describe('useWebhookForm', () => {
|
||||
it('should update operations correctly', () => {
|
||||
const { result } = renderHook(
|
||||
() => useWebhookForm({ mode: WebhookFormMode.Create }),
|
||||
{ wrapper: Wrapper },
|
||||
{ wrapper: ({ children }) => <Wrapper>{children}</Wrapper> },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
@ -277,7 +389,7 @@ describe('useWebhookForm', () => {
|
||||
it('should remove operations correctly', () => {
|
||||
const { result } = renderHook(
|
||||
() => useWebhookForm({ mode: WebhookFormMode.Create }),
|
||||
{ wrapper: Wrapper },
|
||||
{ wrapper: ({ children }) => <Wrapper>{children}</Wrapper> },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
@ -305,7 +417,10 @@ describe('useWebhookForm', () => {
|
||||
const webhookId = 'test-webhook-id';
|
||||
|
||||
it('should delete webhook successfully', async () => {
|
||||
mockDeleteOneRecord.mockResolvedValue({});
|
||||
const mocks = [
|
||||
createGetWebhookMock(webhookId),
|
||||
createSuccessfulDeleteMock(webhookId),
|
||||
];
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
@ -313,14 +428,17 @@ describe('useWebhookForm', () => {
|
||||
mode: WebhookFormMode.Edit,
|
||||
webhookId,
|
||||
}),
|
||||
{ wrapper: Wrapper },
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<Wrapper mocks={mocks}>{children}</Wrapper>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.deleteWebhook();
|
||||
await result.current.handleDelete();
|
||||
});
|
||||
|
||||
expect(mockDeleteOneRecord).toHaveBeenCalledWith(webhookId);
|
||||
expect(mockEnqueueSuccessSnackBar).toHaveBeenCalledWith({
|
||||
message: 'Webhook deleted successfully',
|
||||
});
|
||||
@ -329,11 +447,11 @@ describe('useWebhookForm', () => {
|
||||
it('should handle deletion without webhookId', async () => {
|
||||
const { result } = renderHook(
|
||||
() => useWebhookForm({ mode: WebhookFormMode.Create }),
|
||||
{ wrapper: Wrapper },
|
||||
{ wrapper: ({ children }) => <Wrapper>{children}</Wrapper> },
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.deleteWebhook();
|
||||
await result.current.handleDelete();
|
||||
});
|
||||
|
||||
expect(mockEnqueueErrorSnackBar).toHaveBeenCalledWith({
|
||||
@ -342,10 +460,19 @@ describe('useWebhookForm', () => {
|
||||
});
|
||||
|
||||
it('should handle deletion errors', async () => {
|
||||
const error = new ApolloError({
|
||||
graphQLErrors: [{ message: 'Deletion failed' }],
|
||||
});
|
||||
mockDeleteOneRecord.mockRejectedValue(error);
|
||||
const errorMock = {
|
||||
request: {
|
||||
query: DELETE_WEBHOOK,
|
||||
variables: {
|
||||
input: {
|
||||
id: webhookId,
|
||||
},
|
||||
},
|
||||
},
|
||||
error: new Error('Deletion failed'),
|
||||
};
|
||||
|
||||
const mocks = [createGetWebhookMock(webhookId), errorMock];
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
@ -353,15 +480,19 @@ describe('useWebhookForm', () => {
|
||||
mode: WebhookFormMode.Edit,
|
||||
webhookId,
|
||||
}),
|
||||
{ wrapper: Wrapper },
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<Wrapper mocks={mocks}>{children}</Wrapper>
|
||||
),
|
||||
},
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await result.current.deleteWebhook();
|
||||
await result.current.handleDelete();
|
||||
});
|
||||
|
||||
expect(mockEnqueueErrorSnackBar).toHaveBeenCalledWith({
|
||||
apolloError: error,
|
||||
apolloError: expect.any(Error),
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -370,7 +501,7 @@ describe('useWebhookForm', () => {
|
||||
it('should validate canSave property', () => {
|
||||
const { result } = renderHook(
|
||||
() => useWebhookForm({ mode: WebhookFormMode.Create }),
|
||||
{ wrapper: Wrapper },
|
||||
{ wrapper: ({ children }) => <Wrapper>{children}</Wrapper> },
|
||||
);
|
||||
|
||||
// Initially canSave should be false (form is not valid)
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { WebhookFormMode } from '@/settings/developers/constants/WebhookFormMode';
|
||||
import { Webhook } from '@/settings/developers/types/webhook/Webhook';
|
||||
import { addEmptyOperationIfNecessary } from '@/settings/developers/utils/addEmptyOperationIfNecessary';
|
||||
import {
|
||||
createWebhookCreateInput,
|
||||
createWebhookUpdateInput,
|
||||
} from '@/settings/developers/utils/createWebhookInput';
|
||||
import { parseOperationsFromStrings } from '@/settings/developers/utils/parseOperationsFromStrings';
|
||||
import {
|
||||
webhookFormSchema,
|
||||
WebhookFormValues,
|
||||
@ -16,100 +16,66 @@ import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import {
|
||||
useCreateWebhookMutation,
|
||||
useDeleteWebhookMutation,
|
||||
useGetWebhookQuery,
|
||||
useUpdateWebhookMutation,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { WEBHOOK_EMPTY_OPERATION } from '~/pages/settings/developers/webhooks/constants/WebhookEmptyOperation';
|
||||
import { WebhookOperationType } from '~/pages/settings/developers/webhooks/types/WebhookOperationsType';
|
||||
|
||||
type UseWebhookFormProps = {
|
||||
webhookId?: string;
|
||||
mode: WebhookFormMode;
|
||||
};
|
||||
|
||||
const DEFAULT_FORM_VALUES: WebhookFormValues = {
|
||||
targetUrl: '',
|
||||
description: '',
|
||||
operations: [{ object: '*', action: '*' }],
|
||||
secret: '',
|
||||
};
|
||||
|
||||
export const useWebhookForm = ({ webhookId, mode }: UseWebhookFormProps) => {
|
||||
const navigate = useNavigateSettings();
|
||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const isCreationMode = mode === WebhookFormMode.Create;
|
||||
|
||||
const { createOneRecord } = useCreateOneRecord<Webhook>({
|
||||
objectNameSingular: CoreObjectNameSingular.Webhook,
|
||||
});
|
||||
|
||||
const { updateOneRecord } = useUpdateOneRecord<Webhook>({
|
||||
objectNameSingular: CoreObjectNameSingular.Webhook,
|
||||
});
|
||||
|
||||
const { deleteOneRecord: deleteOneWebhook } = useDeleteOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.Webhook,
|
||||
});
|
||||
const [createWebhook] = useCreateWebhookMutation();
|
||||
const [updateWebhook] = useUpdateWebhookMutation();
|
||||
const [deleteWebhook] = useDeleteWebhookMutation();
|
||||
|
||||
const formConfig = useForm<WebhookFormValues>({
|
||||
mode: isCreationMode ? 'onSubmit' : 'onTouched',
|
||||
resolver: zodResolver(webhookFormSchema),
|
||||
defaultValues: {
|
||||
targetUrl: '',
|
||||
description: '',
|
||||
operations: [
|
||||
{
|
||||
object: '*',
|
||||
action: '*',
|
||||
},
|
||||
],
|
||||
secret: '',
|
||||
},
|
||||
defaultValues: DEFAULT_FORM_VALUES,
|
||||
});
|
||||
|
||||
const addEmptyOperationIfNecessary = (
|
||||
newOperations: WebhookOperationType[],
|
||||
): WebhookOperationType[] => {
|
||||
if (
|
||||
!newOperations.some((op) => op.object === '*' && op.action === '*') &&
|
||||
!newOperations.some((op) => op.object === null)
|
||||
) {
|
||||
return [...newOperations, WEBHOOK_EMPTY_OPERATION];
|
||||
}
|
||||
return newOperations;
|
||||
};
|
||||
|
||||
const cleanAndFormatOperations = (operations: WebhookOperationType[]) => {
|
||||
return Array.from(
|
||||
new Set(
|
||||
operations
|
||||
.filter((op) => isDefined(op.object) && isDefined(op.action))
|
||||
.map((op) => `${op.object}.${op.action}`),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const { loading, error } = useFindOneRecord({
|
||||
skip: isCreationMode,
|
||||
objectNameSingular: CoreObjectNameSingular.Webhook,
|
||||
objectRecordId: webhookId || '',
|
||||
const { loading, error } = useGetWebhookQuery({
|
||||
skip: isCreationMode || !webhookId,
|
||||
variables: {
|
||||
input: { id: webhookId || '' },
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (!data) return;
|
||||
const webhook = data.webhook;
|
||||
if (!webhook) return;
|
||||
|
||||
const baseOperations = data?.operations
|
||||
? data.operations.map((op: string) => {
|
||||
const [object, action] = op.split('.');
|
||||
return { object, action };
|
||||
})
|
||||
: data?.operation
|
||||
? [
|
||||
{
|
||||
object: data.operation.split('.')[0],
|
||||
action: data.operation.split('.')[1],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
const baseOperations = webhook?.operations?.length
|
||||
? parseOperationsFromStrings(webhook.operations)
|
||||
: [];
|
||||
const operations = addEmptyOperationIfNecessary(baseOperations);
|
||||
|
||||
formConfig.reset({
|
||||
targetUrl: data.targetUrl || '',
|
||||
description: data.description || '',
|
||||
targetUrl: webhook.targetUrl || '',
|
||||
description: webhook.description || '',
|
||||
operations,
|
||||
secret: data.secret || '',
|
||||
secret: webhook.secret || '',
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to load webhook`,
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -121,19 +87,9 @@ export const useWebhookForm = ({ webhookId, mode }: UseWebhookFormProps) => {
|
||||
|
||||
const handleCreate = async (formValues: WebhookFormValues) => {
|
||||
try {
|
||||
const cleanedOperations = cleanAndFormatOperations(formValues.operations);
|
||||
|
||||
const webhookData = {
|
||||
targetUrl: formValues.targetUrl.trim(),
|
||||
operations: cleanedOperations,
|
||||
description: formValues.description,
|
||||
secret: formValues.secret,
|
||||
};
|
||||
|
||||
const createdWebhook = await createOneRecord({
|
||||
id: v4(),
|
||||
...webhookData,
|
||||
});
|
||||
const input = createWebhookCreateInput(formValues);
|
||||
const { data } = await createWebhook({ variables: { input } });
|
||||
const createdWebhook = data?.createWebhook;
|
||||
|
||||
const targetUrl = createdWebhook?.targetUrl
|
||||
? `${createdWebhook?.targetUrl}`
|
||||
@ -163,23 +119,15 @@ export const useWebhookForm = ({ webhookId, mode }: UseWebhookFormProps) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const cleanedOperations = cleanAndFormatOperations(formValues.operations);
|
||||
|
||||
const webhookData = {
|
||||
targetUrl: formValues.targetUrl.trim(),
|
||||
operations: cleanedOperations,
|
||||
description: formValues.description,
|
||||
secret: formValues.secret,
|
||||
};
|
||||
|
||||
await updateOneRecord({
|
||||
idToUpdate: webhookId,
|
||||
updateOneRecordInput: webhookData,
|
||||
});
|
||||
const input = createWebhookUpdateInput(formValues, webhookId);
|
||||
const { data } = await updateWebhook({ variables: { input } });
|
||||
const updatedWebhook = data?.updateWebhook;
|
||||
|
||||
formConfig.reset(formValues);
|
||||
|
||||
const targetUrl = webhookData.targetUrl ? `${webhookData.targetUrl}` : '';
|
||||
const targetUrl = updatedWebhook?.targetUrl
|
||||
? `${updatedWebhook.targetUrl}`
|
||||
: '';
|
||||
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Webhook ${targetUrl} updated successfully`,
|
||||
@ -224,7 +172,7 @@ export const useWebhookForm = ({ webhookId, mode }: UseWebhookFormProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const deleteWebhook = async () => {
|
||||
const handleDelete = async () => {
|
||||
if (!webhookId) {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Webhook ID is required for deletion`,
|
||||
@ -233,7 +181,9 @@ export const useWebhookForm = ({ webhookId, mode }: UseWebhookFormProps) => {
|
||||
}
|
||||
|
||||
try {
|
||||
await deleteOneWebhook(webhookId);
|
||||
await deleteWebhook({
|
||||
variables: { input: { id: webhookId } },
|
||||
});
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Webhook deleted successfully`,
|
||||
});
|
||||
@ -253,7 +203,7 @@ export const useWebhookForm = ({ webhookId, mode }: UseWebhookFormProps) => {
|
||||
handleSave,
|
||||
updateOperation,
|
||||
removeOperation,
|
||||
deleteWebhook,
|
||||
handleDelete,
|
||||
isCreationMode,
|
||||
error,
|
||||
};
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
export type ApiFieldItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'internal' | 'published';
|
||||
expiration: string;
|
||||
};
|
||||
@ -1,10 +0,0 @@
|
||||
export type ApiKey = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
name: string;
|
||||
expiresAt: string;
|
||||
revokedAt: string | null;
|
||||
__typename: 'ApiKey';
|
||||
};
|
||||
@ -1,8 +0,0 @@
|
||||
export type Webhook = {
|
||||
id: string;
|
||||
targetUrl: string;
|
||||
description?: string;
|
||||
operations: string[];
|
||||
secret?: string;
|
||||
__typename: 'Webhook';
|
||||
};
|
||||
@ -0,0 +1,67 @@
|
||||
import { WEBHOOK_EMPTY_OPERATION } from '~/pages/settings/developers/webhooks/constants/WebhookEmptyOperation';
|
||||
import { WebhookOperationType } from '~/pages/settings/developers/webhooks/types/WebhookOperationsType';
|
||||
import { addEmptyOperationIfNecessary } from '../addEmptyOperationIfNecessary';
|
||||
|
||||
describe('addEmptyOperationIfNecessary', () => {
|
||||
it('should add empty operation when no wildcard or null object operations exist', () => {
|
||||
const operations: WebhookOperationType[] = [
|
||||
{ object: 'person', action: 'created' },
|
||||
{ object: 'company', action: 'updated' },
|
||||
];
|
||||
|
||||
const result = addEmptyOperationIfNecessary(operations);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ object: 'person', action: 'created' },
|
||||
{ object: 'company', action: 'updated' },
|
||||
WEBHOOK_EMPTY_OPERATION,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not add empty operation when wildcard operation exists', () => {
|
||||
const operations: WebhookOperationType[] = [
|
||||
{ object: '*', action: '*' },
|
||||
{ object: 'person', action: 'created' },
|
||||
];
|
||||
|
||||
const result = addEmptyOperationIfNecessary(operations);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ object: '*', action: '*' },
|
||||
{ object: 'person', action: 'created' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not add empty operation when null object operation exists', () => {
|
||||
const operations: WebhookOperationType[] = [
|
||||
{ object: 'person', action: 'created' },
|
||||
{ object: null, action: 'test' },
|
||||
];
|
||||
|
||||
const result = addEmptyOperationIfNecessary(operations);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ object: 'person', action: 'created' },
|
||||
{ object: null, action: 'test' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle empty array by adding empty operation', () => {
|
||||
const operations: WebhookOperationType[] = [];
|
||||
|
||||
const result = addEmptyOperationIfNecessary(operations);
|
||||
|
||||
expect(result).toEqual([WEBHOOK_EMPTY_OPERATION]);
|
||||
});
|
||||
|
||||
it('should not modify original array', () => {
|
||||
const operations: WebhookOperationType[] = [
|
||||
{ object: 'person', action: 'created' },
|
||||
];
|
||||
const originalLength = operations.length;
|
||||
|
||||
addEmptyOperationIfNecessary(operations);
|
||||
|
||||
expect(operations.length).toBe(originalLength);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,47 @@
|
||||
import { WebhookOperationType } from '~/pages/settings/developers/webhooks/types/WebhookOperationsType';
|
||||
import { cleanAndFormatOperations } from '../cleanAndFormatOperations';
|
||||
|
||||
describe('cleanAndFormatOperations', () => {
|
||||
it('should filter out operations with null object values', () => {
|
||||
const operations: WebhookOperationType[] = [
|
||||
{ object: 'person', action: 'created' },
|
||||
{ object: null, action: 'test' },
|
||||
{ object: 'person', action: 'updated' },
|
||||
];
|
||||
|
||||
const result = cleanAndFormatOperations(operations);
|
||||
|
||||
expect(result).toEqual(['person.created', 'person.updated']);
|
||||
});
|
||||
|
||||
it('should remove duplicate operations', () => {
|
||||
const operations: WebhookOperationType[] = [
|
||||
{ object: 'person', action: 'created' },
|
||||
{ object: 'person', action: 'created' },
|
||||
{ object: 'company', action: 'updated' },
|
||||
];
|
||||
|
||||
const result = cleanAndFormatOperations(operations);
|
||||
|
||||
expect(result).toEqual(['person.created', 'company.updated']);
|
||||
});
|
||||
|
||||
it('should handle empty array', () => {
|
||||
const operations: WebhookOperationType[] = [];
|
||||
|
||||
const result = cleanAndFormatOperations(operations);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle wildcard operations', () => {
|
||||
const operations: WebhookOperationType[] = [
|
||||
{ object: '*', action: '*' },
|
||||
{ object: 'person', action: 'created' },
|
||||
];
|
||||
|
||||
const result = cleanAndFormatOperations(operations);
|
||||
|
||||
expect(result).toEqual(['*.*', 'person.created']);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,71 @@
|
||||
import { WebhookFormValues } from '@/settings/developers/validation-schemas/webhookFormSchema';
|
||||
import {
|
||||
createWebhookCreateInput,
|
||||
createWebhookUpdateInput,
|
||||
} from '../createWebhookInput';
|
||||
|
||||
describe('createWebhookInput', () => {
|
||||
const mockFormValues: WebhookFormValues = {
|
||||
targetUrl: ' https://test.com/webhook ',
|
||||
description: 'Test webhook',
|
||||
operations: [
|
||||
{ object: 'person', action: 'created' },
|
||||
{ object: 'person', action: 'created' }, // duplicate
|
||||
{ object: 'company', action: 'updated' },
|
||||
{ object: null, action: 'test' }, // should be filtered out
|
||||
],
|
||||
secret: 'test-secret',
|
||||
};
|
||||
|
||||
describe('createWebhookCreateInput', () => {
|
||||
it('should create input for webhook creation', () => {
|
||||
const result = createWebhookCreateInput(mockFormValues);
|
||||
|
||||
expect(result).toEqual({
|
||||
targetUrl: 'https://test.com/webhook',
|
||||
operations: ['person.created', 'company.updated'],
|
||||
description: 'Test webhook',
|
||||
secret: 'test-secret',
|
||||
});
|
||||
});
|
||||
|
||||
it('should trim targetUrl', () => {
|
||||
const formValues: WebhookFormValues = {
|
||||
...mockFormValues,
|
||||
targetUrl: ' https://example.com ',
|
||||
};
|
||||
|
||||
const result = createWebhookCreateInput(formValues);
|
||||
|
||||
expect(result.targetUrl).toBe('https://example.com');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createWebhookUpdateInput', () => {
|
||||
it('should create input for webhook update with id', () => {
|
||||
const webhookId = 'test-webhook-id';
|
||||
const result = createWebhookUpdateInput(mockFormValues, webhookId);
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'test-webhook-id',
|
||||
targetUrl: 'https://test.com/webhook',
|
||||
operations: ['person.created', 'company.updated'],
|
||||
description: 'Test webhook',
|
||||
secret: 'test-secret',
|
||||
});
|
||||
});
|
||||
|
||||
it('should trim targetUrl and include id', () => {
|
||||
const formValues: WebhookFormValues = {
|
||||
...mockFormValues,
|
||||
targetUrl: ' https://example.com ',
|
||||
};
|
||||
const webhookId = 'test-webhook-id';
|
||||
|
||||
const result = createWebhookUpdateInput(formValues, webhookId);
|
||||
|
||||
expect(result.targetUrl).toBe('https://example.com');
|
||||
expect(result.id).toBe('test-webhook-id');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,42 @@
|
||||
import { parseOperationsFromStrings } from '../parseOperationsFromStrings';
|
||||
|
||||
describe('parseOperationsFromStrings', () => {
|
||||
it('should parse operation strings into object/action pairs', () => {
|
||||
const operations = ['person.created', 'company.updated', 'lead.deleted'];
|
||||
|
||||
const result = parseOperationsFromStrings(operations);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ object: 'person', action: 'created' },
|
||||
{ object: 'company', action: 'updated' },
|
||||
{ object: 'lead', action: 'deleted' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle wildcard operations', () => {
|
||||
const operations = ['*.*', 'person.created'];
|
||||
|
||||
const result = parseOperationsFromStrings(operations);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ object: '*', action: '*' },
|
||||
{ object: 'person', action: 'created' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle empty array', () => {
|
||||
const operations: string[] = [];
|
||||
|
||||
const result = parseOperationsFromStrings(operations);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle operations with multiple dots by taking first two parts', () => {
|
||||
const operations = ['person.created.test'];
|
||||
|
||||
const result = parseOperationsFromStrings(operations);
|
||||
|
||||
expect(result).toEqual([{ object: 'person', action: 'created' }]);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,14 @@
|
||||
import { WEBHOOK_EMPTY_OPERATION } from '~/pages/settings/developers/webhooks/constants/WebhookEmptyOperation';
|
||||
import { WebhookOperationType } from '~/pages/settings/developers/webhooks/types/WebhookOperationsType';
|
||||
|
||||
export const addEmptyOperationIfNecessary = (
|
||||
newOperations: WebhookOperationType[],
|
||||
): WebhookOperationType[] => {
|
||||
if (
|
||||
!newOperations.some((op) => op.object === '*' && op.action === '*') &&
|
||||
!newOperations.some((op) => op.object === null)
|
||||
) {
|
||||
return [...newOperations, WEBHOOK_EMPTY_OPERATION];
|
||||
}
|
||||
return newOperations;
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { WebhookOperationType } from '~/pages/settings/developers/webhooks/types/WebhookOperationsType';
|
||||
|
||||
export const cleanAndFormatOperations = (
|
||||
operations: WebhookOperationType[],
|
||||
) => {
|
||||
return Array.from(
|
||||
new Set(
|
||||
operations
|
||||
.filter((op) => isDefined(op.object) && isDefined(op.action))
|
||||
.map((op) => `${op.object}.${op.action}`),
|
||||
),
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,28 @@
|
||||
import { WebhookFormValues } from '@/settings/developers/validation-schemas/webhookFormSchema';
|
||||
import { cleanAndFormatOperations } from './cleanAndFormatOperations';
|
||||
|
||||
export const createWebhookCreateInput = (formValues: WebhookFormValues) => {
|
||||
const cleanedOperations = cleanAndFormatOperations(formValues.operations);
|
||||
|
||||
return {
|
||||
targetUrl: formValues.targetUrl.trim(),
|
||||
operations: cleanedOperations,
|
||||
description: formValues.description,
|
||||
secret: formValues.secret,
|
||||
};
|
||||
};
|
||||
|
||||
export const createWebhookUpdateInput = (
|
||||
formValues: WebhookFormValues,
|
||||
webhookId: string,
|
||||
) => {
|
||||
const cleanedOperations = cleanAndFormatOperations(formValues.operations);
|
||||
|
||||
return {
|
||||
id: webhookId,
|
||||
targetUrl: formValues.targetUrl.trim(),
|
||||
operations: cleanedOperations,
|
||||
description: formValues.description,
|
||||
secret: formValues.secret,
|
||||
};
|
||||
};
|
||||
@ -2,8 +2,6 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { NEVER_EXPIRE_DELTA_IN_YEARS } from '@/settings/developers/constants/NeverExpireDeltaInYears';
|
||||
import { ApiFieldItem } from '@/settings/developers/types/api-key/ApiFieldItem';
|
||||
import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
|
||||
import { beautifyDateDiff } from '~/utils/date-utils';
|
||||
|
||||
export const doesNeverExpire = (expiresAt: string) => {
|
||||
@ -28,16 +26,3 @@ export const formatExpiration = (
|
||||
}
|
||||
return withExpiresMention ? `Expires in ${dateDiff}` : `In ${dateDiff}`;
|
||||
};
|
||||
|
||||
export const formatExpirations = (
|
||||
apiKeys: Array<Pick<ApiKey, 'id' | 'name' | 'expiresAt'>>,
|
||||
): ApiFieldItem[] => {
|
||||
return apiKeys.map(({ id, name, expiresAt }) => {
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
expiration: formatExpiration(expiresAt || null),
|
||||
type: 'internal',
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
import { WebhookOperationType } from '~/pages/settings/developers/webhooks/types/WebhookOperationsType';
|
||||
|
||||
export const parseOperationsFromStrings = (
|
||||
operations: string[],
|
||||
): WebhookOperationType[] => {
|
||||
return operations.map((op: string) => {
|
||||
const [object, action] = op.split('.');
|
||||
return { object, action };
|
||||
});
|
||||
};
|
||||
@ -1,6 +1,5 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { fireEvent, userEvent, within } from '@storybook/test';
|
||||
import { HttpResponse, graphql } from 'msw';
|
||||
|
||||
import { SettingsDevelopersApiKeyDetail } from '~/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail';
|
||||
import {
|
||||
@ -21,26 +20,7 @@ const meta: Meta<PageDecoratorArgs> = {
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
msw: {
|
||||
handlers: [
|
||||
...graphqlMocks.handlers,
|
||||
graphql.query('FindOneApiKey', () => {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
apiKey: {
|
||||
__typename: 'ApiKey',
|
||||
id: 'f7c6d736-8fcd-4e9c-ab99-28f6a9031570',
|
||||
revokedAt: null,
|
||||
expiresAt: '2024-03-10T09:23:10.511Z',
|
||||
name: 'sfsfdsf',
|
||||
updatedAt: '2024-02-24T10:23:10.673Z',
|
||||
createdAt: '2024-02-24T10:23:10.673Z',
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
],
|
||||
},
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
export default meta;
|
||||
@ -50,14 +30,14 @@ export type Story = StoryObj<typeof SettingsDevelopersApiKeyDetail>;
|
||||
export const Default: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await canvas.findByText('sfsfdsf', undefined, { timeout: 3000 });
|
||||
await canvas.findByText('Zapier Integration', undefined, { timeout: 3000 });
|
||||
},
|
||||
};
|
||||
|
||||
export const RegenerateApiKey: Story = {
|
||||
play: async ({ step }) => {
|
||||
const canvas = within(document.body);
|
||||
await canvas.findByText('sfsfdsf', undefined, { timeout: 3000 });
|
||||
await canvas.findByText('Zapier Integration', undefined, { timeout: 3000 });
|
||||
|
||||
await userEvent.click(await canvas.findByText('Regenerate Key'));
|
||||
|
||||
@ -85,7 +65,7 @@ export const RegenerateApiKey: Story = {
|
||||
export const DeleteApiKey: Story = {
|
||||
play: async ({ canvasElement, step }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await canvas.findByText('sfsfdsf', undefined, { timeout: 3000 });
|
||||
await canvas.findByText('Zapier Integration', undefined, { timeout: 3000 });
|
||||
|
||||
await userEvent.click(await canvas.findByText('Delete'));
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ export type Story = StoryObj<typeof SettingsDevelopersWebhookNew>;
|
||||
export const Default: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await canvas.findByText('New Webhook', undefined, { timeout: 10000 });
|
||||
await canvas.findByText('New Webhook', undefined, { timeout: 3000 });
|
||||
await canvas.findByText(
|
||||
'We will send POST requests to this endpoint for every new event',
|
||||
);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { within } from '@storybook/test';
|
||||
import { expect, within } from '@storybook/test';
|
||||
|
||||
import {
|
||||
PageDecorator,
|
||||
@ -28,11 +28,20 @@ export type Story = StoryObj<typeof SettingsDevelopersWebhookDetail>;
|
||||
export const Default: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
await canvas.findByText(
|
||||
'We will send POST requests to this endpoint for every new event',
|
||||
await canvas.findByDisplayValue(
|
||||
'https://api.slackbot.io/webhooks/twenty',
|
||||
undefined,
|
||||
{ timeout: 10000 },
|
||||
{
|
||||
timeout: 3000,
|
||||
},
|
||||
);
|
||||
await canvas.findByDisplayValue('Slack notifications for lead updates');
|
||||
|
||||
const allObjectsLabels = await canvas.findAllByText('All Objects');
|
||||
expect(allObjectsLabels).toHaveLength(2);
|
||||
await canvas.findByText('Created');
|
||||
await canvas.findByText('Updated');
|
||||
|
||||
await canvas.findByText('Delete this webhook');
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,19 +1,13 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { ApiKeyInput } from '@/settings/developers/components/ApiKeyInput';
|
||||
import { ApiKeyNameInput } from '@/settings/developers/components/ApiKeyNameInput';
|
||||
import { apiKeyTokenFamilyState } from '@/settings/developers/states/apiKeyTokenFamilyState';
|
||||
import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
|
||||
import { computeNewExpirationDate } from '@/settings/developers/utils/computeNewExpirationDate';
|
||||
import { formatExpiration } from '@/settings/developers/utils/formatExpiration';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
@ -23,10 +17,16 @@ import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModa
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { H2Title, IconRepeat, IconTrash } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { useGenerateApiKeyTokenMutation } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
useCreateApiKeyMutation,
|
||||
useGenerateApiKeyTokenMutation,
|
||||
useGetApiKeyQuery,
|
||||
useRevokeApiKeyMutation,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
@ -67,30 +67,34 @@ export const SettingsDevelopersApiKeyDetail = () => {
|
||||
);
|
||||
|
||||
const [generateOneApiKeyToken] = useGenerateApiKeyTokenMutation();
|
||||
const { createOneRecord: createOneApiKey } = useCreateOneRecord<ApiKey>({
|
||||
objectNameSingular: CoreObjectNameSingular.ApiKey,
|
||||
});
|
||||
const { updateOneRecord: updateApiKey } = useUpdateOneRecord<ApiKey>({
|
||||
objectNameSingular: CoreObjectNameSingular.ApiKey,
|
||||
});
|
||||
|
||||
const [apiKeyName, setApiKeyName] = useState('');
|
||||
|
||||
const { record: apiKeyData, loading } = useFindOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.ApiKey,
|
||||
objectRecordId: apiKeyId,
|
||||
onCompleted: (record) => {
|
||||
setApiKeyName(record.name);
|
||||
const [createApiKey] = useCreateApiKeyMutation();
|
||||
const [revokeApiKey] = useRevokeApiKeyMutation();
|
||||
const { data: apiKeyData } = useGetApiKeyQuery({
|
||||
variables: {
|
||||
input: {
|
||||
id: apiKeyId,
|
||||
},
|
||||
},
|
||||
onCompleted: (data) => {
|
||||
if (isDefined(data?.apiKey)) {
|
||||
setApiKeyName(data.apiKey.name);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const apiKey = apiKeyData?.apiKey;
|
||||
const [apiKeyName, setApiKeyName] = useState('');
|
||||
|
||||
const deleteIntegration = async (redirect = true) => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
await updateApiKey?.({
|
||||
idToUpdate: apiKeyId,
|
||||
updateOneRecordInput: { revokedAt: DateTime.now().toString() },
|
||||
await revokeApiKey({
|
||||
variables: {
|
||||
input: {
|
||||
id: apiKeyId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (redirect) {
|
||||
navigate(SettingsPath.APIs);
|
||||
@ -106,11 +110,17 @@ export const SettingsDevelopersApiKeyDetail = () => {
|
||||
name: string,
|
||||
newExpiresAt: string | null,
|
||||
) => {
|
||||
const newApiKey = await createOneApiKey?.({
|
||||
name: name,
|
||||
expiresAt: newExpiresAt ?? '',
|
||||
const { data: newApiKeyData } = await createApiKey({
|
||||
variables: {
|
||||
input: {
|
||||
name: name,
|
||||
expiresAt: newExpiresAt ?? '',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const newApiKey = newApiKeyData?.createApiKey;
|
||||
|
||||
if (!newApiKey) {
|
||||
return;
|
||||
}
|
||||
@ -130,18 +140,18 @@ export const SettingsDevelopersApiKeyDetail = () => {
|
||||
const regenerateApiKey = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (isNonEmptyString(apiKeyData?.name)) {
|
||||
if (isNonEmptyString(apiKey?.name)) {
|
||||
const newExpiresAt = computeNewExpirationDate(
|
||||
apiKeyData?.expiresAt,
|
||||
apiKeyData?.createdAt,
|
||||
apiKey?.expiresAt,
|
||||
apiKey?.createdAt,
|
||||
);
|
||||
const apiKey = await createIntegration(apiKeyData?.name, newExpiresAt);
|
||||
const newApiKey = await createIntegration(apiKey?.name, newExpiresAt);
|
||||
await deleteIntegration(false);
|
||||
|
||||
if (isNonEmptyString(apiKey?.token)) {
|
||||
setApiKeyTokenCallback(apiKey.id, apiKey.token);
|
||||
if (isNonEmptyString(newApiKey?.token)) {
|
||||
setApiKeyTokenCallback(newApiKey.id, newApiKey.token);
|
||||
navigate(SettingsPath.ApiKeyDetail, {
|
||||
apiKeyId: apiKey.id,
|
||||
apiKeyId: newApiKey.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -158,9 +168,9 @@ export const SettingsDevelopersApiKeyDetail = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{apiKeyData?.name && (
|
||||
{apiKey?.name && (
|
||||
<SubMenuTopBarContainer
|
||||
title={apiKeyData?.name}
|
||||
title={apiKey?.name}
|
||||
links={[
|
||||
{
|
||||
children: t`Workspace`,
|
||||
@ -196,11 +206,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
|
||||
onClick={() => openModal(REGENERATE_API_KEY_MODAL_ID)}
|
||||
/>
|
||||
<StyledInfo>
|
||||
{formatExpiration(
|
||||
apiKeyData?.expiresAt || '',
|
||||
true,
|
||||
false,
|
||||
)}
|
||||
{formatExpiration(apiKey?.expiresAt || '', true, false)}
|
||||
</StyledInfo>
|
||||
</StyledInputContainer>
|
||||
</>
|
||||
@ -210,8 +216,8 @@ export const SettingsDevelopersApiKeyDetail = () => {
|
||||
<H2Title title={t`Name`} description={t`Name of your API key`} />
|
||||
<ApiKeyNameInput
|
||||
apiKeyName={apiKeyName}
|
||||
apiKeyId={apiKeyData?.id}
|
||||
disabled={loading}
|
||||
apiKeyId={apiKey?.id}
|
||||
disabled={isLoading}
|
||||
onNameUpdate={setApiKeyName}
|
||||
/>
|
||||
</Section>
|
||||
@ -221,13 +227,9 @@ export const SettingsDevelopersApiKeyDetail = () => {
|
||||
description={t`When the key will be disabled`}
|
||||
/>
|
||||
<TextInput
|
||||
instanceId={`api-key-expiration-${apiKeyData?.id}`}
|
||||
instanceId={`api-key-expiration-${apiKey?.id}`}
|
||||
placeholder={t`E.g. backoffice integration`}
|
||||
value={formatExpiration(
|
||||
apiKeyData?.expiresAt || '',
|
||||
true,
|
||||
false,
|
||||
)}
|
||||
value={formatExpiration(apiKey?.expiresAt || '', true, false)}
|
||||
disabled
|
||||
fullWidth
|
||||
/>
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { EXPIRATION_DATES } from '@/settings/developers/constants/ExpirationDates';
|
||||
import { apiKeyTokenFamilyState } from '@/settings/developers/states/apiKeyTokenFamilyState';
|
||||
import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
@ -18,7 +15,10 @@ import { Key } from 'ts-key-enum';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { useGenerateApiKeyTokenMutation } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
useCreateApiKeyMutation,
|
||||
useGenerateApiKeyTokenMutation,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
@ -34,9 +34,7 @@ export const SettingsDevelopersApiKeysNew = () => {
|
||||
name: '',
|
||||
});
|
||||
|
||||
const { createOneRecord: createOneApiKey } = useCreateOneRecord<ApiKey>({
|
||||
objectNameSingular: CoreObjectNameSingular.ApiKey,
|
||||
});
|
||||
const [createApiKey] = useCreateApiKeyMutation();
|
||||
|
||||
const setApiKeyTokenCallback = useRecoilCallback(
|
||||
({ set }) =>
|
||||
@ -51,11 +49,17 @@ export const SettingsDevelopersApiKeysNew = () => {
|
||||
.plus({ days: formValues.expirationDate ?? 30 })
|
||||
.toString();
|
||||
|
||||
const newApiKey = await createOneApiKey?.({
|
||||
name: formValues.name,
|
||||
expiresAt,
|
||||
const { data: newApiKeyData } = await createApiKey({
|
||||
variables: {
|
||||
input: {
|
||||
name: formValues.name,
|
||||
expiresAt,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const newApiKey = newApiKeyData?.createApiKey;
|
||||
|
||||
if (!newApiKey) {
|
||||
return;
|
||||
}
|
||||
@ -77,7 +81,7 @@ export const SettingsDevelopersApiKeysNew = () => {
|
||||
});
|
||||
}
|
||||
};
|
||||
const canSave = !!formValues.name && createOneApiKey;
|
||||
const canSave = !!formValues.name && createApiKey;
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
title={t`New key`}
|
||||
|
||||
@ -5,6 +5,7 @@ import { TRACK_ANALYTICS } from '@/analytics/graphql/queries/track';
|
||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import { mockedApiKeys } from '~/testing/mock-data/api-keys';
|
||||
import {
|
||||
getCompaniesMock,
|
||||
getCompanyDuplicateMock,
|
||||
@ -650,26 +651,6 @@ export const graphqlMocks = {
|
||||
});
|
||||
},
|
||||
),
|
||||
graphql.query<GraphQLQuery, { objectRecordId: string }>(
|
||||
'FindOneWebhook',
|
||||
({ variables: { objectRecordId } }) => {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
webhook: {
|
||||
__typename: 'Webhook',
|
||||
id: objectRecordId,
|
||||
createdAt: '2021-08-27T12:00:00Z',
|
||||
updatedAt: '2021-08-27T12:00:00Z',
|
||||
deletedAt: null,
|
||||
targetUrl: 'https://example.com/webhook',
|
||||
description: 'A Sample Description',
|
||||
operations: ['*.created', '*.updated'],
|
||||
secret: 'sample-secret',
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
),
|
||||
graphql.query('FindManyWorkflows', () => {
|
||||
return HttpResponse.json({
|
||||
data: workflowQueryResult,
|
||||
@ -711,5 +692,64 @@ export const graphqlMocks = {
|
||||
{ status: 200 },
|
||||
);
|
||||
}),
|
||||
metadataGraphql.query('GetApiKeys', () => {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
apiKeys: mockedApiKeys.map((apiKey) => ({
|
||||
__typename: 'ApiKey',
|
||||
...apiKey,
|
||||
revokedAt: null,
|
||||
})),
|
||||
},
|
||||
});
|
||||
}),
|
||||
metadataGraphql.query('GetApiKey', ({ variables }) => {
|
||||
const apiKeyId = variables.input?.id;
|
||||
const apiKey = mockedApiKeys.find((key) => key.id === apiKeyId);
|
||||
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
apiKey: apiKey
|
||||
? {
|
||||
__typename: 'ApiKey',
|
||||
...apiKey,
|
||||
revokedAt: null,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
});
|
||||
}),
|
||||
metadataGraphql.query('GetWebhooks', () => {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
webhooks: [
|
||||
{
|
||||
__typename: 'Webhook',
|
||||
id: '1234',
|
||||
targetUrl: 'https://api.slackbot.io/webhooks/twenty',
|
||||
operations: ['*.created', '*.updated'],
|
||||
description: 'Slack notifications for lead updates',
|
||||
secret: 'sample-secret',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}),
|
||||
metadataGraphql.query('GetWebhook', ({ variables }) => {
|
||||
const webhookId = variables.input?.id;
|
||||
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
webhook: {
|
||||
__typename: 'Webhook',
|
||||
id: webhookId || '1234',
|
||||
targetUrl: 'https://api.slackbot.io/webhooks/twenty',
|
||||
operations: ['*.created', '*.updated'],
|
||||
description: 'Slack notifications for lead updates',
|
||||
secret: 'sample-secret',
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ApiKey } from '@/settings/developers/types/api-key/ApiKey';
|
||||
import { ApiKey } from '~/generated-metadata/graphql';
|
||||
|
||||
type MockedApiKey = Pick<
|
||||
ApiKey,
|
||||
|
||||
Reference in New Issue
Block a user