Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/filter search #183

Open
wants to merge 14 commits into
base: staging
Choose a base branch
from
30,294 changes: 20,577 additions & 9,717 deletions radlab-ui/webapp/package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion radlab-ui/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@
"firebase-admin": "^11.0.1",
"formik": "^2.2.9",
"hcl2-parser": "^1.0.3",
"heroicons": "^2.0.18",
"lodash": "^4.17.21",
"lodash.debounce": "^4.0.8",
"lunr": "^2.3.9",
"next": "^12.0.8",
"next-i18next": "^10.2.0",
"next-seo": "^4.29.0",
Expand Down Expand Up @@ -91,4 +93,4 @@
"autoprefixer": "10.4.5"
},
"license": "ISC"
}
}
8 changes: 5 additions & 3 deletions radlab-ui/webapp/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
"access-denied": "Access Denied!",
"access-denied-message": "You are not authorized to use this part of the application",
"gcp-admin": "Please check with your GCP admin to proceed further",
"delete-deployment": "Are you sure you want to delete the deployment?",
"delete-deployment-title": "Delete Deployment",
"delete-deployment-message": "Are you sure you want to delete the deployment/deployments?",
debdeep12 marked this conversation as resolved.
Show resolved Hide resolved
"close": "Close",
"deployment-id": "Deployment ID:",
"available-modules": "Available Modules",
Expand Down Expand Up @@ -76,5 +77,6 @@
"loading": "Loading",
"no-modules-message": "If you are not seeing the module you want, reach out to your RAD Lab Admin",
"build-status": "Build Status",
"history": "History"
}
"history": "History",
"modules": "Modules"
}
8 changes: 5 additions & 3 deletions radlab-ui/webapp/public/locales/es/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
"access-denied": "Acceso denegado!",
"access-denied-message": "No está autorizado para usar esta aplicación.",
"gcp-admin": "Consulte con su administrador de GCP para continuar",
"delete-deployment": "¿Estás segura de que quieres eliminar el ID de despliegue?",
"delete-deployment": "Eliminar implementación",
"delete-deployment-message": "¿Está seguro de que desea eliminar la implementación/implementaciones?",
debdeep12 marked this conversation as resolved.
Show resolved Hide resolved
"close": "Cerca",
"deployment-id": "Id. de implementación:",
"available-modules": "Módulos disponibles",
Expand Down Expand Up @@ -76,5 +77,6 @@
"loading": "Cargando",
"no-modules-message": "Si no ve el módulo que desea, comuníquese con su administrador de RAD Lab",
"build-status": "Estado de construcción",
"history": "Historia"
}
"history": "Historia",
"modules": "Módulos"
}
79 changes: 60 additions & 19 deletions radlab-ui/webapp/src/components/DeleteDeploymentModal.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,54 @@
import axios from "axios"
import { useTranslation } from "next-i18next"
import { useState } from "react"
import { useNavigate } from "react-router-dom"
import { alertStore, userStore } from "@/store"
import { ALERT_TYPE } from "@/utils/types"
import Loading from "@/navigation/Loading"
import { XCircleIcon } from "@heroicons/react/outline"

interface IDeleteDeploymentModal {
deployId: string
deployId?: string
deploymentIds?: string[]
handleClick: Function
handleRefresh?: Function
}

const DeleteDeploymentModal: React.FC<IDeleteDeploymentModal> = ({
deployId,
deploymentIds,
handleClick,
handleRefresh,
}) => {
const [modal, setModal] = useState(true)
const navigate = useNavigate()
const { t } = useTranslation()
const setAlert = alertStore((state) => state.setAlert)
const [loading, setLoading] = useState(false)
const user = userStore((state) => state.user)

const handleDelete = async () => {
const config = {
const config1 = {
data: { deployedByEmail: user?.email, deploymentIds: deploymentIds },
}

const config2 = {
data: { deployedByEmail: user?.email },
}

setLoading(true)
await axios
.delete(`/api/deployments/${deployId}`, config)

const request = deploymentIds?.length
? axios.delete(`/api/deployments`, config1)
: axios.delete(`/api/deployments/${deployId}`, config2)
debdeep12 marked this conversation as resolved.
Show resolved Hide resolved

request
.then((res) => {
if (res.status === 200) {
setAlert({
message: t("delete-success"),
durationMs: 10000,
type: ALERT_TYPE.SUCCESS,
})
navigate("/deployments")
handleRefresh && handleRefresh(true)
} else {
setAlert({
message: t("delete-error"),
Expand All @@ -58,6 +69,8 @@ const DeleteDeploymentModal: React.FC<IDeleteDeploymentModal> = ({
})
.finally(() => {
setLoading(false)
setModal(false)
handleClick(false)
})
}

Expand All @@ -72,27 +85,55 @@ const DeleteDeploymentModal: React.FC<IDeleteDeploymentModal> = ({
/>
<div className="modal">
<div className="modal-box">
<h3 className="font-bold text-lg">{t("delete")}</h3>
<p className="py-4">{t("delete-deployment")}</p>
<p className="text-md font-normal">
{`${t("deployment-id")} ${deployId}`}
<div className="flex justify-center">
<XCircleIcon className="w-12 h-12 text-error" />
</div>
<h3 className="font-bold text-lg text-center">
{t("delete-deployment-title")}
{"?"}
debdeep12 marked this conversation as resolved.
Show resolved Hide resolved
</h3>
<hr className="border border-base-200 mt-2" />
<p className="p-1 bg-error bg-opacity-10 text-sm rounded-md mt-4 text-center text-error font-semibold">
{t("delete-deployment-message")}
</p>
<div className="flex justify-center mt-2">
{loading && <Loading />}
</div>
{deploymentIds?.length ? (
<>
{deploymentIds.map((deploymentId) => {
return (
<p
className="text-sm font-normal mt-6 text-center font-semibold"
key={deploymentId}
>
{`${t("deployment-id")} ${deploymentId}`}
</p>
)
})}
</>
) : (
<p className="text-sm font-normal mt-6 text-center font-semibold">
{`${t("deployment-id")} ${deployId}`}
</p>
)}
<div className="modal-action">
{loading ? (
<Loading />
) : (
<button className="btn btn-error" onClick={handleDelete}>
{t("delete")}
</button>
)}
<button
className="btn btn-outline"
className="btn btn-outline btn-sm"
onClick={() => {
setModal(false), handleClick(false)
}}
>
{t("close")}
</button>
<button
className="btn btn-error btn-sm"
onClick={() => {
handleDelete()
}}
>
{t("delete")}
</button>
</div>
</div>
</div>
Expand Down
177 changes: 177 additions & 0 deletions radlab-ui/webapp/src/components/Filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import Search, { ISearchResults } from "@/components/Search"
import Loading from "@/navigation/Loading"
import { deploymentStore, moduleNamesStore } from "@/store"
import { DEPLOYMENT_STATUSES } from "@/utils/data"
import { startCase } from "lodash"
import { useTranslation } from "next-i18next"
import { useEffect, useRef, useState } from "react"

type IFilterType =
| "module"
| "deploymentId"
| "deployedByEmail"
| "createdAt"
| "status"
// type ICategory = (typeof DemoCategories)[number] | string | ""
// type IVerticals = (typeof DemoCategories)[number] | string | ""
debdeep12 marked this conversation as resolved.
Show resolved Hide resolved

export default function Filter({
filters,
}: {
filters: IFilterType[]
clearFilter: boolean
}) {
const deployments = deploymentStore((state) => state.deployments)
const setFilteredDeployments = deploymentStore(
(state) => state.setFilteredDeployments,
)
const inputRef = useRef(null)
const [search, setSearch] = useState<ISearchResults | null>(null)
const [status, setStatus] = useState("Deleted")
const [moduleName, setModuleName] = useState<string>("")
const statuses = DEPLOYMENT_STATUSES
const { t } = useTranslation()

const moduleNames = moduleNamesStore((state) => state.moduleNames)
const [isLoading, _] = useState<boolean>(false)
// const [category, setCategory] = useState<string>("")
// const [classification, setClassification] = useState("")
debdeep12 marked this conversation as resolved.
Show resolved Hide resolved

// TODO. Likely change this to the firebase.uid for the record
const refField = "id"
const handleQuery = (search: ISearchResults) => setSearch(search)

// Filter based on Selects and Search

useEffect(() => {
setStatus("Deleted")
if (!deployments) {
setFilteredDeployments(null)
return
}

//No active filtering happening
if (
!search?.query &&
status === "" &&
moduleName === ""
// category === "" &&
// classification === ""
) {
setFilteredDeployments(null)
return
}

deployments.map((deployment) => {
if (deployment.hasOwnProperty("deletedAt")) {
//@ts-ignore
deployment["status"] = "DELETED"
}
})
let filtered = deployments

if (search?.query) {
// @ts-ignore TS is wrong about possible type (can't be udefined[] because of filter(Boolean) step)
filtered = search.result
.sort((a, b) => b.score - a.score)
.map((result) => result.ref)
.map((ref) =>
deployments.find((deployment) => deployment[refField] === ref),
)
.filter(Boolean)
}

// if (status !== "")
// // @ts-ignore
// filtered = filtered.filter((demo) => demo.status === status)
// if (category !== "")
// filtered = filtered.filter((demo) => demo.category === category)
// if (vertical !== "")
// filtered = filtered.filter(
// (demo) =>
// demo.vertical === vertical || demo.vertical.includes("Security"),
// )
// if (classification !== "")
// // @ts-ignore
// filtered = filtered.filter((demo) =>
// classification === "demo"
// ? demo.classification === "demo" || demo.classification === undefined
// : demo.classification === "solution",
// )

setFilteredDeployments(filtered)
}, [deployments, search, moduleName])

const clearAllFilters = () => {
// @ts-ignore
inputRef.current.value = ""
setSearch(null)
}

if (isLoading) return <Loading />

return (
<div className="w-full">
<Search
inputRef={inputRef}
placeholder="Search by Module or Project ID or Deployment ID"
// @ts-ignore
documents={deployments}
refField={refField}
handleQuery={handleQuery}
/>

<div className="grid grid-cols-2 lg:grid-cols-10 gap-4 mt-4">
{filters.includes("status") && (
<select
onChange={(e) => setStatus(e.target.value)}
value={status}
className="select select-bordered w-full max-w-xs col-span-4"
>
<option disabled selected>
{t("status")}
</option>
{statuses.map((status) => (
<>
<input
type="checkbox"
//@ts-ignore
checked="checked"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be dynamic and not ignored

className="checkbox checkbox-sm"
/>
<option key={status} value={status}>
{status}
</option>
</>
))}
</select>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image This needs to be a MULTI-select

Example:
image

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also Title Case / Start Case these statuses:
image

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

example INTERNAL_ERROR => Internal Error

)}

{filters.includes("module") && (
<select
onChange={(e) => setModuleName(e.target.value)}
// value={module}
className="select select-bordered w-full max-w-xs col-span-4"
>
<option value="">{t("modules")}</option>
{moduleNames &&
moduleNames.length &&
moduleNames.map((module) => {
return (
<option key={module.name}>{startCase(module.name)}</option>
)
})}
</select>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also needs to be a multi-select. Please make a MultiSelect component and use it twice here in this file.

)}
<div className="flex justify-end mt-2 col-span-2">
<button
className="btn btn-link text-base-content no-underline hover:no-underline btn-sm bg-base-200 border border-base-300 hover:bg-base-300"
onClick={clearAllFilters}
>
Clear
debdeep12 marked this conversation as resolved.
Show resolved Hide resolved
debdeep12 marked this conversation as resolved.
Show resolved Hide resolved
</button>
</div>
</div>
</div>
)
}
4 changes: 2 additions & 2 deletions radlab-ui/webapp/src/components/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ const Search = ({

return (
<div className="relative">
<SearchIcon className="h-6 absolute mt-3 ml-3 text-base-content" />
<SearchIcon className="h-4 absolute mt-2 ml-3 text-base-content" />
<input
ref={inputRef ?? null}
type="text"
placeholder={placeholder || "Search"}
className="input px-10 py-3 w-full"
className="input px-10 py-3 w-full input-sm"
debdeep12 marked this conversation as resolved.
Show resolved Hide resolved
onChange={onChange}
/>
</div>
Expand Down
Loading