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

move pre-deploy jobs to overview and get rid of pre-deploy tab (#3080)

* move pre-deploy jobs to services

* cleanup

* more cleanups

* add strict mode
Feroze Mohideen преди 2 години
родител
ревизия
f8322842be

+ 46 - 46
dashboard/src/main/home/app-dashboard/expanded-app/ExpandedApp.tsx

@@ -622,11 +622,43 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
     });
     return `${time} on ${date}`;
   };
+
   const renderTabContents = () => {
     switch (tab) {
       case "overview":
         return (
           <>
+            {/* pre-deploy stuff - only if this is from github! */}
+            {!isLoading && appData?.app?.git_repo_id != null &&
+              <>
+                <Text size={16}>Pre-deploy job</Text>
+                <Spacer y={0.5} />
+                <Services
+                  setServices={(x) => {
+                    if (buttonStatus !== "") {
+                      setButtonStatus("");
+                    }
+                    setReleaseJob(x as ReleaseService[]);
+                  }}
+                  chart={appData.releaseChart}
+                  services={releaseJob}
+                  limitOne={true}
+                  customOnClick={() => {
+                    setReleaseJob([
+                      Service.default(
+                        "pre-deploy",
+                        "release",
+                        porterJson
+                      ) as ReleaseService,
+                    ]);
+                  }}
+                  addNewText={"Add a new pre-deploy job"}
+                  defaultExpanded={true}
+                />
+              </>
+            }
+            <Text size={16}>Application services</Text>
+            <Spacer y={0.5} />
             {!isLoading && services.length === 0 && (
               <>
                 <Fieldset>
@@ -650,7 +682,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
               addNewText={"Add a new service"}
               setExpandedJob={(x: string) => setExpandedJob(x)}
             />
-            <Spacer y={1} />
+            <Spacer y={0.5} />
             <Button
               onClick={async () => await updatePorterApp({})}
               status={buttonStatus}
@@ -725,7 +757,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
                   <Container row>
                     <PlaceholderIcon src={notFound} />
                     <Text color="helper">
-                      No pre-deploy jobs were found. Add a pre-deploy job to
+                      No pre-deploy jobs were found. You can add a pre-deploy job in the Overview tab to
                       perform an operation before your application services
                       deploy, like a database migration.
                     </Text>
@@ -734,37 +766,6 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
                 <Spacer y={0.5} />
               </>
             )}
-            <Services
-              setServices={(x) => {
-                if (buttonStatus !== "") {
-                  setButtonStatus("");
-                }
-                setReleaseJob(x as ReleaseService[]);
-              }}
-              chart={appData.releaseChart}
-              services={releaseJob}
-              limitOne={true}
-              customOnClick={() => {
-                setReleaseJob([
-                  Service.default(
-                    "pre-deploy",
-                    "release",
-                    porterJson
-                  ) as ReleaseService,
-                ]);
-              }}
-              addNewText={"Add a new pre-deploy job"}
-              defaultExpanded={true}
-            />
-            <Button
-              onClick={async () => await updatePorterApp({})}
-              status={buttonStatus}
-              loadingText={"Updating..."}
-              disabled={releaseJob.length === 0}
-            >
-              Update pre-deploy job
-            </Button>
-            <Spacer y={0.5} />
             {releaseJob.length > 0 && (
               <JobRuns
                 lastRunStatus="all"
@@ -816,15 +817,15 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
               <>
                 <Spacer inline x={1} />
                 <Container row>
-                  <SmallIcon src={github} />
-                  <Text size={13} color="helper">
-                    <Link
-                      target="_blank"
-                      to={`https://github.com/${appData.app.repo_name}`}
-                    >
+                  <Link
+                    target="_blank"
+                    to={`https://github.com/${appData.app.repo_name}`}
+                  >
+                    <SmallIcon src={github} />
+                    <Text size={13}>
                       {appData.app.repo_name}
-                    </Link>
-                  </Text>
+                    </Text>
+                  </Link>
                 </Container>
               </>
             )}
@@ -923,7 +924,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
                           marginBottom: "-20px",
                         }}
                       >
-                        Your build was not successful
+                        Your build was not successful.
                         <Spacer inline width="15px" />
                         <>
                           <Link
@@ -931,7 +932,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
                             target="_blank"
                             onClick={() => setModalVisible(true)}
                           >
-                            View Logs
+                            View logs
                           </Link>
                           {modalVisible && (
                             <GHALogsModal
@@ -1010,7 +1011,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
                         { label: "Logs", value: "logs" },
                         { label: "Metrics", value: "metrics" },
                         { label: "Debug", value: "status" },
-                        { label: "Pre-deploy", value: "pre-deploy" },
+                        { label: "Pre-deploy logs", value: "pre-deploy" },
                         {
                           label: "Environment",
                           value: "environment-variables",
@@ -1021,7 +1022,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
                       : [
                         { label: "Overview", value: "overview" },
                         { label: "Activity", value: "activity" },
-                        { label: "Pre-deploy", value: "pre-deploy" },
+                        { label: "Pre-deploy logs", value: "pre-deploy" },
                         {
                           label: "Environment",
                           value: "environment-variables",
@@ -1036,7 +1037,6 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
                       { label: "Logs", value: "logs" },
                       { label: "Metrics", value: "metrics" },
                       { label: "Debug", value: "status" },
-                      { label: "Pre-deploy", value: "pre-deploy" },
                       {
                         label: "Environment",
                         value: "environment-variables",

+ 35 - 43
dashboard/src/main/home/app-dashboard/expanded-app/StatusFooter.tsx

@@ -138,7 +138,7 @@ const StatusFooter: React.FC<Props> = ({
     }
 
     const options: NewWebsocketOptions = {};
-    options.onopen = () => {};
+    options.onopen = () => { };
 
     options.onmessage = async (evt: MessageEvent) => {
       let event = JSON.parse(evt.data);
@@ -169,7 +169,7 @@ const StatusFooter: React.FC<Props> = ({
       await updatePods();
     };
 
-    options.onclose = () => {};
+    options.onclose = () => { };
 
     options.onerror = (err: ErrorEvent) => {
       console.log(err);
@@ -199,7 +199,7 @@ const StatusFooter: React.FC<Props> = ({
         prev[prev.length - 1].push(currentPod);
         return prev;
       },
-      []);
+        []);
 
     return podsDividedByReplicaSet;
   }, [pods]);
@@ -309,13 +309,10 @@ const StatusFooter: React.FC<Props> = ({
                       <Running>
                         <StatusDot color="#ff0000" />
                         <Text color="helper">
-                          {`${replicaSet.length} replica${
-                            replicaSet.length === 1 ? "" : "s"
-                          } ${
-                            replicaSet.length === 1 ? "is" : "are"
-                          } failing to run Revision ${
-                            replicaSet[0].revisionNumber
-                          }`}
+                          {`${replicaSet.length} replica${replicaSet.length === 1 ? "" : "s"
+                            } ${replicaSet.length === 1 ? "is" : "are"
+                            } failing to run Version ${replicaSet[0].revisionNumber
+                            }`}
                         </Text>
                       </Running>
                       <Button
@@ -336,38 +333,33 @@ const StatusFooter: React.FC<Props> = ({
                       </Button>
                     </>
                   ) : // check if there are more recent replicasets and if the previous replicaset has a crashloop reason
-                  i > 0 &&
-                    !replicaSetArray[i - 1].some(
-                      (p) => p.crashLoopReason != ""
-                    ) ? (
-                    <Running>
-                      <StatusDot color="#FFA500" />
-                      <Text color="helper">
-                        {`${replicaSet.length} replica${
-                          replicaSet.length === 1 ? "" : "s"
-                        } ${
-                          replicaSet.length === 1 ? "is" : "are"
-                        } still running at Revision ${
-                          replicaSet[0].revisionNumber
-                        }. Spinning down...`}
-                      </Text>
-                    </Running>
-                  ) : (
-                    <Running>
-                      {replicaSet.length ? (
-                        <StatusDot />
-                      ) : (
-                        <StatusDot color="#ffffff33" />
-                      )}
-                      <Text color="helper">
-                        {`${replicaSet.length} replica${
-                          replicaSet.length === 1 ? "" : "s"
-                        } ${
-                          replicaSet.length === 1 ? "is" : "are"
-                        } running at Revision ${replicaSet[0].revisionNumber}`}
-                      </Text>
-                    </Running>
-                  )}
+                    i > 0 &&
+                      !replicaSetArray[i - 1].some(
+                        (p) => p.crashLoopReason != ""
+                      ) ? (
+                      <Running>
+                        <StatusDot color="#FFA500" />
+                        <Text color="helper">
+                          {`${replicaSet.length} replica${replicaSet.length === 1 ? "" : "s"
+                            } ${replicaSet.length === 1 ? "is" : "are"
+                            } still running at Version ${replicaSet[0].revisionNumber
+                            }. Spinning down...`}
+                        </Text>
+                      </Running>
+                    ) : (
+                      <Running>
+                        {replicaSet.length ? (
+                          <StatusDot />
+                        ) : (
+                          <StatusDot color="#ffffff33" />
+                        )}
+                        <Text color="helper">
+                          {`${replicaSet.length} replica${replicaSet.length === 1 ? "" : "s"
+                            } ${replicaSet.length === 1 ? "is" : "are"
+                            } running at Version ${replicaSet[0].revisionNumber}`}
+                        </Text>
+                      </Running>
+                    )}
                 </StyledContainer>
               </StyledStatusFooterTop>
               {replicaSet.some((r) => r.crashLoopReason != "") && (
@@ -476,7 +468,7 @@ const StyledStatusFooter = styled.div`
   }
 `;
 
-const StyledStatusFooterTop = styled(StyledStatusFooter)<{
+const StyledStatusFooterTop = styled(StyledStatusFooter) <{
   expanded: boolean;
 }>`
   height: 40px;

+ 2 - 1
dashboard/src/main/home/app-dashboard/expanded-app/activity-feed/ActivityFeed.tsx

@@ -14,6 +14,7 @@ import Fieldset from "components/porter/Fieldset";
 
 import { feedDate } from "shared/string_utils";
 import Pagination from "components/porter/Pagination";
+import { PorterAppEvent } from "shared/types";
 
 type Props = {
   chart: any;
@@ -44,7 +45,7 @@ const ActivityFeed: React.FC<Props> = ({ chart, stackName, appData }) => {
         }
       );
       setNumPages(res.data.num_pages);
-      setEvents(res.data.events);
+      setEvents(res.data.events as PorterAppEvent[]);
       setLoading(false);
     } catch (err) {
       setError(err);

+ 45 - 18
dashboard/src/main/home/app-dashboard/expanded-app/activity-feed/EventCard.tsx

@@ -5,12 +5,10 @@ import app_event from "assets/app_event.png";
 import build from "assets/build.png";
 import deploy from "assets/deploy.png";
 import pre_deploy from "assets/pre_deploy.png";
-import loading from "assets/loading.gif";
 import healthy from "assets/status-healthy.png";
 import failure from "assets/failure.png";
 import run_for from "assets/run_for.png";
 import refresh from "assets/refresh.png";
-import Loading from "components/Loading";
 
 import Text from "components/porter/Text";
 import Container from "components/porter/Container";
@@ -23,17 +21,18 @@ import { Log } from "main/home/cluster-dashboard/expanded-chart/logs-section/use
 import JSZip from "jszip";
 import Anser, { AnserJsonEntry } from "anser";
 import GHALogsModal from "../status/GHALogsModal";
+import { PorterAppEvent, PorterAppEventType } from "shared/types";
 
 type Props = {
-  event: any;
+  event: PorterAppEvent;
   appData: any;
 };
 
-const EventCard: React.FC<Props> = ({ event, i, appData }) => {
+const EventCard: React.FC<Props> = ({ event, appData }) => {
   const [showModal, setShowModal] = useState<boolean>(false);
   const [modalContent, setModalContent] = useState<React.ReactNode>(null);
   const [logModalVisible, setLogModalVisible] = useState(false);
-  const [logs, setLogs] = useState<Log[]>(null);
+  const [logs, setLogs] = useState<Log[]>([]);
   const [loading, setLoading] = useState<boolean>(true);
 
   const getIcon = (eventType: string) => {
@@ -79,8 +78,36 @@ const EventCard: React.FC<Props> = ({ event, i, appData }) => {
     }
   };
 
-  const renderStatusText = (event: any) => {
-    if (event.type === "BUILD") {
+  const getDuration = (event: PorterAppEvent): string => {
+    const startTimeStamp = new Date(event.created_at).getTime();
+    const endTimeStamp = new Date(event.updated_at).getTime();
+
+    const timeDifferenceMilliseconds = endTimeStamp - startTimeStamp;
+
+    const seconds = Math.floor(timeDifferenceMilliseconds / 1000);
+    const hours = Math.floor(seconds / 3600);
+    const minutes = Math.floor((seconds % 3600) / 60);
+    const remainingSeconds = seconds % 60;
+
+    let formattedTime = "";
+
+    if (hours > 0) {
+      formattedTime += `${hours} h `;
+    }
+
+    if (minutes > 0) {
+      formattedTime += `${minutes} m `;
+    }
+
+    if (hours === 0 && minutes === 0) {
+      formattedTime += `${remainingSeconds} s`;
+    }
+
+    return formattedTime.trim();
+  };
+
+  const renderStatusText = (event: PorterAppEvent) => {
+    if (event.type === PorterAppEventType.BUILD) {
       switch (event.status) {
         case "SUCCESS":
           return <Text color="#68BF8B">Build succeeded</Text>;
@@ -91,7 +118,7 @@ const EventCard: React.FC<Props> = ({ event, i, appData }) => {
       }
     }
 
-    if (event.type === "DEPLOY") {
+    if (event.type === PorterAppEventType.DEPLOY) {
       switch (event.status) {
         case "SUCCESS":
           return <Text color="#68BF8B">Deployed v100</Text>;
@@ -102,7 +129,7 @@ const EventCard: React.FC<Props> = ({ event, i, appData }) => {
       }
     }
 
-    if (event.type === "PRE_DEPLOY") {
+    if (event.type === PorterAppEventType.PRE_DEPLOY) {
       switch (event.status) {
         case "SUCCESS":
           return <Text color="#68BF8B">Pre-deploy succeeded . . </Text>;
@@ -137,7 +164,7 @@ const EventCard: React.FC<Props> = ({ event, i, appData }) => {
   };
 
   const renderInfoCta = (event: any) => {
-    if (event.type === "APP_EVENT") {
+    if (event.type === PorterAppEventType.APP_EVENT) {
       return (
         <>
           <Link
@@ -236,7 +263,7 @@ const EventCard: React.FC<Props> = ({ event, i, appData }) => {
       }
     };
 
-    if (event.type === "BUILD") {
+    if (event.type === PorterAppEventType.BUILD) {
       switch (event.status) {
         case "SUCCESS":
           return (
@@ -295,8 +322,8 @@ const EventCard: React.FC<Props> = ({ event, i, appData }) => {
       getBuildLogs();
     }, []);
 
-    if (event.type === "DEPLOY") {
-      if (event.type === "FAILED") {
+    if (event.type === PorterAppEventType.DEPLOY) {
+      if (event.status === "FAILED") {
         return (
           <>
             <Link
@@ -313,7 +340,7 @@ const EventCard: React.FC<Props> = ({ event, i, appData }) => {
       }
     }
 
-    if (event.type === "PRE_DEPLOY") {
+    if (event.type === PorterAppEventType.PRE_DEPLOY) {
       return (
         <>
           <Link hasunderline onClick={() => alert("TODO: open logs modal")}>
@@ -336,22 +363,22 @@ const EventCard: React.FC<Props> = ({ event, i, appData }) => {
         <Container row>
           <Icon height="14px" src={run_for} />
           <Spacer inline width="6px" />
-          <Text color="helper">1h 2m</Text>
+          <Text color="helper">{getDuration(event)}</Text>
         </Container>
       </Container>
       <Spacer y={1} />
       <Container row spaced>
         <Container row>
-          {event.type !== "APP_EVENT" && (
+          {event.type !== PorterAppEventType.APP_EVENT && (
             <>
               <Icon height="18px" src={getStatusIcon(event.status)} />
               <Spacer inline width="10px" />
             </>
           )}
           {renderStatusText(event)}
-          {event.type !== "APP_EVENT" && <Spacer inline x={1} />}
+          {event.type !== PorterAppEventType.APP_EVENT && <Spacer inline x={1} />}
           {renderInfoCta(event)}
-          {event.status === "FAILED" && event.type !== "APP_EVENT" && (
+          {event.status === "FAILED" && event.type !== PorterAppEventType.APP_EVENT && (
             <>
               <Link hasunderline onClick={() => triggerWorkflow()}>
                 <Container row>

+ 17 - 9
dashboard/src/main/home/app-dashboard/new-app-flow/Services.tsx

@@ -23,9 +23,10 @@ interface ServicesProps {
   limitOne?: boolean;
   customOnClick?: () => void;
   setExpandedJob?: (x: string) => void;
+  onUpdate?: () => void;
 }
 
-const Services: React.FC<ServicesProps> = ({ 
+const Services: React.FC<ServicesProps> = ({
   services,
   setServices,
   addNewText,
@@ -34,6 +35,7 @@ const Services: React.FC<ServicesProps> = ({
   limitOne = false,
   customOnClick,
   setExpandedJob,
+  onUpdate = () => ({}),
 }) => {
   const [showAddServiceModal, setShowAddServiceModal] = useState<boolean>(
     false
@@ -50,6 +52,18 @@ const Services: React.FC<ServicesProps> = ({
     return serviceNames.includes(name);
   };
 
+  const maybeGetError = (): string | undefined => {
+    if (serviceName.length > 30) {
+      return "Must be 30 characters or less.";
+    } else if (serviceName != "" && !isServiceNameValid(serviceName)) {
+      return "Lowercase letters, numbers, and '-' only.";
+    } else if (isServiceNameDuplicate(serviceName)) {
+      return "Service name is duplicate";
+    } else {
+      return undefined;
+    }
+  }
+
   const maybeRenderAddServicesButton = () => {
     if (limitOne && services.length > 0) {
       return null;
@@ -64,6 +78,7 @@ const Services: React.FC<ServicesProps> = ({
             }
             setShowAddServiceModal(true);
             setServiceType("web");
+            onUpdate();
           }}
         >
           <i className="material-icons add-icon">add_icon</i>
@@ -130,14 +145,7 @@ const Services: React.FC<ServicesProps> = ({
             placeholder="ex: my-service"
             width="100%"
             value={serviceName}
-            error={
-              (serviceName != "" &&
-                !isServiceNameValid(serviceName) &&
-                'Lowercase letters, numbers, and "-" only.') ||
-              (serviceName.length > 30 && "Must be 30 characters or less.") ||
-              (isServiceNameDuplicate(serviceName) &&
-                "Service name is duplicate")
-            }
+            error={maybeGetError()}
             setValue={setServiceName}
           />
           <Spacer y={1} />

+ 3 - 3
dashboard/src/main/home/app-dashboard/new-app-flow/serviceTypes.ts

@@ -339,8 +339,8 @@ export type ReleaseService = SharedServiceParams & {
     type: 'release';
 };
 const ReleaseService = {
-    default: (porterJson?: PorterJson): ReleaseService => ({
-        name: 'release',
+    default: (name: string, porterJson?: PorterJson): ReleaseService => ({
+        name,
         cpu: ServiceField.string('100', porterJson?.release?.config?.resources?.requests?.cpu ? porterJson?.release?.config?.resources?.requests?.cpu.replace('m', '') : undefined),
         ram: ServiceField.string('256', porterJson?.release?.config?.resources?.requests?.memory ? porterJson?.release?.config?.resources?.requests?.memory.replace('Mi', '') : undefined),
         startCommand: ServiceField.string('', porterJson?.release?.run),
@@ -399,7 +399,7 @@ export const Service = {
             case 'job':
                 return JobService.default(name, porterJson);
             case 'release':
-                return ReleaseService.default(porterJson);
+                return ReleaseService.default(name, porterJson);
         }
     },
 

+ 25 - 6
dashboard/src/shared/types.tsx

@@ -243,15 +243,15 @@ export interface FormElement {
 export type RepoType = {
   FullName: string;
 } & (
-  | {
+    | {
       Kind: "github";
       GHRepoID: number;
     }
-  | {
+    | {
       Kind: "gitlab";
       GitIntegrationId: number;
     }
-);
+  );
 
 export interface FileType {
   path: string;
@@ -309,15 +309,15 @@ export type ActionConfigType = {
   image_repo_uri: string;
   dockerfile_path?: string;
 } & (
-  | {
+    | {
       kind: "gitlab";
       gitlab_integration_id: number;
     }
-  | {
+    | {
       kind: "github";
       git_repo_id: number;
     }
-);
+  );
 
 export type GithubActionConfigType = ActionConfigType & {
   kind: "github";
@@ -660,3 +660,22 @@ export interface PorterAppOptions {
   };
   override_release?: boolean;
 }
+
+export enum PorterAppEventType {
+  BUILD = "BUILD",
+  DEPLOY = "DEPLOY",
+  APP_EVENT = "APP_EVENT",
+  PRE_DEPLOY = "PRE_DEPLOY",
+}
+export interface PorterAppEvent {
+  created_at: string;
+  updated_at: string;
+  id: string;
+  status: string;
+  type: PorterAppEventType;
+  type_source: string;
+  porter_app_id: number;
+  metadata: any;
+}
+
+

+ 5 - 2
dashboard/tsconfig.json

@@ -1,6 +1,8 @@
 {
   "compilerOptions": {
-    "lib": ["ESNext"],
+    "lib": [
+      "ESNext"
+    ],
     "baseUrl": "src",
     "outDir": "./build/",
     "sourceMap": true,
@@ -11,6 +13,7 @@
     "allowJs": true,
     "allowSyntheticDefaultImports": true,
     "removeComments": true,
-    "moduleResolution": "node"
+    "moduleResolution": "node",
+    "strict": true,
   }
 }