Преглед на файлове

support allowConcurrency, suspendCron, and timeoutSeconds (#3686)

Co-authored-by: David Townley <davidtownley@Davids-MacBook-Air.local>
d-g-town преди 2 години
родител
ревизия
a305793c65

+ 7 - 7
dashboard/package-lock.json

@@ -13,7 +13,7 @@
         "@loadable/component": "^5.15.2",
         "@material-ui/core": "^4.11.3",
         "@material-ui/lab": "^4.0.0-alpha.61",
-        "@porter-dev/api-contracts": "^0.2.1",
+        "@porter-dev/api-contracts": "^0.2.6",
         "@react-spring/web": "^9.6.1",
         "@sentry/react": "^6.13.2",
         "@sentry/tracing": "^6.13.2",
@@ -2455,9 +2455,9 @@
       }
     },
     "node_modules/@porter-dev/api-contracts": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.2.1.tgz",
-      "integrity": "sha512-YNXlmHwoLOft1q3FEKoAOCa73t8wz0sG9DYMs7+lModWK/kljts1COculJEVs9e/CzkCs4rxTXSX4lH2BnDX2w==",
+      "version": "0.2.6",
+      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.2.6.tgz",
+      "integrity": "sha512-IxwcsqQOZs8EfP3K9wzTArhR5y2vZUBEh6YRiXZD/73ORObN4bWI3eRQz39J0TQt5p/D00J8Dqo01nN9QY8rcA==",
       "dependencies": {
         "@bufbuild/protobuf": "^1.1.0"
       }
@@ -16956,9 +16956,9 @@
       "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="
     },
     "@porter-dev/api-contracts": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.2.1.tgz",
-      "integrity": "sha512-YNXlmHwoLOft1q3FEKoAOCa73t8wz0sG9DYMs7+lModWK/kljts1COculJEVs9e/CzkCs4rxTXSX4lH2BnDX2w==",
+      "version": "0.2.6",
+      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.2.6.tgz",
+      "integrity": "sha512-IxwcsqQOZs8EfP3K9wzTArhR5y2vZUBEh6YRiXZD/73ORObN4bWI3eRQz39J0TQt5p/D00J8Dqo01nN9QY8rcA==",
       "requires": {
         "@bufbuild/protobuf": "^1.1.0"
       }

+ 1 - 1
dashboard/package.json

@@ -8,7 +8,7 @@
     "@loadable/component": "^5.15.2",
     "@material-ui/core": "^4.11.3",
     "@material-ui/lab": "^4.0.0-alpha.61",
-    "@porter-dev/api-contracts": "^0.2.1",
+    "@porter-dev/api-contracts": "^0.2.6",
     "@react-spring/web": "^9.6.1",
     "@sentry/react": "^6.13.2",
     "@sentry/tracing": "^6.13.2",

+ 24 - 7
dashboard/src/lib/porter-apps/services.ts

@@ -50,8 +50,10 @@ export const serviceValidator = z.object({
     }),
     z.object({
       type: z.literal("job"),
-      allowConcurrent: serviceBooleanValidator,
+      allowConcurrent: serviceBooleanValidator.optional(),
       cron: serviceStringValidator,
+      suspendCron: serviceBooleanValidator.optional(),
+      timeoutSeconds: serviceNumberValidator
     }),
     z.object({
       type: z.literal("predeploy"),
@@ -86,8 +88,10 @@ export type SerializedService = {
       }
     | {
         type: "job";
-        allowConcurrent: boolean;
+        allowConcurrent?: boolean;
         cron: string;
+        suspendCron?: boolean;
+        timeoutSeconds: number;
       }
     | {
         type: "predeploy";
@@ -158,6 +162,8 @@ export function defaultSerialized({
         type: "job" as const,
         allowConcurrent: false,
         cron: "",
+        suspendCron: false,
+        timeoutSeconds: 3600,
       },
     }))
     .with("predeploy", () => ({
@@ -221,8 +227,10 @@ export function serializeService(service: ClientService): SerializedService {
         ramMegabytes: service.ramMegabytes.value,
         config: {
           type: "job" as const,
-          allowConcurrent: config.allowConcurrent.value,
+          allowConcurrent: config.allowConcurrent?.value,
           cron: config.cron.value,
+          suspendCron: config.suspendCron?.value,
+          timeoutSeconds: config.timeoutSeconds.value,
         },
       })
     )
@@ -326,11 +334,18 @@ export function deserializeService({
         ...baseService,
         config: {
           type: "job" as const,
-          allowConcurrent: ServiceField.boolean(
-            config.allowConcurrent,
-            overrideJobConfig?.allowConcurrent
-          ),
+          allowConcurrent:
+            typeof config.allowConcurrent === "boolean" ||
+            typeof overrideJobConfig?.allowConcurrent === "boolean"
+              ? ServiceField.boolean(config.allowConcurrent, overrideJobConfig?.allowConcurrent)
+              : ServiceField.boolean(false, undefined),
           cron: ServiceField.string(config.cron, overrideJobConfig?.cron),
+          suspendCron:
+            typeof config.suspendCron === "boolean" ||
+            typeof overrideJobConfig?.suspendCron === "boolean"
+              ? ServiceField.boolean(config.suspendCron, overrideJobConfig?.suspendCron)
+              : ServiceField.boolean(false, undefined),
+           timeoutSeconds: config.timeoutSeconds == 0 ? ServiceField.number(3600, overrideJobConfig?.timeoutSeconds) : ServiceField.number(config.timeoutSeconds, overrideJobConfig?.timeoutSeconds),
         },
       };
     })
@@ -394,6 +409,7 @@ export function serviceProto(service: SerializedService): Service {
           config: {
             value: {
               ...config,
+              allowConcurrentOptional: config.allowConcurrent,
             },
             case: "jobConfig",
           },
@@ -456,6 +472,7 @@ export function serializedServiceFromProto({
       config: {
         type: isPredeploy ? ("predeploy" as const) : ("job" as const),
         ...value,
+        allowConcurrent: value.allowConcurrentOptional
       },
     }))
     .exhaustive();

+ 1 - 1
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/ServiceContainer.tsx

@@ -114,7 +114,7 @@ const ServiceContainer: React.FC<ServiceProps> = ({
           if (data) {
             let largestInstanceType = {
               vCPUs: 2,
-              RAM: 4294,
+              RAM: 4,
             };
 
             data.forEach((node: any) => {

+ 14 - 1
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/JobTabs.tsx

@@ -11,6 +11,7 @@ import MainTab from "./Main";
 import Resources from "./Resources";
 import { Controller, useFormContext } from "react-hook-form";
 import { PorterAppFormData } from "lib/porter-apps";
+import {ControlledInput} from "../../../../../../components/porter/ControlledInput";
 
 interface Props {
   index: number;
@@ -32,7 +33,7 @@ const JobTabs: React.FC<Props> = ({
   maxCPU,
   isPredeploy,
 }) => {
-  const { control } = useFormContext<PorterAppFormData>();
+  const { control, register } = useFormContext<PorterAppFormData>();
   const [currentTab, setCurrentTab] = React.useState<
     "main" | "resources" | "advanced"
   >("main");
@@ -90,6 +91,18 @@ const JobTabs: React.FC<Props> = ({
                 </Checkbox>
               )}
             />
+            <Spacer y={1} />
+            <ControlledInput
+                type="text"
+                label="Timeout (seconds)"
+                placeholder="ex: 3600"
+                width="300px"
+                disabled={service.config.timeoutSeconds.readOnly}
+                disabledTooltip={
+                  "You may only edit this field in your porter.yaml."
+                }
+                {...register(`app.services.${index}.config.timeoutSeconds.value`)}
+            />
           </>
         ))
         .exhaustive()}

+ 23 - 2
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Main.tsx

@@ -1,6 +1,6 @@
 import React, { useCallback } from "react";
 import cronstrue from "cronstrue";
-import { useFormContext } from "react-hook-form";
+import {Controller, useFormContext} from "react-hook-form";
 
 import { ControlledInput } from "components/porter/ControlledInput";
 import Spacer from "components/porter/Spacer";
@@ -8,6 +8,7 @@ import { PorterAppFormData } from "lib/porter-apps";
 import { ClientService } from "lib/porter-apps/services";
 import Text from "components/porter/Text";
 import Link from "components/porter/Link";
+import Checkbox from "components/porter/Checkbox";
 
 type MainTabProps = {
   index: number;
@@ -16,7 +17,7 @@ type MainTabProps = {
 };
 
 const MainTab: React.FC<MainTabProps> = ({ index, service, isPredeploy = false }) => {
-  const { register, watch } = useFormContext<PorterAppFormData>();
+  const { register, control, watch } = useFormContext<PorterAppFormData>();
   const cron = watch(`app.services.${index}.config.cron.value`);
 
   const getScheduleDescription = useCallback((cron: string) => {
@@ -62,8 +63,28 @@ const MainTab: React.FC<MainTabProps> = ({ index, service, isPredeploy = false }
             }
             {...register(`app.services.${index}.config.cron.value`)}
           />
+
           <Spacer y={0.5} />
           {getScheduleDescription(cron)}
+            <Spacer y={0.5} />
+            <Controller
+                name={`app.services.${index}.config.suspendCron.value`}
+                control={control}
+                render={({ field: { value, onChange } }) => (
+                    <Checkbox
+                        checked={value}
+                        disabled={service.config.suspendCron?.readOnly}
+                        toggleChecked={() => {
+                            onChange(!value);
+                        }}
+                        disabledTooltip={
+                            "You may only edit this field in your porter.yaml."
+                        }
+                    >
+                        <Text color="helper">Suspend cron job</Text>
+                    </Checkbox>
+                )}
+            />
         </>
       )}
     </>

+ 1 - 1
go.mod

@@ -82,7 +82,7 @@ require (
 	github.com/matryer/is v1.4.0
 	github.com/nats-io/nats.go v1.24.0
 	github.com/open-policy-agent/opa v0.44.0
-	github.com/porter-dev/api-contracts v0.2.5
+	github.com/porter-dev/api-contracts v0.2.6
 	github.com/riandyrn/otelchi v0.5.1
 	github.com/santhosh-tekuri/jsonschema/v5 v5.0.1
 	github.com/stefanmcshane/helm v0.0.0-20221213002717-88a4a2c6e77d

+ 2 - 2
go.sum

@@ -1516,8 +1516,8 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
-github.com/porter-dev/api-contracts v0.2.5 h1:2ZSnjnDLKtGPrjk1rfP7KajxCJtEiTpbmAMgs6z+NhM=
-github.com/porter-dev/api-contracts v0.2.5/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
+github.com/porter-dev/api-contracts v0.2.6 h1:5Z/Qr1Qv6iAM4rCUfpa9+HougO8K2HFjGOeSLDZFfDw=
+github.com/porter-dev/api-contracts v0.2.6/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
 github.com/porter-dev/switchboard v0.0.3 h1:dBuYkiVLa5Ce7059d6qTe9a1C2XEORFEanhbtV92R+M=
 github.com/porter-dev/switchboard v0.0.3/go.mod h1:xSPzqSFMQ6OSbp42fhCi4AbGbQbsm6nRvOkrblFeXU4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=