Pārlūkot izejas kodu

Replace metrics filter with new version in v2 (#3819)

Feroze Mohideen 2 gadi atpakaļ
vecāks
revīzija
0de3d9f407

+ 28 - 10
dashboard/src/components/porter/Filter.tsx

@@ -1,46 +1,64 @@
-import React, {  useState } from "react";
+import React, {  useMemo, useState } from "react";
 import styled from "styled-components";
 import Select from "./Select";
 import Spacer from "./Spacer";
 
 import filter from "assets/filter.svg";
-import { GenericLogFilter, LogFilterName } from "main/home/app-dashboard/expanded-app/logs/types";
+import { GenericFilter, FilterName } from "main/home/app-dashboard/expanded-app/logs/types";
 
 type Props = {
-  filters: GenericLogFilter[];
-  filterString: string;
-  selectedFilterValues: Record<LogFilterName, string>;
+  filters: GenericFilter[];
+  selectedFilterValues: Partial<Record<FilterName, string>>;
 };
 
 const Filter: React.FC<Props> = ({
   filters,
-  filterString,
   selectedFilterValues,
 }) => {
   const [isExpanded, setIsExpanded] = useState(false);
 
+  const filterLabelString = useMemo(() => {
+    let filterString = "";
+    const serviceName = selectedFilterValues["service_name"];
+    const podName = selectedFilterValues["pod_name"];
+    const revision = selectedFilterValues["revision"];
+
+    if (serviceName && serviceName !== "all") {
+      filterString += serviceName;
+    } else if (podName && podName !== "all") {
+      filterString += podName.replace(/-[^-]*$/, '');
+    }
+    if (revision && revision !== "all") {
+      if (filterString !== "") {
+        filterString += " ";
+      }
+      filterString += "v" + revision;
+    }
+    return filterString;
+},[JSON.stringify(selectedFilterValues)]);
+
   return (
     <Relative>
       <StyledFilter onClick={() => setIsExpanded(!isExpanded)}>
         <img src={filter} />
         Filter
-        {filterString !== "" && (
+        {filterLabelString !== "" && (
           <>
             <Bar />
             <Spacer width="10px" />
-            {filterString}
+            {filterLabelString}
           </>
         )}
       </StyledFilter>
       <CloseOverlay onClick={() => setIsExpanded(false)} isExpanded={isExpanded} />
       <Dropdown isExpanded={isExpanded}>
-        {filters.map((filter: GenericLogFilter, i: number) => {
+        {filters.map((filter: GenericFilter, i: number) => {
           return (
             <React.Fragment key={i}>
               <FilterLabel>{filter.displayName}</FilterLabel>
               <Spacer y={0.5} />
               <Select
-                options={[filter.default, ...filter.options]}
+                options={filter.default ? [filter.default, ...filter.options] : filter.options}
                 setValue={filter.setValue}
                 value={selectedFilterValues[filter.name]}
               />

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

@@ -14,9 +14,9 @@ import Spacer from "components/porter/Spacer";
 import Link from "components/porter/Link";
 import notFound from "assets/not-found.png";
 import styled from "styled-components";
-import { SourceOptions } from "lib/porter-apps";
+import { SourceOptions, clientAppFromProto } from "lib/porter-apps";
 import { usePorterYaml } from "lib/hooks/usePorterYaml";
-import { DetectedServices } from "lib/porter-apps/services";
+import { ClientService, DetectedServices } from "lib/porter-apps/services";
 import {
   DeploymentTarget,
   useDeploymentTarget,
@@ -33,11 +33,13 @@ export const LatestRevisionContext = createContext<{
   servicesFromYaml: DetectedServices | null;
   clusterId: number;
   projectId: number;
+  appName: string;
   deploymentTarget: DeploymentTarget & { namespace: string };
   previewRevision: AppRevision | null;
   attachedEnvGroups: PopulatedEnvGroup[];
   appEnv?: PopulatedEnvGroup;
   setPreviewRevision: Dispatch<SetStateAction<AppRevision | null>>;
+  latestClientServices: ClientService[];
 } | null>(null);
 
 export const useLatestRevision = () => {
@@ -258,6 +260,15 @@ export const LatestRevisionProvider = ({
     });
   }, [latestRevision]);
 
+  const latestClientServices = useMemo(() => {
+    if (!latestProto) {
+      return [];
+    }
+
+    const app = clientAppFromProto({proto: latestProto, overrides: detectedServices});
+    return app.services;
+  }, [latestProto, detectedServices]);
+
   if (
     status === "loading" ||
     porterAppStatus === "loading" ||
@@ -307,6 +318,8 @@ export const LatestRevisionProvider = ({
         appEnv,
         previewRevision,
         setPreviewRevision,
+        latestClientServices,
+        appName,
       }}
     >
       {children}

+ 8 - 12
dashboard/src/main/home/app-dashboard/app-view/tabs/MetricsTab.tsx

@@ -3,20 +3,16 @@ import { useLatestRevision } from "../LatestRevisionContext";
 import MetricsSection from "../../validate-apply/metrics/MetricsSection";
 
 const MetricsTab: React.FC = () => {
-    const { projectId, clusterId, latestProto , deploymentTarget} = useLatestRevision();
-
-    const appName = latestProto.name
+    const { projectId, clusterId, appName, latestClientServices, deploymentTarget} = useLatestRevision();
 
     return (
-        <>
-            <MetricsSection
-                projectId={projectId}
-                clusterId={clusterId}
-                appName={appName}
-                services={latestProto.services}
-                deploymentTargetId={deploymentTarget.id}
-            />
-        </>
+        <MetricsSection
+            projectId={projectId}
+            clusterId={clusterId}
+            appName={appName}
+            services={latestClientServices}
+            deploymentTargetId={deploymentTarget.id}
+        />
     );
 };
 

+ 9 - 11
dashboard/src/main/home/app-dashboard/app-view/tabs/activity-feed/events/cards/PreDeployEventCard.tsx

@@ -18,6 +18,7 @@ import document from "assets/document.svg";
 import { PorterAppPreDeployEvent } from "../types";
 import { useLatestRevision } from "main/home/app-dashboard/app-view/LatestRevisionContext";
 import pull_request_icon from "assets/pull_request_icon.svg";
+import { match } from "ts-pattern";
 
 type Props = {
   event: PorterAppPreDeployEvent;
@@ -39,16 +40,13 @@ const PreDeployEventCard: React.FC<Props> = ({
   const { porterApp } = useLatestRevision();
 
   const renderStatusText = (event: PorterAppPreDeployEvent) => {
-    switch (event.status) {
-      case "SUCCESS":
-        return <Text color={getStatusColor(event.status)}>Pre-deploy succeeded</Text>;
-      case "FAILED":
-        return <Text color={getStatusColor(event.status)}>Pre-deploy failed</Text>;
-      case "CANCELED":
-        return <Text color={getStatusColor(event.status)}>Pre-deploy canceled</Text>;
-      default:
-        return <Text color={getStatusColor(event.status)}>Pre-deploy in progress...</Text>;
-    }
+    const color = getStatusColor(event.status);
+    const text = match(event.status)
+      .with("SUCCESS", () => "Pre-deploy successful")
+      .with("FAILED", () => "Pre-deploy failed")
+      .with("CANCELED", () => "Pre-deploy canceled")
+      .otherwise(() => "Pre-deploy  in progress...")
+    return <Text color={color}>{text}</Text>;
   };
 
   return (
@@ -88,7 +86,7 @@ const PreDeployEventCard: React.FC<Props> = ({
               <Container row>
                 <Icon src={document} height="10px" />
                 <Spacer inline width="5px" />
-                View details
+                View pre-deploy logs
               </Container>
             </Link>
             {(event.status !== "SUCCESS") &&

+ 4 - 2
dashboard/src/main/home/app-dashboard/app-view/tabs/activity-feed/events/cards/ServiceStatusDetail.tsx

@@ -5,13 +5,15 @@ import React from 'react'
 import styled from 'styled-components';
 import { getStatusColor, getStatusIcon } from '../utils';
 import Link from 'components/porter/Link';
-import { PorterAppDeployEvent } from "../types";
 import { Service } from 'main/home/app-dashboard/new-app-flow/serviceTypes';
 import { useLatestRevision } from 'main/home/app-dashboard/app-view/LatestRevisionContext';
 import { deserializeService, serializedServiceFromProto } from 'lib/porter-apps/services';
 
 type Props = {
-    serviceDeploymentMetadata: PorterAppDeployEvent["metadata"]["service_deployment_metadata"];
+    serviceDeploymentMetadata: Record<string, {
+        status: string;
+        type: string;
+    }>;
     appName: string;
     revision: number;
 }

+ 0 - 1
dashboard/src/main/home/app-dashboard/app-view/tabs/activity-feed/events/focus-views/PredeployEventFocusView.tsx

@@ -1,6 +1,5 @@
 import Spacer from "components/porter/Spacer";
 import React from "react";
-import dayjs from "dayjs";
 import Text from "components/porter/Text";
 import { readableDate } from "shared/string_utils";
 import { getDuration, getStatusColor } from "../utils";

+ 2 - 2
dashboard/src/main/home/app-dashboard/expanded-app/logs/LogFilterComponent.tsx

@@ -1,12 +1,12 @@
 import Text from "components/porter/Text";
 import React from "react";
 import styled from "styled-components";
-import { GenericLogFilter } from "./types";
+import { GenericFilter } from "./types";
 import Spacer from "components/porter/Spacer";
 import Select from "components/porter/Select";
 
 type Props = {
-    filter: GenericLogFilter;
+    filter: GenericFilter;
     selectedValue: string;
 };
 

+ 4 - 4
dashboard/src/main/home/app-dashboard/expanded-app/logs/LogFilterContainer.tsx

@@ -3,19 +3,19 @@ import React from "react";
 import styled from "styled-components";
 import filterOutline from "assets/filter-outline-icon.svg";
 import filterOutlineWhite from "assets/filter-outline-white.svg";
-import { GenericLogFilter, LogFilterName } from "./types";
+import { GenericFilter, FilterName } from "./types";
 import Icon from "components/porter/Icon";
 import Spacer from "components/porter/Spacer";
 import LogFilterComponent from "./LogFilterComponent";
 
 type Props = {
-    filters: GenericLogFilter[];
-    selectedFilterValues: Record<LogFilterName, string>;
+    filters: GenericFilter[];
+    selectedFilterValues: Record<FilterName, string>;
 };
 
 const LogFilterContainer: React.FC<Props> = (props) => {
     const getIcon = () => {
-        if (props.filters.every((filter) => GenericLogFilter.isDefault(filter, props.selectedFilterValues[filter.name]))) {
+        if (props.filters.every((filter) => GenericFilter.isDefault(filter, props.selectedFilterValues[filter.name]))) {
             return filterOutline;
         }
         return filterOutlineWhite;

+ 11 - 28
dashboard/src/main/home/app-dashboard/expanded-app/logs/LogSection.tsx

@@ -12,7 +12,7 @@ import spinner from "assets/loading.gif";
 import { Context } from "shared/Context";
 import api from "shared/api";
 import { getPodSelectorFromServiceName, useLogs } from "./utils";
-import { Direction, GenericFilterOption, GenericLogFilter, LogFilterName, LogFilterQueryParamOpts } from "./types";
+import { Direction, GenericFilterOption, GenericFilter, FilterName, LogFilterQueryParamOpts } from "./types";
 import dayjs, { Dayjs } from "dayjs";
 import Loading from "components/Loading";
 import _ from "lodash";
@@ -62,11 +62,11 @@ const LogSection: React.FC<Props> = ({
   const [isLoading, setIsLoading] = useState(true);
   const [logsError, setLogsError] = useState<string | undefined>(undefined);
 
-  const [selectedFilterValues, setSelectedFilterValues] = useState<Record<LogFilterName, string>>({
-    revision: filterOpts?.revision ?? GenericLogFilter.getDefaultOption("revision").value,
-    output_stream: filterOpts?.output_stream ?? GenericLogFilter.getDefaultOption("output_stream").value,
-    pod_name: getPodSelectorFromServiceName(filterOpts?.service, services) ?? GenericLogFilter.getDefaultOption("pod_name").value,
-    service_name: filterOpts?.service ?? GenericLogFilter.getDefaultOption("service_name").value,
+  const [selectedFilterValues, setSelectedFilterValues] = useState<Record<FilterName, string>>({
+    revision: filterOpts?.revision ?? GenericFilter.getDefaultOption("revision").value,
+    output_stream: filterOpts?.output_stream ?? GenericFilter.getDefaultOption("output_stream").value,
+    pod_name: getPodSelectorFromServiceName(filterOpts?.service, services) ?? GenericFilter.getDefaultOption("pod_name").value,
+    service_name: filterOpts?.service ?? GenericFilter.getDefaultOption("service_name").value,
   });
 
   const createVersionOptions = (number: number) => {
@@ -110,11 +110,11 @@ const LogSection: React.FC<Props> = ({
     return patch >= 4;
   }
 
-  const [filters, setFilters] = useState<GenericLogFilter[]>(showFilter ? [
+  const [filters, setFilters] = useState<GenericFilter[]>(showFilter ? [
     {
       name: "pod_name",
       displayName: "Service",
-      default: GenericLogFilter.getDefaultOption("pod_name"),
+      default: GenericFilter.getDefaultOption("pod_name"),
       options: services?.map(s => {
         return GenericFilterOption.of(s.name, `${s.name}-${s.type == "worker" ? "wkr" : s.type}`)
       }) ?? [],
@@ -128,7 +128,7 @@ const LogSection: React.FC<Props> = ({
     {
       name: "revision",
       displayName: "Version",
-      default: GenericLogFilter.getDefaultOption("revision"),
+      default: GenericFilter.getDefaultOption("revision"),
       options: currentChart != null ? createVersionOptions(currentChart.version) : [],
       setValue: (value: string) => {
         setSelectedFilterValues((s) => ({
@@ -140,7 +140,7 @@ const LogSection: React.FC<Props> = ({
     {
       name: "output_stream",
       displayName: "Output Stream",
-      default: GenericLogFilter.getDefaultOption("output_stream"),
+      default: GenericFilter.getDefaultOption("output_stream"),
       options: [
         GenericFilterOption.of('stdout', 'stdout'),
         GenericFilterOption.of("stderr", "stderr"),
@@ -199,22 +199,6 @@ const LogSection: React.FC<Props> = ({
     }
   };
 
-  const generateFilterString = () => {
-    let filterString = "";
-    if (selectedFilterValues["service_name"] !== "all") {
-      filterString += selectedFilterValues["service_name"];
-    } else if (selectedFilterValues["pod_name"] !== "all") {
-      filterString += selectedFilterValues["pod_name"].replace(/-[^-]*$/, '');
-    }
-    if (selectedFilterValues["revision"] !== "all") {
-      if (filterString !== "") {
-        filterString += " ";
-      }
-      filterString += "v" + selectedFilterValues["revision"];
-    }
-    return filterString;
-  };
-
   const renderContents = () => {
     return (
       <>
@@ -236,7 +220,6 @@ const LogSection: React.FC<Props> = ({
             {showFilter && (
               <Filter
                 filters={filters}
-                filterString={generateFilterString()}
                 selectedFilterValues={selectedFilterValues}
               />
             )}
@@ -347,7 +330,7 @@ const LogSection: React.FC<Props> = ({
           {
             name: "pod_name",
             displayName: "Service",
-            default: GenericLogFilter.getDefaultOption("pod_name"),
+            default: GenericFilter.getDefaultOption("pod_name"),
             options: services?.map(s => {
               return GenericFilterOption.of(s.name, `${s.name}-${s.type == "worker" ? "wkr" : s.type}`)
             }) ?? [],

+ 5 - 5
dashboard/src/main/home/app-dashboard/expanded-app/logs/StyledLogs.tsx

@@ -1,5 +1,5 @@
 import React from "react";
-import { GenericLogFilter, PorterLog } from "./types";
+import { GenericFilter, PorterLog } from "./types";
 import styled from "styled-components";
 import Anser from "anser";
 import dayjs from "dayjs";
@@ -10,7 +10,7 @@ import { Service } from "../../new-app-flow/serviceTypes";
 type Props = {
     logs: PorterLog[];
     appName: string;
-    filters: GenericLogFilter[];
+    filters: GenericFilter[];
     services?: Service[];
 };
 
@@ -20,7 +20,7 @@ const StyledLogs: React.FC<Props> = ({
     filters,
     services,
 }) => {
-    const renderFilterTagForLog = (filter: GenericLogFilter, log: PorterLog, index: number) => {
+    const renderFilterTagForLog = (filter: GenericFilter, log: PorterLog, index: number) => {
         if (log.metadata == null) {
             return null;
         }
@@ -49,7 +49,7 @@ const StyledLogs: React.FC<Props> = ({
                         <LogInnerPill
                             color={"white"}
                             key={index}
-                            onClick={() => filter.setValue(getPodSelectorFromServiceName(getServiceNameFromPodNameAndAppName(log.metadata.pod_name, appName), services) ?? GenericLogFilter.getDefaultOption("pod_name").value)}
+                            onClick={() => filter.setValue(getPodSelectorFromServiceName(getServiceNameFromPodNameAndAppName(log.metadata.pod_name, appName), services) ?? GenericFilter.getDefaultOption("pod_name").value)}
                         >
                             {getServiceNameFromPodNameAndAppName(log.metadata.pod_name, appName)}
                         </LogInnerPill>
@@ -64,7 +64,7 @@ const StyledLogs: React.FC<Props> = ({
                         <LogInnerPill
                             color={"white"}
                             key={index}
-                            onClick={() => filter.setValue(log.metadata?.raw_labels?.porter_run_service_name ?? GenericLogFilter.getDefaultOption("service_name").value)}
+                            onClick={() => filter.setValue(log.metadata?.raw_labels?.porter_run_service_name ?? GenericFilter.getDefaultOption("service_name").value)}
                         >
                             {log.metadata.raw_labels?.porter_run_service_name}
                         </LogInnerPill>

+ 8 - 9
dashboard/src/main/home/app-dashboard/expanded-app/logs/types.ts

@@ -18,7 +18,6 @@ export interface PaginationInfo {
     nextCursor: string | null;
 }
 
-
 const rawLabelsValidator = z.object({
     porter_run_absolute_name: z.string().optional(),
     porter_run_app_id: z.string().optional(),
@@ -54,20 +53,20 @@ export const GenericFilterOption = {
         return { label, value };
     }
 }
-export type LogFilterName = 'revision' | 'output_stream' | 'pod_name' | 'service_name';
-export interface GenericLogFilter {
-    name: LogFilterName;
+export type FilterName = 'revision' | 'output_stream' | 'pod_name' | 'service_name';
+export interface GenericFilter {
+    name: FilterName;
     displayName: string;
-    default: GenericFilterOption;
+    default: GenericFilterOption | undefined;
     options: GenericFilterOption[];
     setValue: (value: string) => void;
 }
-export const GenericLogFilter = {
-    isDefault: (filter: GenericLogFilter, value: string) => {
-        return filter.default.value === value;
+export const GenericFilter = {
+    isDefault: (filter: GenericFilter, value: string) => {
+        return filter.default && filter.default.value === value;
     },
 
-    getDefaultOption: (filterName: LogFilterName) => {
+    getDefaultOption: (filterName: FilterName) => {
         switch (filterName) {
             case 'service_name':
                 return GenericFilterOption.of('All', 'all');

+ 5 - 5
dashboard/src/main/home/app-dashboard/expanded-app/logs/utils.ts

@@ -6,7 +6,7 @@ import Anser from "anser";
 import { Context } from "shared/Context";
 import { useWebsockets, NewWebsocketOptions } from "shared/hooks/useWebsockets";
 import { ChartType } from "shared/types";
-import { AgentLog, agentLogValidator, Direction, PorterLog, PaginationInfo, GenericLogFilter, LogFilterName } from "./types";
+import { AgentLog, agentLogValidator, Direction, PorterLog, PaginationInfo, GenericFilter, FilterName } from "./types";
 import { Service } from "../../new-app-flow/serviceTypes";
 
 const MAX_LOGS = 5000;
@@ -47,7 +47,7 @@ export const parseLogs = (logs: any[] = []): PorterLog[] => {
 };
 
 export const useLogs = (
-  selectedFilterValues: Record<LogFilterName, string>,
+  selectedFilterValues: Record<FilterName, string>,
   appName: string,
   namespace: string,
   searchParam: string,
@@ -73,7 +73,7 @@ export const useLogs = (
   });
 
   // if currentPodName is default value we are looking at all chart pod logs
-  const currentPodSelector = selectedFilterValues.pod_name === GenericLogFilter.getDefaultOption("pod_name").value
+  const currentPodSelector = selectedFilterValues.pod_name === GenericFilter.getDefaultOption("pod_name").value
     ? `${currentChart?.name ?? ''}-.*` : `${currentChart?.name}-${selectedFilterValues.pod_name}-.*`;
 
   // if we are live:
@@ -229,12 +229,12 @@ export const useLogs = (
         return false;
       }
 
-      if (selectedFilterValues.output_stream !== GenericLogFilter.getDefaultOption("output_stream").value &&
+      if (selectedFilterValues.output_stream !== GenericFilter.getDefaultOption("output_stream").value &&
         log.metadata.output_stream !== selectedFilterValues.output_stream) {
         return false;
       }
 
-      if (selectedFilterValues.revision !== GenericLogFilter.getDefaultOption("revision").value &&
+      if (selectedFilterValues.revision !== GenericFilter.getDefaultOption("revision").value &&
         log.metadata.revision !== selectedFilterValues.revision) {
         return false;
       }

+ 22 - 39
dashboard/src/main/home/app-dashboard/validate-apply/logs/Logs.tsx

@@ -1,8 +1,6 @@
 import React, {
     useCallback,
-    useContext,
     useEffect,
-    useMemo,
     useRef,
     useState,
 } from "react";
@@ -12,7 +10,7 @@ import styled from "styled-components";
 import spinner from "assets/loading.gif";
 import api from "shared/api";
 import { useLogs } from "./utils";
-import { Direction, GenericFilterOption, GenericLogFilter, LogFilterName } from "../../expanded-app/logs/types";
+import { Direction, GenericFilterOption, GenericFilter, FilterName } from "../../expanded-app/logs/types";
 import dayjs, { Dayjs } from "dayjs";
 import Loading from "components/Loading";
 import _ from "lodash";
@@ -37,7 +35,7 @@ type Props = {
     serviceNames: string[];
     deploymentTargetId: string;
     appRevisionId?: string;
-    logFilterNames?: LogFilterName[];
+    logFilterNames?: FilterName[];
     timeRange?: {
         startTime?: Dayjs;
         endTime?: Dayjs;
@@ -78,11 +76,11 @@ const Logs: React.FC<Props> = ({
     const [isLoading, setIsLoading] = useState(true);
     const [logsError, setLogsError] = useState<string | undefined>(undefined);
 
-    const [selectedFilterValues, setSelectedFilterValues] = useState<Record<LogFilterName, string>>({
-        service_name: logQueryParamOpts?.service ?? GenericLogFilter.getDefaultOption("service_name").value,
+    const [selectedFilterValues, setSelectedFilterValues] = useState<Record<FilterName, string>>({
+        service_name: logQueryParamOpts?.service ?? GenericFilter.getDefaultOption("service_name").value,
         pod_name: "", // not supported in v2
-        revision: logQueryParamOpts.revision ?? GenericLogFilter.getDefaultOption("revision").value,
-        output_stream: logQueryParamOpts.output_stream ?? GenericLogFilter.getDefaultOption("output_stream").value,
+        revision: logQueryParamOpts.revision ?? GenericFilter.getDefaultOption("revision").value,
+        output_stream: logQueryParamOpts.output_stream ?? GenericFilter.getDefaultOption("output_stream").value,
     });
 
     const { revisionIdToNumber } = useRevisionList({ appName, deploymentTargetId, projectId, clusterId });
@@ -129,11 +127,11 @@ const Logs: React.FC<Props> = ({
         }).reverse().slice(0, 3);
     }
 
-    const [filters, setFilters] = useState<GenericLogFilter[]>([
+    const [filters, setFilters] = useState<GenericFilter[]>([
         {
             name: "service_name",
             displayName: "Service",
-            default: GenericLogFilter.getDefaultOption("service_name"),
+            default: GenericFilter.getDefaultOption("service_name"),
             options: serviceNames.map(s => {
                 return GenericFilterOption.of(s, s)
             }) ?? [],
@@ -143,11 +141,11 @@ const Logs: React.FC<Props> = ({
                     service_name: value,
                 }));
             }
-        } as GenericLogFilter,
+        } as GenericFilter,
         {
             name: "revision",
             displayName: "Version",
-            default: GenericLogFilter.getDefaultOption("revision"),
+            default: GenericFilter.getDefaultOption("revision"),
             options: createVersionOptions(latestRevisionNumber),
             setValue: (value: string) => {
                 setSelectedFilterValues((s) => ({
@@ -155,11 +153,11 @@ const Logs: React.FC<Props> = ({
                     revision: value,
                 }));
             }
-        } as GenericLogFilter,
+        } as GenericFilter,
         {
             name: "output_stream",
             displayName: "Output Stream",
-            default: GenericLogFilter.getDefaultOption("output_stream"),
+            default: GenericFilter.getDefaultOption("output_stream"),
             options: [
                 GenericFilterOption.of('stdout', 'stdout'),
                 GenericFilterOption.of("stderr", "stderr"),
@@ -170,8 +168,8 @@ const Logs: React.FC<Props> = ({
                     output_stream: value,
                 }));
             }
-        } as GenericLogFilter,
-    ].filter((f: GenericLogFilter) => logFilterNames.includes(f.name)));
+        } as GenericFilter,
+    ].filter((f: GenericFilter) => logFilterNames.includes(f.name)));
 
 
     const notify = (message: string) => {
@@ -205,7 +203,7 @@ const Logs: React.FC<Props> = ({
             {
                 name: "service_name",
                 displayName: "Service",
-                default: GenericLogFilter.getDefaultOption("service_name"),
+                default: GenericFilter.getDefaultOption("service_name"),
                 options: serviceNames.map(s => {
                     return GenericFilterOption.of(s, s)
                 }) ?? [],
@@ -215,11 +213,11 @@ const Logs: React.FC<Props> = ({
                         service_name: value,
                     }));
                 }
-            } as GenericLogFilter,
+            } as GenericFilter,
             {
                 name: "revision",
                 displayName: "Version",
-                default: GenericLogFilter.getDefaultOption("revision"),
+                default: GenericFilter.getDefaultOption("revision"),
                 options: createVersionOptions(latestRevisionNumber),
                 setValue: (value: string) => {
                     setSelectedFilterValues((s) => ({
@@ -227,11 +225,11 @@ const Logs: React.FC<Props> = ({
                         revision: value,
                     }));
                 }
-            } as GenericLogFilter,
+            } as GenericFilter,
             {
                 name: "output_stream",
                 displayName: "Output Stream",
-                default: GenericLogFilter.getDefaultOption("output_stream"),
+                default: GenericFilter.getDefaultOption("output_stream"),
                 options: [
                     GenericFilterOption.of('stdout', 'stdout'),
                     GenericFilterOption.of("stderr", "stderr"),
@@ -242,8 +240,8 @@ const Logs: React.FC<Props> = ({
                         output_stream: value,
                     }));
                 }
-            } as GenericLogFilter,
-        ].filter((f: GenericLogFilter) => logFilterNames.includes(f.name)))
+            } as GenericFilter,
+        ].filter((f: GenericFilter) => logFilterNames.includes(f.name)))
 
         if (latestRevisionNumber && !logQueryParamOpts.revision) {
             setSelectedFilterValues({
@@ -278,20 +276,6 @@ const Logs: React.FC<Props> = ({
         }
     };
 
-    const filterLabelString = useMemo(() => {
-        let filterString = "";
-        if (selectedFilterValues["service_name"] !== null && selectedFilterValues["service_name"] !== "all") {
-          filterString += selectedFilterValues["service_name"];
-        } 
-        if (selectedFilterValues["revision"] != null && selectedFilterValues["revision"] !== "all") {
-          if (filterString !== "") {
-            filterString += " ";
-          }
-          filterString += "v" + selectedFilterValues["revision"];
-        }
-        return filterString;
-    },[JSON.stringify(selectedFilterValues)]);
-
     const renderContents = () => {
         return (
             <>
@@ -312,7 +296,6 @@ const Logs: React.FC<Props> = ({
                     <Flex>
                         <Filter
                             filters={filters}
-                            filterString={filterLabelString} 
                             selectedFilterValues={selectedFilterValues}
                         />
                         <Spacer inline x={1} />
@@ -354,7 +337,7 @@ const Logs: React.FC<Props> = ({
                                     appName={appName}
                                 />
                                 <LoadMoreButton
-                                    active={selectedDate && logs.length !== 0}
+                                    active={selectedDate != null && logs.length !== 0}
                                     role="button"
                                     onClick={() => moveCursor(Direction.forward)}
                                 >

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

@@ -10,8 +10,8 @@ import {
   Direction,
   PorterLog,
   PaginationInfo,
-  LogFilterName,
-  GenericLogFilter
+  FilterName,
+  GenericFilter
 } from "../../expanded-app/logs/types";
 
 const MAX_LOGS = 5000;
@@ -60,7 +60,7 @@ export const useLogs = ({
 }: {
   projectID: number,
   clusterID: number,
-  selectedFilterValues: Record<LogFilterName, string>,
+  selectedFilterValues: Record<FilterName, string>,
   appName: string,
   serviceName: string,
   deploymentTargetId: string,
@@ -244,7 +244,7 @@ export const useLogs = ({
         return true;
       }
 
-      if (selectedFilterValues.output_stream !== GenericLogFilter.getDefaultOption("output_stream").value &&
+      if (selectedFilterValues.output_stream !== GenericFilter.getDefaultOption("output_stream").value &&
         log.metadata.output_stream !== selectedFilterValues.output_stream) {
         return false;
       }
@@ -253,7 +253,7 @@ export const useLogs = ({
         return false;
       }
 
-      if (selectedFilterValues.revision !== GenericLogFilter.getDefaultOption("revision").value &&
+      if (selectedFilterValues.revision !== GenericFilter.getDefaultOption("revision").value &&
         log.metadata.revision !== selectedFilterValues.revision) {
         return false;
       }

+ 54 - 54
dashboard/src/main/home/app-dashboard/validate-apply/metrics/MetricsSection.tsx

@@ -4,7 +4,6 @@ import styled from "styled-components";
 import api from "shared/api";
 
 import TabSelector from "components/TabSelector";
-import SelectRow from "components/form-components/SelectRow";
 import { MetricNormalizer, resolutions, secondsBeforeNow } from "../../expanded-app/metrics/utils";
 import { Metric, MetricType, NginxStatusMetric } from "../../expanded-app/metrics/types";
 import { match } from "ts-pattern";
@@ -13,22 +12,19 @@ import MetricsChart from "../../expanded-app/metrics/MetricsChart";
 import { useQuery } from "@tanstack/react-query";
 import Loading from "components/Loading";
 import CheckboxRow from "components/CheckboxRow";
-import { PorterApp } from "@porter-dev/api-contracts";
 import { useLocation } from "react-router";
+import Filter from "components/porter/Filter";
+import { GenericFilterOption, GenericFilter, FilterName } from "../../expanded-app/logs/types";
+import { ClientService } from "lib/porter-apps/services";
 
 type PropsType = {
   projectId: number;
   clusterId: number;
   appName: string;
-  services: PorterApp["services"];
+  services: ClientService[];
   deploymentTargetId: string;
 };
 
-type ServiceOption = {
-  label: string;
-  value: string;
-}
-
 const MetricsSection: React.FunctionComponent<PropsType> = ({
   projectId,
   clusterId,
@@ -39,65 +35,74 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
   const { search } = useLocation();
   const queryParams = new URLSearchParams(search);
   const serviceFromQueryParams = queryParams.get("service");
-
-  const [selectedServiceName, setSelectedServiceName] = useState<string>(serviceFromQueryParams ?? "");
   const [selectedRange, setSelectedRange] = useState("1H");
   const [showAutoscalingThresholds, setShowAutoscalingThresholds] = useState(true);
 
-  const serviceOptions: ServiceOption[] = useMemo(() => {
-    return Object.keys(services).map((name) => {
-      return {
-        label: name,
-        value: name,
-      };
-    });
+  // filter out jobs until we can display metrics on them
+  const serviceOptions: GenericFilterOption[] = useMemo(() => {
+    const nonJobServiceNames = services.filter((s) => s.config.type !== "job").map((s) => s.name);
+    return nonJobServiceNames.map(({ value }) => GenericFilterOption.of(value, value));
   }, [services]);
 
+  const filters: GenericFilter[] = useMemo(() => {
+    return [
+      {
+        name: "service_name",
+        displayName: "Service",
+        default: undefined,
+        options: serviceOptions,
+        setValue: (value: string) => {
+          setSelectedFilterValues((prev) => ({ ...prev, service_name: value }));
+        },
+      } as GenericFilter,
+    ];
+  },[serviceOptions]);
+
+  const [selectedFilterValues, setSelectedFilterValues] = useState<Partial<Record<FilterName, string>>>({
+    service_name: serviceFromQueryParams && Object.keys(services).includes(serviceFromQueryParams) ? serviceFromQueryParams : "",
+  }); 
+
   useEffect(() => {
-    if (serviceOptions.length > 0 && selectedServiceName === "") {
-      setSelectedServiceName(serviceOptions[0].value)
+    if (serviceOptions.length > 0 && selectedFilterValues.service_name === "") {
+      setSelectedFilterValues((prev) => ({ ...prev, service_name: serviceOptions[0].value }));
     }
-  }, []);
+  }, [serviceOptions, selectedFilterValues.service_name]);
 
   const [serviceName, serviceKind, metricTypes, isHpaEnabled] = useMemo(() => {
-    if (selectedServiceName === "") {
-      return ["", "", [], false]
+    if (!selectedFilterValues.service_name) {
+      return ["", "", [], false];
     }
 
-    const service = services[selectedServiceName]
+    const service = services.find(s => s.name.value === selectedFilterValues.service_name);
+    if (!service) {
+      return ["", "", [], false];
+    }
 
-    const serviceName = service.absoluteName === "" ? (appName + "-" + selectedServiceName) : service.absoluteName
+    const serviceName = `${appName}-${service.name.value}`;
 
-    let serviceKind = ""
+    let serviceKind = "";
     const metricTypes: MetricType[] = ["cpu", "memory"];
-    let isHpaEnabled = false
-
-    if (service.config.case === "webConfig") {
-      serviceKind = "web"
-      metricTypes.push("network");
-      if (service.config.value.autoscaling != null && service.config.value.autoscaling.enabled) {
-        isHpaEnabled = true
-      }
-      if (!service.config.value.private) {
-        metricTypes.push("nginx:status")
-      }
-    }
+    let isHpaEnabled = false;
 
-    if (service.config.case === "workerConfig") {
-      serviceKind = "worker"
-      if (service.config.value.autoscaling != null && service.config.value.autoscaling.enabled) {
-        isHpaEnabled = true
+    if (service.config.type === "web" || service.config.type === "worker") {
+      serviceKind = service.config.type === "web" ? "web" : "worker";
+      if (service.config.autoscaling?.enabled.value) {
+        isHpaEnabled = true;
       }
-    }
-
-
+      if (service.config.type === "web") {
+        metricTypes.push("network");
+        if (!service.config.private) {
+          metricTypes.push("nginx:status");
+        }
+      } 
+    } 
 
     if (isHpaEnabled) {
       metricTypes.push("hpa_replicas");
     }
 
     return [serviceName, serviceKind, metricTypes, isHpaEnabled]
-  }, [selectedServiceName])
+  }, [selectedFilterValues.service_name])
 
 
   const { data: metricsData, isLoading: isMetricsDataLoading, refetch } = useQuery(
@@ -110,7 +115,6 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
       deploymentTargetId,
     ],
     async () => {
-
       if (serviceName === "" || serviceKind === "" || metricTypes.length === 0) {
         return;
       }
@@ -240,7 +244,7 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
       return metrics;
     },
     {
-      enabled: selectedServiceName !== "",
+      enabled: !!selectedFilterValues.service_name,
       refetchOnWindowFocus: false,
       refetchInterval: 10000, // refresh metrics every 10 seconds
     }
@@ -284,13 +288,9 @@ const MetricsSection: React.FunctionComponent<PropsType> = ({
     <StyledMetricsSection>
       <MetricsHeader>
         <Flex>
-          <SelectRow
-            displayFlex={true}
-            label="Service"
-            value={selectedServiceName}
-            setActiveValue={(x: any) => setSelectedServiceName(x)}
-            options={serviceOptions}
-            width="200px"
+          <Filter
+            filters={filters}
+            selectedFilterValues={selectedFilterValues}
           />
           <Highlight color={"#7d7d81"} onClick={() => refetch()}>
             <i className="material-icons">autorenew</i>