Преглед изворни кода

Implemented next run over chart list and next run sorting

jnfrati пре 4 година
родитељ
комит
cd752499b5

+ 33 - 0
dashboard/package-lock.json

@@ -30,6 +30,7 @@
         "clipboard": "^2.0.8",
         "cohere-js": "^1.0.19",
         "core-js": "^3.16.1",
+        "cron-parser": "^4.3.0",
         "cron-validator": "^1.3.1",
         "cronstrue": "^2.2.0",
         "d3-array": "^2.11.0",
@@ -5054,6 +5055,17 @@
         "sha.js": "^2.4.8"
       }
     },
+    "node_modules/cron-parser": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.3.0.tgz",
+      "integrity": "sha512-mK6qJ6k9Kn0/U7Cv6LKQnReUW3GqAW4exgwmHJGb3tPgcy0LrS+PeqxPPiwL8uW/4IJsMsCZrCc4vf1nnXMjzA==",
+      "dependencies": {
+        "luxon": "^1.28.0"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/cron-validator": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/cron-validator/-/cron-validator-1.3.1.tgz",
@@ -8321,6 +8333,14 @@
         "node": ">=10"
       }
     },
+    "node_modules/luxon": {
+      "version": "1.28.0",
+      "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz",
+      "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==",
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/make-dir": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -17969,6 +17989,14 @@
         "sha.js": "^2.4.8"
       }
     },
+    "cron-parser": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.3.0.tgz",
+      "integrity": "sha512-mK6qJ6k9Kn0/U7Cv6LKQnReUW3GqAW4exgwmHJGb3tPgcy0LrS+PeqxPPiwL8uW/4IJsMsCZrCc4vf1nnXMjzA==",
+      "requires": {
+        "luxon": "^1.28.0"
+      }
+    },
     "cron-validator": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/cron-validator/-/cron-validator-1.3.1.tgz",
@@ -20553,6 +20581,11 @@
         "yallist": "^4.0.0"
       }
     },
+    "luxon": {
+      "version": "1.28.0",
+      "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz",
+      "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ=="
+    },
     "make-dir": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",

+ 1 - 0
dashboard/package.json

@@ -25,6 +25,7 @@
     "clipboard": "^2.0.8",
     "cohere-js": "^1.0.19",
     "core-js": "^3.16.1",
+    "cron-parser": "^4.3.0",
     "cron-validator": "^1.3.1",
     "cronstrue": "^2.2.0",
     "d3-array": "^2.11.0",

+ 2 - 0
dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx

@@ -177,6 +177,7 @@ class ClusterDashboard extends Component<PropsType, StateType> {
             <SortSelector
               setSortType={(sortType) => this.setState({ sortType })}
               sortType={this.state.sortType}
+              currentView={currentView}
             />
           </SortFilterWrapper>
         </ControlRow>
@@ -247,6 +248,7 @@ class ClusterDashboard extends Component<PropsType, StateType> {
             <SortSelector
               setSortType={(sortType) => this.setState({ sortType })}
               sortType={this.state.sortType}
+              currentView={currentView}
             />
           </SortFilterWrapper>
         </ControlRow>

+ 20 - 1
dashboard/src/main/home/cluster-dashboard/SortSelector.tsx

@@ -8,6 +8,7 @@ import Selector from "components/Selector";
 type PropsType = {
   setSortType: (x: string) => void;
   sortType: string;
+  currentView: string;
 };
 
 type StateType = {
@@ -21,9 +22,27 @@ export default class SortSelector extends Component<PropsType, StateType> {
       { label: "Newest", value: "Newest" },
       { label: "Oldest", value: "Oldest" },
       { label: "Alphabetical", value: "Alphabetical" },
+      { label: "Next Run", value: "Next Run" },
     ] as { label: string; value: string }[],
   };
 
+  getSortOptions() {
+    if (this.props.currentView === "jobs") {
+      return [
+        { label: "Newest", value: "Newest" },
+        { label: "Oldest", value: "Oldest" },
+        { label: "Alphabetical", value: "Alphabetical" },
+        { label: "Next Run", value: "Next Run" },
+      ];
+    }
+
+    return [
+      { label: "Newest", value: "Newest" },
+      { label: "Oldest", value: "Oldest" },
+      { label: "Alphabetical", value: "Alphabetical" },
+    ];
+  }
+
   render() {
     return (
       <StyledSortSelector>
@@ -33,7 +52,7 @@ export default class SortSelector extends Component<PropsType, StateType> {
         <Selector
           activeValue={this.props.sortType}
           setActiveValue={(sortType) => this.props.setSortType(sortType)}
-          options={this.state.sortOptions}
+          options={this.getSortOptions()}
           dropdownLabel="Sort By"
           width="150px"
           dropdownWidth="230px"

+ 24 - 0
dashboard/src/main/home/cluster-dashboard/chart/Chart.tsx

@@ -14,6 +14,7 @@ import { pushFiltered } from "shared/routing";
 import api from "shared/api";
 import { readableDate } from "shared/string_utils";
 import { Tooltip, Zoom } from "@material-ui/core";
+import CronParser from "cron-parser";
 
 type Props = {
   chart: ChartType;
@@ -85,6 +86,21 @@ const Chart: React.FunctionComponent<Props> = ({
     return tmpControllers;
   }, [chartControllers, controllers]);
 
+  let interval = null;
+  if (chart?.config?.schedule?.enabled) {
+    interval = CronParser.parseExpression(chart?.config?.schedule.value, {
+      currentDate: new Date(),
+    });
+  }
+
+  // @ts-ignore
+  const rtf = new Intl.DateTimeFormat("en", {
+    localeMatcher: "best fit", // other values: "lookup"
+    // @ts-ignore
+    dateStyle: "full",
+    timeStyle: "long",
+  });
+
   return (
     <StyledChart
       onMouseEnter={() => setExpand(true)}
@@ -155,6 +171,14 @@ const Chart: React.FunctionComponent<Props> = ({
                 </JobStatus>
               </>
             )}
+            {chart.config?.schedule?.enabled ? (
+              <>
+                <Dot style={{ marginLeft: "10px" }}>•</Dot>
+                <JobStatus>
+                  Next run {rtf.format(interval?.next().toDate() || new Date())}
+                </JobStatus>
+              </>
+            ) : null}
           </LastDeployed>
         </InfoWrapper>
 

+ 36 - 0
dashboard/src/main/home/cluster-dashboard/chart/ChartList.tsx

@@ -16,6 +16,7 @@ import { PorterUrl } from "shared/routing";
 import Chart from "./Chart";
 import Loading from "components/Loading";
 import { useWebsockets } from "shared/hooks/useWebsockets";
+import CronParser from "cron-parser";
 
 type Props = {
   currentCluster: ClusterType;
@@ -360,6 +361,41 @@ const ChartList: React.FunctionComponent<Props> = ({
       );
     } else if (sortType == "Alphabetical") {
       result.sort((a: any, b: any) => (a.name > b.name ? 1 : -1));
+    } else if (sortType == "Next Run" && currentView === "jobs") {
+      const cronJobs = result.filter(
+        (chart) => chart?.config?.schedule?.enabled
+      );
+      const nonCronJobs = result.filter(
+        (chart) => !chart?.config?.schedule?.enabled
+      );
+      cronJobs.sort((a: any, b: any) => {
+        let firstInterval = null;
+        if (a?.config?.schedule?.enabled) {
+          firstInterval = CronParser.parseExpression(
+            a?.config?.schedule.value,
+            {
+              currentDate: new Date(),
+            }
+          );
+        }
+
+        let secondInterval = null;
+        if (b?.config?.schedule?.enabled) {
+          secondInterval = CronParser.parseExpression(
+            b?.config?.schedule.value,
+            {
+              currentDate: new Date(),
+            }
+          );
+        }
+
+        return Date.parse(firstInterval.next().toISOString()) >
+          Date.parse(secondInterval.next().toISOString())
+          ? 1
+          : -1;
+      });
+
+      return [...cronJobs, ...nonCronJobs];
     }
 
     return result;

+ 63 - 15
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedJobChart.tsx

@@ -25,6 +25,8 @@ import { useChart } from "shared/hooks/useChart";
 import Modal from "main/home/modals/Modal";
 import ConnectToJobInstructionsModal from "./jobs/ConnectToJobInstructionsModal";
 import CommandLineIcon from "assets/command-line-icon";
+import CronParser from "cron-parser";
+import CronPrettifier from "cronstrue";
 
 const readableDate = (s: string) => {
   let ts = new Date(s);
@@ -131,10 +133,28 @@ export const ExpandedJobChartFC: React.FC<{
       );
     }
 
+    let interval = null;
+    if (chart?.config?.schedule.enabled) {
+      interval = CronParser.parseExpression(chart?.config?.schedule.value, {
+        currentDate: new Date(),
+      });
+    }
+    // @ts-ignore
+    const rtf = new Intl.DateTimeFormat("en", {
+      localeMatcher: "best fit", // other values: "lookup"
+      // @ts-ignore
+      dateStyle: "full",
+      timeStyle: "long",
+    });
+
     if (currentTab === "jobs") {
       return (
         <TabWrapper>
-          <ButtonWrapper>
+          <ButtonWrapper
+            style={{
+              marginBottom: chart?.config?.schedule?.enabled ? "0px" : "35px",
+            }}
+          >
             <SaveButton
               onClick={() => {
                 runJob();
@@ -158,20 +178,40 @@ export const ExpandedJobChartFC: React.FC<{
             </CLIModalIconWrapper>
           </ButtonWrapper>
 
+          {chart?.config?.schedule?.enabled ? (
+            <RunsDescription>
+              Runs{" "}
+              {CronPrettifier.toString(
+                chart?.config?.schedule.value
+              ).toLowerCase()}
+              <Dot
+                style={{
+                  color: "#ffffff88",
+                }}
+              >
+                •
+              </Dot>{" "}
+              Next run on
+              {" " + rtf.format(interval.next().toDate())}
+            </RunsDescription>
+          ) : null}
+
           {jobsStatus === "loading" ? (
             <Loading></Loading>
           ) : (
-            <JobList
-              jobs={jobs}
-              setJobs={() => {}}
-              expandJob={(job: any) => {
-                setSelectedJob(job);
-              }}
-              isDeployedFromGithub={!!chart?.git_action_config?.git_repo}
-              repositoryUrl={chart?.git_action_config?.git_repo}
-              currentChartVersion={Number(chart.version)}
-              latestChartVersion={Number(chart.latest_version)}
-            />
+            <>
+              <JobList
+                jobs={jobs}
+                setJobs={() => {}}
+                expandJob={(job: any) => {
+                  setSelectedJob(job);
+                }}
+                isDeployedFromGithub={!!chart?.git_action_config?.git_repo}
+                repositoryUrl={chart?.git_action_config?.git_repo}
+                currentChartVersion={Number(chart.version)}
+                latestChartVersion={Number(chart.latest_version)}
+              />
+            </>
           )}
         </TabWrapper>
       );
@@ -380,6 +420,14 @@ const ExpandedJobHeader: React.FC<{
   </HeaderWrapper>
 );
 
+const RunsDescription = styled.div`
+  color: #ffffff;
+  font-size: 14px;
+  margin-top: 15px;
+  margin-bottom: 35px;
+  display: flex;
+`;
+
 const Description = styled.div`
   color: #ffffff88;
   position: relative;
@@ -437,7 +485,7 @@ const LineBreak = styled.div`
 
 const ButtonWrapper = styled.div`
   display: flex;
-  margin: 5px 0 35px;
+  margin: 5px 0 0 0;
   justify-content: space-between;
 `;
 const BackButton = styled.div`
@@ -519,9 +567,9 @@ const Dot = styled.div`
 
 const InfoWrapper = styled.div`
   display: flex;
-  align-items: center;
+  flex-direction: column;
+  justify-content: center;
   margin: 24px 0px 17px 0px;
-  height: 20px;
 `;
 
 const LastDeployed = styled.div`

+ 4 - 0
dashboard/src/shared/types.tsx

@@ -109,6 +109,10 @@ export interface ChartTypeWithExtendedConfig extends ChartType {
     showStartCommand: boolean;
     statefulset: { enabled: boolean };
     terminationGracePeriodSeconds: number;
+    schedule: {
+      enabled: boolean;
+      value: string;
+    };
   };
 }