diff --git a/charts/budibase/README.md b/charts/budibase/README.md
index b803da18a4..207992087d 100644
--- a/charts/budibase/README.md
+++ b/charts/budibase/README.md
@@ -152,6 +152,8 @@ $ helm install --create-namespace --namespace budibase budibase . -f values.yaml
| services.apps.autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage for the apps service. Note that for autoscaling to work, you will need to have metrics-server configured, and resources set for the apps pods. |
| services.apps.extraContainers | list | `[]` | Additional containers to be added to the apps pod. |
| services.apps.extraEnv | list | `[]` | Extra environment variables to set for apps pods. Takes a list of name=value pairs. |
+| services.apps.extraVolumeMounts | list | `[]` | Additional volumeMounts to the main apps container. |
+| services.apps.extraVolumes | list | `[]` | Additional volumes to the apps pod. |
| services.apps.httpLogging | int | `1` | Whether or not to log HTTP requests to the apps service. |
| services.apps.livenessProbe | object | HTTP health checks. | Liveness probe configuration for apps pods. You shouldn't need to change this, but if you want to you can find more information here: |
| services.apps.logLevel | string | `"info"` | The log level for the apps service. |
@@ -166,6 +168,8 @@ $ helm install --create-namespace --namespace budibase budibase . -f values.yaml
| services.automationWorkers.enabled | bool | `true` | Whether or not to enable the automation worker service. If you disable this, automations will be processed by the apps service. |
| services.automationWorkers.extraContainers | list | `[]` | Additional containers to be added to the automationWorkers pod. |
| services.automationWorkers.extraEnv | list | `[]` | Extra environment variables to set for automation worker pods. Takes a list of name=value pairs. |
+| services.automationWorkers.extraVolumeMounts | list | `[]` | Additional volumeMounts to the main automationWorkers container. |
+| services.automationWorkers.extraVolumes | list | `[]` | Additional volumes to the automationWorkers pod. |
| services.automationWorkers.livenessProbe | object | HTTP health checks. | Liveness probe configuration for automation worker pods. You shouldn't need to change this, but if you want to you can find more information here: |
| services.automationWorkers.logLevel | string | `"info"` | The log level for the automation worker service. |
| services.automationWorkers.readinessProbe | object | HTTP health checks. | Readiness probe configuration for automation worker pods. You shouldn't need to change this, but if you want to you can find more information here: |
@@ -185,6 +189,8 @@ $ helm install --create-namespace --namespace budibase budibase . -f values.yaml
| services.objectStore.cloudfront.privateKey64 | string | `""` | Base64 encoded private key for the above public key. |
| services.objectStore.cloudfront.publicKeyId | string | `""` | ID of public key stored in cloudfront. |
| services.objectStore.extraContainers | list | `[]` | Additional containers to be added to the objectStore pod. |
+| services.objectStore.extraVolumeMounts | list | `[]` | Additional volumeMounts to the main objectStore container. |
+| services.objectStore.extraVolumes | list | `[]` | Additional volumes to the objectStore pod. |
| services.objectStore.minio | bool | `true` | Set to false if using another object store, such as S3. You will need to set `services.objectStore.url` to point to your bucket if you do this. |
| services.objectStore.region | string | `""` | AWS_REGION if using S3 |
| services.objectStore.resources | object | `{}` | The resources to use for Minio pods. See for more information on how to set these. |
@@ -197,6 +203,8 @@ $ helm install --create-namespace --namespace budibase budibase . -f values.yaml
| services.proxy.autoscaling.minReplicas | int | `1` | |
| services.proxy.autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage for the proxy service. Note that for autoscaling to work, you will need to have metrics-server configured, and resources set for the proxy pods. |
| services.proxy.extraContainers | list | `[]` | |
+| services.proxy.extraVolumeMounts | list | `[]` | Additional volumeMounts to the main proxy container. |
+| services.proxy.extraVolumes | list | `[]` | Additional volumes to the proxy pod. |
| services.proxy.livenessProbe | object | HTTP health checks. | Liveness probe configuration for proxy pods. You shouldn't need to change this, but if you want to you can find more information here: |
| services.proxy.readinessProbe | object | HTTP health checks. | Readiness probe configuration for proxy pods. You shouldn't need to change this, but if you want to you can find more information here: |
| services.proxy.replicaCount | int | `1` | The number of proxy replicas to run. |
@@ -204,6 +212,9 @@ $ helm install --create-namespace --namespace budibase budibase . -f values.yaml
| services.proxy.startupProbe | object | HTTP health checks. | Startup probe configuration for proxy pods. You shouldn't need to change this, but if you want to you can find more information here: |
| services.redis.enabled | bool | `true` | Whether or not to deploy a Redis pod into your cluster. |
| services.redis.extraContainers | list | `[]` | Additional containers to be added to the redis pod. |
+| services.redis.extraVolumeMounts | list | `[]` | Additional volumeMounts to the main redis container. |
+| services.redis.extraVolumes | list | `[]` | Additional volumes to the redis pod. |
+| services.redis.image | string | `"redis"` | The Redis image to use. |
| services.redis.password | string | `"budibase"` | The password to use when connecting to Redis. It's recommended that you change this from the default if you're running Redis in-cluster. |
| services.redis.port | int | `6379` | Port to expose Redis on. |
| services.redis.resources | object | `{}` | The resources to use for Redis pods. See for more information on how to set these. |
@@ -216,6 +227,8 @@ $ helm install --create-namespace --namespace budibase budibase . -f values.yaml
| services.worker.autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage for the worker service. Note that for autoscaling to work, you will need to have metrics-server configured, and resources set for the worker pods. |
| services.worker.extraContainers | list | `[]` | Additional containers to be added to the worker pod. |
| services.worker.extraEnv | list | `[]` | Extra environment variables to set for worker pods. Takes a list of name=value pairs. |
+| services.worker.extraVolumeMounts | list | `[]` | Additional volumeMounts to the main worker container. |
+| services.worker.extraVolumes | list | `[]` | Additional volumes to the worker pod. |
| services.worker.httpLogging | int | `1` | Whether or not to log HTTP requests to the worker service. |
| services.worker.livenessProbe | object | HTTP health checks. | Liveness probe configuration for worker pods. You shouldn't need to change this, but if you want to you can find more information here: |
| services.worker.logLevel | string | `"info"` | The log level for the worker service. |
diff --git a/charts/budibase/templates/app-service-deployment.yaml b/charts/budibase/templates/app-service-deployment.yaml
index 2fd8506e30..b380908dd1 100644
--- a/charts/budibase/templates/app-service-deployment.yaml
+++ b/charts/budibase/templates/app-service-deployment.yaml
@@ -235,6 +235,10 @@ spec:
args:
{{- toYaml .Values.services.apps.args | nindent 10 }}
{{ end }}
+ {{ if .Values.services.apps.extraVolumeMounts }}
+ volumeMounts:
+ {{- toYaml .Values.services.apps.extraVolumeMounts | nindent 10 }}
+ {{- end }}
{{- if .Values.services.apps.extraContainers }}
{{- toYaml .Values.services.apps.extraContainers | nindent 6 }}
{{- end }}
@@ -261,4 +265,8 @@ spec:
- name: ndots
value: {{ .Values.services.apps.ndots | quote }}
{{ end }}
+ {{ if .Values.services.apps.extraVolumes }}
+ volumes:
+ {{- toYaml .Values.services.apps.extraVolumes | nindent 6 }}
+ {{- end }}
status: {}
diff --git a/charts/budibase/templates/automation-worker-service-deployment.yaml b/charts/budibase/templates/automation-worker-service-deployment.yaml
index 53d5fcc860..51fa9ee4bb 100644
--- a/charts/budibase/templates/automation-worker-service-deployment.yaml
+++ b/charts/budibase/templates/automation-worker-service-deployment.yaml
@@ -235,6 +235,10 @@ spec:
args:
{{- toYaml .Values.services.automationWorkers.args | nindent 10 }}
{{ end }}
+ {{ if .Values.services.automationWorkers.extraVolumeMounts }}
+ volumeMounts:
+ {{- toYaml .Values.services.automationWorkers.extraVolumeMounts | nindent 10 }}
+ {{ end }}
{{- if .Values.services.automationWorkers.extraContainers }}
{{- toYaml .Values.services.automationWorkers.extraContainers | nindent 6 }}
{{- end }}
@@ -261,5 +265,9 @@ spec:
- name: ndots
value: {{ .Values.services.automationWorkers.ndots | quote }}
{{ end }}
+ {{ if .Values.services.automationWorkers.extraVolumes }}
+ volumes:
+ {{- toYaml .Values.services.automationWorkers.extraVolumes | nindent 8 }}
+ {{ end }}
status: {}
{{- end }}
\ No newline at end of file
diff --git a/charts/budibase/templates/minio-service-deployment.yaml b/charts/budibase/templates/minio-service-deployment.yaml
index ade1d37cd2..901ead2b46 100644
--- a/charts/budibase/templates/minio-service-deployment.yaml
+++ b/charts/budibase/templates/minio-service-deployment.yaml
@@ -54,6 +54,9 @@ spec:
volumeMounts:
- mountPath: /data
name: minio-data
+ {{ if .Values.services.objectStore.extraVolumeMounts }}
+ {{- toYaml .Values.services.objectStore.extraVolumeMounts | nindent 8 }}
+ {{- end }}
{{- if .Values.services.objectStore.extraContainers }}
{{- toYaml .Values.services.objectStore.extraContainers | nindent 6 }}
{{- end }}
@@ -78,5 +81,8 @@ spec:
- name: minio-data
persistentVolumeClaim:
claimName: minio-data
+ {{ if .Values.services.objectStore.extraVolumes }}
+ {{- toYaml .Values.services.objectStore.extraVolumes | nindent 6 }}
+ {{- end }}
status: {}
{{- end }}
diff --git a/charts/budibase/templates/proxy-service-deployment.yaml b/charts/budibase/templates/proxy-service-deployment.yaml
index 462c6a0749..d5ea696431 100644
--- a/charts/budibase/templates/proxy-service-deployment.yaml
+++ b/charts/budibase/templates/proxy-service-deployment.yaml
@@ -82,6 +82,10 @@ spec:
resources:
{{- toYaml . | nindent 10 }}
{{ end }}
+ {{ if .Values.services.proxy.extraVolumeMounts }}
+ volumeMounts:
+ {{- toYaml .Values.services.proxy.extraVolumeMounts | nindent 8 }}
+ {{- end }}
{{- if .Values.services.proxy.extraContainers }}
{{- toYaml .Values.services.proxy.extraContainers | nindent 6 }}
{{- end }}
@@ -110,7 +114,10 @@ spec:
args:
{{- toYaml .Values.services.proxy.args | nindent 8 }}
{{ end }}
+ {{ if .Values.services.proxy.extraVolumes }}
volumes:
+ {{- toYaml .Values.services.proxy.extraVolumes | nindent 6 }}
+ {{ end }}
{{ if .Values.services.proxy.ndots }}
dnsConfig:
options:
diff --git a/charts/budibase/templates/redis-service-deployment.yaml b/charts/budibase/templates/redis-service-deployment.yaml
index 1a003d3814..9ad12e0167 100644
--- a/charts/budibase/templates/redis-service-deployment.yaml
+++ b/charts/budibase/templates/redis-service-deployment.yaml
@@ -22,7 +22,7 @@ spec:
- redis-server
- --requirepass
- {{ .Values.services.redis.password }}
- image: redis
+ image: {{ .Values.services.redis.image }}
imagePullPolicy: ""
name: redis-service
ports:
@@ -34,6 +34,9 @@ spec:
volumeMounts:
- mountPath: /data
name: redis-data
+ {{ if .Values.services.redis.extraVolumeMounts }}
+ {{- toYaml .Values.services.redis.extraVolumeMounts | nindent 8 }}
+ {{- end }}
{{- if .Values.services.redis.extraContainers }}
{{- toYaml .Values.services.redis.extraContainers | nindent 6 }}
{{- end }}
@@ -58,6 +61,9 @@ spec:
- name: redis-data
persistentVolumeClaim:
claimName: redis-data
+ {{ if .Values.services.redis.extraVolumes }}
+ {{- toYaml .Values.services.redis.extraVolumes | nindent 6 }}
+ {{- end }}
status: {}
{{- end }}
diff --git a/charts/budibase/templates/worker-service-deployment.yaml b/charts/budibase/templates/worker-service-deployment.yaml
index cc27bf429e..e37b2bc0e4 100644
--- a/charts/budibase/templates/worker-service-deployment.yaml
+++ b/charts/budibase/templates/worker-service-deployment.yaml
@@ -221,6 +221,10 @@ spec:
args:
{{- toYaml .Values.services.worker.args | nindent 10 }}
{{ end }}
+ {{ if .Values.services.worker.extraVolumeMounts }}
+ volumeMounts:
+ {{- toYaml .Values.services.worker.extraVolumeMounts | nindent 10 }}
+ {{- end }}
{{- if .Values.services.worker.extraContainers }}
{{- toYaml .Values.services.worker.extraContainers | nindent 6 }}
{{- end }}
@@ -247,4 +251,8 @@ spec:
- name: ndots
value: {{ .Values.services.worker.ndots | quote }}
{{ end }}
+ {{ if .Values.services.worker.extraVolumes }}
+ volumes:
+ {{- toYaml .Values.services.worker.extraVolumes | nindent 6 }}
+ {{- end }}
status: {}
diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml
index dfbbca6cad..9ace768625 100644
--- a/charts/budibase/values.yaml
+++ b/charts/budibase/values.yaml
@@ -211,6 +211,16 @@ services:
# - name: my-sidecar
# image: myimage:latest
+ # -- Additional volumeMounts to the main proxy container.
+ extraVolumeMounts: []
+ # - name: my-volume
+ # mountPath: /path/to/mount
+
+ # -- Additional volumes to the proxy pod.
+ extraVolumes: []
+ # - name: my-volume
+ # emptyDir: {}
+
apps:
# @ignore (you shouldn't need to change this)
port: 4002
@@ -283,6 +293,16 @@ services:
# - name: my-sidecar
# image: myimage:latest
+ # -- Additional volumeMounts to the main apps container.
+ extraVolumeMounts: []
+ # - name: my-volume
+ # mountPath: /path/to/mount
+
+ # -- Additional volumes to the apps pod.
+ extraVolumes: []
+ # - name: my-volume
+ # emptyDir: {}
+
automationWorkers:
# -- Whether or not to enable the automation worker service. If you disable this,
# automations will be processed by the apps service.
@@ -359,6 +379,16 @@ services:
# - name: my-sidecar
# image: myimage:latest
+ # -- Additional volumeMounts to the main automationWorkers container.
+ extraVolumeMounts: []
+ # - name: my-volume
+ # mountPath: /path/to/mount
+
+ # -- Additional volumes to the automationWorkers pod.
+ extraVolumes: []
+ # - name: my-volume
+ # emptyDir: {}
+
worker:
# @ignore (you shouldn't need to change this)
port: 4003
@@ -431,6 +461,16 @@ services:
# - name: my-sidecar
# image: myimage:latest
+ # -- Additional volumeMounts to the main worker container.
+ extraVolumeMounts: []
+ # - name: my-volume
+ # mountPath: /path/to/mount
+
+ # -- Additional volumes to the worker pod.
+ extraVolumes: []
+ # - name: my-volume
+ # emptyDir: {}
+
couchdb:
# -- Whether or not to spin up a CouchDB instance in your cluster. True by
# default, and the configuration for the CouchDB instance is under the
@@ -456,6 +496,8 @@ services:
resources: {}
redis:
+ # -- The Redis image to use.
+ image: redis
# -- Whether or not to deploy a Redis pod into your cluster.
enabled: true
# -- Port to expose Redis on.
@@ -484,6 +526,16 @@ services:
# - name: my-sidecar
# image: myimage:latest
+ # -- Additional volumeMounts to the main redis container.
+ extraVolumeMounts: []
+ # - name: my-volume
+ # mountPath: /path/to/mount
+
+ # -- Additional volumes to the redis pod.
+ extraVolumes: []
+ # - name: my-volume
+ # emptyDir: {}
+
objectStore:
# -- Set to false if using another object store, such as S3. You will need
# to set `services.objectStore.url` to point to your bucket if you do this.
@@ -530,6 +582,16 @@ services:
# - name: my-sidecar
# image: myimage:latest
+ # -- Additional volumeMounts to the main objectStore container.
+ extraVolumeMounts: []
+ # - name: my-volume
+ # mountPath: /path/to/mount
+
+ # -- Additional volumes to the objectStore pod.
+ extraVolumes: []
+ # - name: my-volume
+ # emptyDir: {}
+
# Override values in couchDB subchart. We're only specifying the values we're changing.
# If you want to see all of the available values, see:
# https://github.com/apache/couchdb-helm/tree/couchdb-4.3.0/couchdb
diff --git a/lerna.json b/lerna.json
index 78a3aa13e9..9839b8b166 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
- "version": "2.23.4",
+ "version": "2.23.5",
"npmClient": "yarn",
"packages": [
"packages/*",
diff --git a/package.json b/package.json
index 2816247939..e520b7c2cf 100644
--- a/package.json
+++ b/package.json
@@ -56,6 +56,7 @@
"dev:noserver": "yarn run kill-builder && lerna run --stream dev:stack:up --ignore @budibase/account-portal-server && lerna run --stream dev --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker --ignore=@budibase/account-portal-ui --ignore @budibase/account-portal-server",
"dev:server": "yarn run kill-server && lerna run --stream dev --scope @budibase/worker --scope @budibase/server",
"dev:accountportal": "yarn kill-accountportal && lerna run dev --stream --scope @budibase/account-portal-ui --scope @budibase/account-portal-server",
+ "dev:camunda": "./scripts/deploy-camunda.sh",
"dev:all": "yarn run kill-all && lerna run --stream dev",
"dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream dev:built",
"dev:docker": "yarn build --scope @budibase/server --scope @budibase/worker && docker-compose -f hosting/docker-compose.build.yaml -f hosting/docker-compose.dev.yaml --env-file hosting/.env up --build --scale proxy-service=0",
diff --git a/packages/account-portal b/packages/account-portal
index a0ee9cad8c..bd0e01d639 160000
--- a/packages/account-portal
+++ b/packages/account-portal
@@ -1 +1 @@
-Subproject commit a0ee9cad8cefb8f9f40228705711be174f018fa9
+Subproject commit bd0e01d639ec3b2547e7c859a1c43b622dce8344
diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts
index ceef421fab..6acdfcd465 100644
--- a/packages/server/src/api/controllers/application.ts
+++ b/packages/server/src/api/controllers/application.ts
@@ -320,6 +320,7 @@ async function performAppCreate(ctx: UserCtx) {
"theme",
"customTheme",
"icon",
+ "snippets",
]
keys.forEach(key => {
if (existing[key]) {
diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts
index fdf1ed7603..5b71ec9044 100644
--- a/packages/server/src/api/routes/tests/search.spec.ts
+++ b/packages/server/src/api/routes/tests/search.spec.ts
@@ -8,6 +8,8 @@ import {
FieldType,
RowSearchParams,
SearchFilters,
+ SortOrder,
+ SortType,
Table,
TableSchema,
} from "@budibase/types"
@@ -62,7 +64,32 @@ describe.each([
class SearchAssertion {
constructor(private readonly query: RowSearchParams) {}
- async toFind(expectedRows: any[]) {
+ // Asserts that the query returns rows matching exactly the set of rows
+ // passed in. The order of the rows matters. Rows returned in an order
+ // different to the one passed in will cause the assertion to fail. Extra
+ // rows returned by the query will also cause the assertion to fail.
+ async toMatchExactly(expectedRows: any[]) {
+ const { rows: foundRows } = await config.api.row.search(table._id!, {
+ ...this.query,
+ tableId: table._id!,
+ })
+
+ // eslint-disable-next-line jest/no-standalone-expect
+ expect(foundRows).toHaveLength(expectedRows.length)
+ // eslint-disable-next-line jest/no-standalone-expect
+ expect(foundRows).toEqual(
+ expectedRows.map((expectedRow: any) =>
+ expect.objectContaining(
+ foundRows.find(foundRow => _.isMatch(foundRow, expectedRow))
+ )
+ )
+ )
+ }
+
+ // Asserts that the query returns rows matching exactly the set of rows
+ // passed in. The order of the rows is not important, but extra rows will
+ // cause the assertion to fail.
+ async toContainExactly(expectedRows: any[]) {
const { rows: foundRows } = await config.api.row.search(table._id!, {
...this.query,
tableId: table._id!,
@@ -82,8 +109,39 @@ describe.each([
)
}
+ // Asserts that the query returns rows matching the set of rows passed in.
+ // The order of the rows is not important. Extra rows will not cause the
+ // assertion to fail.
+ async toContain(expectedRows: any[]) {
+ const { rows: foundRows } = await config.api.row.search(table._id!, {
+ ...this.query,
+ tableId: table._id!,
+ })
+
+ // eslint-disable-next-line jest/no-standalone-expect
+ expect(foundRows).toEqual(
+ expect.arrayContaining(
+ expectedRows.map((expectedRow: any) =>
+ expect.objectContaining(
+ foundRows.find(foundRow => _.isMatch(foundRow, expectedRow))
+ )
+ )
+ )
+ )
+ }
+
async toFindNothing() {
- await this.toFind([])
+ await this.toContainExactly([])
+ }
+
+ async toHaveLength(length: number) {
+ const { rows: foundRows } = await config.api.row.search(table._id!, {
+ ...this.query,
+ tableId: table._id!,
+ })
+
+ // eslint-disable-next-line jest/no-standalone-expect
+ expect(foundRows).toHaveLength(length)
}
}
@@ -105,28 +163,33 @@ describe.each([
describe("misc", () => {
it("should return all if no query is passed", () =>
- expectSearch({} as RowSearchParams).toFind([
+ expectSearch({} as RowSearchParams).toContainExactly([
{ name: "foo" },
{ name: "bar" },
]))
it("should return all if empty query is passed", () =>
- expectQuery({}).toFind([{ name: "foo" }, { name: "bar" }]))
+ expectQuery({}).toContainExactly([{ name: "foo" }, { name: "bar" }]))
it("should return all if onEmptyFilter is RETURN_ALL", () =>
expectQuery({
onEmptyFilter: EmptyFilterOption.RETURN_ALL,
- }).toFind([{ name: "foo" }, { name: "bar" }]))
+ }).toContainExactly([{ name: "foo" }, { name: "bar" }]))
it("should return nothing if onEmptyFilter is RETURN_NONE", () =>
expectQuery({
onEmptyFilter: EmptyFilterOption.RETURN_NONE,
}).toFindNothing())
+
+ it("should respect limit", () =>
+ expectSearch({ limit: 1, paginate: true, query: {} }).toHaveLength(1))
})
describe("equal", () => {
it("successfully finds a row", () =>
- expectQuery({ equal: { name: "foo" } }).toFind([{ name: "foo" }]))
+ expectQuery({ equal: { name: "foo" } }).toContainExactly([
+ { name: "foo" },
+ ]))
it("fails to find nonexistent row", () =>
expectQuery({ equal: { name: "none" } }).toFindNothing())
@@ -134,15 +197,21 @@ describe.each([
describe("notEqual", () => {
it("successfully finds a row", () =>
- expectQuery({ notEqual: { name: "foo" } }).toFind([{ name: "bar" }]))
+ expectQuery({ notEqual: { name: "foo" } }).toContainExactly([
+ { name: "bar" },
+ ]))
it("fails to find nonexistent row", () =>
- expectQuery({ notEqual: { name: "bar" } }).toFind([{ name: "foo" }]))
+ expectQuery({ notEqual: { name: "bar" } }).toContainExactly([
+ { name: "foo" },
+ ]))
})
describe("oneOf", () => {
it("successfully finds a row", () =>
- expectQuery({ oneOf: { name: ["foo"] } }).toFind([{ name: "foo" }]))
+ expectQuery({ oneOf: { name: ["foo"] } }).toContainExactly([
+ { name: "foo" },
+ ]))
it("fails to find nonexistent row", () =>
expectQuery({ oneOf: { name: ["none"] } }).toFindNothing())
@@ -150,11 +219,69 @@ describe.each([
describe("fuzzy", () => {
it("successfully finds a row", () =>
- expectQuery({ fuzzy: { name: "oo" } }).toFind([{ name: "foo" }]))
+ expectQuery({ fuzzy: { name: "oo" } }).toContainExactly([
+ { name: "foo" },
+ ]))
it("fails to find nonexistent row", () =>
expectQuery({ fuzzy: { name: "none" } }).toFindNothing())
})
+
+ describe("range", () => {
+ it("successfully finds multiple rows", () =>
+ expectQuery({
+ range: { name: { low: "a", high: "z" } },
+ }).toContainExactly([{ name: "bar" }, { name: "foo" }]))
+
+ it("successfully finds a row with a high bound", () =>
+ expectQuery({
+ range: { name: { low: "a", high: "c" } },
+ }).toContainExactly([{ name: "bar" }]))
+
+ it("successfully finds a row with a low bound", () =>
+ expectQuery({
+ range: { name: { low: "f", high: "z" } },
+ }).toContainExactly([{ name: "foo" }]))
+
+ it("successfully finds no rows", () =>
+ expectQuery({
+ range: { name: { low: "g", high: "h" } },
+ }).toFindNothing())
+ })
+
+ describe("sort", () => {
+ it("sorts ascending", () =>
+ expectSearch({
+ query: {},
+ sort: "name",
+ sortOrder: SortOrder.ASCENDING,
+ }).toMatchExactly([{ name: "bar" }, { name: "foo" }]))
+
+ it("sorts descending", () =>
+ expectSearch({
+ query: {},
+ sort: "name",
+ sortOrder: SortOrder.DESCENDING,
+ }).toMatchExactly([{ name: "foo" }, { name: "bar" }]))
+
+ describe("sortType STRING", () => {
+ it("sorts ascending", () =>
+ expectSearch({
+ query: {},
+ sort: "name",
+ sortType: SortType.STRING,
+ sortOrder: SortOrder.ASCENDING,
+ }).toMatchExactly([{ name: "bar" }, { name: "foo" }]))
+
+ it("sorts descending", () =>
+ expectSearch({
+ query: {},
+ sort: "name",
+ sortType: SortType.STRING,
+ sortOrder: SortOrder.DESCENDING,
+ }).toMatchExactly([{ name: "foo" }, { name: "bar" }]))
+ })
+ })
})
describe("numbers", () => {
@@ -167,7 +294,7 @@ describe.each([
describe("equal", () => {
it("successfully finds a row", () =>
- expectQuery({ equal: { age: 1 } }).toFind([{ age: 1 }]))
+ expectQuery({ equal: { age: 1 } }).toContainExactly([{ age: 1 }]))
it("fails to find nonexistent row", () =>
expectQuery({ equal: { age: 2 } }).toFindNothing())
@@ -175,15 +302,15 @@ describe.each([
describe("notEqual", () => {
it("successfully finds a row", () =>
- expectQuery({ notEqual: { age: 1 } }).toFind([{ age: 10 }]))
+ expectQuery({ notEqual: { age: 1 } }).toContainExactly([{ age: 10 }]))
it("fails to find nonexistent row", () =>
- expectQuery({ notEqual: { age: 10 } }).toFind([{ age: 1 }]))
+ expectQuery({ notEqual: { age: 10 } }).toContainExactly([{ age: 1 }]))
})
describe("oneOf", () => {
it("successfully finds a row", () =>
- expectQuery({ oneOf: { age: [1] } }).toFind([{ age: 1 }]))
+ expectQuery({ oneOf: { age: [1] } }).toContainExactly([{ age: 1 }]))
it("fails to find nonexistent row", () =>
expectQuery({ oneOf: { age: [2] } }).toFindNothing())
@@ -193,17 +320,56 @@ describe.each([
it("successfully finds a row", () =>
expectQuery({
range: { age: { low: 1, high: 5 } },
- }).toFind([{ age: 1 }]))
+ }).toContainExactly([{ age: 1 }]))
it("successfully finds multiple rows", () =>
expectQuery({
range: { age: { low: 1, high: 10 } },
- }).toFind([{ age: 1 }, { age: 10 }]))
+ }).toContainExactly([{ age: 1 }, { age: 10 }]))
it("successfully finds a row with a high bound", () =>
expectQuery({
range: { age: { low: 5, high: 10 } },
- }).toFind([{ age: 10 }]))
+ }).toContainExactly([{ age: 10 }]))
+
+ it("successfully finds no rows", () =>
+ expectQuery({
+ range: { age: { low: 5, high: 9 } },
+ }).toFindNothing())
+ })
+
+ describe("sort", () => {
+ it("sorts ascending", () =>
+ expectSearch({
+ query: {},
+ sort: "age",
+ sortOrder: SortOrder.ASCENDING,
+ }).toMatchExactly([{ age: 1 }, { age: 10 }]))
+
+ it("sorts descending", () =>
+ expectSearch({
+ query: {},
+ sort: "age",
+ sortOrder: SortOrder.DESCENDING,
+ }).toMatchExactly([{ age: 10 }, { age: 1 }]))
+ })
+
+ describe("sortType NUMBER", () => {
+ it("sorts ascending", () =>
+ expectSearch({
+ query: {},
+ sort: "age",
+ sortType: SortType.NUMBER,
+ sortOrder: SortOrder.ASCENDING,
+ }).toMatchExactly([{ age: 1 }, { age: 10 }]))
+
+ it("sorts descending", () =>
+ expectSearch({
+ query: {},
+ sort: "age",
+ sortType: SortType.NUMBER,
+ sortOrder: SortOrder.DESCENDING,
+ }).toMatchExactly([{ age: 10 }, { age: 1 }]))
})
})
@@ -211,6 +377,7 @@ describe.each([
const JAN_1ST = "2020-01-01T00:00:00.000Z"
const JAN_2ND = "2020-01-02T00:00:00.000Z"
const JAN_5TH = "2020-01-05T00:00:00.000Z"
+ const JAN_9TH = "2020-01-09T00:00:00.000Z"
const JAN_10TH = "2020-01-10T00:00:00.000Z"
beforeAll(async () => {
@@ -223,7 +390,9 @@ describe.each([
describe("equal", () => {
it("successfully finds a row", () =>
- expectQuery({ equal: { dob: JAN_1ST } }).toFind([{ dob: JAN_1ST }]))
+ expectQuery({ equal: { dob: JAN_1ST } }).toContainExactly([
+ { dob: JAN_1ST },
+ ]))
it("fails to find nonexistent row", () =>
expectQuery({ equal: { dob: JAN_2ND } }).toFindNothing())
@@ -231,15 +400,21 @@ describe.each([
describe("notEqual", () => {
it("successfully finds a row", () =>
- expectQuery({ notEqual: { dob: JAN_1ST } }).toFind([{ dob: JAN_10TH }]))
+ expectQuery({ notEqual: { dob: JAN_1ST } }).toContainExactly([
+ { dob: JAN_10TH },
+ ]))
it("fails to find nonexistent row", () =>
- expectQuery({ notEqual: { dob: JAN_10TH } }).toFind([{ dob: JAN_1ST }]))
+ expectQuery({ notEqual: { dob: JAN_10TH } }).toContainExactly([
+ { dob: JAN_1ST },
+ ]))
})
describe("oneOf", () => {
it("successfully finds a row", () =>
- expectQuery({ oneOf: { dob: [JAN_1ST] } }).toFind([{ dob: JAN_1ST }]))
+ expectQuery({ oneOf: { dob: [JAN_1ST] } }).toContainExactly([
+ { dob: JAN_1ST },
+ ]))
it("fails to find nonexistent row", () =>
expectQuery({ oneOf: { dob: [JAN_2ND] } }).toFindNothing())
@@ -249,17 +424,130 @@ describe.each([
it("successfully finds a row", () =>
expectQuery({
range: { dob: { low: JAN_1ST, high: JAN_5TH } },
- }).toFind([{ dob: JAN_1ST }]))
+ }).toContainExactly([{ dob: JAN_1ST }]))
it("successfully finds multiple rows", () =>
expectQuery({
range: { dob: { low: JAN_1ST, high: JAN_10TH } },
- }).toFind([{ dob: JAN_1ST }, { dob: JAN_10TH }]))
+ }).toContainExactly([{ dob: JAN_1ST }, { dob: JAN_10TH }]))
it("successfully finds a row with a high bound", () =>
expectQuery({
range: { dob: { low: JAN_5TH, high: JAN_10TH } },
- }).toFind([{ dob: JAN_10TH }]))
+ }).toContainExactly([{ dob: JAN_10TH }]))
+
+ it("successfully finds no rows", () =>
+ expectQuery({
+ range: { dob: { low: JAN_5TH, high: JAN_9TH } },
+ }).toFindNothing())
+ })
+
+ describe("sort", () => {
+ it("sorts ascending", () =>
+ expectSearch({
+ query: {},
+ sort: "dob",
+ sortOrder: SortOrder.ASCENDING,
+ }).toMatchExactly([{ dob: JAN_1ST }, { dob: JAN_10TH }]))
+
+ it("sorts descending", () =>
+ expectSearch({
+ query: {},
+ sort: "dob",
+ sortOrder: SortOrder.DESCENDING,
+ }).toMatchExactly([{ dob: JAN_10TH }, { dob: JAN_1ST }]))
+
+ describe("sortType STRING", () => {
+ it("sorts ascending", () =>
+ expectSearch({
+ query: {},
+ sort: "dob",
+ sortType: SortType.STRING,
+ sortOrder: SortOrder.ASCENDING,
+ }).toMatchExactly([{ dob: JAN_1ST }, { dob: JAN_10TH }]))
+
+ it("sorts descending", () =>
+ expectSearch({
+ query: {},
+ sort: "dob",
+ sortType: SortType.STRING,
+ sortOrder: SortOrder.DESCENDING,
+ }).toMatchExactly([{ dob: JAN_10TH }, { dob: JAN_1ST }]))
+ })
+ })
+ })
+
+ describe("array of strings", () => {
+ beforeAll(async () => {
+ await createTable({
+ numbers: {
+ name: "numbers",
+ type: FieldType.ARRAY,
+ constraints: { inclusion: ["one", "two", "three"] },
+ },
+ })
+ await createRows([{ numbers: ["one", "two"] }, { numbers: ["three"] }])
+ })
+
+ describe("contains", () => {
+ it("successfully finds a row", () =>
+ expectQuery({ contains: { numbers: ["one"] } }).toContainExactly([
+ { numbers: ["one", "two"] },
+ ]))
+
+ it("fails to find nonexistent row", () =>
+ expectQuery({ contains: { numbers: ["none"] } }).toFindNothing())
+
+ it("fails to find row containing all", () =>
+ expectQuery({
+ contains: { numbers: ["one", "two", "three"] },
+ }).toFindNothing())
+
+ it("finds all with empty list", () =>
+ expectQuery({ contains: { numbers: [] } }).toContainExactly([
+ { numbers: ["one", "two"] },
+ { numbers: ["three"] },
+ ]))
+ })
+
+ describe("notContains", () => {
+ it("successfully finds a row", () =>
+ expectQuery({ notContains: { numbers: ["one"] } }).toContainExactly([
+ { numbers: ["three"] },
+ ]))
+
+ it("fails to find nonexistent row", () =>
+ expectQuery({
+ notContains: { numbers: ["one", "two", "three"] },
+ }).toContainExactly([
+ { numbers: ["one", "two"] },
+ { numbers: ["three"] },
+ ]))
+
+ it("finds all with empty list", () =>
+ expectQuery({ notContains: { numbers: [] } }).toContainExactly([
+ { numbers: ["one", "two"] },
+ { numbers: ["three"] },
+ ]))
+ })
+
+ describe("containsAny", () => {
+ it("successfully finds rows", () =>
+ expectQuery({
+ containsAny: { numbers: ["one", "two", "three"] },
+ }).toContainExactly([
+ { numbers: ["one", "two"] },
+ { numbers: ["three"] },
+ ]))
+
+ it("fails to find nonexistent row", () =>
+ expectQuery({ containsAny: { numbers: ["none"] } }).toFindNothing())
+
+ it("finds all with empty list", () =>
+ expectQuery({ containsAny: { numbers: [] } }).toContainExactly([
+ { numbers: ["one", "two"] },
+ { numbers: ["three"] },
+ ]))
})
})
})
diff --git a/packages/server/src/constants/index.ts b/packages/server/src/constants/index.ts
index 42a1b53224..37c275c8a3 100644
--- a/packages/server/src/constants/index.ts
+++ b/packages/server/src/constants/index.ts
@@ -20,6 +20,7 @@ export enum FilterTypes {
NOT_EMPTY = "notEmpty",
CONTAINS = "contains",
NOT_CONTAINS = "notContains",
+ CONTAINS_ANY = "containsAny",
ONE_OF = "oneOf",
}
@@ -30,6 +31,7 @@ export const NoEmptyFilterStrings = [
FilterTypes.NOT_EQUAL,
FilterTypes.CONTAINS,
FilterTypes.NOT_CONTAINS,
+ FilterTypes.CONTAINS_ANY,
]
export const CanSwitchTypes = [
diff --git a/packages/server/src/integrations/base/sql.ts b/packages/server/src/integrations/base/sql.ts
index f5828f9419..259abec106 100644
--- a/packages/server/src/integrations/base/sql.ts
+++ b/packages/server/src/integrations/base/sql.ts
@@ -233,6 +233,11 @@ class InternalBuilder {
(statement ? andOr : "") +
`LOWER(${likeKey(this.client, key)}) LIKE ?`
}
+
+ if (statement === "") {
+ return
+ }
+
// @ts-ignore
query = query[rawFnc](`${not}(${statement})`, value)
})
diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts
index f681bfeb90..5a016c821f 100644
--- a/packages/server/src/sdk/app/rows/search.ts
+++ b/packages/server/src/sdk/app/rows/search.ts
@@ -29,6 +29,10 @@ function pickApi(tableId: any) {
return internal
}
+function isEmptyArray(value: any) {
+ return Array.isArray(value) && value.length === 0
+}
+
// don't do a pure falsy check, as 0 is included
// https://github.com/Budibase/budibase/issues/10118
export function removeEmptyFilters(filters: SearchFilters) {
@@ -47,7 +51,7 @@ export function removeEmptyFilters(filters: SearchFilters) {
for (let [key, value] of Object.entries(
filters[filterType] as object
)) {
- if (value == null || value === "") {
+ if (value == null || value === "" || isEmptyArray(value)) {
// @ts-ignore
delete filters[filterField][key]
}
diff --git a/packages/server/src/sdk/app/rows/search/sqs.ts b/packages/server/src/sdk/app/rows/search/sqs.ts
index 5b0b6e3bc7..7abd7d9e72 100644
--- a/packages/server/src/sdk/app/rows/search/sqs.ts
+++ b/packages/server/src/sdk/app/rows/search/sqs.ts
@@ -132,7 +132,7 @@ export async function search(
type: "row",
}
- if (params.sort && !params.sortType) {
+ if (params.sort) {
const sortField = table.schema[params.sort]
const sortType =
sortField.type === FieldType.NUMBER ? SortType.NUMBER : SortType.STRING
diff --git a/packages/types/src/documents/account/account.ts b/packages/types/src/documents/account/account.ts
index 2f74b9e7b3..239d845722 100644
--- a/packages/types/src/documents/account/account.ts
+++ b/packages/types/src/documents/account/account.ts
@@ -102,6 +102,7 @@ export function isVerifiableSSOProvider(provider: AccountSSOProvider): boolean {
}
export interface AccountSSO {
+ ssoId?: string
provider: AccountSSOProvider
providerType: AccountSSOProviderType
oauth2?: OAuthTokens
diff --git a/packages/types/src/documents/app/row.ts b/packages/types/src/documents/app/row.ts
index 222c346591..865ab4ba64 100644
--- a/packages/types/src/documents/app/row.ts
+++ b/packages/types/src/documents/app/row.ts
@@ -1,22 +1,111 @@
import { Document } from "../document"
export enum FieldType {
+ /**
+ * a primitive type, stores a string, called Text within Budibase. This is one of the default
+ * types of Budibase, if an external type is not fully understood, we will treat it as text.
+ */
STRING = "string",
+ /**
+ * similar to string type, called Long Form Text within Budibase. This is mainly a frontend
+ * orientated type which enables a larger text input area. This can also be used
+ * in conjunction with the 'useRichText' option to support a markdown editor/viewer.
+ */
LONGFORM = "longform",
+ /**
+ * similar to string type, called Options within Budibase. This works very similarly to
+ * the string type within the backend, but is validated to a list of options. This will
+ * display a