Feroze Mohideen há 2 anos atrás
pai
commit
ecac5ae2a7

+ 4 - 4
dashboard/src/components/porter/Banner.tsx

@@ -12,7 +12,7 @@ interface Props {
   suffix?: React.ReactNode;
 }
 
-const Banner: React.FC<Props> = ({ 
+const Banner: React.FC<Props> = ({
   type,
   icon,
   children,
@@ -32,12 +32,12 @@ const Banner: React.FC<Props> = ({
 
   return (
     <StyledBanner
-      color={type === "error" ? "#ff385d" : type === "warning" && "#f5cb42"}
+      color={type === "error" ? "#ff385d" : type === "warning" ? "#f5cb42" : ""}
       noMargin={noMargin}
     >
       <>
-      {renderIcon()}
-      <span>{children}</span>
+        {renderIcon()}
+        <span>{children}</span>
       </>
       {suffix && (
         <Suffix>{suffix}</Suffix>

+ 12 - 6
dashboard/src/lib/hooks/useGithubWorkflow.ts

@@ -1,5 +1,4 @@
 import { useQuery } from "@tanstack/react-query";
-import { ImageInfo } from "main/home/app-dashboard/new-app-flow/serviceTypes";
 import { useContext, useEffect, useState } from "react";
 import { Context } from "shared/Context";
 import api from "shared/api";
@@ -7,8 +6,9 @@ import api from "shared/api";
 export const useGithubWorkflow = (appData: any, hasBuiltImage: boolean) => {
     const { currentProject, currentCluster } = useContext(Context);
     const [githubWorkflowFilename, setGithubWorkflowName] = useState<string>("");
+    const [userHasGithubAccess, setUserHasGithubAccess] = useState<boolean>(true);
 
-    const createUseQuery = (fileName: string, hasBuiltImage: boolean) => {
+    const createUseQuery = (fileName: string) => {
         return useQuery(
             [`checkForApplicationWorkflow_${fileName}`, currentProject?.id, currentCluster?.id, githubWorkflowFilename, appData, hasBuiltImage],
             async () => {
@@ -41,15 +41,21 @@ export const useGithubWorkflow = (appData: any, hasBuiltImage: boolean) => {
             },
             {
                 enabled: !hasBuiltImage && !!currentProject && !!currentCluster && !!appData && githubWorkflowFilename === "",
-                refetchInterval: 5000,
+                retryDelay: 5000,
+                retry: (_failureCount, error) => {
+                    if (error.response?.status === 403) {
+                        setUserHasGithubAccess(false);
+                    }
+                    return true;
+                },
                 refetchOnWindowFocus: false,
             }
         );
     }
 
-    const { data: applicationWorkflowCheck, isLoading: isLoadingApplicationWorkflow } = createUseQuery(`porter_stack_${appData?.name}.yml`, hasBuiltImage);
+    const { data: applicationWorkflowCheck, isLoading: isLoadingApplicationWorkflow } = createUseQuery(`porter_stack_${appData?.name}.yml`);
 
-    const { data: defaultWorkflowCheck, isLoading: isLoadingDefaultWorkflow } = createUseQuery(`porter.yml`, hasBuiltImage);
+    const { data: defaultWorkflowCheck, isLoading: isLoadingDefaultWorkflow } = createUseQuery(`porter.yml`);
 
     useEffect(() => {
         if (!!applicationWorkflowCheck) {
@@ -59,5 +65,5 @@ export const useGithubWorkflow = (appData: any, hasBuiltImage: boolean) => {
         }
     }, [applicationWorkflowCheck, defaultWorkflowCheck]);
 
-    return { githubWorkflowFilename, isLoading: isLoadingApplicationWorkflow || isLoadingDefaultWorkflow };
+    return { githubWorkflowFilename, isLoading: isLoadingApplicationWorkflow || isLoadingDefaultWorkflow, userHasGithubAccess };
 } 

+ 2 - 6
dashboard/src/lib/hooks/useHasBuiltImage.ts

@@ -11,10 +11,6 @@ export const useHasBuiltImage = (appName: string | undefined) => {
     const { data } = useQuery(
         ["checkForBuiltImage", currentProject?.id, currentCluster?.id, hasBuiltImage],
         async () => {
-            if (hasBuiltImage) {
-                return true;
-            }
-
             if (currentProject == null || currentCluster == null || appName == null) {
                 return false;
             }
@@ -34,8 +30,8 @@ export const useHasBuiltImage = (appName: string | undefined) => {
             return globalImage != null &&
                 globalImage.repository != null &&
                 globalImage.tag != null &&
-                globalImage.repository !== ImageInfo.BASE_IMAGE.repository &&
-                globalImage.tag !== ImageInfo.BASE_IMAGE.tag
+                !(globalImage.repository === ImageInfo.BASE_IMAGE.repository &&
+                    globalImage.tag === ImageInfo.BASE_IMAGE.tag)
         },
         {
             enabled: !!currentProject && !!currentCluster && !hasBuiltImage,

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

@@ -9,7 +9,6 @@ import box from "assets/box.png";
 import github from "assets/github-white.png";
 import pr_icon from "assets/pull_request_icon.svg";
 import loadingImg from "assets/loading.gif";
-import refresh from "assets/refresh.png";
 import save from "assets/save-01.svg";
 
 import api from "shared/api";
@@ -29,13 +28,12 @@ import { ChartType, CreateUpdatePorterAppOptions } from "shared/types";
 import BuildSettingsTab from "../build-settings/BuildSettingsTab";
 import Button from "components/porter/Button";
 import Services from "../new-app-flow/Services";
-import { ImageInfo, Service } from "../new-app-flow/serviceTypes";
+import { Service } from "../new-app-flow/serviceTypes";
 import Fieldset from "components/porter/Fieldset";
 import { PorterJson, createFinalPorterYaml } from "../new-app-flow/schema";
 import { KeyValueType } from "main/home/cluster-dashboard/env-groups/EnvGroupArray";
 import { PorterYamlSchema } from "../new-app-flow/schema";
 import { EnvVariablesTab } from "./env-vars/EnvVariablesTab";
-import GHABanner from "./GHABanner";
 import LogSection from "./logs/LogSection";
 import ActivityFeed from "./activity-feed/ActivityFeed";
 import MetricsSection from "./metrics/MetricsSection";
@@ -48,9 +46,9 @@ import { BuildMethod, PorterApp } from "../types/porterApp";
 import EventFocusView from "./activity-feed/events/focus-views/EventFocusView";
 import HelmValuesTab from "./HelmValuesTab";
 import SettingsTab from "./SettingsTab";
-import PorterAppRevisionSection from "./PorterAppRevisionSection";
 import { useHasBuiltImage } from "lib/hooks/useHasBuiltImage";
 import { useGithubWorkflow } from "lib/hooks/useGithubWorkflow";
+import ExpandedAppBanner from "./ExpandedAppBanner";
 
 type Props = RouteComponentProps & {};
 
@@ -86,18 +84,11 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
   const {
     currentCluster,
     currentProject,
-    setCurrentError,
     user,
   } = useContext(Context);
   const [isLoading, setIsLoading] = useState(true);
   const [deleting, setDeleting] = useState(false);
   const [appData, setAppData] = useState(null);
-  const [forceRefreshRevisions, setForceRefreshRevisions] = useState<boolean>(
-    false
-  );
-
-  const [showRevisions, setShowRevisions] = useState<boolean>(false);
-
   // this is what we read from their porter.yaml in github
   const [porterJson, setPorterJson] = useState<PorterJson | undefined>(undefined);
   // this is what we use to update the release. the above is a subset of this
@@ -131,7 +122,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
   const selectedTab: ValidTab = tab != null && validTabs.includes(tab) ? tab : DEFAULT_TAB;
   const { appName } = props.match.params as any;
   const hasBuiltImage = useHasBuiltImage(appName);
-  const { githubWorkflowFilename, isLoading: isLoadingWorkflowFile } = useGithubWorkflow(appData?.app, hasBuiltImage);
+  const { githubWorkflowFilename, userHasGithubAccess } = useGithubWorkflow(appData?.app, hasBuiltImage);
 
   useEffect(() => {
     if (!_.isEqual(_.omitBy(porterApp, _.isEmpty), _.omitBy(tempPorterApp, _.isEmpty))) {
@@ -777,7 +768,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
             </Fieldset>
           ) : (
             <>
-              {hasBuiltImage ? (
+              {/* {hasBuiltImage ? (
                 <>
                   <DarkMatter />
                   <PorterAppRevisionSection
@@ -823,7 +814,15 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
                     gitRepoId={appData.app.git_repo_id}
                     porterYamlPath={appData.app.porter_yaml_path}
                   />
-                )}
+                )} */}
+              <ExpandedAppBanner
+                appData={appData}
+                hasBuiltImage={hasBuiltImage}
+                githubWorkflowFilename={githubWorkflowFilename}
+                setRevision={setRevision}
+                updatePorterApp={updatePorterApp}
+                userHasGithubAccess={userHasGithubAccess}
+              />
               <Spacer y={1} />
               <AnimateHeight height={showUnsavedChangesBanner ? 67 : 0}>
                 <Banner

+ 113 - 0
dashboard/src/main/home/app-dashboard/expanded-app/ExpandedAppBanner.tsx

@@ -0,0 +1,113 @@
+import React, { useContext, useEffect } from "react";
+import styled from "styled-components";
+import PorterAppRevisionSection from "./PorterAppRevisionSection";
+import Banner from "components/porter/Banner";
+import Spacer from "components/porter/Spacer";
+import Link from "components/porter/Link";
+import GHABanner from "./GHABanner";
+import { ChartType, CreateUpdatePorterAppOptions } from "shared/types";
+import { Context } from "shared/Context";
+
+type Props = {
+    appData: any;
+    hasBuiltImage: boolean;
+    githubWorkflowFilename: string;
+    setRevision: (chart: ChartType, isCurrent?: boolean) => void;
+    updatePorterApp: (options: Partial<CreateUpdatePorterAppOptions>) => Promise<void>;
+    userHasGithubAccess: boolean;
+};
+
+const ExpandedAppBanner: React.FC<Props> = ({
+    appData,
+    hasBuiltImage,
+    githubWorkflowFilename,
+    setRevision,
+    updatePorterApp,
+    userHasGithubAccess,
+}) => {
+
+    const { setCurrentModal } = useContext(Context);
+
+    if (!hasBuiltImage && !userHasGithubAccess) {
+        return (
+            <>
+                <DarkMatter />
+                <PorterAppRevisionSection
+                    chart={appData.chart}
+                    setRevision={setRevision}
+                    forceRefreshRevisions={false}
+                    refreshRevisionsOff={() => ({})}
+                    shouldUpdate={
+                        appData.chart.latest_version &&
+                        appData.chart.latest_version !==
+                        appData.chart.chart.metadata.version
+                    }
+                    updatePorterApp={updatePorterApp}
+                    latestVersion={appData.chart.latest_version}
+                    appName={appData.app.name}
+                />
+                <Banner type="warning">
+                    You do not have access to the GitHub repo associated with this application.
+                    <Spacer inline width="5px" />
+                    <Link
+                        hasunderline
+                        onClick={() => setCurrentModal?.("AccountSettingsModal", {})}
+                    >
+                        Check account settings
+                    </Link>
+                </Banner>
+            </>
+        )
+    } else {
+        return hasBuiltImage ? (
+            <>
+                <DarkMatter />
+                <PorterAppRevisionSection
+                    chart={appData.chart}
+                    setRevision={setRevision}
+                    forceRefreshRevisions={false}
+                    refreshRevisionsOff={() => ({})}
+                    shouldUpdate={
+                        appData.chart.latest_version &&
+                        appData.chart.latest_version !==
+                        appData.chart.chart.metadata.version
+                    }
+                    updatePorterApp={updatePorterApp}
+                    latestVersion={appData.chart.latest_version}
+                    appName={appData.app.name}
+                />
+                <DarkMatter antiHeight="-18px" />
+            </>
+        )
+            :
+            githubWorkflowFilename ? (
+                <Banner>
+                    Your GitHub repo has not been built yet.
+                    <Spacer inline width="5px" />
+                    <Link
+                        hasunderline
+                        target="_blank"
+                        to={`https://github.com/${appData.app.repo_name}/actions`}
+                    >
+                        Check status
+                    </Link>
+                </Banner>
+            ) : (
+                <GHABanner
+                    repoName={appData.app.repo_name}
+                    branchName={appData.app.git_branch}
+                    pullRequestUrl={appData.app.pull_request_url}
+                    stackName={appData.app.name}
+                    gitRepoId={appData.app.git_repo_id}
+                    porterYamlPath={appData.app.porter_yaml_path}
+                />
+            );
+    }
+};
+
+export default ExpandedAppBanner;
+
+const DarkMatter = styled.div<{ antiHeight?: string }>`
+  width: 100%;
+  margin-top: ${(props) => props.antiHeight || "-20px"};
+`;

+ 10 - 9
dashboard/src/main/home/app-dashboard/expanded-app/PorterAppRevisionSection.tsx

@@ -24,8 +24,6 @@ type PropsType = WithAuthProps & {
     shouldUpdate: boolean;
     upgradeVersion: (version: string, cb: () => void) => void;
     latestVersion: string;
-    showRevisions?: boolean;
-    toggleShowRevisions?: () => void;
     updatePorterApp: (options: Partial<CreateUpdatePorterAppOptions>) => Promise<void>;
     appName: string;
 };
@@ -37,6 +35,7 @@ type StateType = {
     loading: boolean;
     maxVersion: number;
     expandRevisions: boolean;
+    showRevisions: boolean;
 };
 
 // TODO: refactor this component it's so gross
@@ -48,6 +47,7 @@ class PorterAppRevisionSection extends Component<PropsType, StateType> {
         loading: false,
         maxVersion: 0, // Track most recent version even when previewing old revisions
         expandRevisions: false,
+        showRevisions: false,
     };
 
     ws: WebSocket | null = null;
@@ -109,7 +109,7 @@ class PorterAppRevisionSection extends Component<PropsType, StateType> {
 
             if (event.event_type == "UPDATE" || event.event_type == "ADD") {
                 let object = event.Object;
-
+                let revisionIndex = -1
                 this.setState(
                     (prevState) => {
                         const { revisions: oldRevisions } = prevState;
@@ -117,7 +117,7 @@ class PorterAppRevisionSection extends Component<PropsType, StateType> {
                         const prevRevisions = [...oldRevisions];
 
                         // Check if it's an update of a revision or if it's a new one
-                        const revisionIndex = prevRevisions.findIndex((rev) => {
+                        revisionIndex = prevRevisions.findIndex((rev) => {
                             if (rev.version === object.version) {
                                 return true;
                             }
@@ -133,7 +133,10 @@ class PorterAppRevisionSection extends Component<PropsType, StateType> {
                         return { ...prevState, revisions: prevRevisions, maxVersion: Math.max(...prevRevisions.map(rev => rev.version)) };
                     },
                     () => {
-                        this.props.setRevision(this.state.revisions[0], true);
+                        // only update the dashboard if the revision is new or the latest one
+                        if (revisionIndex === -1 || revisionIndex === this.state.revisions.length - 1) {
+                            this.props.setRevision(this.state.revisions[0], true);
+                        }
                     }
                 );
             }
@@ -321,15 +324,13 @@ class PorterAppRevisionSection extends Component<PropsType, StateType> {
                     </Modal>
                 )}
                 <RevisionHeader
-                    showRevisions={this.props.showRevisions}
+                    showRevisions={this.state.showRevisions}
                     isCurrent={isCurrent}
                     onClick={() => {
-                        if (typeof this.props.toggleShowRevisions === "function") {
-                            this.props.toggleShowRevisions();
-                        }
                         this.setState((prev) => ({
                             ...prev,
                             expandRevisions: !prev.expandRevisions,
+                            showRevisions: !prev.showRevisions,
                         }));
                     }}
                 >

+ 5 - 0
dashboard/src/main/home/app-dashboard/new-app-flow/NewAppFlow.tsx

@@ -33,6 +33,7 @@ import { NewPopulatedEnvGroup, PartialEnvGroup, PopulatedEnvGroup } from "compon
 import EnvGroupArrayStacks from "main/home/cluster-dashboard/env-groups/EnvGroupArrayStacks";
 import EnvGroupModal from "../expanded-app/env-vars/EnvGroupModal";
 import ExpandableEnvGroup from "../expanded-app/env-vars/ExpandableEnvGroup";
+import { useQueryClient } from "@tanstack/react-query";
 
 type Props = RouteComponentProps & {};
 
@@ -110,6 +111,8 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
   const [showEnvModal, setShowEnvModal] = useState(false);
   const [deletedEnvGroups, setDeleteEnvGroups] = useState<NewPopulatedEnvGroup[]>([])
 
+  const queryClient = useQueryClient();
+
   // this advances the step in the case that a user chooses a repo that doesn't have a porter.yaml
   useEffect(() => {
     if (porterApp.git_branch !== "") {
@@ -366,6 +369,8 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
         }
       );
 
+      await queryClient.invalidateQueries();
+
       if (porterAppRequest.repo_name === "") {
         props.history.push(`/apps/${porterApp.name}`);
       }