sts-sponsors team mailing list archive
-
sts-sponsors team
-
Mailing list archive
-
Message #05826
[Merge] ~jonesogolo/maas-site-manager:1390-create-search-filter-component into maas-site-manager:main
Jones Ogolo has proposed merging ~jonesogolo/maas-site-manager:1390-create-search-filter-component into maas-site-manager:main.
Commit message:
Create request for search and filtering
Requested reviews:
MAAS Committers (maas-committers)
For more details, see:
https://code.launchpad.net/~jonesogolo/maas-site-manager/+git/maas-site-manager/+merge/438670
Added Request for filtering
Created custom param serializer to account for duplicate search keys
--
Your team MAAS Committers is requested to review the proposed merge of ~jonesogolo/maas-site-manager:1390-create-search-filter-component into maas-site-manager:main.
diff --git a/frontend/src/api/handlers.ts b/frontend/src/api/handlers.ts
index 9eff927..0c9ecdd 100644
--- a/frontend/src/api/handlers.ts
+++ b/frontend/src/api/handlers.ts
@@ -1,3 +1,5 @@
+import { customParamSerializer } from "../utils";
+
import api from "./api";
import urls from "./urls";
@@ -15,3 +17,18 @@ export const getSites = async (params: GetSitesQueryParams) => {
console.error(error);
}
};
+
+export const getFilteredSites = async (params: GetSitesQueryParams, queryText: string) => {
+ try {
+ const response = await api.get(urls.sites, {
+ params,
+ paramsSerializer: {
+ serialize: (params) => customParamSerializer(params, queryText),
+ },
+ });
+ return response.data;
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error(error);
+ }
+};
diff --git a/frontend/src/components/SitesList/SitesList.tsx b/frontend/src/components/SitesList/SitesList.tsx
index 1b520ea..fcaa759 100644
--- a/frontend/src/components/SitesList/SitesList.tsx
+++ b/frontend/src/components/SitesList/SitesList.tsx
@@ -2,7 +2,8 @@ import { useState } from "react";
import { Pagination } from "@canonical/react-components";
-import { useSitesQuery } from "../../hooks/api";
+import { useSitesQuery, useSitesFilterQuery } from "../../hooks/api";
+import { parseSearchTextToQueryParams } from "../../utils";
import SitesTable from "./components/SitesTable";
@@ -11,19 +12,33 @@ const DEFAULT_PAGE_SIZE = 50;
const SitesList = () => {
const [page, setPage] = useState(0);
const [size] = useState(DEFAULT_PAGE_SIZE);
+ const [searchText, setSearchText] = useState("");
const { data, isLoading, isFetchedAfterMount } = useSitesQuery({ page: `${page}`, size: `${size}` });
+ const { data: searchData, isLoading: searchLoading } = useSitesFilterQuery(
+ { page: `${page}`, size: `${size}` },
+ parseSearchTextToQueryParams(searchText),
+ );
+
+ const isDataLoading = isLoading || searchLoading;
+ // Prioritize search results if any
+ const tableData = searchText ? searchData : data;
return (
<div>
- <SitesTable data={data} isFetchedAfterMount={isFetchedAfterMount} isLoading={isLoading} />
+ <SitesTable
+ data={tableData}
+ isFetchedAfterMount={isFetchedAfterMount}
+ isLoading={isDataLoading}
+ setSearchText={setSearchText}
+ />
<Pagination
currentPage={page + 1}
- disabled={isLoading}
+ disabled={isDataLoading}
itemsPerPage={size}
paginate={(page) => {
setPage(page - 1);
}}
- totalItems={data?.total || 0}
+ totalItems={tableData?.total || 0}
/>
</div>
);
diff --git a/frontend/src/components/SitesList/components/SitesTable.tsx b/frontend/src/components/SitesList/components/SitesTable.tsx
index ebaf1bb..800c2c2 100644
--- a/frontend/src/components/SitesList/components/SitesTable.tsx
+++ b/frontend/src/components/SitesList/components/SitesTable.tsx
@@ -34,7 +34,10 @@ const SitesTable = ({
data,
isFetchedAfterMount,
isLoading,
-}: Pick<UseSitesQueryResult, "data" | "isLoading" | "isFetchedAfterMount">) => {
+ setSearchText,
+}: Pick<UseSitesQueryResult, "data" | "isLoading" | "isFetchedAfterMount"> & {
+ setSearchText: (text: string) => void;
+}) => {
const [columnVisibility, setColumnVisibility] = useLocalStorageState("sitesTableColumnVisibility", {
defaultValue: {},
});
@@ -194,7 +197,12 @@ const SitesTable = ({
return (
<>
- <SitesTableControls allColumns={table.getAllLeafColumns()} data={data} isLoading={isLoading} />
+ <SitesTableControls
+ allColumns={table.getAllLeafColumns()}
+ data={data}
+ isLoading={isLoading}
+ setSearchText={setSearchText}
+ />
<table aria-label="sites" className="sites-table">
<thead>
{table.getHeaderGroups().map((headerGroup) => (
diff --git a/frontend/src/components/SitesList/components/SitesTableControls.tsx b/frontend/src/components/SitesList/components/SitesTableControls.tsx
index 255b698..7204b02 100644
--- a/frontend/src/components/SitesList/components/SitesTableControls.tsx
+++ b/frontend/src/components/SitesList/components/SitesTableControls.tsx
@@ -1,5 +1,3 @@
-import { useState } from "react";
-
import { Row, Col, SearchBox } from "@canonical/react-components";
import type { UseSitesQueryResult } from "../../../hooks/api";
@@ -12,8 +10,11 @@ const SitesTableControls = ({
data,
isLoading,
allColumns,
-}: { allColumns: SitesColumn[] } & Pick<UseSitesQueryResult, "data" | "isLoading">) => {
- const [searchText, setSearchText] = useState("");
+ setSearchText,
+}: { allColumns: SitesColumn[]; setSearchText: (text: string) => void } & Pick<
+ UseSitesQueryResult,
+ "data" | "isLoading"
+>) => {
const handleSearchInput = (inputValue: string) => {
setSearchText(inputValue);
};
@@ -26,12 +27,7 @@ const SitesTableControls = ({
</h2>
</Col>
<Col size={8}>
- <SearchBox
- externallyControlled
- onChange={handleSearchInput}
- placeholder="Search and filter"
- value={searchText}
- />
+ <SearchBox externallyControlled onChange={handleSearchInput} placeholder="Search and filter" />
</Col>
<Col className="u-flex u-flex--align-end u-flex--column" size={2}>
<ColumnsVisibilityControl columns={allColumns} />
diff --git a/frontend/src/hooks/api.ts b/frontend/src/hooks/api.ts
index ccf0b4d..9ea4707 100644
--- a/frontend/src/hooks/api.ts
+++ b/frontend/src/hooks/api.ts
@@ -1,7 +1,7 @@
import { useQuery } from "@tanstack/react-query";
import type { GetSitesQueryParams } from "../api/handlers";
-import { getSites } from "../api/handlers";
+import { getSites, getFilteredSites } from "../api/handlers";
import type { SitesQueryResult } from "../api/types";
export type UseSitesQueryResult = ReturnType<typeof useSitesQuery>;
@@ -12,3 +12,13 @@ export const useSitesQuery = ({ page, size }: GetSitesQueryParams) =>
queryFn: () => getSites({ page, size }),
keepPreviousData: true,
});
+
+export type UseSitesFilterQueryResult = ReturnType<typeof useSitesFilterQuery>;
+
+export const useSitesFilterQuery = ({ page, size }: GetSitesQueryParams, queryText: string) =>
+ useQuery<SitesQueryResult>({
+ queryKey: ["sites", page, size, queryText],
+ queryFn: () => getFilteredSites({ page, size }, queryText),
+ keepPreviousData: true,
+ enabled: !!queryText,
+ });
diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts
index 4849540..0e98457 100644
--- a/frontend/src/utils.ts
+++ b/frontend/src/utils.ts
@@ -8,3 +8,21 @@ if (typeof window !== "undefined") {
export const getCountryName = (countryCode: string) => {
return getName(countryCode, "en", { select: "official" });
};
+
+export const parseSearchTextToQueryParams = (text: string) => {
+ // if just text => q=text
+ // if text:result => text=result
+ if (!text) return "";
+ return text
+ .split(" ")
+ .map((item) => (item.includes(":") ? item.replaceAll(":", "=") : `q=${item}`))
+ .join("&");
+};
+
+export const customParamSerializer = (params: Record<string, string>, queryText?: string) => {
+ return (
+ Object.entries(Object.assign({}, params))
+ .map(([key, value]) => `${key}=${value}`)
+ .join("&") + `${queryText ? "&" + queryText : ""}`
+ );
+};
Follow ups