Logic function refactorization (#17861)

As title
This commit is contained in:
martmull
2026-02-12 11:40:49 +01:00
committed by GitHub
parent b456f79167
commit a4ed043d43
122 changed files with 1441 additions and 1897 deletions
@@ -1,4 +1,4 @@
import { useGetAvailablePackages } from '@/settings/logic-functions/hooks/useGetAvailablePackages';
import { useGetAvailablePackages } from '@/logic-functions/hooks/useGetAvailablePackages';
import { type EditorProps, type Monaco } from '@monaco-editor/react';
import { type editor } from 'monaco-editor';
import { AutoTypings } from 'monaco-editor-auto-typings';
@@ -1,4 +1,4 @@
import { type LogicFunctionNewFormValues } from '@/settings/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { type LogicFunctionNewFormValues } from '@/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { TextArea } from '@/ui/input/components/TextArea';
import styled from '@emotion/styled';
@@ -21,13 +21,11 @@ export const SettingsLogicFunctionCodeEditorTab = ({
handleExecute,
onChange,
isTesting = false,
isManaged = false,
}: {
files: File[];
handleExecute: () => void;
onChange: (filePath: string, value: string) => void;
isTesting?: boolean;
isManaged?: boolean;
}) => {
const activeTabId = useRecoilComponentValue(
activeTabIdComponentState,
@@ -68,16 +66,6 @@ export const SettingsLogicFunctionCodeEditorTab = ({
onChange={(newCodeValue: string) =>
onChange(activeTabId, newCodeValue)
}
options={
isManaged
? {
readOnly: true,
readOnlyMessage: {
value: t`Managed logic functions are not editable`,
},
}
: undefined
}
/>
)}
</Section>
@@ -1,6 +1,6 @@
import { SettingsLogicFunctionNewForm } from '@/settings/logic-functions/components/SettingsLogicFunctionNewForm';
import { SettingsLogicFunctionTabEnvironmentVariablesSection } from '@/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection';
import { type LogicFunctionFormValues } from '@/settings/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { type LogicFunctionFormValues } from '@/logic-functions/hooks/useLogicFunctionUpdateFormState';
export const SettingsLogicFunctionSettingsTab = ({
formValues,
@@ -5,21 +5,24 @@ import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { H2Title } from 'twenty-ui/display';
import { H2Title, OverflowingTextWithTooltip } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { isDefined } from 'twenty-shared/utils';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { Tag } from 'twenty-ui/components';
import { type LogicFunction } from '~/generated-metadata/graphql';
export const StyledRouteTriggerTableRow = styled(TableRow)`
grid-template-columns: 1fr 120px 120px;
`;
// TODO: @Charles put back with new sources
// const StyledTableCell = styled(TableCell)`
// color: ${({ theme }) => theme.font.color.tertiary};
// gap: ${({ theme }) => theme.spacing(2)};
// min-width: 0;
// overflow: hidden;
// `;
const StyledTableCell = styled(TableCell)`
color: ${({ theme }) => theme.font.color.tertiary};
gap: ${({ theme }) => theme.spacing(2)};
min-width: 0;
overflow: hidden;
`;
const StyledRouteTriggerTableHeaderRow = styled(StyledRouteTriggerTableRow)`
margin-bottom: ${({ theme }) => theme.spacing(2)};
@@ -36,22 +39,31 @@ const StyledEmptyState = styled.div`
`;
export const SettingsLogicFunctionTriggersTab = ({
logicFunction: _logicFunction,
logicFunction,
}: {
logicFunction: LogicFunction;
}) => {
const { t } = useLingui();
const cronTriggers: [] = [];
const cronTrigger = logicFunction.cronTriggerSettings;
const routeTriggers: [] = [];
const routeTrigger = logicFunction.httpRouteTriggerSettings;
const databaseEvents: [] = [];
const databaseEventTriggerSettings =
logicFunction.databaseEventTriggerSettings;
const hasNoTriggers =
databaseEvents.length === 0 &&
cronTriggers.length === 0 &&
routeTriggers.length === 0;
let databaseEventTrigger = undefined;
if (isDefined(databaseEventTriggerSettings)) {
const [object, action]: [string, string] =
databaseEventTriggerSettings.eventName.split('.');
databaseEventTrigger = {
object,
action,
updatedFields: databaseEventTriggerSettings.updatedFields,
};
}
const hasNoTriggers = !cronTrigger && !routeTrigger && !databaseEventTrigger;
if (hasNoTriggers) {
return (
@@ -69,37 +81,37 @@ export const SettingsLogicFunctionTriggersTab = ({
return (
<>
{databaseEvents.length > 0 && (
{isDefined(databaseEventTrigger) && (
<Section>
<H2Title
title={t`Database event`}
description={t`Select the events that should trigger the function`}
/>
<SettingsDatabaseEventsForm events={databaseEvents} disabled />
<SettingsDatabaseEventsForm
events={[databaseEventTrigger]}
disabled
/>
</Section>
)}
{cronTriggers.length > 0 && (
{isDefined(cronTrigger) && (
<Section>
<H2Title
title={t`Cron`}
description={t`Triggers the function at regular intervals`}
/>
{cronTriggers.map((cronTrigger, index) => (
<FormTextFieldInput
key={index}
label={t`Expression`}
placeholder="0 */1 * * *"
hint={t`Format: [Minute] [Hour] [Day of Month] [Month] [Day of Week]`}
onChange={() => {}}
readonly
defaultValue={cronTrigger}
/>
))}
<FormTextFieldInput
label={t`Expression`}
placeholder="0 */1 * * *"
hint={t`Format: [Minute] [Hour] [Day of Month] [Month] [Day of Week]`}
onChange={() => {}}
readonly
defaultValue={cronTrigger.pattern}
/>
</Section>
)}
{routeTriggers.length > 0 && (
{isDefined(routeTrigger) && (
<Section>
<H2Title
title={t`Http`}
@@ -111,24 +123,21 @@ export const SettingsLogicFunctionTriggersTab = ({
<TableHeader>{t`Method`}</TableHeader>
<TableHeader>{t`Auth Required`}</TableHeader>
</StyledRouteTriggerTableHeaderRow>
{routeTriggers.map((_, _index) => (
<></>
// <StyledRouteTriggerTableRow key={index}>
// <StyledTableCell>
// <OverflowingTextWithTooltip
// text={`${REACT_APP_SERVER_BASE_URL}/s${routeTrigger.path}`}
// />
// </StyledTableCell>
// <StyledTableCell>{routeTrigger.httpMethod}</StyledTableCell>
// <StyledTableCell>
// <Tag
// text={routeTrigger.isAuthRequired ? t`True` : t`False`}
// color={routeTrigger.isAuthRequired ? 'green' : 'orange'}
// weight="medium"
// />
// </StyledTableCell>
// </StyledRouteTriggerTableRow>
))}
<StyledRouteTriggerTableRow>
<StyledTableCell>
<OverflowingTextWithTooltip
text={`${REACT_APP_SERVER_BASE_URL}/s${routeTrigger.path}`}
/>
</StyledTableCell>
<StyledTableCell>{routeTrigger.httpMethod}</StyledTableCell>
<StyledTableCell>
<Tag
text={routeTrigger.isAuthRequired ? t`True` : t`False`}
color={routeTrigger.isAuthRequired ? 'green' : 'orange'}
weight="medium"
/>
</StyledTableCell>
</StyledRouteTriggerTableRow>
</Table>
</Section>
)}
@@ -1,19 +0,0 @@
import { gql } from '@apollo/client';
export const LOGIC_FUNCTION_FRAGMENT = gql`
fragment LogicFunctionFields on LogicFunction {
id
name
description
runtime
timeoutSeconds
sourceHandlerPath
builtHandlerPath
handlerName
toolInputSchema
isTool
applicationId
createdAt
updatedAt
}
`;
@@ -1,13 +0,0 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const CREATE_DEFAULT_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
mutation CreateDefaultLogicFunctionItem(
$input: CreateDefaultLogicFunctionInput!
) {
createDefaultLogicFunction(input: $input) {
...LogicFunctionFields
}
}
`;
@@ -1,11 +0,0 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const DELETE_ONE_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
mutation DeleteOneLogicFunction($input: LogicFunctionIdInput!) {
deleteOneLogicFunction(input: $input) {
...LogicFunctionFields
}
}
`;
@@ -1,7 +0,0 @@
import { gql } from '@apollo/client';
export const FIND_MANY_AVAILABLE_PACKAGES = gql`
query FindManyAvailablePackages($input: LogicFunctionIdInput!) {
getAvailablePackages(input: $input)
}
`;
@@ -1,11 +0,0 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const FIND_MANY_LOGIC_FUNCTIONS = gql`
${LOGIC_FUNCTION_FRAGMENT}
query GetManyLogicFunctions {
findManyLogicFunctions {
...LogicFunctionFields
}
}
`;
@@ -1,11 +0,0 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const FIND_ONE_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
query GetOneLogicFunction($input: LogicFunctionIdInput!) {
findOneLogicFunction(input: $input) {
...LogicFunctionFields
}
}
`;
@@ -1,34 +0,0 @@
import { useLogicFunctionUpdateFormState } from '@/settings/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
jest.mock('@/settings/logic-functions/hooks/useGetOneLogicFunction', () => ({
useGetOneLogicFunction: jest.fn(),
}));
describe('useLogicFunctionUpdateFormState', () => {
test('should return a form', () => {
const logicFunctionId = 'logicFunctionId';
const useGetOneLogicFunctionMock = jest.requireMock(
'@/settings/logic-functions/hooks/useGetOneLogicFunction',
);
useGetOneLogicFunctionMock.useGetOneLogicFunction.mockReturnValue({
logicFunction: { name: 'name' },
loading: false,
});
const { result } = renderHook(
() => useLogicFunctionUpdateFormState({ logicFunctionId }),
{
wrapper: RecoilRoot,
},
);
const { formValues } = result.current;
expect(formValues).toEqual({
name: '',
description: '',
code: { src: { 'index.ts': '' } },
});
});
});
@@ -1,24 +0,0 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useQuery } from '@apollo/client';
import { FIND_MANY_AVAILABLE_PACKAGES } from '@/settings/logic-functions/graphql/queries/findManyAvailablePackages';
import {
type FindManyAvailablePackagesQuery,
type FindManyAvailablePackagesQueryVariables,
type LogicFunctionIdInput,
} from '~/generated-metadata/graphql';
export const useGetAvailablePackages = (input: LogicFunctionIdInput) => {
const apolloMetadataClient = useApolloCoreClient();
const { data } = useQuery<
FindManyAvailablePackagesQuery,
FindManyAvailablePackagesQueryVariables
>(FIND_MANY_AVAILABLE_PACKAGES, {
client: apolloMetadataClient ?? undefined,
variables: {
input,
},
});
return {
availablePackages: data?.getAvailablePackages || null,
};
};
@@ -1,31 +0,0 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { FIND_ONE_LOGIC_FUNCTION } from '@/settings/logic-functions/graphql/queries/findOneLogicFunction';
import { useQuery } from '@apollo/client';
import {
type GetOneLogicFunctionQuery,
type GetOneLogicFunctionQueryVariables,
type LogicFunctionIdInput,
} from '~/generated-metadata/graphql';
export const useGetOneLogicFunction = ({
id,
onCompleted,
}: LogicFunctionIdInput & {
onCompleted?: (data: GetOneLogicFunctionQuery) => void;
}) => {
const apolloMetadataClient = useApolloCoreClient();
const { data, loading } = useQuery<
GetOneLogicFunctionQuery,
GetOneLogicFunctionQueryVariables
>(FIND_ONE_LOGIC_FUNCTION, {
client: apolloMetadataClient ?? undefined,
variables: {
input: { id },
},
onCompleted,
});
return {
logicFunction: data?.findOneLogicFunction || null,
loading,
};
};
@@ -1,61 +0,0 @@
import { useGetOneLogicFunction } from '@/settings/logic-functions/hooks/useGetOneLogicFunction';
import { type Dispatch, type SetStateAction, useState } from 'react';
import { type Sources } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
type GetOneLogicFunctionQuery,
type LogicFunction,
} from '~/generated-metadata/graphql';
export type LogicFunctionNewFormValues = {
name: string;
description: string;
};
export type LogicFunctionFormValues = LogicFunctionNewFormValues & {
code: Sources;
};
type SetLogicFunctionFormValues = Dispatch<
SetStateAction<LogicFunctionFormValues>
>;
export const useLogicFunctionUpdateFormState = ({
logicFunctionId,
}: {
logicFunctionId: string;
}): {
formValues: LogicFunctionFormValues;
logicFunction: LogicFunction | null;
setFormValues: SetLogicFunctionFormValues;
loading: boolean;
} => {
const [formValues, setFormValues] = useState<LogicFunctionFormValues>({
name: '',
description: '',
code: { src: { 'index.ts': '' } },
});
const { logicFunction, loading: logicFunctionLoading } =
useGetOneLogicFunction({
id: logicFunctionId,
onCompleted: (data: GetOneLogicFunctionQuery) => {
const fn = data?.findOneLogicFunction;
if (isDefined(fn)) {
setFormValues((prevState) => ({
...prevState,
name: fn.name || '',
description: fn.description || '',
}));
}
},
});
return {
formValues,
setFormValues,
logicFunction,
loading: logicFunctionLoading,
};
};
@@ -1,8 +1,8 @@
import { createState } from 'twenty-ui/utilities';
import { type GetManyLogicFunctionsQuery } from '~/generated-metadata/graphql';
import { type FindManyLogicFunctionsQuery } from '~/generated-metadata/graphql';
export type LogicFunction =
GetManyLogicFunctionsQuery['findManyLogicFunctions'][number];
FindManyLogicFunctionsQuery['findManyLogicFunctions'][number];
export const logicFunctionsState = createState<LogicFunction[]>({
key: 'logicFunctionsState',