mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-23 12:02:10 +03:00
7415 serverless functions update environment variables in a dedicated tab in settings functions not a env file (#7939)
![image](https://github.com/user-attachments/assets/0ef9551d-d867-479e-9a76-faee6930bc0a) ![image](https://github.com/user-attachments/assets/a7aac417-4dd8-401f-8d5b-5b72f31710f6) ![image](https://github.com/user-attachments/assets/16c98e52-a2db-4ed3-b5d2-77745b4d2918) ![image](https://github.com/user-attachments/assets/847d23d6-8a58-4d8f-aff1-4f8a81862964)
This commit is contained in:
parent
7fc844ea8f
commit
e767f16dbe
@ -33,15 +33,15 @@ const documents = {
|
||||
"\n mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n settings\n }\n }\n": types.DeleteOneFieldMetadataItemDocument,
|
||||
"\n mutation DeleteOneRelationMetadataItem($idToDelete: UUID!) {\n deleteOneRelation(input: { id: $idToDelete }) {\n id\n }\n }\n": types.DeleteOneRelationMetadataItemDocument,
|
||||
"\n query ObjectMetadataItems(\n $objectFilter: objectFilter\n $fieldFilter: fieldFilter\n ) {\n objects(paging: { first: 1000 }, filter: $objectFilter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n indexMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n name\n indexWhereClause\n indexType\n isUnique\n indexFieldMetadatas(paging: { first: 100 }) {\n edges {\n node {\n id\n createdAt\n updatedAt\n order\n fieldMetadataId\n }\n }\n }\n }\n }\n }\n fields(paging: { first: 1000 }, filter: $fieldFilter) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n isUnique\n createdAt\n updatedAt\n defaultValue\n options\n settings\n relationDefinition {\n relationId\n direction\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n": types.ObjectMetadataItemsDocument,
|
||||
"\n fragment ServerlessFunctionFields on ServerlessFunction {\n id\n name\n description\n runtime\n syncStatus\n latestVersion\n createdAt\n updatedAt\n }\n": types.ServerlessFunctionFieldsFragmentDoc,
|
||||
"\n fragment ServerlessFunctionFields on ServerlessFunction {\n id\n name\n description\n runtime\n syncStatus\n latestVersion\n publishedVersions\n createdAt\n updatedAt\n }\n": types.ServerlessFunctionFieldsFragmentDoc,
|
||||
"\n \n mutation CreateOneServerlessFunctionItem(\n $input: CreateServerlessFunctionInput!\n ) {\n createOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n": types.CreateOneServerlessFunctionItemDocument,
|
||||
"\n \n mutation DeleteOneServerlessFunction($input: DeleteServerlessFunctionInput!) {\n deleteOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n": types.DeleteOneServerlessFunctionDocument,
|
||||
"\n \n mutation DeleteOneServerlessFunction($input: ServerlessFunctionIdInput!) {\n deleteOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n": types.DeleteOneServerlessFunctionDocument,
|
||||
"\n mutation ExecuteOneServerlessFunction(\n $input: ExecuteServerlessFunctionInput!\n ) {\n executeOneServerlessFunction(input: $input) {\n data\n duration\n status\n error\n }\n }\n": types.ExecuteOneServerlessFunctionDocument,
|
||||
"\n \n mutation PublishOneServerlessFunction(\n $input: PublishServerlessFunctionInput!\n ) {\n publishServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n": types.PublishOneServerlessFunctionDocument,
|
||||
"\n \n mutation UpdateOneServerlessFunction($input: UpdateServerlessFunctionInput!) {\n updateOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n": types.UpdateOneServerlessFunctionDocument,
|
||||
"\n query FindManyAvailablePackages {\n getAvailablePackages\n }\n": types.FindManyAvailablePackagesDocument,
|
||||
"\n \n query GetManyServerlessFunctions {\n serverlessFunctions(paging: { first: 100 }) {\n edges {\n node {\n ...ServerlessFunctionFields\n }\n }\n }\n }\n": types.GetManyServerlessFunctionsDocument,
|
||||
"\n \n query GetOneServerlessFunction($id: UUID!) {\n serverlessFunction(id: $id) {\n ...ServerlessFunctionFields\n }\n }\n": types.GetOneServerlessFunctionDocument,
|
||||
"\n \n query GetManyServerlessFunctions {\n findManyServerlessFunctions {\n ...ServerlessFunctionFields\n }\n }\n": types.GetManyServerlessFunctionsDocument,
|
||||
"\n \n query GetOneServerlessFunction($input: ServerlessFunctionIdInput!) {\n findOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n": types.GetOneServerlessFunctionDocument,
|
||||
"\n query FindOneServerlessFunctionSourceCode(\n $input: GetServerlessFunctionSourceCodeInput!\n ) {\n getServerlessFunctionSourceCode(input: $input)\n }\n": types.FindOneServerlessFunctionSourceCodeDocument,
|
||||
};
|
||||
|
||||
@ -142,7 +142,7 @@ export function graphql(source: "\n query ObjectMetadataItems(\n $objectFilt
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment ServerlessFunctionFields on ServerlessFunction {\n id\n name\n description\n runtime\n syncStatus\n latestVersion\n createdAt\n updatedAt\n }\n"): (typeof documents)["\n fragment ServerlessFunctionFields on ServerlessFunction {\n id\n name\n description\n runtime\n syncStatus\n latestVersion\n createdAt\n updatedAt\n }\n"];
|
||||
export function graphql(source: "\n fragment ServerlessFunctionFields on ServerlessFunction {\n id\n name\n description\n runtime\n syncStatus\n latestVersion\n publishedVersions\n createdAt\n updatedAt\n }\n"): (typeof documents)["\n fragment ServerlessFunctionFields on ServerlessFunction {\n id\n name\n description\n runtime\n syncStatus\n latestVersion\n publishedVersions\n createdAt\n updatedAt\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@ -150,7 +150,7 @@ export function graphql(source: "\n \n mutation CreateOneServerlessFunctionIte
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n \n mutation DeleteOneServerlessFunction($input: DeleteServerlessFunctionInput!) {\n deleteOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n"): (typeof documents)["\n \n mutation DeleteOneServerlessFunction($input: DeleteServerlessFunctionInput!) {\n deleteOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n"];
|
||||
export function graphql(source: "\n \n mutation DeleteOneServerlessFunction($input: ServerlessFunctionIdInput!) {\n deleteOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n"): (typeof documents)["\n \n mutation DeleteOneServerlessFunction($input: ServerlessFunctionIdInput!) {\n deleteOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@ -170,11 +170,11 @@ export function graphql(source: "\n query FindManyAvailablePackages {\n getA
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n \n query GetManyServerlessFunctions {\n serverlessFunctions(paging: { first: 100 }) {\n edges {\n node {\n ...ServerlessFunctionFields\n }\n }\n }\n }\n"): (typeof documents)["\n \n query GetManyServerlessFunctions {\n serverlessFunctions(paging: { first: 100 }) {\n edges {\n node {\n ...ServerlessFunctionFields\n }\n }\n }\n }\n"];
|
||||
export function graphql(source: "\n \n query GetManyServerlessFunctions {\n findManyServerlessFunctions {\n ...ServerlessFunctionFields\n }\n }\n"): (typeof documents)["\n \n query GetManyServerlessFunctions {\n findManyServerlessFunctions {\n ...ServerlessFunctionFields\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n \n query GetOneServerlessFunction($id: UUID!) {\n serverlessFunction(id: $id) {\n ...ServerlessFunctionFields\n }\n }\n"): (typeof documents)["\n \n query GetOneServerlessFunction($id: UUID!) {\n serverlessFunction(id: $id) {\n ...ServerlessFunctionFields\n }\n }\n"];
|
||||
export function graphql(source: "\n \n query GetOneServerlessFunction($input: ServerlessFunctionIdInput!) {\n findOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n"): (typeof documents)["\n \n query GetOneServerlessFunction($input: ServerlessFunctionIdInput!) {\n findOneServerlessFunction(input: $input) {\n ...ServerlessFunctionFields\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
File diff suppressed because one or more lines are too long
@ -176,11 +176,6 @@ export type DeleteOneObjectInput = {
|
||||
id: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type DeleteServerlessFunctionInput = {
|
||||
/** The id of the function. */
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
||||
export type DeleteSsoInput = {
|
||||
identityProviderId: Scalars['String'];
|
||||
};
|
||||
@ -540,7 +535,7 @@ export type MutationDeleteOneObjectArgs = {
|
||||
|
||||
|
||||
export type MutationDeleteOneServerlessFunctionArgs = {
|
||||
input: DeleteServerlessFunctionInput;
|
||||
input: ServerlessFunctionIdInput;
|
||||
};
|
||||
|
||||
|
||||
@ -776,6 +771,8 @@ export type Query = {
|
||||
clientConfig: ClientConfig;
|
||||
currentUser: User;
|
||||
currentWorkspace: Workspace;
|
||||
findManyServerlessFunctions: Array<ServerlessFunction>;
|
||||
findOneServerlessFunction: ServerlessFunction;
|
||||
findWorkspaceFromInviteHash: Workspace;
|
||||
findWorkspaceInvitations: Array<WorkspaceInvitation>;
|
||||
getAvailablePackages: Scalars['JSON'];
|
||||
@ -791,8 +788,6 @@ export type Query = {
|
||||
listSSOIdentityProvidersByWorkspaceId: Array<FindAvailableSsoidpOutput>;
|
||||
object: Object;
|
||||
objects: ObjectConnection;
|
||||
serverlessFunction: ServerlessFunction;
|
||||
serverlessFunctions: ServerlessFunctionConnection;
|
||||
validatePasswordResetToken: ValidatePasswordResetToken;
|
||||
};
|
||||
|
||||
@ -813,6 +808,11 @@ export type QueryCheckWorkspaceInviteHashIsValidArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryFindOneServerlessFunctionArgs = {
|
||||
input: ServerlessFunctionIdInput;
|
||||
};
|
||||
|
||||
|
||||
export type QueryFindWorkspaceFromInviteHashArgs = {
|
||||
inviteHash: Scalars['String'];
|
||||
};
|
||||
@ -957,27 +957,12 @@ export type ServerlessFunction = {
|
||||
id: Scalars['UUID'];
|
||||
latestVersion?: Maybe<Scalars['String']>;
|
||||
name: Scalars['String'];
|
||||
publishedVersions: Array<Scalars['String']>;
|
||||
runtime: Scalars['String'];
|
||||
syncStatus: ServerlessFunctionSyncStatus;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
export type ServerlessFunctionConnection = {
|
||||
__typename?: 'ServerlessFunctionConnection';
|
||||
/** Array of edges. */
|
||||
edges: Array<ServerlessFunctionEdge>;
|
||||
/** Paging information */
|
||||
pageInfo: PageInfo;
|
||||
};
|
||||
|
||||
export type ServerlessFunctionEdge = {
|
||||
__typename?: 'ServerlessFunctionEdge';
|
||||
/** Cursor for this node. */
|
||||
cursor: Scalars['ConnectionCursor'];
|
||||
/** The node containing the ServerlessFunction */
|
||||
node: ServerlessFunction;
|
||||
};
|
||||
|
||||
export type ServerlessFunctionExecutionResult = {
|
||||
__typename?: 'ServerlessFunctionExecutionResult';
|
||||
/** Execution result in JSON format */
|
||||
@ -996,6 +981,11 @@ export enum ServerlessFunctionExecutionStatus {
|
||||
Success = 'SUCCESS'
|
||||
}
|
||||
|
||||
export type ServerlessFunctionIdInput = {
|
||||
/** The id of the function. */
|
||||
id: Scalars['ID'];
|
||||
};
|
||||
|
||||
/** SyncStatus of the serverlessFunction */
|
||||
export enum ServerlessFunctionSyncStatus {
|
||||
NotReady = 'NOT_READY',
|
||||
|
@ -72,23 +72,25 @@ export const SettingsServerlessFunctionCodeEditor = ({
|
||||
);
|
||||
|
||||
const environmentDefinition = `
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
${Object.keys(environmentVariables)
|
||||
.map((key) => `${key}: string;`)
|
||||
.join('\n')}
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
${Object.keys(environmentVariables)
|
||||
.map((key) => `${key}: string;`)
|
||||
.join('\n')}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare const process: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
};
|
||||
`;
|
||||
|
||||
declare const process: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
};
|
||||
`;
|
||||
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
environmentDefinition,
|
||||
'ts:process-env.d.ts',
|
||||
);
|
||||
monaco.languages.typescript.typescriptDefaults.setExtraLibs([
|
||||
{
|
||||
content: environmentDefinition,
|
||||
filePath: 'ts:process-env.d.ts',
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
await AutoTypings.create(editor, {
|
||||
|
@ -80,9 +80,11 @@ export const SettingsServerlessFunctionCodeEditorTab = ({
|
||||
const HeaderTabList = (
|
||||
<StyledTabList
|
||||
tabListId={SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID}
|
||||
tabs={files.map((file) => {
|
||||
return { id: file.path, title: file.path.split('/').at(-1) || '' };
|
||||
})}
|
||||
tabs={files
|
||||
.filter((file) => file.path !== '.env')
|
||||
.map((file) => {
|
||||
return { id: file.path, title: file.path.split('/').at(-1) || '' };
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -13,15 +13,18 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { H2Title } from 'twenty-ui';
|
||||
import { useHotkeyScopeOnMount } from '~/hooks/useHotkeyScopeOnMount';
|
||||
import { SettingsServerlessFunctionTabEnvironmentVariablesSection } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTabEnvironmentVariablesSection';
|
||||
|
||||
export const SettingsServerlessFunctionSettingsTab = ({
|
||||
formValues,
|
||||
serverlessFunctionId,
|
||||
onChange,
|
||||
onCodeChange,
|
||||
}: {
|
||||
formValues: ServerlessFunctionFormValues;
|
||||
serverlessFunctionId: string;
|
||||
onChange: (key: string) => (value: string) => void;
|
||||
onCodeChange: (filePath: string, value: string) => void;
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const [isDeleteFunctionModalOpen, setIsDeleteFunctionModalOpen] =
|
||||
@ -58,6 +61,10 @@ export const SettingsServerlessFunctionSettingsTab = ({
|
||||
formValues={formValues}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<SettingsServerlessFunctionTabEnvironmentVariablesSection
|
||||
formValues={formValues}
|
||||
onCodeChange={onCodeChange}
|
||||
/>
|
||||
<Section>
|
||||
<H2Title title="Danger zone" description="Delete this function" />
|
||||
<Button
|
||||
|
@ -0,0 +1,142 @@
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||
import {
|
||||
IconCheck,
|
||||
IconDotsVertical,
|
||||
IconPencil,
|
||||
IconTrash,
|
||||
IconX,
|
||||
OverflowingTextWithTooltip,
|
||||
} from 'twenty-ui';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import React, { useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { EnvironmentVariable } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTabEnvironmentVariablesSection';
|
||||
|
||||
const StyledEditModeTableRow = styled(TableRow)`
|
||||
grid-template-columns: 180px auto 56px;
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
grid-template-columns: 180px 300px 32px;
|
||||
`;
|
||||
|
||||
export const SettingsServerlessFunctionTabEnvironmentVariableTableRow = ({
|
||||
envVariable,
|
||||
onChange,
|
||||
onDelete,
|
||||
initialEditMode = false,
|
||||
}: {
|
||||
envVariable: EnvironmentVariable;
|
||||
onChange: (newEnvVariable: EnvironmentVariable) => void;
|
||||
onDelete: () => void;
|
||||
initialEditMode?: boolean;
|
||||
}) => {
|
||||
const [editedEnvVariable, setEditedEnvVariable] = useState(envVariable);
|
||||
const [editMode, setEditMode] = useState(initialEditMode);
|
||||
const dropDownId = `settings-environment-variable-dropdown-${envVariable.id}`;
|
||||
const { closeDropdown } = useDropdown(dropDownId);
|
||||
|
||||
return editMode ? (
|
||||
<StyledEditModeTableRow>
|
||||
<TableCell>
|
||||
<TextInputV2
|
||||
autoFocus
|
||||
value={editedEnvVariable.key}
|
||||
onChange={(newKey) =>
|
||||
setEditedEnvVariable({ ...editedEnvVariable, key: newKey })
|
||||
}
|
||||
placeholder="Name"
|
||||
fullWidth
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TextInputV2
|
||||
value={editedEnvVariable.value}
|
||||
onChange={(newValue) =>
|
||||
setEditedEnvVariable({ ...editedEnvVariable, value: newValue })
|
||||
}
|
||||
placeholder="Value"
|
||||
fullWidth
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<LightIconButton
|
||||
accent={'tertiary'}
|
||||
Icon={IconX}
|
||||
onClick={() => {
|
||||
if (envVariable.key === '' && envVariable.value === '') {
|
||||
onDelete();
|
||||
}
|
||||
setEditedEnvVariable(envVariable);
|
||||
setEditMode(false);
|
||||
}}
|
||||
/>
|
||||
<LightIconButton
|
||||
accent={'tertiary'}
|
||||
Icon={IconCheck}
|
||||
disabled={
|
||||
editedEnvVariable.key === '' || editedEnvVariable.value === ''
|
||||
}
|
||||
onClick={() => {
|
||||
onChange(editedEnvVariable);
|
||||
setEditMode(false);
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</StyledEditModeTableRow>
|
||||
) : (
|
||||
<StyledTableRow onClick={() => setEditMode(true)}>
|
||||
<TableCell>
|
||||
<OverflowingTextWithTooltip text={envVariable.key} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<OverflowingTextWithTooltip text={envVariable.value} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Dropdown
|
||||
dropdownMenuWidth="100px"
|
||||
dropdownId={dropDownId}
|
||||
clickableComponent={
|
||||
<LightIconButton
|
||||
aria-label="Env Variable Options"
|
||||
Icon={IconDotsVertical}
|
||||
accent="tertiary"
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenu disableBlur disableBorder width="auto">
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text={'Edit'}
|
||||
LeftIcon={IconPencil}
|
||||
onClick={() => {
|
||||
setEditMode(true);
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
<MenuItem
|
||||
text={'Delete'}
|
||||
LeftIcon={IconTrash}
|
||||
onClick={() => {
|
||||
onDelete();
|
||||
closeDropdown();
|
||||
}}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
}
|
||||
dropdownHotkeyScope={{
|
||||
scope: dropDownId,
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</StyledTableRow>
|
||||
);
|
||||
};
|
@ -0,0 +1,151 @@
|
||||
import dotenv from 'dotenv';
|
||||
import { H2Title, IconPlus, IconSearch, MOBILE_VIEWPORT } from 'twenty-ui';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import styled from '@emotion/styled';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { TableBody } from '@/ui/layout/table/components/TableBody';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { ServerlessFunctionFormValues } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { SettingsServerlessFunctionTabEnvironmentVariableTableRow } from '@/settings/serverless-functions/components/tabs/SettingsServerlessFunctionTabEnvironmentVariableTableRow';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
const StyledSearchInput = styled(TextInput)`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
padding-top: ${({ theme }) => theme.spacing(5)};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTableBody = styled(TableBody)`
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
grid-template-columns: 180px auto 32px;
|
||||
`;
|
||||
|
||||
export type EnvironmentVariable = { id: string; key: string; value: string };
|
||||
|
||||
export const SettingsServerlessFunctionTabEnvironmentVariablesSection = ({
|
||||
formValues,
|
||||
onCodeChange,
|
||||
}: {
|
||||
formValues: ServerlessFunctionFormValues;
|
||||
onCodeChange: (filePath: string, value: string) => void;
|
||||
}) => {
|
||||
const environmentVariables = formValues.code?.['.env']
|
||||
? dotenv.parse(formValues.code['.env'])
|
||||
: {};
|
||||
const environmentVariablesList = Object.entries(environmentVariables).map(
|
||||
([key, value]) => ({ id: v4(), key, value }),
|
||||
);
|
||||
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [newEnvVarAdded, setNewEnvVarAdded] = useState(false);
|
||||
const [envVariables, setEnvVariables] = useState<EnvironmentVariable[]>(
|
||||
environmentVariablesList,
|
||||
);
|
||||
const filteredEnvVariable = useMemo(() => {
|
||||
return envVariables.filter(
|
||||
({ key, value }) =>
|
||||
key.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
value.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||
);
|
||||
}, [envVariables, searchTerm]);
|
||||
|
||||
const getFormattedEnvironmentVariables = (
|
||||
newEnvVariables: EnvironmentVariable[],
|
||||
) => {
|
||||
return newEnvVariables.reduce(
|
||||
(acc, { key, value }) =>
|
||||
key.length > 0 && value.length > 0 ? `${acc}\n${key}=${value}` : acc,
|
||||
'',
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Environment variables"
|
||||
description="Set your function environment variables"
|
||||
/>
|
||||
<StyledSearchInput
|
||||
LeftIcon={IconSearch}
|
||||
placeholder="Search a variable"
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
/>
|
||||
<Table>
|
||||
<StyledTableRow>
|
||||
<TableHeader>Name</TableHeader>
|
||||
<TableHeader>Value</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
</StyledTableRow>
|
||||
{filteredEnvVariable.length > 0 && (
|
||||
<StyledTableBody>
|
||||
{filteredEnvVariable.map((envVariable) => (
|
||||
<SettingsServerlessFunctionTabEnvironmentVariableTableRow
|
||||
key={envVariable.id}
|
||||
envVariable={envVariable}
|
||||
initialEditMode={newEnvVarAdded && envVariable.value === ''}
|
||||
onChange={(newEnvVariable) => {
|
||||
const newEnvVariables = envVariables.reduce(
|
||||
(acc, { id, key }) => {
|
||||
if (id === newEnvVariable.id) {
|
||||
acc.push(newEnvVariable);
|
||||
} else if (key !== newEnvVariable.key) {
|
||||
acc.push(envVariable);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[] as EnvironmentVariable[],
|
||||
);
|
||||
setEnvVariables(newEnvVariables);
|
||||
onCodeChange(
|
||||
'.env',
|
||||
getFormattedEnvironmentVariables(newEnvVariables),
|
||||
);
|
||||
}}
|
||||
onDelete={() => {
|
||||
const newEnvVariables = envVariables.filter(
|
||||
({ id }) => id !== envVariable.id,
|
||||
);
|
||||
setEnvVariables(newEnvVariables);
|
||||
onCodeChange(
|
||||
'.env',
|
||||
getFormattedEnvironmentVariables(newEnvVariables),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</StyledTableBody>
|
||||
)}
|
||||
</Table>
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title="Add Variable"
|
||||
size="small"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setEnvVariables((prevState) => {
|
||||
return [...prevState, { id: v4(), key: '', value: '' }];
|
||||
});
|
||||
setNewEnvVarAdded(true);
|
||||
}}
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
</Section>
|
||||
);
|
||||
};
|
@ -8,6 +8,7 @@ export const SERVERLESS_FUNCTION_FRAGMENT = gql`
|
||||
runtime
|
||||
syncStatus
|
||||
latestVersion
|
||||
publishedVersions
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/gr
|
||||
|
||||
export const DELETE_ONE_SERVERLESS_FUNCTION = gql`
|
||||
${SERVERLESS_FUNCTION_FRAGMENT}
|
||||
mutation DeleteOneServerlessFunction($input: DeleteServerlessFunctionInput!) {
|
||||
mutation DeleteOneServerlessFunction($input: ServerlessFunctionIdInput!) {
|
||||
deleteOneServerlessFunction(input: $input) {
|
||||
...ServerlessFunctionFields
|
||||
}
|
||||
|
@ -4,12 +4,8 @@ import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/gr
|
||||
export const FIND_MANY_SERVERLESS_FUNCTIONS = gql`
|
||||
${SERVERLESS_FUNCTION_FRAGMENT}
|
||||
query GetManyServerlessFunctions {
|
||||
serverlessFunctions(paging: { first: 100 }) {
|
||||
edges {
|
||||
node {
|
||||
...ServerlessFunctionFields
|
||||
}
|
||||
}
|
||||
findManyServerlessFunctions {
|
||||
...ServerlessFunctionFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -3,8 +3,8 @@ import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/gr
|
||||
|
||||
export const FIND_ONE_SERVERLESS_FUNCTION = gql`
|
||||
${SERVERLESS_FUNCTION_FRAGMENT}
|
||||
query GetOneServerlessFunction($id: UUID!) {
|
||||
serverlessFunction(id: $id) {
|
||||
query GetOneServerlessFunction($input: ServerlessFunctionIdInput!) {
|
||||
findOneServerlessFunction(input: $input) {
|
||||
...ServerlessFunctionFields
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { ApolloClient, useMutation } from '@apollo/client';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { DELETE_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/deleteOneServerlessFunction';
|
||||
import {
|
||||
DeleteServerlessFunctionInput,
|
||||
ServerlessFunctionIdInput,
|
||||
DeleteOneServerlessFunctionMutation,
|
||||
DeleteOneServerlessFunctionMutationVariables,
|
||||
} from '~/generated-metadata/graphql';
|
||||
@ -19,7 +19,7 @@ export const useDeleteOneServerlessFunction = () => {
|
||||
});
|
||||
|
||||
const deleteOneServerlessFunction = async (
|
||||
input: DeleteServerlessFunctionInput,
|
||||
input: ServerlessFunctionIdInput,
|
||||
) => {
|
||||
return await mutate({
|
||||
variables: {
|
||||
|
@ -15,7 +15,6 @@ export const useGetManyServerlessFunctions = () => {
|
||||
client: apolloMetadataClient ?? undefined,
|
||||
});
|
||||
return {
|
||||
serverlessFunctions:
|
||||
data?.serverlessFunctions?.edges.map(({ node }) => node) || [],
|
||||
serverlessFunctions: data?.findManyServerlessFunctions || [],
|
||||
};
|
||||
};
|
||||
|
@ -2,11 +2,14 @@ import { useQuery } from '@apollo/client';
|
||||
import { useApolloMetadataClient } from '@/object-metadata/hooks/useApolloMetadataClient';
|
||||
import { FIND_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/queries/findOneServerlessFunction';
|
||||
import {
|
||||
ServerlessFunctionIdInput,
|
||||
GetOneServerlessFunctionQuery,
|
||||
GetOneServerlessFunctionQueryVariables,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const useGetOneServerlessFunction = (id: string) => {
|
||||
export const useGetOneServerlessFunction = (
|
||||
input: ServerlessFunctionIdInput,
|
||||
) => {
|
||||
const apolloMetadataClient = useApolloMetadataClient();
|
||||
const { data } = useQuery<
|
||||
GetOneServerlessFunctionQuery,
|
||||
@ -14,10 +17,10 @@ export const useGetOneServerlessFunction = (id: string) => {
|
||||
>(FIND_ONE_SERVERLESS_FUNCTION, {
|
||||
client: apolloMetadataClient ?? undefined,
|
||||
variables: {
|
||||
id,
|
||||
input,
|
||||
},
|
||||
});
|
||||
return {
|
||||
serverlessFunction: data?.serverlessFunction || null,
|
||||
serverlessFunction: data?.findOneServerlessFunction || null,
|
||||
};
|
||||
};
|
||||
|
@ -29,8 +29,9 @@ export const useServerlessFunctionUpdateFormState = (
|
||||
code: undefined,
|
||||
});
|
||||
|
||||
const { serverlessFunction } =
|
||||
useGetOneServerlessFunction(serverlessFunctionId);
|
||||
const { serverlessFunction } = useGetOneServerlessFunction({
|
||||
id: serverlessFunctionId,
|
||||
});
|
||||
|
||||
const { loading } = useGetOneServerlessFunctionSourceCode({
|
||||
id: serverlessFunctionId,
|
||||
|
@ -217,6 +217,7 @@ export const SettingsServerlessFunctionDetail = () => {
|
||||
formValues={formValues}
|
||||
serverlessFunctionId={serverlessFunctionId}
|
||||
onChange={onChange}
|
||||
onCodeChange={onCodeChange}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
|
@ -0,0 +1,19 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class UpdateServerlessFunctionColumns1729162426186
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'UpdateServerlessFunctionColumns1729162426186';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."serverlessFunction" ADD "publishedVersions" jsonb NOT NULL DEFAULT '[]'`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."serverlessFunction" DROP COLUMN "publishedVersions"`,
|
||||
);
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ import session from 'express-session';
|
||||
|
||||
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
|
||||
import { CacheStorageType } from 'src/engine/core-modules/cache-storage/types/cache-storage-type.enum';
|
||||
import { MessageQueueDriverType } from 'src/engine/core-modules/message-queue/interfaces';
|
||||
|
||||
export const getSessionStorageOptions = (
|
||||
environmentService: EnvironmentService,
|
||||
|
@ -3,7 +3,7 @@ import { ID, InputType } from '@nestjs/graphql';
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
@InputType()
|
||||
export class DeleteServerlessFunctionInput {
|
||||
export class ServerlessFunctionIdInput {
|
||||
@IDField(() => ID, { description: 'The id of the function.' })
|
||||
id!: string;
|
||||
}
|
@ -11,6 +11,7 @@ import {
|
||||
QueryOptions,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
import {
|
||||
IsArray,
|
||||
IsDateString,
|
||||
IsEnum,
|
||||
IsNotEmpty,
|
||||
@ -59,6 +60,10 @@ export class ServerlessFunctionDTO {
|
||||
@Field({ nullable: true })
|
||||
latestVersion: string;
|
||||
|
||||
@IsArray()
|
||||
@Field(() => [String], { nullable: false })
|
||||
publishedVersions: string[];
|
||||
|
||||
@IsEnum(ServerlessFunctionSyncStatus)
|
||||
@IsNotEmpty()
|
||||
@Field(() => ServerlessFunctionSyncStatus)
|
||||
|
@ -29,6 +29,9 @@ export class ServerlessFunctionEntity {
|
||||
@Column({ nullable: true })
|
||||
latestVersion: string;
|
||||
|
||||
@Column({ nullable: false, type: 'jsonb', default: [] })
|
||||
publishedVersions: string[];
|
||||
|
||||
@Column({ nullable: false, default: ServerlessFunctionRuntime.NODE18 })
|
||||
runtime: ServerlessFunctionRuntime;
|
||||
|
||||
|
@ -1,55 +1,23 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { SortDirection } from '@ptc-org/nestjs-query-core';
|
||||
import {
|
||||
NestjsQueryGraphQLModule,
|
||||
PagingStrategies,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { ServerlessModule } from 'src/engine/core-modules/serverless/serverless.module';
|
||||
import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { ServerlessFunctionDTO } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function.dto';
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { ServerlessFunctionResolver } from 'src/engine/metadata-modules/serverless-function/serverless-function.resolver';
|
||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
NestjsQueryGraphQLModule.forFeature({
|
||||
imports: [
|
||||
FileUploadModule,
|
||||
NestjsQueryTypeOrmModule.forFeature(
|
||||
[ServerlessFunctionEntity],
|
||||
'metadata',
|
||||
),
|
||||
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
|
||||
FileModule,
|
||||
ThrottlerModule,
|
||||
],
|
||||
services: [ServerlessFunctionService],
|
||||
resolvers: [
|
||||
{
|
||||
EntityClass: ServerlessFunctionEntity,
|
||||
DTOClass: ServerlessFunctionDTO,
|
||||
ServiceClass: ServerlessFunctionService,
|
||||
pagingStrategy: PagingStrategies.CURSOR,
|
||||
read: {
|
||||
defaultSort: [{ field: 'id', direction: SortDirection.DESC }],
|
||||
},
|
||||
create: { disabled: true },
|
||||
update: { disabled: true },
|
||||
delete: { disabled: true },
|
||||
guards: [WorkspaceAuthGuard],
|
||||
},
|
||||
],
|
||||
}),
|
||||
ServerlessModule,
|
||||
FileUploadModule,
|
||||
NestjsQueryTypeOrmModule.forFeature([ServerlessFunctionEntity], 'metadata'),
|
||||
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
|
||||
FileModule,
|
||||
ThrottlerModule,
|
||||
],
|
||||
providers: [ServerlessFunctionService, ServerlessFunctionResolver],
|
||||
exports: [ServerlessFunctionService],
|
||||
|
@ -11,7 +11,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input';
|
||||
import { DeleteServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/delete-serverless-function.input';
|
||||
import { ServerlessFunctionIdInput } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-id.input';
|
||||
import { ExecuteServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/execute-serverless-function.input';
|
||||
import { GetServerlessFunctionSourceCodeInput } from 'src/engine/metadata-modules/serverless-function/dtos/get-serverless-function-source-code.input';
|
||||
import { PublishServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/publish-serverless-function.input';
|
||||
@ -50,6 +50,39 @@ export class ServerlessFunctionResolver {
|
||||
}
|
||||
}
|
||||
|
||||
@Query(() => ServerlessFunctionDTO)
|
||||
async findOneServerlessFunction(
|
||||
@Args('input') { id }: ServerlessFunctionIdInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
try {
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
|
||||
return (
|
||||
await this.serverlessFunctionService.findManyServerlessFunctions({
|
||||
id,
|
||||
})
|
||||
)?.[0];
|
||||
} catch (error) {
|
||||
serverlessFunctionGraphQLApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
@Query(() => [ServerlessFunctionDTO])
|
||||
async findManyServerlessFunctions(
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
try {
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
|
||||
return await this.serverlessFunctionService.findManyServerlessFunctions({
|
||||
workspaceId,
|
||||
});
|
||||
} catch (error) {
|
||||
serverlessFunctionGraphQLApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
@Query(() => graphqlTypeJson)
|
||||
async getAvailablePackages(@AuthWorkspace() { id: workspaceId }: Workspace) {
|
||||
try {
|
||||
@ -81,7 +114,7 @@ export class ServerlessFunctionResolver {
|
||||
|
||||
@Mutation(() => ServerlessFunctionDTO)
|
||||
async deleteOneServerlessFunction(
|
||||
@Args('input') input: DeleteServerlessFunctionInput,
|
||||
@Args('input') input: ServerlessFunctionIdInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
try {
|
||||
|
@ -3,7 +3,6 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { basename, dirname, join } from 'path';
|
||||
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import deepEqual from 'deep-equal';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
@ -34,7 +33,7 @@ import {
|
||||
import { isDefined } from 'src/utils/is-defined';
|
||||
|
||||
@Injectable()
|
||||
export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFunctionEntity> {
|
||||
export class ServerlessFunctionService {
|
||||
constructor(
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly serverlessService: ServerlessService,
|
||||
@ -42,8 +41,10 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun
|
||||
private readonly serverlessFunctionRepository: Repository<ServerlessFunctionEntity>,
|
||||
private readonly throttlerService: ThrottlerService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
) {
|
||||
super(serverlessFunctionRepository);
|
||||
) {}
|
||||
|
||||
async findManyServerlessFunctions(where) {
|
||||
return this.serverlessFunctionRepository.findBy(where);
|
||||
}
|
||||
|
||||
async getServerlessFunctionSourceCode(
|
||||
@ -51,12 +52,11 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun
|
||||
id: string,
|
||||
version: string,
|
||||
): Promise<{ [filePath: string]: string } | undefined> {
|
||||
const serverlessFunction = await this.serverlessFunctionRepository.findOne({
|
||||
where: {
|
||||
const serverlessFunction =
|
||||
await this.serverlessFunctionRepository.findOneBy({
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (!serverlessFunction) {
|
||||
throw new ServerlessFunctionException(
|
||||
@ -101,12 +101,12 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun
|
||||
): Promise<ServerlessExecuteResult> {
|
||||
await this.throttleExecution(workspaceId);
|
||||
|
||||
const functionToExecute = await this.serverlessFunctionRepository.findOne({
|
||||
where: {
|
||||
const functionToExecute = await this.serverlessFunctionRepository.findOneBy(
|
||||
{
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
if (!functionToExecute) {
|
||||
throw new ServerlessFunctionException(
|
||||
@ -120,9 +120,7 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun
|
||||
|
||||
async publishOneServerlessFunction(id: string, workspaceId: string) {
|
||||
const existingServerlessFunction =
|
||||
await this.serverlessFunctionRepository.findOne({
|
||||
where: { id, workspaceId },
|
||||
});
|
||||
await this.serverlessFunctionRepository.findOneBy({ id, workspaceId });
|
||||
|
||||
if (!existingServerlessFunction) {
|
||||
throw new ServerlessFunctionException(
|
||||
@ -154,17 +152,29 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun
|
||||
existingServerlessFunction,
|
||||
);
|
||||
|
||||
await super.updateOne(existingServerlessFunction.id, {
|
||||
latestVersion: newVersion,
|
||||
});
|
||||
const newPublishedVersions = [
|
||||
...existingServerlessFunction.publishedVersions,
|
||||
newVersion,
|
||||
];
|
||||
|
||||
return await this.findById(existingServerlessFunction.id);
|
||||
await this.serverlessFunctionRepository.update(
|
||||
existingServerlessFunction.id,
|
||||
{
|
||||
latestVersion: newVersion,
|
||||
publishedVersions: newPublishedVersions,
|
||||
},
|
||||
);
|
||||
|
||||
return this.serverlessFunctionRepository.findOneBy({
|
||||
id: existingServerlessFunction.id,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteOneServerlessFunction(id: string, workspaceId: string) {
|
||||
const existingServerlessFunction =
|
||||
await this.serverlessFunctionRepository.findOne({
|
||||
where: { id, workspaceId },
|
||||
await this.serverlessFunctionRepository.findOneBy({
|
||||
id,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (!existingServerlessFunction) {
|
||||
@ -174,7 +184,7 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun
|
||||
);
|
||||
}
|
||||
|
||||
await super.deleteOne(id);
|
||||
await this.serverlessFunctionRepository.delete(id);
|
||||
|
||||
await this.serverlessService.delete(existingServerlessFunction);
|
||||
|
||||
@ -192,8 +202,9 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun
|
||||
workspaceId: string,
|
||||
) {
|
||||
const existingServerlessFunction =
|
||||
await this.serverlessFunctionRepository.findOne({
|
||||
where: { id: serverlessFunctionInput.id, workspaceId },
|
||||
await this.serverlessFunctionRepository.findOneBy({
|
||||
id: serverlessFunctionInput.id,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (!existingServerlessFunction) {
|
||||
@ -203,11 +214,14 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun
|
||||
);
|
||||
}
|
||||
|
||||
await super.updateOne(existingServerlessFunction.id, {
|
||||
name: serverlessFunctionInput.name,
|
||||
description: serverlessFunctionInput.description,
|
||||
syncStatus: ServerlessFunctionSyncStatus.NOT_READY,
|
||||
});
|
||||
await this.serverlessFunctionRepository.update(
|
||||
existingServerlessFunction.id,
|
||||
{
|
||||
name: serverlessFunctionInput.name,
|
||||
description: serverlessFunctionInput.description,
|
||||
syncStatus: ServerlessFunctionSyncStatus.NOT_READY,
|
||||
},
|
||||
);
|
||||
|
||||
const fileFolder = getServerlessFolder({
|
||||
serverlessFunction: existingServerlessFunction,
|
||||
@ -224,11 +238,16 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun
|
||||
}
|
||||
|
||||
await this.serverlessService.build(existingServerlessFunction, 'draft');
|
||||
await super.updateOne(existingServerlessFunction.id, {
|
||||
syncStatus: ServerlessFunctionSyncStatus.READY,
|
||||
});
|
||||
await this.serverlessFunctionRepository.update(
|
||||
existingServerlessFunction.id,
|
||||
{
|
||||
syncStatus: ServerlessFunctionSyncStatus.READY,
|
||||
},
|
||||
);
|
||||
|
||||
return await this.findById(existingServerlessFunction.id);
|
||||
return this.serverlessFunctionRepository.findOneBy({
|
||||
id: existingServerlessFunction.id,
|
||||
});
|
||||
}
|
||||
|
||||
async getAvailablePackages() {
|
||||
@ -255,11 +274,15 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun
|
||||
serverlessFunctionInput: CreateServerlessFunctionInput,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const createdServerlessFunction = await super.createOne({
|
||||
...serverlessFunctionInput,
|
||||
workspaceId,
|
||||
layerVersion: LAST_LAYER_VERSION,
|
||||
});
|
||||
const serverlessFunctionToCreate =
|
||||
await this.serverlessFunctionRepository.create({
|
||||
...serverlessFunctionInput,
|
||||
workspaceId,
|
||||
layerVersion: LAST_LAYER_VERSION,
|
||||
});
|
||||
|
||||
const createdServerlessFunction =
|
||||
await this.serverlessFunctionRepository.save(serverlessFunctionToCreate);
|
||||
|
||||
const draftFileFolder = getServerlessFolder({
|
||||
serverlessFunction: createdServerlessFunction,
|
||||
@ -277,7 +300,9 @@ export class ServerlessFunctionService extends TypeOrmQueryService<ServerlessFun
|
||||
|
||||
await this.serverlessService.build(createdServerlessFunction, 'draft');
|
||||
|
||||
return await this.findById(createdServerlessFunction.id);
|
||||
return this.serverlessFunctionRepository.findOneBy({
|
||||
id: createdServerlessFunction.id,
|
||||
});
|
||||
}
|
||||
|
||||
private async throttleExecution(workspaceId: string) {
|
||||
|
@ -6,20 +6,17 @@ describe('serverlessFunctionsResolver (e2e)', () => {
|
||||
it('should find many serverlessFunctions', () => {
|
||||
const queryData = {
|
||||
query: `
|
||||
query serverlessFunctions {
|
||||
serverlessFunctions {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
description
|
||||
runtime
|
||||
latestVersion
|
||||
syncStatus
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
query GetManyServerlessFunctions {
|
||||
findManyServerlessFunctions {
|
||||
id
|
||||
name
|
||||
description
|
||||
runtime
|
||||
syncStatus
|
||||
latestVersion
|
||||
publishedVersions
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
`,
|
||||
@ -35,24 +32,23 @@ describe('serverlessFunctionsResolver (e2e)', () => {
|
||||
expect(res.body.errors).toBeUndefined();
|
||||
})
|
||||
.expect((res) => {
|
||||
const data = res.body.data.serverlessFunctions;
|
||||
const serverlessFunctions = res.body.data.findManyServerlessFunctions;
|
||||
|
||||
expect(data).toBeDefined();
|
||||
expect(Array.isArray(data.edges)).toBe(true);
|
||||
expect(serverlessFunctions).toBeDefined();
|
||||
expect(Array.isArray(serverlessFunctions)).toBe(true);
|
||||
|
||||
const edges = data.edges;
|
||||
if (serverlessFunctions.length > 0) {
|
||||
const serverlessFunction = serverlessFunctions[0];
|
||||
|
||||
if (edges.length > 0) {
|
||||
const serverlessFunctions = edges[0].node;
|
||||
|
||||
expect(serverlessFunctions).toHaveProperty('id');
|
||||
expect(serverlessFunctions).toHaveProperty('name');
|
||||
expect(serverlessFunctions).toHaveProperty('description');
|
||||
expect(serverlessFunctions).toHaveProperty('runtime');
|
||||
expect(serverlessFunctions).toHaveProperty('latestVersion');
|
||||
expect(serverlessFunctions).toHaveProperty('syncStatus');
|
||||
expect(serverlessFunctions).toHaveProperty('createdAt');
|
||||
expect(serverlessFunctions).toHaveProperty('updatedAt');
|
||||
expect(serverlessFunction).toHaveProperty('id');
|
||||
expect(serverlessFunction).toHaveProperty('name');
|
||||
expect(serverlessFunction).toHaveProperty('description');
|
||||
expect(serverlessFunction).toHaveProperty('runtime');
|
||||
expect(serverlessFunction).toHaveProperty('syncStatus');
|
||||
expect(serverlessFunction).toHaveProperty('latestVersion');
|
||||
expect(serverlessFunction).toHaveProperty('publishedVersions');
|
||||
expect(serverlessFunction).toHaveProperty('createdAt');
|
||||
expect(serverlessFunction).toHaveProperty('updatedAt');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user