Răsfoiți Sursa

[POR-2209] add filters to db dashboard (#4175)

Feroze Mohideen 2 ani în urmă
părinte
comite
51a39d89ed

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

@@ -34,7 +34,7 @@ const StyledIcon = styled.img<{
   opacity?: number;
   additionalStyles?: string;
 }>`
-  height: ${props => props.size || 20}px;
-  opacity: ${props => props.opacity || 1};
-  ${props => props.additionalStyles ? props.additionalStyles : ""}
-`;
+  height: ${(props) => props.size || 20}px;
+  opacity: ${(props) => props.opacity || 1};
+  ${(props) => (props.additionalStyles ? props.additionalStyles : "")}
+`;

+ 29 - 31
dashboard/src/components/porter/Select.tsx

@@ -1,14 +1,16 @@
-import React, { useEffect, useRef, useState } from "react";
+import React from "react";
 import styled from "styled-components";
+
 import arrow from "assets/arrow-down.svg";
+
 import Container from "./Container";
 
 type Props = {
   width?: string;
-  options: Array<{ 
+  options: Array<{
     label: string;
     value: string;
-    icon?: HTMLImageElement | string;
+    icon?: string;
     disabled?: boolean;
   }>;
   label?: string | React.ReactNode;
@@ -23,48 +25,46 @@ type Props = {
 };
 
 const Select: React.FC<Props> = ({
-  width,
   options,
   label,
   labelColor,
-  height,
   error,
   children,
   disabled,
   value,
   setValue,
   prefix,
+  width = "200px",
+  height = "35px",
 }) => {
-  const prefixRef = useRef<HTMLDivElement>(null);
-
   return (
     <Block width={width}>
       {label && <Label color={labelColor}>{label}</Label>}
       <SelectWrapper>
         <AbsoluteWrapper>
-        <Prefix>{prefix}</Prefix>
-        <Bar />
-        {options.map((option) => {
-          if (option.value === value) {
-            return (
-              <Container key={1} row>
-                {option.icon && <Img src={option?.icon} />}
-                {option.label}
-              </Container>
-            )
-          }
-          return null;
-        })}
-        <img src={arrow} />
+          <Prefix>{prefix}</Prefix>
+          <Bar />
+          {options.map((option) => {
+            if (option.value === value) {
+              return (
+                <Container key={1} row>
+                  {option.icon && <Img src={option?.icon} />}
+                  {option.label}
+                </Container>
+              );
+            }
+            return null;
+          })}
+          <img src={arrow} />
         </AbsoluteWrapper>
         <StyledSelect
           onChange={(e) => {
-            setValue(e.target.value);
+            setValue?.(e.target.value);
           }}
           width={width}
           height={height}
           hasError={(error && true) || error === ""}
-          disabled={disabled ? disabled : false}
+          disabled={disabled || false}
           value={value}
         >
           {options.map((option, i) => {
@@ -134,9 +134,9 @@ const Block = styled.div<{
   width: ${(props) => props.width || "200px"};
 `;
 
-const Label = styled.div<{color?: string}>`
+const Label = styled.div<{ color?: string }>`
   font-size: 13px;
-  color: ${({color = "#aaaabb"}) => color};
+  color: ${({ color = "#aaaabb" }) => color};
   margin-bottom: 10px;
 `;
 
@@ -156,9 +156,9 @@ const Error = styled.div`
 const SelectWrapper = styled.div`
   position: relative;
   background: ${(props) => props.theme.fg};
-  border: 1px solid ${(props) => (props.hasError ? "#ff3b62" : "#494b4f")};
+  border: 1px solid #494b4f;
   :hover {
-    border: 1px solid ${(props) => (props.hasError ? "#ff3b62" : "#7a7b80")};
+    border: 1px solid #7a7b80;
   }
   z-index: 0;
   display: flex;
@@ -172,12 +172,10 @@ const StyledSelect = styled.select<{
   width: string;
   height: string;
   hasError: boolean;
-  paddingLeft: number;
 }>`
-  height: ${(props) => props.height || "35px"};
+  height: ${(props) => props.height};
   padding: 5px 10px;
-  padding-left: ${(props) => props.paddingLeft}px;
-  width: ${(props) => props.width || "200px"};
+  width: ${(props) => props.width};
   color: #ffffff;
   font-size: 13px;
   outline: none;

+ 19 - 22
dashboard/src/lib/databases/types.ts

@@ -21,8 +21,8 @@ export type DatastoreMetadataWithSource = z.infer<
 
 export const datastoreValidator = z.object({
   name: z.string(),
-  type: z.string(),
-  engine: z.string(),
+  type: z.enum(["RDS", "ELASTICACHE"]),
+  engine: z.enum(["POSTGRES", "AURORA-POSTGRES", "REDIS", "MEMCACHED"]),
   created_at: z.string().default(""),
   metadata: datastoreMetadataValidator.array().default([]),
   env: datastoreEnvValidator.optional(),
@@ -70,11 +70,10 @@ export type CloudProviderDatastore = z.infer<
   typeof cloudProviderDatastoreSchema
 >;
 
-export type DatabaseEngine =
-  | typeof DATABASE_ENGINE_POSTGRES
-  | typeof DATABASE_ENGINE_AURORA_POSTGRES
-  | typeof DATABASE_ENGINE_REDIS
-  | typeof DATABASE_ENGINE_MEMCACHED;
+export type DatabaseEngine = {
+  name: z.infer<typeof datastoreValidator>["engine"];
+  displayName: string;
+};
 export const DATABASE_ENGINE_POSTGRES = {
   name: "POSTGRES" as const,
   displayName: "PostgreSQL",
@@ -92,9 +91,7 @@ export const DATABASE_ENGINE_MEMCACHED = {
   displayName: "Memcached",
 };
 
-export type DatabaseType =
-  | typeof DATABASE_TYPE_RDS
-  | typeof DATABASE_TYPE_ELASTICACHE;
+export type DatabaseType = z.infer<typeof datastoreValidator>["type"];
 export const DATABASE_TYPE_RDS = "RDS" as const;
 export const DATABASE_TYPE_ELASTICACHE = "ELASTICACHE" as const;
 
@@ -102,28 +99,28 @@ export type DatabaseState = {
   state: z.infer<typeof datastoreValidator>["status"];
   displayName: string;
 };
-export const DATABASE_STATE_CREATING = {
-  state: "CREATING" as const,
+export const DATABASE_STATE_CREATING: DatabaseState = {
+  state: "CREATING",
   displayName: "Creating",
 };
-export const DATABASE_STATE_CONFIGURING_LOG_EXPORTS = {
-  state: "CONFIGURING_LOG_EXPORTS" as const,
+export const DATABASE_STATE_CONFIGURING_LOG_EXPORTS: DatabaseState = {
+  state: "CONFIGURING_LOG_EXPORTS",
   displayName: "Configuring log exports",
 };
-export const DATABASE_STATE_MODIFYING = {
-  state: "MODIFYING" as const,
+export const DATABASE_STATE_MODIFYING: DatabaseState = {
+  state: "MODIFYING",
   displayName: "Modifying",
 };
-export const DATABASE_STATE_CONFIGURING_ENHANCED_MONITORING = {
-  state: "CONFIGURING_ENHANCED_MONITORING" as const,
+export const DATABASE_STATE_CONFIGURING_ENHANCED_MONITORING: DatabaseState = {
+  state: "CONFIGURING_ENHANCED_MONITORING",
   displayName: "Configuring enhanced monitoring",
 };
-export const DATABASE_STATE_BACKING_UP = {
-  state: "BACKING_UP" as const,
+export const DATABASE_STATE_BACKING_UP: DatabaseState = {
+  state: "BACKING_UP",
   displayName: "Backing up",
 };
-export const DATABASE_STATE_AVAILABLE = {
-  state: "AVAILABLE" as const,
+export const DATABASE_STATE_AVAILABLE: DatabaseState = {
+  state: "AVAILABLE",
   displayName: "Finishing provision",
 };
 

+ 114 - 5
dashboard/src/main/home/database-dashboard/DatabaseDashboard.tsx

@@ -9,8 +9,10 @@ import Button from "components/porter/Button";
 import Container from "components/porter/Container";
 import DashboardPlaceholder from "components/porter/DashboardPlaceholder";
 import Fieldset from "components/porter/Fieldset";
+import Image from "components/porter/Image";
 import PorterLink from "components/porter/Link";
 import SearchBar from "components/porter/SearchBar";
+import Select from "components/porter/Select";
 import Spacer from "components/porter/Spacer";
 import StatusDot from "components/porter/StatusDot";
 import Text from "components/porter/Text";
@@ -22,12 +24,14 @@ import { useDatabaseList } from "lib/hooks/useDatabaseList";
 import { Context } from "shared/Context";
 import { search } from "shared/search";
 import { readableDate } from "shared/string_utils";
+import engine from "assets/computer-chip.svg";
 import database from "assets/database.svg";
 import grid from "assets/grid.png";
 import list from "assets/list.png";
 import notFound from "assets/not-found.png";
 import time from "assets/time.png";
 
+import { getDatastoreIcon, getEngineIcon } from "./icons";
 import EngineTag from "./tags/EngineTag";
 
 const DatabaseDashboard: React.FC = () => {
@@ -35,6 +39,12 @@ const DatabaseDashboard: React.FC = () => {
 
   const [searchValue, setSearchValue] = useState("");
   const [view, setView] = useState<"grid" | "list">("grid");
+  const [typeFilter, setTypeFilter] = useState<"all" | "RDS" | "ELASTICACHE">(
+    "all"
+  );
+  const [engineFilter, setEngineFilter] = useState<
+    "all" | "POSTGRES" | "AURORA-POSTGRES" | "REDIS"
+  >("all");
 
   const { datastores, isLoading } = useDatabaseList();
 
@@ -44,8 +54,28 @@ const DatabaseDashboard: React.FC = () => {
       isCaseSensitive: false,
     });
 
-    return _.sortBy(filteredBySearch, ["name"]);
-  }, [datastores, searchValue]);
+    const sortedFilteredBySearch = _.sortBy(filteredBySearch, ["name"]);
+
+    const filteredByDatastoreType = sortedFilteredBySearch.filter(
+      (datastore: ClientDatastore) => {
+        if (typeFilter === "all") {
+          return true;
+        }
+        return datastore.type === typeFilter;
+      }
+    );
+
+    const filteredByEngine = filteredByDatastoreType.filter(
+      (datastore: ClientDatastore) => {
+        if (engineFilter === "all") {
+          return true;
+        }
+        return datastore.template.engine.name === engineFilter;
+      }
+    );
+
+    return filteredByEngine;
+  }, [datastores, searchValue, typeFilter, engineFilter]);
 
   const renderContents = (): JSX.Element => {
     if (currentCluster?.status === "UPDATING_UNAVAILABLE") {
@@ -86,6 +116,83 @@ const DatabaseDashboard: React.FC = () => {
     return (
       <>
         <Container row spaced>
+          <Select
+            options={[
+              { value: "all", label: "All" },
+              {
+                value: "RDS",
+                label: "RDS",
+                icon: getDatastoreIcon("RDS"),
+              },
+              {
+                value: "ELASTICACHE",
+                label: "Elasticache",
+                icon: getDatastoreIcon("ELASTICACHE"),
+              },
+            ]}
+            width="210px"
+            height="29px"
+            value={typeFilter}
+            setValue={(value) => {
+              if (
+                value === "all" ||
+                value === "RDS" ||
+                value === "ELASTICACHE"
+              ) {
+                setTypeFilter(value);
+                setEngineFilter("all");
+              }
+            }}
+            prefix={
+              <Container row>
+                <Image src={database} size={15} opacity={0.6} />
+                <Spacer inline x={0.5} />
+                Type
+              </Container>
+            }
+          />
+          <Spacer inline x={1} />
+          <Select
+            options={[
+              { value: "all", label: "All" },
+              {
+                value: "POSTGRES",
+                label: "PostgreSQL",
+                icon: getEngineIcon("POSTGRES"),
+              },
+              {
+                value: "AURORA-POSTGRES",
+                label: "Aurora PostgreSQL",
+                icon: getEngineIcon("POSTGRES"),
+              },
+              {
+                value: "REDIS",
+                label: "Redis",
+                icon: getEngineIcon("REDIS"),
+              },
+            ]}
+            width="270px"
+            height="29px"
+            value={engineFilter}
+            setValue={(value) => {
+              if (
+                value === "all" ||
+                value === "POSTGRES" ||
+                value === "AURORA-POSTGRES" ||
+                value === "REDIS"
+              ) {
+                setEngineFilter(value);
+              }
+            }}
+            prefix={
+              <Container row>
+                <Image src={engine} size={15} opacity={0.6} />
+                <Spacer inline x={0.5} />
+                Engine
+              </Container>
+            }
+          />
+          <Spacer inline x={2} />
           <SearchBar
             value={searchValue}
             setValue={(x) => {
@@ -119,9 +226,9 @@ const DatabaseDashboard: React.FC = () => {
                 true
               }
               height="30px"
-              width="140px"
+              width="70px"
             >
-              <I className="material-icons">add</I> New database
+              <I className="material-icons">add</I> New
             </Button>
           </PorterLink>
         </Container>
@@ -131,7 +238,9 @@ const DatabaseDashboard: React.FC = () => {
           <Fieldset>
             <Container row>
               <PlaceholderIcon src={notFound} />
-              <Text color="helper">No matching databases were found.</Text>
+              <Text color="helper">
+                No databases matching filters were found.
+              </Text>
             </Container>
           </Fieldset>
         ) : isLoading ? (

+ 19 - 2
dashboard/src/main/home/database-dashboard/icons.tsx

@@ -1,16 +1,33 @@
 import {
+  DATABASE_ENGINE_AURORA_POSTGRES,
+  DATABASE_ENGINE_POSTGRES,
+  DATABASE_ENGINE_REDIS,
   DATABASE_TYPE_ELASTICACHE,
   DATABASE_TYPE_RDS,
 } from "lib/databases/types";
 
 import awsRDS from "assets/amazon-rds.png";
 import awsElasticache from "assets/aws-elasticache.png";
+import engine from "assets/computer-chip.svg";
+import database from "assets/database.svg";
+import postgresql from "assets/postgresql.svg";
+import redis from "assets/redis.svg";
 
-export const datastoreIcons: Record<string, string> = {
+const datastoreIcons: Record<string, string> = {
   [DATABASE_TYPE_ELASTICACHE]: awsElasticache,
   [DATABASE_TYPE_RDS]: awsRDS,
 };
 
+const engineIcons: Record<string, string> = {
+  [DATABASE_ENGINE_POSTGRES.name]: postgresql,
+  [DATABASE_ENGINE_AURORA_POSTGRES.name]: postgresql,
+  [DATABASE_ENGINE_REDIS.name]: redis,
+};
+
 export const getDatastoreIcon = (datastoreType: string): string => {
-  return datastoreIcons[datastoreType] ?? awsRDS;
+  return datastoreIcons[datastoreType] ?? database;
+};
+
+export const getEngineIcon = (engineName: string): string => {
+  return engineIcons[engineName] ?? engine;
 };