diff --git a/packages/base/components/ODataGridBase.tsx b/packages/base/components/ODataGridBase.tsx index cdf93cf..61fb966 100644 --- a/packages/base/components/ODataGridBase.tsx +++ b/packages/base/components/ODataGridBase.tsx @@ -72,29 +72,34 @@ const ODataGridBase = fields.add(s)); } - // group all expands by the navigation field - const groupedExpands = GroupArrayBy( - props.columns - .filter(c => visibleColumns.includes(c.field) && !!c.expand) - .map(c => c.expand!), - (e) => e.navigationField - ); + const expands = props.columns + .filter(c => visibleColumns.includes(c.field) && c.expand) + .map(c => c.expand!); - // construct a single expand for each navigation field, combining nested query options - const expands: Expand[] = []; - groupedExpands.forEach((e, k) => { - expands.push({ - navigationField: k, - top: e[0].top, - orderBy: e[0].orderBy, - count: e.some(e2 => e2.count), - select: Array.from(new Set(e.filter(e2 => e2.select).map(e2 => e2.select))).join(","), - }); - }); + // group all expands by the navigation field + // const groupedExpands = GroupArrayBy( + // props.columns + // .filter(c => visibleColumns.includes(c.field) && !!c.expand) + // .map(c => c.expand!), + // (e) => e.navigationField + // ); + + // // construct a single expand for each navigation field, combining nested query options + // const expands: Expand[] = []; + // groupedExpands.forEach((e, k) => { + // expands.push({ + // navigationField: k, + // top: e.find(e2 => e2.top)?.top, + // orderBy: e.find(e2 => e2.orderBy)?.orderBy, + // count: e.some(e2 => e2.count), + // select: Array.from(new Set(e.filter(e2 => e2.select).map(e2 => e2.select))).join(","), + // expand: e.find(e2 => e2.expand) + // }); + // }); const query = new URLSearchParams(); query.append("$select", Array.from(fields).join(",")); - query.append("$expand", expands.map(e => ExpandToQuery(e)).join(",")); + query.append("$expand", ExpandToQuery(expands)); query.append("$top", pageSize.toString()); query.append("$skip", (pageNumber * pageSize).toString()); diff --git a/packages/base/types.ts b/packages/base/types.ts index 943eaf6..ac3f16c 100644 --- a/packages/base/types.ts +++ b/packages/base/types.ts @@ -61,7 +61,7 @@ export type ODataResponse = { export type Expand = { navigationField: string, select?: string, - expand?: Expand, + expand?: Expand[] | Expand, orderBy?: string, top?: number, count?: boolean diff --git a/packages/base/utils.ts b/packages/base/utils.ts index 9ff3de5..69637c6 100644 --- a/packages/base/utils.ts +++ b/packages/base/utils.ts @@ -7,26 +7,51 @@ import { defaultPageSize } from "./constants"; * @param e Expand to convert * @returns OData expand clause string */ -export const ExpandToQuery = (e?: Expand) => { - if (e === undefined) { +export const ExpandToQuery = (expand?: Expand[] | Expand): string => { + if (expand === undefined) { return ""; } - let result = `${e.navigationField}`; + if (!Array.isArray(expand)) { + return ExpandToQuery([expand]); + } - const options = [ - { type: "select", value: e.select }, - { type: "expand", value: ExpandToQuery(e.expand) }, - { type: "orderby", value: e.orderBy }, - { type: "top", value: e.top }, - { type: "count", value: e.count } - ]; + // group all expands by the navigation field + const groupedExpands = GroupArrayBy(expand, (e) => e.navigationField); - if (options.some(o => o.value)) { - result += `(${options.filter(o => o.value).map(o => `$${o.type}=${o.value}`).join(";")})` - } + // construct a single expand for each navigation field, combining nested query options (where possible) + const expands: Expand[] = []; + groupedExpands.forEach((e, k) => { + expands.push({ + navigationField: k, + top: e.find(e2 => e2.top)?.top, + orderBy: e.find(e2 => e2.orderBy)?.orderBy, + count: e.some(e2 => e2.count), + select: Array.from(new Set(e.filter(e2 => e2.select).map(e2 => e2.select))).join(","), + expand: e.filter(e2 => e2.expand) + .map(e2 => e2.expand!) + .reduce((a: Expand[], b) => Array.isArray(b) ? a.concat(b) : [...a, b], []) + }); + }); + + return expands.map(e => { + let result = `${e.navigationField}`; + + const options = [ + { type: "select", value: e.select }, + { type: "expand", value: ExpandToQuery(e.expand) }, + { type: "orderby", value: e.orderBy }, + { type: "top", value: e.top }, + { type: "count", value: e.count } + ]; + + if (options.some(o => o.value)) { + result += `(${options.filter(o => o.value).map(o => `$${o.type}=${o.value}`).join(";")})` + } + + return result; - return result; + }).join(",") } /** diff --git a/packages/o-data-grid/dev/App.tsx b/packages/o-data-grid/dev/App.tsx index 3a2844e..52221bc 100644 --- a/packages/o-data-grid/dev/App.tsx +++ b/packages/o-data-grid/dev/App.tsx @@ -6,6 +6,7 @@ import { ODataGridColDef, ODataColumnVisibilityModel, escapeODataString } from " import ODataGrid from "../src/ODataGrid"; import { CacheProvider } from "@emotion/react"; import createCache from "@emotion/cache"; +import { ExpandToQuery } from "../../base/utils"; const theme = createTheme({ palette: { @@ -19,6 +20,18 @@ export const muiCache = createCache({ }); const App = () => { + console.debug(ExpandToQuery({ + navigationField: "jobCategories", + expand: { + navigationField: "category", + select: "name", + expand: { + navigationField: "job", + select: "title" + } + } + })); + return ( @@ -27,7 +40,6 @@ const App = () => { url="http://0.0.0.0:5000/api/odata/job" columns={columns} columnVisibilityModel={columnVisibility} - getRowId={(row) => row.Id} defaultSortModel={defaultSort} filterBuilderProps={filterBuilderProps} alwaysSelect={alwaysFetch} @@ -47,13 +59,13 @@ const filterBuilderProps = { autocompleteGroups: ["Job", "Company"] }; const alwaysFetch = ["Id", "Archived"]; const columns: ODataGridColDef[] = [ { - field: "Title", + field: "title", headerName: "Job Title", flex: 2, autocompleteGroup: "Job" }, { - field: "Location", + field: "location", headerName: "Location", flex: 1, renderCustomFilter: (value, setValue) => ( @@ -94,11 +106,11 @@ const columns: ODataGridColDef[] = [ } }; }, - valueGetter: (params) => `${params.row.Location}${params.row.Distance ? ` (${params.row.Distance.toFixed(1)} mi away)` : ""}`, + valueGetter: (params) => `${params.row.location}${params.row.distance ? ` (${params.row.distance.toFixed(1)} mi away)` : ""}`, autocompleteGroup: "Job" }, { - field: "Company/Name", + field: "company/name", headerName: "Company", flex: 2, renderCell: (params) => ( @@ -106,18 +118,19 @@ const columns: ODataGridColDef[] = [ {params.value} - {params.row["Company/Recruiter"] && } - {params.row["Company/Blacklisted"] && } + {params.row["company/recruiter"] && } + {params.row["company/blacklisted"] && } ), - expand: { navigationField: "Company", select: "Id,Name,Recruiter,Blacklisted,Watched" }, + expand: { navigationField: "company", select: "id,name,recruiter,blacklisted,watched" }, autocompleteGroup: "Company" }, { - field: "Salary", + field: "salary", + headerName: "Salary", type: "number", - filterField: "AvgYearlySalary", - sortField: "AvgYearlySalary", + filterField: "avgYearlySalary", + sortField: "avgYearlySalary", label: "Median Annual Salary", filterType: "number", filterOperators: ["eq", "ne", "gt", "lt", "ge", "le", "null", "notnull"], @@ -125,29 +138,47 @@ const columns: ODataGridColDef[] = [ autocompleteGroup: "Job" }, { - field: "Status", + field: "status", + headerName: "Status", type: "singleSelect", valueOptions: ["Not Applied", "Awaiting Response", "In Progress", "Rejected", "Dropped Out"], filterOperators: ["eq", "ne"], autocompleteGroup: "Job" }, { - field: "JobCategories", + field: "jobCategories", headerName: "Categories", label: "Category", expand: { - navigationField: "JobCategories/Category", - select: "Name" + navigationField: "jobCategories", + expand: { + navigationField: "category", + select: "name", + expand: [ + { + navigationField: "companyCategories", + count: true + }, + { + navigationField: "companyCategories", + count: true + }, + { + navigationField: "jobCategories", + count: true + }, + ] + } }, sortable: false, filterable: false, flex: 1, - renderCell: (params) => params.row.JobCategories.map((c: any) => c["Category/Name"]).join(", "), + renderCell: (params) => params.row.jobCategories.map((c: any) => c["category/name"]).join(", "), autocompleteGroup: "Job" }, { - field: "Source/DisplayName", - expand: { navigationField: "Source", select: "DisplayName" }, + field: "source/displayName", + expand: { navigationField: "source", select: "displayName" }, headerName: "Source", filterable: false, sortable: false, @@ -156,8 +187,8 @@ const columns: ODataGridColDef[] = [ autocompleteGroup: "Job" }, { - field: "Posted", - select: "Posted,Seen,Archived", + field: "posted", + select: "posted,seen,archived", headerName: "Posted", type: "date", flex: .9,