Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
4267475
feat: add runtimeMaxHeartbeatIntervalMinutes to ApplicationUpdate mut…
nicosammito Jul 11, 2026
7b4a871
feat: add runtimeMaxHeartbeatIntervalMinutes field to Application fra…
nicosammito Jul 11, 2026
7b98282
feat: update @code0-tech/triangulum to version 0.26.0 for improved fu…
nicosammito Jul 11, 2026
4ca08ad
feat: add runtimeMaxHeartbeatIntervalMinutes input to ApplicationGene…
nicosammito Jul 11, 2026
c63389e
feat: add additional error codes for improved user feedback
nicosammito Jul 11, 2026
9a7fb23
feat: fix flow settings node mapping to use correct property
nicosammito Jul 11, 2026
dccd6f5
feat: fix flow settings mapping to use correct nodes property
nicosammito Jul 11, 2026
19db050
feat: enhance flowTypeSettings to support pagination with first and a…
nicosammito Jul 11, 2026
58aa8f1
feat: update flowTypeSettings to use nodes property for improved data…
nicosammito Jul 11, 2026
f7b8f25
feat: add bulk invite mutation for members with error handling
nicosammito Jul 11, 2026
63140e5
feat: implement member bulk invite mutation with improved member hand…
nicosammito Jul 11, 2026
a6f2b7f
feat: refactor member invitation to use bulk invite method for improv…
nicosammito Jul 11, 2026
0ea2f82
feat: add blocked field to user fragments and update mutation for use…
nicosammito Jul 11, 2026
75252b7
feat: update project creation redirect to namespace level for improve…
nicosammito Jul 11, 2026
05cd6ed
feat: implement user refetching on organization creation for updated …
nicosammito Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"dependencies": {
"@apollo/client": "^4.0.9",
"@code0-tech/pictor": "^0.11.0",
"@code0-tech/triangulum": "^0.25.8",
"@code0-tech/triangulum": "^0.26.0",
"@codemirror/lang-javascript": "^6.2.5",
"@codemirror/lint": "^6.9.5",
"@icons-pack/react-simple-icons": "^13.13.0",
Expand Down
7 changes: 4 additions & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ const inter = Inter({
*/
const ErrorCodeDescription: Record<string, string> = {
CANNOT_DELETE_LAST_ADMIN_ROLE: "Cannot delete role",
CANNOT_MODERATE_USER: "Cannot moderate user",
CANNOT_MODIFY_ADMIN: "Cannot modify admin",
CANNOT_MODIFY_OWN_ADMIN: "Cannot modify self",
CANNOT_MODIFY_OWN_BLOCKED_STATUS: "Cannot block self",
CANNOT_REMOVE_LAST_ADMINISTRATOR: "Cannot remove admin",
CANNOT_REMOVE_LAST_ADMIN_ABILITY: "Cannot remove ability",
CYCLIC_DATA_TYPE_REFERENCE: "Dependency cycle detected",
Expand Down Expand Up @@ -112,11 +114,11 @@ const ErrorCodeDescription: Record<string, string> = {
NAMESPACE_ROLE_NOT_FOUND: "Namespace role missing",
NODE_NOT_FOUND: "Node not found",
NO_DATA_TYPE_FOR_IDENTIFIER: "No data type",
NO_DEFINITIONS: "No definitions available",
NO_FREE_LICENSE_SEATS: "No license seats",
NO_PRIMARY_RUNTIME: "No primary runtime",
ORGANIZATION_NOT_FOUND: "Organization not found",
OUTDATED_DEFINITION: "Outdated definition",
PRIMARY_LEVEL_NOT_FOUND: "Primary level missing",
PROJECT_NOT_FOUND: "Project not found",
REFERENCED_VALUE_NOT_FOUND: "Referenced value missing",
REGISTRATION_DISABLED: "Registration disabled",
Expand All @@ -125,11 +127,10 @@ const ErrorCodeDescription: Record<string, string> = {
RUNTIME_NOT_ASSIGNED: "Runtime not assigned",
RUNTIME_NOT_COMPATIBLE: "Runtime not compatible",
RUNTIME_NOT_FOUND: "Runtime not found",
SECONDARY_LEVEL_NOT_FOUND: "Secondary level missing",
TERTIARY_LEVEL_EXCEEDS_PARAMETERS: "Tertiary level exceeded",
TOTP_SECRET_ALREADY_SET: "TOTP already set",
UNMODIFIABLE_FIELD: "Unmodifiable field",
UNSUPPORTED_AUTHENTICATION: "Unsupported authentication",
USER_BLOCKED: "User blocked",
USER_NOT_FOUND: "User not found",
USER_SESSION_NOT_FOUND: "Session not found",
WRONG_TOTP: "Wrong TOTP code",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ fragment Application on Application {
adminStatusVisible
organizationCreationRestricted
userRegistrationEnabled
runtimeMaxHeartbeatIntervalMinutes
}
userAbilities {
__typename
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
mutation ApplicationUpdate($userRegistrationEnabled: Boolean, $organizationCreationRestricted: Boolean, $adminStatusVisible: Boolean, $termsAndConditionsUrl: String, $privacyUrl: String, $legalNoticeUrl: String) {
mutation ApplicationUpdate($userRegistrationEnabled: Boolean, $organizationCreationRestricted: Boolean, $adminStatusVisible: Boolean, $termsAndConditionsUrl: String, $privacyUrl: String, $legalNoticeUrl: String, $runtimeMaxHeartbeatIntervalMinutes: Int) {
applicationSettingsUpdate(input: {
userRegistrationEnabled: $userRegistrationEnabled
organizationCreationRestricted: $organizationCreationRestricted
adminStatusVisible: $adminStatusVisible
termsAndConditionsUrl: $termsAndConditionsUrl
privacyUrl: $privacyUrl
legalNoticeUrl: $legalNoticeUrl
runtimeMaxHeartbeatIntervalMinutes: $runtimeMaxHeartbeatIntervalMinutes
}) {
errors {
...on Error {
Expand All @@ -24,6 +25,7 @@ mutation ApplicationUpdate($userRegistrationEnabled: Boolean, $organizationCreat
adminStatusVisible
organizationCreationRestricted
userRegistrationEnabled
runtimeMaxHeartbeatIntervalMinutes
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {startTransition} from "react";
import {Badge, Button, Card, Flex, Spacing, Text, TextInput, useForm, useService, useStore} from "@code0-tech/pictor";
import {Badge, Button, Card, Flex, NumberInput, Spacing, Text, TextInput, useForm, useService, useStore} from "@code0-tech/pictor";
import CardSection from "@code0-tech/pictor/dist/components/card/CardSection";
import {TabContent} from "@code0-tech/pictor/dist/components/tab/Tab";
import {ApplicationService} from "@edition/application/services/Application.service";
Expand All @@ -20,20 +20,30 @@ export const ApplicationGeneralSettingsView: React.FC = () => {
legalNoticeUrl: !!application?.settings?.legalNoticeUrl ? application?.settings?.legalNoticeUrl : null,
privacyUrl: !!application?.settings?.privacyUrl ? application?.settings?.privacyUrl : null,
termsAndConditionsUrl: !!application?.settings?.termsAndConditionsUrl ? application?.settings?.termsAndConditionsUrl : null,
runtimeMaxHeartbeatIntervalMinutes: application?.settings?.runtimeMaxHeartbeatIntervalMinutes != null
? String(application?.settings?.runtimeMaxHeartbeatIntervalMinutes)
: null,
}),
[application]
)

// The +/- buttons of NumberInput write to the input element directly without
// triggering a React change event, so the useForm value goes stale. Read the
// value from the element itself on submit instead.
const runtimeHeartbeatInputRef = React.useRef<HTMLInputElement>(null)

const [inputs, validate] = useForm({
useInitialValidation: false,
initialValues: initialValues,
validate: {},
onSubmit: (values) => {
const runtimeMaxHeartbeatIntervalMinutes = runtimeHeartbeatInputRef.current?.value
startTransition(() => {
applicationService.applicationUpdate({
legalNoticeUrl: !!values.legalNoticeUrl ? values.legalNoticeUrl : null,
privacyUrl: !!values.privacyUrl ? values.privacyUrl : null,
termsAndConditionsUrl: !!values.termsAndConditionsUrl ? values.termsAndConditionsUrl : null,
runtimeMaxHeartbeatIntervalMinutes: !!runtimeMaxHeartbeatIntervalMinutes ? Number(runtimeMaxHeartbeatIntervalMinutes) : null,
}).then(payload => {
if ((payload?.errors?.length ?? 0) <= 0) {
addIslandSuccessNotification({
Expand Down Expand Up @@ -112,5 +122,24 @@ export const ApplicationGeneralSettingsView: React.FC = () => {
</Flex>
</CardSection>
</Card>
<Spacing spacing={"xl"}/>
<Flex justify={"space-between"} align={"center"}>
<Text size={"lg"} hierarchy={"primary"}>Runtimes</Text>
<Button color={"success"} onClick={validate}>Save</Button>
</Flex>
<Spacing spacing={"xl"}/>
<Card color={"secondary"}>
<CardSection border>
<Flex justify={"space-between"} align={"center"}>
<Flex style={{gap: ".35rem", flexDirection: "column"}}>
<Text size={"md"} hierarchy={"primary"}>Max heartbeat interval</Text>
<Text size={"md"} hierarchy={"tertiary"}>The maximum amount of minutes a runtime is
shown as connected after the last heartbeat.</Text>
</Flex>
<NumberInput ref={runtimeHeartbeatInputRef}
{...inputs.getInputProps("runtimeMaxHeartbeatIntervalMinutes")}/>
</Flex>
</CardSection>
</Card>
</TabContent>
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export const FlowCreateDialogComponent: React.FC<FlowCreateDialogComponentProps>
flowService.flowCreate({
// @ts-ignore
flow: {
settings: selectedFlowType?.flowTypeSettings?.map(setting => ({
settings: selectedFlowType?.flowTypeSettings?.nodes?.map(setting => ({
value: setting?.defaultValue !== null && setting?.defaultValue !== undefined ? setting?.defaultValue : null,
})) ?? [],
name: name,
Expand Down
2 changes: 1 addition & 1 deletion src/packages/ce/src/flow/services/Flow.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ export class FlowService extends ReactiveArrayService<FlowView, FlowDependencies
flow.settings.nodes = []
}

flow.settings.nodes = flowType?.flowTypeSettings?.map((flowTypeSetting, index) => {
flow.settings.nodes = flowType?.flowTypeSettings?.nodes?.map((flowTypeSetting, index) => {
const flowSetting = flow.settings?.nodes?.[index]
if (!flowSetting) {
return {
Expand Down
3 changes: 3 additions & 0 deletions src/packages/ce/src/flowtype/services/FlowType.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ export class FlowTypeService extends ReactiveArrayService<FlowType, FlowTypeDepe

firstFlowType: 50,
afterFlowType: null,

firstFlowTypeSetting: 50,
afterFlowTypeSetting: null,
}
}).then(res => {
const nodes = res.data?.namespace?.project?.primaryRuntime?.modules?.nodes?.flatMap(module => module?.flowTypes?.nodes ?? []) ?? []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,33 @@ fragment FlowType on FlowType {
id
}
signature
flowTypeSettings {
flowTypeSettings (first: $firstFlowTypeSetting, after: $afterFlowTypeSetting) {
__typename
id
createdAt
updatedAt
identifier
unique
optional
hidden
defaultValue
names {
count
nodes {
__typename
code
content
id
createdAt
updatedAt
identifier
unique
optional
hidden
defaultValue
names {
__typename
code
content
}
descriptions {
__typename
code
content
}
}
descriptions {
__typename
code
content
pageInfo {
endCursor
hasNextPage
}
}
editable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ query FlowTypes (
$afterModule: String
$firstFlowType: Int!
$afterFlowType: String
$firstFlowTypeSetting: Int!
$afterFlowTypeSetting: String
) {
namespace(id: $namespaceId) {
project(id: $projectId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ export const FunctionFileTriggerComponent: React.FC<FunctionFileTriggerComponent

const initialValues: Record<string | "inputType", any> = React.useMemo(() => {
const values: Record<string, any> = {}
definition?.flowTypeSettings?.forEach((setting, index) => {
definition?.flowTypeSettings?.nodes?.forEach((setting, index) => {
const flowSetting = instance?.settings?.nodes?.[index]
values[setting.id!] = {
values[setting!.id!] = {
__typename: "LiteralValue",
value: flowSetting?.value,
}
Expand All @@ -63,7 +63,7 @@ export const FunctionFileTriggerComponent: React.FC<FunctionFileTriggerComponent

const settingsValidations = React.useMemo(() => {
const values: Record<string, any> = {}
definition?.flowTypeSettings?.forEach((setting, index) => {
definition?.flowTypeSettings?.nodes?.forEach((setting, index) => {
values[setting!.id!] = (_: any) => {
const validationForSetting = validation?.find(v => v.parameterIndex === index && !v.nodeId)
if (validationForSetting) {
Expand All @@ -77,17 +77,17 @@ export const FunctionFileTriggerComponent: React.FC<FunctionFileTriggerComponent

const onSubmit = React.useCallback((values: Record<string, LiteralValue | undefined>) => {
React.startTransition(async () => {
for (const flowTypeSetting of definition?.flowTypeSettings ?? []) {
for (const flowTypeSetting of definition?.flowTypeSettings?.nodes ?? []) {

if (!changedSettings.current.has(flowTypeSetting.id!)) continue
if (!changedSettings.current.has(flowTypeSetting!.id!)) continue

const index = definition?.flowTypeSettings?.findIndex(p => p?.id === flowTypeSetting?.id)
const index = definition?.flowTypeSettings?.nodes?.findIndex(p => p?.id === flowTypeSetting?.id)
if (typeof index !== "number") return

const value = values[flowTypeSetting.id!]
const value = values[flowTypeSetting!.id!]
await flowService.setSettingValue(flowId, index, value?.value, definition!)

changedSettings.current.delete(flowTypeSetting.id!)
changedSettings.current.delete(flowTypeSetting!.id!)
}
})
}, [flowService, definition])
Expand Down Expand Up @@ -121,7 +121,7 @@ export const FunctionFileTriggerComponent: React.FC<FunctionFileTriggerComponent
<Text size={"md"}>Settings</Text>
<Spacing spacing={"xl"}/>
{(() => {
const indexedSettings = definition?.flowTypeSettings
const indexedSettings = definition?.flowTypeSettings?.nodes
?.map((settingDefinition, index) => ({settingDefinition, index}))
?.filter(({settingDefinition}) => settingDefinition && !settingDefinition.hidden) ?? []

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ export const FunctionNodeTriggerComponent: React.FC<FunctionNodeTriggerComponent
const displayMessage = React.useMemo(() => splitTemplate(flowType?.displayMessages?.[0]?.content ?? FALLBACK_FLOW_TYPE_DISPLAY_MESSAGE).map(item => {

const nodeParameter = flow?.settings?.nodes?.find((_, index) => {
const parameterDefinition = flowType?.flowTypeSettings?.[index]
const parameterDefinition = flowType?.flowTypeSettings?.nodes?.[index]
return parameterDefinition?.identifier == item
})

const parameterDefinition = flowType?.flowTypeSettings?.find(pd => pd?.identifier == item)
const parameterIndex = parameterDefinition ? flowType?.flowTypeSettings?.findIndex(p => p?.id === parameterDefinition.id) : undefined
const parameterDefinition = flowType?.flowTypeSettings?.nodes?.find(pd => pd?.identifier == item)
const parameterIndex = parameterDefinition ? flowType?.flowTypeSettings?.nodes?.findIndex(p => p?.id === parameterDefinition.id) : undefined
const parameterValidation = validation?.filter(v => v.parameterIndex === parameterIndex && !v.nodeId)
const decorationStyle: CSSProperties =
parameterValidation?.length
Expand Down
14 changes: 6 additions & 8 deletions src/packages/ce/src/member/pages/MemberAddPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,17 @@ export const MemberAddPage: React.FC = () => {
onSubmit: (values) => {
startTransition(async () => {

const invitePromises = values.users!!.map((value) =>
memberService.memberInvite({
namespaceId: namespaceId!!,
userId: (value.value).id!!
})
)
const payload = await memberService.memberBulkInvite({
namespaceId: namespaceId!!,
userIds: values.users!!.map((value) => (value.value).id!!)
})

await Promise.all(invitePromises).then(() => {
if ((payload?.errors?.length ?? 0) <= 0) {
router.push(`/namespace/${namespaceIndex}/members`)
addIslandSuccessNotification({
message: "Added members",
})
})
}
})
}
})
Expand Down
Loading