Respond to PR comment.

This commit is contained in:
Sam Rose 2024-10-23 11:34:55 +01:00
parent d56ca8eb06
commit 6f4e1dd711
No known key found for this signature in database
7 changed files with 122 additions and 127 deletions

View File

@ -27,13 +27,14 @@ import {
ViewV2Schema, ViewV2Schema,
ViewV2Type, ViewV2Type,
JsonTypes, JsonTypes,
UILogicalOperator,
EmptyFilterOption, EmptyFilterOption,
JsonFieldSubType, JsonFieldSubType,
UISearchFilter, UISearchFilter,
LegacyFilter, LegacyFilter,
SearchViewRowRequest, SearchViewRowRequest,
ArrayOperator, ArrayOperator,
UILogicalOperator,
SearchFilters,
} from "@budibase/types" } from "@budibase/types"
import { generator, mocks } from "@budibase/backend-core/tests" import { generator, mocks } from "@budibase/backend-core/tests"
import { DatabaseName, getDatasource } from "../../../integrations/tests/utils" import { DatabaseName, getDatasource } from "../../../integrations/tests/utils"
@ -159,11 +160,8 @@ describe.each([
tableId: table._id!, tableId: table._id!,
primaryDisplay: "id", primaryDisplay: "id",
queryUI: { queryUI: {
logicalOperator: UILogicalOperator.ALL,
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
groups: [ groups: [
{ {
logicalOperator: UILogicalOperator.ALL,
filters: [ filters: [
{ {
operator: BasicOperator.EQUAL, operator: BasicOperator.EQUAL,
@ -256,6 +254,8 @@ describe.each([
}, },
}, },
queryUI: { queryUI: {
logicalOperator: UILogicalOperator.ALL,
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
groups: [ groups: [
{ {
logicalOperator: UILogicalOperator.ALL, logicalOperator: UILogicalOperator.ALL,
@ -952,29 +952,37 @@ describe.each([
], ],
}) })
expect((await config.api.table.get(tableId)).views).toEqual({ const expected: ViewV2 = {
[view.name]: { ...view,
...view, query: [
query: [ {
{ operator: "equal", field: "newField", value: "thatValue" }, operator: BasicOperator.EQUAL,
], field: "newField",
// Should also update queryUI because query was not previously set. value: "thatValue",
queryUI: {
groups: [
{
logicalOperator: "all",
filters: [
{
operator: "equal",
field: "newField",
value: "thatValue",
},
],
},
],
}, },
schema: expect.anything(), ],
// Should also update queryUI because query was not previously set.
queryUI: {
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
logicalOperator: UILogicalOperator.ALL,
groups: [
{
logicalOperator: UILogicalOperator.ALL,
filters: [
{
operator: BasicOperator.EQUAL,
field: "newField",
value: "thatValue",
},
],
},
],
}, },
schema: expect.anything(),
}
expect((await config.api.table.get(tableId)).views).toEqual({
[view.name]: expected,
}) })
}) })
@ -1014,38 +1022,42 @@ describe.each([
} }
await config.api.viewV2.update(updatedData) await config.api.viewV2.update(updatedData)
expect((await config.api.table.get(tableId)).views).toEqual({ const expected: ViewV2 = {
[view.name]: { ...updatedData,
...updatedData, // queryUI gets generated from query
// queryUI gets generated from query queryUI: {
queryUI: { logicalOperator: UILogicalOperator.ALL,
groups: [ onEmptyFilter: EmptyFilterOption.RETURN_ALL,
{ groups: [
logicalOperator: "all", {
filters: [ logicalOperator: UILogicalOperator.ALL,
{ filters: [
operator: "equal", {
field: "newField", operator: BasicOperator.EQUAL,
value: "newValue", field: "newField",
}, value: "newValue",
], },
}, ],
], },
}, ],
schema: {
...table.schema,
id: expect.objectContaining({
visible: true,
}),
Category: expect.objectContaining({
visible: false,
}),
Price: expect.objectContaining({
visible: true,
readonly: true,
}),
},
}, },
schema: {
...table.schema,
id: expect.objectContaining({
visible: true,
}),
Category: expect.objectContaining({
visible: false,
}),
Price: expect.objectContaining({
visible: true,
readonly: true,
}),
},
}
expect((await config.api.table.get(tableId)).views).toEqual({
[view.name]: expected,
}) })
}) })
@ -1283,7 +1295,6 @@ describe.each([
queryUI: { queryUI: {
groups: [ groups: [
{ {
logicalOperator: UILogicalOperator.ALL,
filters: [ filters: [
{ {
operator: BasicOperator.EQUAL, operator: BasicOperator.EQUAL,
@ -1297,7 +1308,8 @@ describe.each([
}) })
let updatedView = await config.api.viewV2.get(view.id) let updatedView = await config.api.viewV2.get(view.id)
expect(updatedView.query).toEqual({ let expected: SearchFilters = {
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
$and: { $and: {
conditions: [ conditions: [
{ {
@ -1311,14 +1323,14 @@ describe.each([
}, },
], ],
}, },
}) }
expect(updatedView.query).toEqual(expected)
await config.api.viewV2.update({ await config.api.viewV2.update({
...updatedView, ...updatedView,
queryUI: { queryUI: {
groups: [ groups: [
{ {
logicalOperator: UILogicalOperator.ALL,
filters: [ filters: [
{ {
operator: BasicOperator.EQUAL, operator: BasicOperator.EQUAL,
@ -1332,7 +1344,8 @@ describe.each([
}) })
updatedView = await config.api.viewV2.get(view.id) updatedView = await config.api.viewV2.get(view.id)
expect(updatedView.query).toEqual({ expected = {
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
$and: { $and: {
conditions: [ conditions: [
{ {
@ -1346,7 +1359,8 @@ describe.each([
}, },
], ],
}, },
}) }
expect(updatedView.query).toEqual(expected)
}) })
it("can delete either query and it will get regenerated from queryUI", async () => { it("can delete either query and it will get regenerated from queryUI", async () => {
@ -1386,7 +1400,6 @@ describe.each([
queryUI: { queryUI: {
groups: [ groups: [
{ {
logicalOperator: UILogicalOperator.ALL,
filters: [ filters: [
{ {
operator: BasicOperator.EQUAL, operator: BasicOperator.EQUAL,
@ -1810,7 +1823,9 @@ describe.each([
}) })
const view = await getDelegate(res) const view = await getDelegate(res)
expect(view.queryUI).toEqual({ const expected: UISearchFilter = {
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
logicalOperator: UILogicalOperator.ALL,
groups: [ groups: [
{ {
logicalOperator: UILogicalOperator.ALL, logicalOperator: UILogicalOperator.ALL,
@ -1823,7 +1838,8 @@ describe.each([
], ],
}, },
], ],
}) }
expect(view.queryUI).toEqual(expected)
}) })
}) })
@ -2669,11 +2685,8 @@ describe.each([
tableId: table._id!, tableId: table._id!,
name: generator.guid(), name: generator.guid(),
queryUI: { queryUI: {
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
logicalOperator: UILogicalOperator.ALL,
groups: [ groups: [
{ {
logicalOperator: UILogicalOperator.ALL,
filters: [ filters: [
{ {
operator: BasicOperator.EQUAL, operator: BasicOperator.EQUAL,
@ -2733,11 +2746,8 @@ describe.each([
tableId: table._id!, tableId: table._id!,
name: generator.guid(), name: generator.guid(),
queryUI: { queryUI: {
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
logicalOperator: UILogicalOperator.ALL,
groups: [ groups: [
{ {
logicalOperator: UILogicalOperator.ALL,
filters: [ filters: [
{ {
operator: BasicOperator.EQUAL, operator: BasicOperator.EQUAL,
@ -3022,11 +3032,8 @@ describe.each([
tableId: table._id!, tableId: table._id!,
name: generator.guid(), name: generator.guid(),
queryUI: { queryUI: {
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
logicalOperator: UILogicalOperator.ALL,
groups: [ groups: [
{ {
logicalOperator: UILogicalOperator.ALL,
filters: [ filters: [
{ {
operator: BasicOperator.NOT_EQUAL, operator: BasicOperator.NOT_EQUAL,
@ -3080,11 +3087,8 @@ describe.each([
tableId: table._id!, tableId: table._id!,
name: generator.guid(), name: generator.guid(),
queryUI: { queryUI: {
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
logicalOperator: UILogicalOperator.ALL,
groups: [ groups: [
{ {
logicalOperator: UILogicalOperator.ALL,
filters: [ filters: [
{ {
operator: BasicOperator.NOT_EQUAL, operator: BasicOperator.NOT_EQUAL,
@ -3175,11 +3179,8 @@ describe.each([
tableId: table._id!, tableId: table._id!,
name: generator.guid(), name: generator.guid(),
queryUI: { queryUI: {
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
logicalOperator: UILogicalOperator.ALL,
groups: [ groups: [
{ {
logicalOperator: UILogicalOperator.ALL,
filters: [ filters: [
{ {
operator: BasicOperator.EQUAL, operator: BasicOperator.EQUAL,
@ -3619,11 +3620,8 @@ describe.each([
name: generator.guid(), name: generator.guid(),
type: ViewV2Type.CALCULATION, type: ViewV2Type.CALCULATION,
queryUI: { queryUI: {
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
logicalOperator: UILogicalOperator.ALL,
groups: [ groups: [
{ {
logicalOperator: UILogicalOperator.ALL,
filters: [ filters: [
{ {
operator: BasicOperator.EQUAL, operator: BasicOperator.EQUAL,
@ -3912,11 +3910,8 @@ describe.each([
tableId: table._id!, tableId: table._id!,
name: generator.guid(), name: generator.guid(),
queryUI: { queryUI: {
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
logicalOperator: UILogicalOperator.ALL,
groups: [ groups: [
{ {
logicalOperator: UILogicalOperator.ALL,
filters: [ filters: [
{ {
operator: BasicOperator.EQUAL, operator: BasicOperator.EQUAL,

View File

@ -12,8 +12,6 @@ import {
TableResponse, TableResponse,
TableSourceType, TableSourceType,
TableViewsResponse, TableViewsResponse,
View,
ViewV2,
FeatureFlag, FeatureFlag,
} from "@budibase/types" } from "@budibase/types"
import datasources from "../datasources" import datasources from "../datasources"
@ -21,19 +19,6 @@ import sdk from "../../../sdk"
import { ensureQueryUISet } from "../views/utils" import { ensureQueryUISet } from "../views/utils"
import { isV2 } from "../views" import { isV2 } from "../views"
function processView(view: ViewV2 | View) {
if (!isV2(view)) {
return
}
ensureQueryUISet(view)
}
function processViews(views: (ViewV2 | View)[]) {
for (const view of views) {
processView(view)
}
}
export async function processTable(table: Table): Promise<Table> { export async function processTable(table: Table): Promise<Table> {
if (!table) { if (!table) {
return table return table
@ -41,7 +26,12 @@ export async function processTable(table: Table): Promise<Table> {
table = { ...table } table = { ...table }
if (table.views) { if (table.views) {
processViews(Object.values(table.views)) for (const [key, view] of Object.entries(table.views)) {
if (!isV2(view)) {
continue
}
table.views[key] = ensureQueryUISet(view)
}
} }
if (table._id && isExternalTableID(table._id)) { if (table._id && isExternalTableID(table._id)) {
// Old created external tables via Budibase might have a missing field name breaking some UI such as filters // Old created external tables via Budibase might have a missing field name breaking some UI such as filters

View File

@ -19,8 +19,7 @@ export async function get(viewId: string): Promise<ViewV2> {
if (!found) { if (!found) {
throw new Error("No view found") throw new Error("No view found")
} }
ensureQueryUISet(found) return ensureQueryUISet(found)
return found
} }
export async function getEnriched(viewId: string): Promise<ViewV2Enriched> { export async function getEnriched(viewId: string): Promise<ViewV2Enriched> {
@ -35,22 +34,21 @@ export async function getEnriched(viewId: string): Promise<ViewV2Enriched> {
if (!found) { if (!found) {
throw new Error("No view found") throw new Error("No view found")
} }
ensureQueryUISet(found) return await enrichSchema(ensureQueryUISet(found), table.schema)
return await enrichSchema(found, table.schema)
} }
export async function create( export async function create(
tableId: string, tableId: string,
viewRequest: Omit<ViewV2, "id" | "version"> viewRequest: Omit<ViewV2, "id" | "version">
): Promise<ViewV2> { ): Promise<ViewV2> {
const view: ViewV2 = { let view: ViewV2 = {
...viewRequest, ...viewRequest,
id: utils.generateViewID(tableId), id: utils.generateViewID(tableId),
version: 2, version: 2,
} }
ensureQuerySet(view) view = ensureQuerySet(view)
ensureQueryUISet(view) view = ensureQueryUISet(view)
const db = context.getAppDB() const db = context.getAppDB()
@ -62,7 +60,10 @@ export async function create(
return view return view
} }
export async function update(tableId: string, view: ViewV2): Promise<ViewV2> { export async function update(
tableId: string,
view: Readonly<ViewV2>
): Promise<ViewV2> {
const db = context.getAppDB() const db = context.getAppDB()
const { datasourceId, tableName } = breakExternalTableId(tableId) const { datasourceId, tableName } = breakExternalTableId(tableId)
@ -80,8 +81,8 @@ export async function update(tableId: string, view: ViewV2): Promise<ViewV2> {
throw new HTTPError(`Cannot update view type after creation`, 400) throw new HTTPError(`Cannot update view type after creation`, 400)
} }
ensureQuerySet(view) view = ensureQuerySet(view)
ensureQueryUISet(view) view = ensureQueryUISet(view)
delete views[existingView.name] delete views[existingView.name]
views[view.name] = view views[view.name] = view

View File

@ -14,8 +14,7 @@ export async function get(viewId: string): Promise<ViewV2> {
if (!found) { if (!found) {
throw new Error("No view found") throw new Error("No view found")
} }
ensureQueryUISet(found) return ensureQueryUISet(found)
return found
} }
export async function getEnriched(viewId: string): Promise<ViewV2Enriched> { export async function getEnriched(viewId: string): Promise<ViewV2Enriched> {
@ -26,22 +25,21 @@ export async function getEnriched(viewId: string): Promise<ViewV2Enriched> {
if (!found) { if (!found) {
throw new Error("No view found") throw new Error("No view found")
} }
ensureQueryUISet(found) return await enrichSchema(ensureQueryUISet(found), table.schema)
return await enrichSchema(found, table.schema)
} }
export async function create( export async function create(
tableId: string, tableId: string,
viewRequest: Omit<ViewV2, "id" | "version"> viewRequest: Omit<ViewV2, "id" | "version">
): Promise<ViewV2> { ): Promise<ViewV2> {
const view: ViewV2 = { let view: ViewV2 = {
...viewRequest, ...viewRequest,
id: utils.generateViewID(tableId), id: utils.generateViewID(tableId),
version: 2, version: 2,
} }
ensureQuerySet(view) view = ensureQuerySet(view)
ensureQueryUISet(view) view = ensureQueryUISet(view)
const db = context.getAppDB() const db = context.getAppDB()
@ -53,7 +51,10 @@ export async function create(
return view return view
} }
export async function update(tableId: string, view: ViewV2): Promise<ViewV2> { export async function update(
tableId: string,
view: Readonly<ViewV2>
): Promise<ViewV2> {
const db = context.getAppDB() const db = context.getAppDB()
const table = await sdk.tables.getTable(tableId) const table = await sdk.tables.getTable(tableId)
table.views ??= {} table.views ??= {}
@ -69,8 +70,8 @@ export async function update(tableId: string, view: ViewV2): Promise<ViewV2> {
throw new HTTPError(`Cannot update view type after creation`, 400) throw new HTTPError(`Cannot update view type after creation`, 400)
} }
ensureQuerySet(view) view = ensureQuerySet(view)
ensureQueryUISet(view) view = ensureQueryUISet(view)
delete table.views[existingView.name] delete table.views[existingView.name]
table.views[view.name] = view table.views[view.name] = view

View File

@ -1,13 +1,14 @@
import { ViewV2 } from "@budibase/types" import { ViewV2 } from "@budibase/types"
import { utils, dataFilters } from "@budibase/shared-core" import { utils, dataFilters } from "@budibase/shared-core"
import { isPlainObject } from "lodash" import { cloneDeep, isPlainObject } from "lodash"
import { HTTPError } from "@budibase/backend-core" import { HTTPError } from "@budibase/backend-core"
function isEmptyObject(obj: any) { function isEmptyObject(obj: any) {
return obj && isPlainObject(obj) && Object.keys(obj).length === 0 return obj && isPlainObject(obj) && Object.keys(obj).length === 0
} }
export function ensureQueryUISet(view: ViewV2) { export function ensureQueryUISet(viewArg: Readonly<ViewV2>): ViewV2 {
const view = cloneDeep<ViewV2>(viewArg)
if (!view.queryUI && view.query && !isEmptyObject(view.query)) { if (!view.queryUI && view.query && !isEmptyObject(view.query)) {
if (!Array.isArray(view.query)) { if (!Array.isArray(view.query)) {
// In practice this should not happen. `view.query`, at the time this code // In practice this should not happen. `view.query`, at the time this code
@ -27,13 +28,16 @@ export function ensureQueryUISet(view: ViewV2) {
view.queryUI = utils.processSearchFilters(view.query) view.queryUI = utils.processSearchFilters(view.query)
} }
return view
} }
export function ensureQuerySet(view: ViewV2) { export function ensureQuerySet(viewArg: Readonly<ViewV2>): ViewV2 {
const view = cloneDeep<ViewV2>(viewArg)
// We consider queryUI to be the source of truth, so we don't check for the // We consider queryUI to be the source of truth, so we don't check for the
// presence of query here. We will overwrite it regardless of whether it is // presence of query here. We will overwrite it regardless of whether it is
// present or not. // present or not.
if (view.queryUI && !isEmptyObject(view.queryUI)) { if (view.queryUI && !isEmptyObject(view.queryUI)) {
view.query = dataFilters.buildQuery(view.queryUI) view.query = dataFilters.buildQuery(view.queryUI)
} }
return view
} }

View File

@ -475,6 +475,8 @@ export function buildQuery(
const query: SearchFilters = {} const query: SearchFilters = {}
if (filter.onEmptyFilter) { if (filter.onEmptyFilter) {
query.onEmptyFilter = filter.onEmptyFilter query.onEmptyFilter = filter.onEmptyFilter
} else {
query.onEmptyFilter = EmptyFilterOption.RETURN_ALL
} }
query[operator] = { query[operator] = {

View File

@ -7,6 +7,7 @@ import {
ArrayOperator, ArrayOperator,
isLogicalSearchOperator, isLogicalSearchOperator,
SearchFilter, SearchFilter,
EmptyFilterOption,
} from "@budibase/types" } from "@budibase/types"
import * as Constants from "./constants" import * as Constants from "./constants"
import { removeKeyNumbering, splitFiltersArray } from "./filters" import { removeKeyNumbering, splitFiltersArray } from "./filters"
@ -139,10 +140,11 @@ export function isSupportedUserSearch(query: SearchFilters) {
export const processSearchFilters = ( export const processSearchFilters = (
filterArray: LegacyFilter[] filterArray: LegacyFilter[]
): UISearchFilter => { ): Required<UISearchFilter> => {
const { allOr, onEmptyFilter, filters } = splitFiltersArray(filterArray) const { allOr, onEmptyFilter, filters } = splitFiltersArray(filterArray)
return { return {
onEmptyFilter, logicalOperator: UILogicalOperator.ALL,
onEmptyFilter: onEmptyFilter || EmptyFilterOption.RETURN_ALL,
groups: [ groups: [
{ {
logicalOperator: allOr ? UILogicalOperator.ANY : UILogicalOperator.ALL, logicalOperator: allOr ? UILogicalOperator.ANY : UILogicalOperator.ALL,