Kaynağa Gözat

misc status footer refactors (#3624)

Feroze Mohideen 2 yıl önce
ebeveyn
işleme
7d1280c56b

+ 3 - 3
api/server/handlers/porter_app/latest_app_revisions.go

@@ -15,7 +15,7 @@ import (
 	"github.com/porter-dev/porter/internal/telemetry"
 )
 
-// LatestAppRevisionsHandler handles requests to the /apps/{porter_app_name}/revisions endpoint
+// LatestAppRevisionsHandler handles requests to the /apps/revisions endpoint
 type LatestAppRevisionsHandler struct {
 	handlers.PorterHandlerReadWriter
 }
@@ -31,7 +31,7 @@ func NewLatestAppRevisionsHandler(
 	}
 }
 
-// LatestAppRevisionsRequest represents the response from the /apps/{porter_app_name}/revisions endpoint
+// LatestAppRevisionsRequest represents the response from the /apps/revisions endpoint
 type LatestAppRevisionsRequest struct{}
 
 // LatestRevisionWithSource is an app revision and its source porter app
@@ -40,7 +40,7 @@ type LatestRevisionWithSource struct {
 	Source      types.PorterApp     `json:"source"`
 }
 
-// LatestAppRevisionsResponse represents the response from the /apps/{porter_app_name}/revisions endpoint
+// LatestAppRevisionsResponse represents the response from the /apps/revisions endpoint
 type LatestAppRevisionsResponse struct {
 	AppRevisions []LatestRevisionWithSource `json:"app_revisions"`
 }

+ 13 - 3
api/server/handlers/porter_app/pod_status.go

@@ -1,6 +1,7 @@
 package porter_app
 
 import (
+	"fmt"
 	"net/http"
 
 	"connectrpc.com/connect"
@@ -10,6 +11,7 @@ import (
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
 	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/server/shared/requestutils"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/telemetry"
@@ -37,7 +39,7 @@ func NewPodStatusHandler(
 // PodStatusRequest is the expected format for a request body on GET /apps/pods
 type PodStatusRequest struct {
 	DeploymentTargetID string `schema:"deployment_target_id"`
-	Selectors          string `schema:"selectors"`
+	ServiceName        string `schema:"service"`
 }
 
 func (c *PodStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -51,10 +53,17 @@ func (c *PodStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	appName, reqErr := requestutils.GetURLParamString(r, types.URLParamPorterAppName)
+	if reqErr != nil {
+		err := telemetry.Error(ctx, span, reqErr, "porter app name not found in request")
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
+		return
+	}
+
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 
-	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "selectors", Value: request.Selectors})
+	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "service-name", Value: request.ServiceName}, telemetry.AttributeKV{Key: "app-name", Value: appName})
 
 	if request.DeploymentTargetID == "" {
 		err := telemetry.Error(ctx, span, nil, "must provide deployment target id")
@@ -99,7 +108,8 @@ func (c *PodStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	pods := []v1.Pod{}
 
-	podsList, err := agent.GetPodsByLabel(request.Selectors, namespace)
+	selectors := fmt.Sprintf("porter.run/service-name=%s,porter.run/deployment-target-id=%s,porter.run/app-name=%s", request.ServiceName, request.DeploymentTargetID, appName)
+	podsList, err := agent.GetPodsByLabel(selectors, namespace)
 	if err != nil {
 		err = telemetry.Error(ctx, span, err, "unable to get pods by label")
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))

+ 2 - 2
api/server/router/porter_app.go

@@ -980,14 +980,14 @@ func getPorterAppRoutes(
 		Router:   r,
 	})
 
-	// GET /api/projects/{project_id}/clusters/{cluster_id}/apps/pods -> cluster.NewPodStatusHandler
+	// GET /api/projects/{project_id}/clusters/{cluster_id}/apps/{porter_app_name}/pods -> cluster.NewPodStatusHandler
 	appPodStatusEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{
 			Verb:   types.APIVerbGet,
 			Method: types.HTTPVerbGet,
 			Path: &types.Path{
 				Parent:       basePath,
-				RelativePath: fmt.Sprintf("%s/pods", relPathV2),
+				RelativePath: fmt.Sprintf("%s/{%s}/pods", relPathV2, types.URLParamPorterAppName),
 			},
 			Scopes: []types.PermissionScope{
 				types.UserScope,

+ 9 - 9
dashboard/src/lib/hooks/useAppStatus.ts

@@ -2,7 +2,7 @@ import _ from "lodash";
 import { useEffect, useMemo, useState } from "react";
 import api from "shared/api";
 import { NewWebsocketOptions, useWebsockets } from "shared/hooks/useWebsockets";
-import { useRevisionIdToNumber } from "./useRevisionList";
+import { useRevisionList } from "./useRevisionList";
 import { valueExists } from "shared/util";
 
 export type PorterAppVersionStatus = {
@@ -38,7 +38,7 @@ export const useAppStatus = (
 ) => {
     const [servicePodMap, setServicePodMap] = useState<Record<string, ClientPod[]>>({});
 
-    const revisionIdToNumber = useRevisionIdToNumber(appName, deploymentTargetId);
+    const { revisionIdToNumber } = useRevisionList({ appName, deploymentTargetId, projectId, clusterId });
 
     const {
         newWebsocket,
@@ -76,18 +76,17 @@ export const useAppStatus = (
     };
 
     const updatePods = async (serviceName: string) => {
-        const selectors = `porter.run/service-name=${serviceName},porter.run/deployment-target-id=${deploymentTargetId}`;
-
         try {
             const res = await api.appPodStatus(
                 "<token>",
                 {
                     deployment_target_id: deploymentTargetId,
-                    selectors,
+                    service: serviceName,
                 },
                 {
-                    id: projectId,
+                    project_id: projectId,
                     cluster_id: clusterId,
+                    app_name: appName,
                 }
             );
             // TODO: type the response
@@ -143,7 +142,7 @@ export const useAppStatus = (
             setupWebsocket(serviceName);
         }
         return () => closeAllWebsockets();
-    }, [projectId, clusterId, deploymentTargetId, appName, JSON.stringify(revisionIdToNumber)]);
+    }, [projectId, clusterId, deploymentTargetId, appName]);
 
     const processReplicaSetArray = (replicaSetArray: ClientPod[][]): PorterAppVersionStatus[] => {
         return replicaSetArray.map((replicaSet, i) => {
@@ -161,7 +160,8 @@ export const useAppStatus = (
                 message = `${replicaSet.length} replica${replicaSet.length === 1 ? "" : "s"} ${replicaSet.length === 1 ? "is" : "are"
                     } failing to run Version ${version}`;
             } else if (
-                i > 0 && replicaSetArray[i - 1].every(p => !p.isFailing)
+                // last check ensures that we don't say 'spinning down' unless there exists a version status above it
+                i > 0 && replicaSetArray[i - 1].every(p => !p.isFailing) && revisionIdToNumber[replicaSetArray[i - 1][0].revisionId] != null
             ) {
                 status = "spinningDown";
                 message = `${replicaSet.length} replica${replicaSet.length === 1 ? "" : "s"} ${replicaSet.length === 1 ? "is" : "are"
@@ -207,7 +207,7 @@ export const useAppStatus = (
         }));
 
         return serviceReplicaSetMap;
-    }, [JSON.stringify(servicePodMap)]);
+    }, [JSON.stringify(servicePodMap), JSON.stringify(revisionIdToNumber)]);
 
     return {
         serviceVersionStatus,

+ 24 - 31
dashboard/src/lib/hooks/useRevisionList.ts

@@ -1,26 +1,30 @@
 import { useQuery } from "@tanstack/react-query";
-import { useContext, useEffect, useState } from "react";
-import { Context } from "shared/Context";
+import { useEffect, useState } from "react";
 import api from "shared/api";
 import { z } from "zod";
 import { AppRevision, appRevisionValidator } from "../revisions/types";
-import { useLatestRevision } from "../../main/home/app-dashboard/app-view/LatestRevisionContext";
-
-export function useRevisionList(appName: string, deploymentTargetId: string) {
-  const { currentProject, currentCluster } = useContext(Context);
-  const { latestRevision } = useLatestRevision();
-
+import { useLatestRevision } from "main/home/app-dashboard/app-view/LatestRevisionContext";
+
+export function useRevisionList({
+  appName,
+  deploymentTargetId,
+  projectId,
+  clusterId,
+}: {
+  appName: string,
+  deploymentTargetId: string,
+  projectId: number,
+  clusterId: number
+}): { revisionList: AppRevision[], revisionIdToNumber: Record<string, number> } {
   const [
     revisionList,
     setRevisionList,
   ] = useState<AppRevision[]>([]);
-
-  if (currentProject == null || currentCluster == null) {
-    return [];
-  }
+  const [revisionIdToNumber, setRevisionIdToNumber] = useState<Record<string, number>>({});
+  const { latestRevision } = useLatestRevision();
 
   const { data } = useQuery(
-    ["listAppRevisions", currentProject.id, currentCluster.id, appName, deploymentTargetId, latestRevision],
+    ["listAppRevisions", projectId, clusterId, appName, deploymentTargetId, latestRevision],
     async () => {
       const res = await api.listAppRevisions(
         "<token>",
@@ -28,8 +32,8 @@ export function useRevisionList(appName: string, deploymentTargetId: string) {
           deployment_target_id: deploymentTargetId,
         },
         {
-          project_id: currentProject.id,
-          cluster_id: currentCluster.id,
+          project_id: projectId,
+          cluster_id: clusterId,
           porter_app_name: appName,
         }
       );
@@ -39,32 +43,21 @@ export function useRevisionList(appName: string, deploymentTargetId: string) {
           app_revisions: z.array(appRevisionValidator),
         })
         .parseAsync(res.data);
-
       return revisions;
     },
     {
-      enabled: !!currentProject && !!currentCluster,
       refetchInterval: 5000,
+      refetchOnWindowFocus: false,
     }
   );
 
   useEffect(() => {
     if (data) {
-      setRevisionList(data.app_revisions);
+      const revisionList = data.app_revisions
+      setRevisionList(revisionList);
+      setRevisionIdToNumber(Object.fromEntries(revisionList.map(r => ([r.id, r.revision_number]))))
     }
   }, [data]);
 
-  return revisionList;
-}
-
-export function useRevisionIdToNumber(appName: string, deploymentTargetId: string) {
-  const revisionList = useRevisionList(appName, deploymentTargetId);
-  const revisionIdToNumber: Record<string, number> = Object.fromEntries(revisionList.map(r => ([r.id, r.revision_number])))
-
-  return revisionIdToNumber;
-}
-
-export function useLatestRevisionNumber(appName: string, deploymentTargetId: string) {
-  const revisionList = useRevisionList(appName, deploymentTargetId);
-  return revisionList.map((revision) => revision.revision_number).reduce((a, b) => Math.max(a, b), 0)
+  return { revisionList, revisionIdToNumber };
 }

+ 2 - 2
dashboard/src/main/home/app-dashboard/app-view/AppHeader.tsx

@@ -77,7 +77,7 @@ const AppHeader: React.FC = () => {
       []
     );
 
-    return domains.length === 1 ? prefixSubdomain(domains[0]) : "";
+    return domains.length === 1 ? domains[0] : "";
   }, [latestProto]);
 
   return (
@@ -125,7 +125,7 @@ const AppHeader: React.FC = () => {
         <>
           <Container>
             <Text>
-              <a href={displayDomain} target="_blank">
+              <a href={prefixSubdomain(displayDomain)} target="_blank">
                 {displayDomain}
               </a>
             </Text>

+ 1 - 1
dashboard/src/main/home/app-dashboard/app-view/LatestRevisionContext.tsx

@@ -109,12 +109,12 @@ export const LatestRevisionProvider = ({
           app_revision: appRevisionValidator,
         })
         .parseAsync(res.data);
-
       return revisionData.app_revision;
     },
     {
       enabled: appParamsExist,
       refetchInterval: 5000,
+      refetchOnWindowFocus: false,
     }
   );
 

+ 5 - 3
dashboard/src/main/home/app-dashboard/app-view/tabs/activity-feed/events/cards/DeployEventCard.tsx

@@ -12,22 +12,24 @@ import { PorterAppDeployEvent } from "../types";
 import AnimateHeight from "react-animate-height";
 import ServiceStatusDetail from "./ServiceStatusDetail";
 import { useLatestRevision } from "main/home/app-dashboard/app-view/LatestRevisionContext";
-import { useRevisionIdToNumber } from "lib/hooks/useRevisionList";
+import { useRevisionList } from "lib/hooks/useRevisionList";
 
 type Props = {
   event: PorterAppDeployEvent;
   appName: string;
   showServiceStatusDetail?: boolean;
   deploymentTargetId: string;
+  projectId: number;
+  clusterId: number;
 };
 
-const DeployEventCard: React.FC<Props> = ({ event, appName, deploymentTargetId, showServiceStatusDetail = false }) => {
+const DeployEventCard: React.FC<Props> = ({ event, appName, deploymentTargetId, projectId, clusterId, showServiceStatusDetail = false }) => {
   const { latestRevision } = useLatestRevision();
   const [diffModalVisible, setDiffModalVisible] = useState(false);
   const [revertModalVisible, setRevertModalVisible] = useState(false);
   const [serviceStatusVisible, setServiceStatusVisible] = useState(showServiceStatusDetail);
 
-  const revisionIdToNumber = useRevisionIdToNumber(appName, deploymentTargetId);
+  const { revisionIdToNumber } = useRevisionList({ appName, deploymentTargetId, projectId, clusterId });
 
   const renderStatusText = () => {
     switch (event.status) {

+ 1 - 1
dashboard/src/main/home/app-dashboard/app-view/tabs/activity-feed/events/cards/EventCard.tsx

@@ -21,7 +21,7 @@ const EventCard: React.FC<Props> = ({ event, deploymentTargetId, isLatestDeployE
   return match(event)
     .with({ type: "APP_EVENT" }, (ev) => <AppEventCard event={ev} deploymentTargetId={deploymentTargetId} projectId={projectId} clusterId={clusterId} appName={appName} />)
     .with({ type: "BUILD" }, (ev) => <BuildEventCard event={ev} projectId={projectId} clusterId={clusterId} appName={appName} />)
-    .with({ type: "DEPLOY" }, (ev) => <DeployEventCard event={ev} appName={appName} showServiceStatusDetail={isLatestDeployEvent} deploymentTargetId={deploymentTargetId} appName={appName} />)
+    .with({ type: "DEPLOY" }, (ev) => <DeployEventCard event={ev} appName={appName} showServiceStatusDetail={isLatestDeployEvent} deploymentTargetId={deploymentTargetId} projectId={projectId} clusterId={clusterId} />)
     .with({ type: "PRE_DEPLOY" }, (ev) => <PreDeployEventCard event={ev} appName={appName} projectId={projectId} clusterId={clusterId} />)
     .exhaustive();
 };

+ 4 - 3
dashboard/src/main/home/app-dashboard/validate-apply/logs/Logs.tsx

@@ -25,8 +25,9 @@ import Container from "components/porter/Container";
 import Button from "components/porter/Button";
 import LogFilterContainer from "../../expanded-app/logs/LogFilterContainer";
 import StyledLogs from "../../expanded-app/logs/StyledLogs";
-import { useLatestRevisionNumber, useRevisionIdToNumber } from "lib/hooks/useRevisionList";
+import { useRevisionList } from "lib/hooks/useRevisionList";
 import { useLocation } from "react-router";
+import { useLatestRevision } from "../../app-view/LatestRevisionContext";
 
 type Props = {
     projectId: number;
@@ -81,8 +82,8 @@ const Logs: React.FC<Props> = ({
         output_stream: logQueryParamOpts.output_stream ?? GenericLogFilter.getDefaultOption("output_stream").value,
     });
 
-    const revisionIdToNumber = useRevisionIdToNumber(appName, deploymentTargetId)
-    const latestRevisionNumber = useLatestRevisionNumber(appName, deploymentTargetId)
+    const { revisionIdToNumber } = useRevisionList({ appName, deploymentTargetId, projectId, clusterId });
+    const { latestRevision: { revision_number: latestRevisionNumber } } = useLatestRevision();
 
     const isAgentVersionUpdated = (agentImage: string | undefined) => {
         if (agentImage == null) {

+ 1 - 1
dashboard/src/main/home/app-dashboard/validate-apply/logs/utils.ts

@@ -461,7 +461,7 @@ export const useLogs = ({
     setDate,
     JSON.stringify(selectedFilterValues),
     JSON.stringify(timeRange?.endTime),
-    filterPredeploy
+    filterPredeploy,
   ]);
 
   useEffect(() => {

+ 4 - 4
dashboard/src/shared/api.tsx

@@ -320,11 +320,11 @@ const appJobs = baseApi<
 const appPodStatus = baseApi<
   {
     deployment_target_id: string;
-    selectors: string;
+    service: string;
   },
-  { id: number; cluster_id: number }
->("GET", (pathParams) => {
-  return `/api/projects/${pathParams.id}/clusters/${pathParams.cluster_id}/apps/pods`;
+  { project_id: number; cluster_id: number, app_name: string }
+>("GET", ({ project_id, cluster_id, app_name }) => {
+  return `/api/projects/${project_id}/clusters/${cluster_id}/apps/${app_name}/pods`;
 });
 
 const getFeedEvents = baseApi<