diff --git a/lerna.json b/lerna.json index 9b22d286b5..c2d038db02 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.13.12", + "version": "2.13.14", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/packages/bbui/src/Link/Link.svelte b/packages/bbui/src/Link/Link.svelte index 5eaeceaf76..58f999473a 100644 --- a/packages/bbui/src/Link/Link.svelte +++ b/packages/bbui/src/Link/Link.svelte @@ -16,10 +16,9 @@ const dispatch = createEventDispatcher() - const onClick = e => { + const onClick = () => { if (!disabled) { dispatch("click") - e.stopPropagation() } } diff --git a/packages/builder/src/components/deploy/AppActions.svelte b/packages/builder/src/components/deploy/AppActions.svelte index 7259e7e402..7d14fd0e87 100644 --- a/packages/builder/src/components/deploy/AppActions.svelte +++ b/packages/builder/src/components/deploy/AppActions.svelte @@ -20,7 +20,12 @@ import analytics, { Events, EventSource } from "analytics" import { API } from "api" import { apps } from "stores/portal" - import { deploymentStore, store, isOnlyUser } from "builderStore" + import { + deploymentStore, + store, + isOnlyUser, + sortedScreens, + } from "builderStore" import TourWrap from "components/portal/onboarding/TourWrap.svelte" import { TOUR_STEP_KEYS } from "components/portal/onboarding/tours.js" import { goto } from "@roxi/routify" @@ -48,7 +53,7 @@ $store.upgradableVersion && $store.version && $store.upgradableVersion !== $store.version - $: canPublish = !publishing && loaded + $: canPublish = !publishing && loaded && $sortedScreens.length > 0 $: lastDeployed = getLastDeployedString($deploymentStore) const initialiseApp = async () => { @@ -175,7 +180,12 @@
diff --git a/packages/shared-core/src/filters.ts b/packages/shared-core/src/filters.ts index 1839a53525..564e8a52c9 100644 --- a/packages/shared-core/src/filters.ts +++ b/packages/shared-core/src/filters.ts @@ -9,6 +9,7 @@ import { SortDirection, SortType, } from "@budibase/types" +import dayjs from "dayjs" import { OperatorOptions, SqlNumberTypeRangeMap } from "./constants" import { deepGet } from "./helpers" @@ -302,12 +303,19 @@ export const runLuceneQuery = (docs: any[], query?: SearchQuery) => { docValue: string | number | null, testValue: { low: number; high: number } ) => { - return ( - docValue == null || - docValue === "" || - +docValue < testValue.low || - +docValue > testValue.high - ) + if (docValue == null || docValue === "") { + return true + } + if (!isNaN(+docValue)) { + return +docValue < testValue.low || +docValue > testValue.high + } + if (dayjs(docValue).isValid()) { + return ( + new Date(docValue).getTime() < new Date(testValue.low).getTime() || + new Date(docValue).getTime() > new Date(testValue.high).getTime() + ) + } + throw "Cannot perform range filter - invalid type." } ) diff --git a/packages/shared-core/src/tests/filters.test.ts b/packages/shared-core/src/tests/filters.test.ts new file mode 100644 index 0000000000..6f488cffbd --- /dev/null +++ b/packages/shared-core/src/tests/filters.test.ts @@ -0,0 +1,174 @@ +import { SearchQuery, SearchQueryOperators } from "@budibase/types" +import { runLuceneQuery } from "../filters" +import { expect, describe, it } from "vitest" + +describe("runLuceneQuery", () => { + const docs = [ + { + order_id: 1, + customer_id: 259, + order_status: 4, + order_date: "2016-01-01T00:00:00.000Z", + required_date: "2016-01-03T00:00:00.000Z", + shipped_date: "2016-01-03T00:00:00.000Z", + store_id: 1, + staff_id: 2, + description: "Large box", + label: undefined, + }, + { + order_id: 2, + customer_id: 1212, + order_status: 4, + order_date: "2016-01-05T00:00:00.000Z", + required_date: "2016-01-04T00:00:00.000Z", + shipped_date: "2016-01-03T00:00:00.000Z", + store_id: 2, + staff_id: 6, + description: "Small box", + label: "FRAGILE", + }, + { + order_id: 3, + customer_id: 523, + order_status: 5, + order_date: "2016-01-12T00:00:00.000Z", + required_date: "2016-01-05T00:00:00.000Z", + shipped_date: "2016-01-03T00:00:00.000Z", + store_id: 2, + staff_id: 7, + description: "Heavy box", + label: "HEAVY", + }, + ] + + function buildQuery( + filterKey: string, + value: { [key: string]: any } + ): SearchQuery { + const query: SearchQuery = { + string: {}, + fuzzy: {}, + range: {}, + equal: {}, + notEqual: {}, + empty: {}, + notEmpty: {}, + contains: {}, + notContains: {}, + oneOf: {}, + containsAny: {}, + } + query[filterKey as SearchQueryOperators] = value + return query + } + + it("should return input docs if no search query is provided", () => { + expect(runLuceneQuery(docs)).toBe(docs) + }) + + it("should return matching rows for equal filter", () => { + const query = buildQuery("equal", { + order_status: 4, + }) + expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([1, 2]) + }) + + it("should return matching row for notEqual filter", () => { + const query = buildQuery("notEqual", { + order_status: 4, + }) + expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([3]) + }) + + it("should return starts with matching rows for fuzzy and string filters", () => { + expect( + runLuceneQuery( + docs, + buildQuery("fuzzy", { + description: "sm", + }) + ).map(row => row.description) + ).toEqual(["Small box"]) + expect( + runLuceneQuery( + docs, + buildQuery("string", { + description: "SM", + }) + ).map(row => row.description) + ).toEqual(["Small box"]) + }) + + it("should return rows within a range filter", () => { + const query = buildQuery("range", { + customer_id: { + low: 500, + high: 1000, + }, + }) + expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([3]) + }) + + it("should return rows with numeric strings within a range filter", () => { + const query = buildQuery("range", { + customer_id: { + low: "500", + high: "1000", + }, + }) + expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([3]) + }) + + it("should return rows with ISO date strings within a range filter", () => { + const query = buildQuery("range", { + order_date: { + low: "2016-01-04T00:00:00.000Z", + high: "2016-01-11T00:00:00.000Z", + }, + }) + expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([2]) + }) + + it("should throw an error is an invalid doc value is passed into a range filter", async () => { + const query = buildQuery("range", { + order_date: { + low: "2016-01-04T00:00:00.000Z", + high: "2016-01-11T00:00:00.000Z", + }, + }) + expect(() => + runLuceneQuery( + [ + { + order_id: 4, + customer_id: 1758, + order_status: 5, + order_date: "INVALID", + required_date: "2017-03-05T00:00:00.000Z", + shipped_date: "2017-03-03T00:00:00.000Z", + store_id: 2, + staff_id: 7, + description: undefined, + label: "", + }, + ], + query + ) + ).toThrowError("Cannot perform range filter - invalid type.") + }) + + it("should return rows with matches on empty filter", () => { + const query = buildQuery("empty", { + label: null, + }) + expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([1]) + }) + + it("should return rows with matches on notEmpty filter", () => { + const query = buildQuery("notEmpty", { + label: null, + }) + expect(runLuceneQuery(docs, query).map(row => row.order_id)).toEqual([2, 3]) + }) +}) diff --git a/packages/shared-core/tsconfig.build.json b/packages/shared-core/tsconfig.build.json index 1969c286b1..8a7f0ea216 100644 --- a/packages/shared-core/tsconfig.build.json +++ b/packages/shared-core/tsconfig.build.json @@ -24,6 +24,7 @@ "dist", "**/*.spec.ts", "**/*.spec.js", - "__mocks__" + "__mocks__", + "src/tests" ] }