diff --git a/charts/budibase/templates/secrets.yaml b/charts/budibase/templates/secrets.yaml index 861442acac..a44d2c0d9b 100644 --- a/charts/budibase/templates/secrets.yaml +++ b/charts/budibase/templates/secrets.yaml @@ -23,7 +23,7 @@ data: jwtSecret: {{ template "budibase.defaultsecret" .Values.globals.jwtSecret }} objectStoreAccess: {{ template "budibase.defaultsecret" .Values.services.objectStore.accessKey }} objectStoreSecret: {{ template "budibase.defaultsecret" .Values.services.objectStore.secretKey }} - bbEncryptionKey: {{ template "budibase.defaultsecret" "" }} - apiEncryptionKey: {{ template "budibase.defaultsecret" "" }} + bbEncryptionKey: {{ template "budibase.defaultsecret" .Values.globals.bbEncryptionKey }} + apiEncryptionKey: {{ template "budibase.defaultsecret" .Values.globals.apiEncryptionKey }} {{- end }} {{- end }} diff --git a/lerna.json b/lerna.json index 9720b01fb9..5d04ae9f3c 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "version": "2.30.4", + "version": "2.31.1", "npmClient": "yarn", "packages": [ "packages/*", diff --git a/packages/backend-core/src/sql/sql.ts b/packages/backend-core/src/sql/sql.ts index ebae09e156..6a8518ea7b 100644 --- a/packages/backend-core/src/sql/sql.ts +++ b/packages/backend-core/src/sql/sql.ts @@ -36,6 +36,7 @@ import { } from "@budibase/types" import environment from "../environment" import { dataFilters, helpers } from "@budibase/shared-core" +import { cloneDeep } from "lodash" type QueryFunction = (query: SqlQuery | SqlQuery[], operation: Operation) => any @@ -268,6 +269,7 @@ class InternalBuilder { } private parseFilters(filters: SearchFilters): SearchFilters { + filters = cloneDeep(filters) for (const op of Object.values(BasicOperator)) { const filter = filters[op] if (!filter) { @@ -337,7 +339,7 @@ class InternalBuilder { if (!filters) { return query } - filters = this.parseFilters(filters) + filters = this.parseFilters({ ...filters }) const aliases = this.query.tableAliases // if all or specified in filters, then everything is an or const allOr = filters.allOr @@ -371,10 +373,11 @@ class InternalBuilder { ), castedTypeValue.values ) - } else if (!opts?.relationship && !isRelationshipField) { + } else if (!isRelationshipField) { const alias = getTableAlias(tableName) fn(alias ? `${alias}.${updatedKey}` : updatedKey, value) - } else if (opts?.relationship && isRelationshipField) { + } + if (opts?.relationship && isRelationshipField) { const [filterTableName, property] = updatedKey.split(".") const alias = getTableAlias(filterTableName) fn(alias ? `${alias}.${property}` : property, value) @@ -465,18 +468,20 @@ class InternalBuilder { if (filters.$and) { const { $and } = filters - query = query.where(x => { - for (const condition of $and.conditions) { - x = this.addFilters(x, condition, opts) - } - }) + for (const condition of $and.conditions) { + query = query.where(b => { + this.addFilters(b, condition, opts) + }) + } } if (filters.$or) { const { $or } = filters - query = query.where(x => { + query = query.where(b => { for (const condition of $or.conditions) { - x = this.addFilters(x, { ...condition, allOr: true }, opts) + b.orWhere(c => + this.addFilters(c, { ...condition, allOr: true }, opts) + ) } }) } diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 1b3510cf3b..0830f8ab6f 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -72,6 +72,7 @@ "@spectrum-css/switch": "1.0.2", "@spectrum-css/table": "3.0.1", "@spectrum-css/tabs": "3.2.12", + "@spectrum-css/tag": "3.0.0", "@spectrum-css/tags": "3.0.2", "@spectrum-css/textfield": "3.0.1", "@spectrum-css/toast": "3.0.1", diff --git a/packages/builder/src/components/design/ScreenDetailsModal.svelte b/packages/builder/src/components/design/ScreenDetailsModal.svelte index 295f03c518..34872daf8c 100644 --- a/packages/builder/src/components/design/ScreenDetailsModal.svelte +++ b/packages/builder/src/components/design/ScreenDetailsModal.svelte @@ -6,8 +6,8 @@ export let onConfirm export let onCancel - export let screenUrl - export let screenRole + export let route + export let role export let confirmText = "Continue" const appPrefix = "/app" @@ -15,17 +15,17 @@ let error let modal - $: appUrl = screenUrl - ? `${window.location.origin}${appPrefix}${screenUrl}` + $: appUrl = route + ? `${window.location.origin}${appPrefix}${route}` : `${window.location.origin}${appPrefix}` const routeChanged = event => { if (!event.detail.startsWith("/")) { - screenUrl = "/" + event.detail + route = "/" + event.detail } touched = true - screenUrl = sanitizeUrl(screenUrl) - if (routeExists(screenUrl)) { + route = sanitizeUrl(route) + if (routeExists(route)) { error = "This URL is already taken for this access role" } else { error = null @@ -33,19 +33,19 @@ } const routeExists = url => { - if (!screenRole) { + if (!role) { return false } return get(screenStore).screens.some( screen => screen.routing.route.toLowerCase() === url.toLowerCase() && - screen.routing.roleId === screenRole + screen.routing.roleId === role ) } const confirmScreenDetails = async () => { await onConfirm({ - screenUrl, + route, }) } @@ -58,13 +58,13 @@ onConfirm={confirmScreenDetails} {onCancel} cancelText={"Back"} - disabled={!screenUrl || error || !touched} + disabled={!route || error || !touched} >
modal.confirm()}>
diff --git a/packages/builder/src/components/design/settings/controls/DataSourceSelect/DataSourceCategory.svelte b/packages/builder/src/components/design/settings/controls/DataSourceSelect/DataSourceCategory.svelte index fa6f477ed9..f6d7cfc2c3 100644 --- a/packages/builder/src/components/design/settings/controls/DataSourceSelect/DataSourceCategory.svelte +++ b/packages/builder/src/components/design/settings/controls/DataSourceSelect/DataSourceCategory.svelte @@ -29,7 +29,7 @@ on:click={() => onSelect(data)} > - {data.datasource?.name ? `${data.datasource.name} - ` : ""}{data.label} + {data.datasourceName ? `${data.datasourceName} - ` : ""}{data.label} ({ - label: m.name, - tableId: m._id, - type: "table", - datasource: $datasources.list.find( - ds => ds._id === m.sourceId || m.datasourceId - ), - })) + $: tables = $tablesStore.list.map(table => + format.table(table, $datasources.list) + ) $: viewsV1 = $viewsStore.list.map(view => ({ ...view, label: view.name, type: "view", })) - $: viewsV2 = $viewsV2Store.list.map(view => ({ - ...view, - label: view.name, - type: "viewV2", - })) + $: viewsV2 = $viewsV2Store.list.map(format.viewV2) $: views = [...(viewsV1 || []), ...(viewsV2 || [])] $: queries = $queriesStore.list .filter(q => showAllQueries || q.queryVerb === "read" || q.readable) diff --git a/packages/builder/src/components/design/settings/controls/TableSelect.svelte b/packages/builder/src/components/design/settings/controls/TableSelect.svelte index 7b73d0a7cf..545110a8cc 100644 --- a/packages/builder/src/components/design/settings/controls/TableSelect.svelte +++ b/packages/builder/src/components/design/settings/controls/TableSelect.svelte @@ -2,24 +2,14 @@ import { Select } from "@budibase/bbui" import { createEventDispatcher, onMount } from "svelte" import { tables as tablesStore, viewsV2 } from "stores/builder" + import { tableSelect as format } from "helpers/data/format" export let value const dispatch = createEventDispatcher() - $: tables = $tablesStore.list.map(table => ({ - type: "table", - label: table.name, - tableId: table._id, - resourceId: table._id, - })) - $: views = $viewsV2.list.map(view => ({ - type: "viewV2", - id: view.id, - label: view.name, - tableId: view.tableId, - resourceId: view.id, - })) + $: tables = $tablesStore.list.map(format.table) + $: views = $viewsV2.list.map(format.viewV2) $: options = [...(tables || []), ...(views || [])] const onChange = e => { diff --git a/packages/builder/src/helpers/data/format.js b/packages/builder/src/helpers/data/format.js new file mode 100644 index 0000000000..5b9714008b --- /dev/null +++ b/packages/builder/src/helpers/data/format.js @@ -0,0 +1,33 @@ +export const datasourceSelect = { + table: (table, datasources) => { + const sourceId = table.sourceId || table.datasourceId + const datasource = datasources.find(ds => ds._id === sourceId) + return { + label: table.name, + tableId: table._id, + type: "table", + datasourceName: datasource?.name, + } + }, + viewV2: view => ({ + ...view, + label: view.name, + type: "viewV2", + }), +} + +export const tableSelect = { + table: table => ({ + type: "table", + label: table.name, + tableId: table._id, + resourceId: table._id, + }), + viewV2: view => ({ + type: "viewV2", + id: view.id, + label: view.name, + tableId: view.tableId, + resourceId: view.id, + }), +} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte index 54241ea1cc..d5a696c6bf 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte @@ -1,5 +1,4 @@ diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte index cecfdba858..62fe593602 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte @@ -14,11 +14,90 @@ import sanitizeUrl from "helpers/sanitizeUrl" import ButtonActionEditor from "components/design/settings/controls/ButtonActionEditor/ButtonActionEditor.svelte" import { getBindableProperties } from "dataBinding" + import BarButtonList from "components/design/settings/controls/BarButtonList.svelte" $: bindings = getBindableProperties($selectedScreen, null) + $: screenSettings = getScreenSettings($selectedScreen) let errors = {} + const getScreenSettings = screen => { + let settings = [ + { + key: "routing.homeScreen", + control: Checkbox, + props: { + text: "Set as home screen", + }, + }, + { + key: "routing.route", + label: "Route", + control: Input, + parser: val => { + if (!val.startsWith("/")) { + val = "/" + val + } + return sanitizeUrl(val) + }, + validate: route => { + const existingRoute = screen.routing.route + if (route !== existingRoute && routeTaken(route)) { + return "That URL is already in use for this role" + } + return null + }, + }, + { + key: "routing.roleId", + label: "Access", + control: RoleSelect, + validate: role => { + const existingRole = screen.routing.roleId + if (role !== existingRole && roleTaken(role)) { + return "That role is already in use for this URL" + } + return null + }, + }, + { + key: "onLoad", + label: "On screen load", + control: ButtonActionEditor, + }, + { + key: "width", + label: "Width", + control: Select, + props: { + options: ["Extra small", "Small", "Medium", "Large", "Max"], + placeholder: "Default", + disabled: !!screen.layoutId, + }, + }, + { + key: "props.layout", + label: "Layout", + defaultValue: "flex", + control: BarButtonList, + props: { + options: [ + { + barIcon: "ModernGridView", + value: "flex", + }, + { + barIcon: "ViewGrid", + value: "grid", + }, + ], + }, + }, + ] + + return settings + } + const routeTaken = url => { const roleId = get(selectedScreen).routing.roleId || "BASIC" return get(screenStore).screens.some( @@ -71,61 +150,6 @@ } } - $: screenSettings = [ - { - key: "routing.homeScreen", - control: Checkbox, - props: { - text: "Set as home screen", - }, - }, - { - key: "routing.route", - label: "Route", - control: Input, - parser: val => { - if (!val.startsWith("/")) { - val = "/" + val - } - return sanitizeUrl(val) - }, - validate: route => { - const existingRoute = get(selectedScreen).routing.route - if (route !== existingRoute && routeTaken(route)) { - return "That URL is already in use for this role" - } - return null - }, - }, - { - key: "routing.roleId", - label: "Access", - control: RoleSelect, - validate: role => { - const existingRole = get(selectedScreen).routing.roleId - if (role !== existingRole && roleTaken(role)) { - return "That role is already in use for this URL" - } - return null - }, - }, - { - key: "onLoad", - label: "On screen load", - control: ButtonActionEditor, - }, - { - key: "width", - label: "Width", - control: Select, - props: { - options: ["Extra small", "Small", "Medium", "Large", "Max"], - placeholder: "Default", - disabled: !!$selectedScreen.layoutId, - }, - }, - ] - const removeCustomLayout = async () => { return screenStore.removeCustomLayout(get(selectedScreen)) } @@ -149,6 +173,7 @@ value={Helpers.deepGet($selectedScreen, setting.key)} onChange={val => setScreenSetting(setting, val)} props={{ ...setting.props, error: errors[setting.key] }} + defaultValue={setting.defaultValue} {bindings} /> {/each} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/index.svelte index 8a544eac86..405b8b0c21 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/index.svelte @@ -33,7 +33,7 @@ {/each}
- + {#if activeTab === "theme"} {:else} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte index 0ce9e096f2..055ecd88e0 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte @@ -144,7 +144,12 @@ const rootComponent = get(selectedScreen).props const component = findComponent(rootComponent, data.id) componentStore.copy(component) - await componentStore.paste(component) + await componentStore.paste( + component, + data.mode, + null, + data.selectComponent + ) } else if (type === "preview-loaded") { // Wait for this event to show the client library if intelligent // loading is supported @@ -246,13 +251,13 @@ -
+
{#if loading} -
+
+
diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/FormTypeModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/FormTypeModal.svelte deleted file mode 100644 index b7108fd9aa..0000000000 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/FormTypeModal.svelte +++ /dev/null @@ -1,115 +0,0 @@ - - - - onConfirm(type)} - {onCancel} - disabled={!type} - size="L" - > - - - -
(type = "Create")} - > -
-
- Create a new row - - For capturing and storing new data from your users - -
- {#if type === "Create"} - - - - {/if} -
-
-
(type = "Update")} - > -
-
- Update an existing row - For viewing and updating existing data -
- {#if type === "Update"} - - - - {/if} -
-
-
(type = "View")} - > -
-
- View an existing row - For a read only view of your data -
- {#if type === "View"} - - - - {/if} -
-
-
-
-
- - diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/TableOrViewOption.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/TableOrViewOption.svelte index b0b9f3dfc1..47a4a834ff 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/TableOrViewOption.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/TableOrViewOption.svelte @@ -1,49 +1,14 @@
-
- - {tableOrView.name} -
- -
- -
- read - -
-
- -
- write - -
-
-
+ + {tableOrView.name}
diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/TypeModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/TypeModal.svelte new file mode 100644 index 0000000000..045e3000e7 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/TypeModal.svelte @@ -0,0 +1,82 @@ + + + onConfirm(selectedType)} + {onCancel} + disabled={!selectedType} + size="L" +> + + + + {#each types as type} +
(selectedType = type.id)} + > +
+ {type.img.alt} +
+
+ {type.title} + {type.description} +
+
+ {/each} +
+
+ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/formTypes.js b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/formTypes.js new file mode 100644 index 0000000000..c1e0d93f5a --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/formTypes.js @@ -0,0 +1,35 @@ +import formView from "./images/formView.svg" +import formUpdate from "./images/formUpdate.svg" +import formCreate from "./images/formCreate.svg" + +const tableTypes = [ + { + id: "create", + img: { + alt: "A form containing new data", + src: formCreate, + }, + title: "Create a new row", + description: "For capturing and storing new data from your users", + }, + { + id: "update", + img: { + alt: "A form containing edited data", + src: formUpdate, + }, + title: "Update an existing row", + description: "For viewing and updating existing data", + }, + { + id: "view", + img: { + alt: "A form containing read-only data", + src: formView, + }, + title: "View an existing row", + description: "For a read only view of your data", + }, +] + +export default tableTypes diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/blank.png b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/blank.png deleted file mode 100644 index 4867cc8dc3..0000000000 Binary files a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/blank.png and /dev/null differ diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/blank.svg b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/blank.svg new file mode 100644 index 0000000000..6a225a3271 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/blank.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/form.png b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/form.png deleted file mode 100644 index cb9b57d23b..0000000000 Binary files a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/form.png and /dev/null differ diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/formCreate.svg b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/formCreate.svg new file mode 100644 index 0000000000..d8d0220142 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/formCreate.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/formUpdate.svg b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/formUpdate.svg new file mode 100644 index 0000000000..674dbb12cc --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/formUpdate.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/formView.svg b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/formView.svg new file mode 100644 index 0000000000..0b65846b16 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/formView.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/grid.png b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/grid.png deleted file mode 100644 index c3efa30a67..0000000000 Binary files a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/grid.png and /dev/null differ diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/table.png b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/table.png deleted file mode 100644 index 0bfda10bbe..0000000000 Binary files a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/table.png and /dev/null differ diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableDetails.png b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableDetails.png deleted file mode 100644 index f67495f3aa..0000000000 Binary files a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableDetails.png and /dev/null differ diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableInline.png b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableInline.png deleted file mode 100644 index 905294a9ae..0000000000 Binary files a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableInline.png and /dev/null differ diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableInline.svg b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableInline.svg new file mode 100644 index 0000000000..d2d2fb8134 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableInline.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableModal.svg b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableModal.svg new file mode 100644 index 0000000000..a638c0eec8 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableModal.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableNewScreen.svg b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableNewScreen.svg new file mode 100644 index 0000000000..462ddfc4a2 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableNewScreen.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableSidePanel.svg b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableSidePanel.svg new file mode 100644 index 0000000000..c590f1ab70 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/tableSidePanel.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte index ef07b277ef..3101ce41ea 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte @@ -1,10 +1,9 @@ -{#if url} -
-
- -
+
+
+
-{:else if $builderStore.inBuilder} -
- -
-{/if} +
diff --git a/packages/client/src/components/app/ButtonGroup.svelte b/packages/client/src/components/app/ButtonGroup.svelte index 2cf6b3db7d..b3523cdd21 100644 --- a/packages/client/src/components/app/ButtonGroup.svelte +++ b/packages/client/src/components/app/ButtonGroup.svelte @@ -19,6 +19,11 @@ gap, wrap: true, }} + styles={{ + normal: { + height: "100%", + }, + }} > {#each buttons as { text, type, quiet, disabled, onClick, size, icon, gap }} - import { getContext } from "svelte" - - const component = getContext("component") - const { styleable, builderStore } = getContext("sdk") - - export let cols = 12 - export let rows = 12 - - // Deliberately non-reactive as we want this fixed whenever the grid renders - const defaultColSpan = Math.ceil((cols + 1) / 2) - const defaultRowSpan = Math.ceil((rows + 1) / 2) - - $: coords = generateCoords(rows, cols) - - const generateCoords = (rows, cols) => { - let grid = [] - for (let row = 0; row < rows; row++) { - for (let col = 0; col < cols; col++) { - grid.push({ row, col }) - } - } - return grid - } - - -
- {#if $builderStore.inBuilder} -
- {#each coords as _} -
- {/each} -
- {/if} - -
- - diff --git a/packages/client/src/components/app/GridBlock.svelte b/packages/client/src/components/app/GridBlock.svelte index e86f163dba..30a35b0713 100644 --- a/packages/client/src/components/app/GridBlock.svelte +++ b/packages/client/src/components/app/GridBlock.svelte @@ -198,7 +198,7 @@ overflow: hidden; height: 410px; } - div.in-builder :global(*) { - pointer-events: none; + div.in-builder :global(> *) { + pointer-events: none !important; } diff --git a/packages/client/src/components/app/Layout.svelte b/packages/client/src/components/app/Layout.svelte index af74e14aa0..04dbb9efda 100644 --- a/packages/client/src/components/app/Layout.svelte +++ b/packages/client/src/components/app/Layout.svelte @@ -36,7 +36,6 @@ export let logoLinkUrl export let openLogoLinkInNewTab export let textAlign - export let embedded = false const NavigationClasses = { @@ -339,6 +338,7 @@ />
+ diff --git a/packages/client/src/components/app/container/Container.svelte b/packages/client/src/components/app/container/Container.svelte new file mode 100644 index 0000000000..9cfe74cf35 --- /dev/null +++ b/packages/client/src/components/app/container/Container.svelte @@ -0,0 +1,12 @@ + + + + + diff --git a/packages/client/src/components/app/Container.svelte b/packages/client/src/components/app/container/FlexContainer.svelte similarity index 94% rename from packages/client/src/components/app/Container.svelte rename to packages/client/src/components/app/container/FlexContainer.svelte index f11636b339..34368dcc8c 100644 --- a/packages/client/src/components/app/Container.svelte +++ b/packages/client/src/components/app/container/FlexContainer.svelte @@ -12,7 +12,7 @@ export let wrap export let onClick - $: directionClass = direction ? `valid-container direction-${direction}` : "" + $: directionClass = direction ? `flex-container direction-${direction}` : "" $: hAlignClass = hAlign ? `hAlign-${hAlign}` : "" $: vAlignClass = vAlign ? `vAlign-${vAlign}` : "" $: sizeClass = size ? `size-${size}` : "" @@ -39,11 +39,11 @@
diff --git a/packages/client/src/components/app/index.js b/packages/client/src/components/app/index.js index 6d9df6e588..63cdb95ac1 100644 --- a/packages/client/src/components/app/index.js +++ b/packages/client/src/components/app/index.js @@ -13,7 +13,7 @@ import "@spectrum-css/page/dist/index-vars.css" export { default as Placeholder } from "./Placeholder.svelte" // User facing components -export { default as container } from "./Container.svelte" +export { default as container } from "./container/Container.svelte" export { default as section } from "./Section.svelte" export { default as dataprovider } from "./DataProvider.svelte" export { default as divider } from "./Divider.svelte" @@ -35,7 +35,6 @@ export { default as spectrumcard } from "./SpectrumCard.svelte" export { default as tag } from "./Tag.svelte" export { default as markdownviewer } from "./MarkdownViewer.svelte" export { default as embeddedmap } from "./embedded-map/EmbeddedMap.svelte" -export { default as grid } from "./Grid.svelte" export { default as sidepanel } from "./SidePanel.svelte" export { default as modal } from "./Modal.svelte" export { default as gridblock } from "./GridBlock.svelte" diff --git a/packages/client/src/components/preview/DNDHandler.svelte b/packages/client/src/components/preview/DNDHandler.svelte index 767efc9e3a..bdd538748b 100644 --- a/packages/client/src/components/preview/DNDHandler.svelte +++ b/packages/client/src/components/preview/DNDHandler.svelte @@ -13,6 +13,7 @@ import { Utils } from "@budibase/frontend-core" import { findComponentById } from "utils/components.js" import { DNDPlaceholderID } from "constants" + import { isGridEvent } from "utils/grid" const ThrottleRate = 130 @@ -25,15 +26,6 @@ // Local flag for whether we are awaiting an async drop event let dropping = false - // Util to check if a DND event originates from a grid (or inside a grid). - // This is important as we do not handle grid DND in this handler. - const isGridEvent = e => { - return e.target - ?.closest?.(".component") - ?.parentNode?.closest?.(".component") - ?.childNodes[0]?.classList.contains("grid") - } - // Util to get the inner DOM node by a component ID const getDOMNode = id => { return document.getElementsByClassName(`${id}-dom`)[0] @@ -267,7 +259,7 @@ // Check if we're adding a new component rather than moving one if (source.newComponentType) { dropping = true - await builderStore.actions.dropNewComponent( + builderStore.actions.dropNewComponent( source.newComponentType, drop.parent, drop.index diff --git a/packages/client/src/components/preview/DNDPlaceholderOverlay.svelte b/packages/client/src/components/preview/DNDPlaceholderOverlay.svelte index 0be7faff1b..0ad4280e07 100644 --- a/packages/client/src/components/preview/DNDPlaceholderOverlay.svelte +++ b/packages/client/src/components/preview/DNDPlaceholderOverlay.svelte @@ -1,7 +1,7 @@ diff --git a/packages/client/src/components/preview/GridStylesButton.svelte b/packages/client/src/components/preview/GridStylesButton.svelte new file mode 100644 index 0000000000..430a0659ec --- /dev/null +++ b/packages/client/src/components/preview/GridStylesButton.svelte @@ -0,0 +1,42 @@ + + + + +
{ + builderStore.actions.updateStyles({ [style]: value }, componentId) + }} +> + +
+ + diff --git a/packages/client/src/components/preview/Indicator.svelte b/packages/client/src/components/preview/Indicator.svelte index 0480ea91ce..dce7945b29 100644 --- a/packages/client/src/components/preview/Indicator.svelte +++ b/packages/client/src/components/preview/Indicator.svelte @@ -1,5 +1,6 @@ diff --git a/packages/client/src/components/preview/SettingsBar.svelte b/packages/client/src/components/preview/SettingsBar.svelte index c5109c6bca..ec861ab5b4 100644 --- a/packages/client/src/components/preview/SettingsBar.svelte +++ b/packages/client/src/components/preview/SettingsBar.svelte @@ -1,117 +1,180 @@ {#if showBar}
+ {#if showGridStyles} + + + + +
+ + + + +
+ {/if} {#each settings as setting, idx} {#if setting.type === "select"} {#if setting.barStyle === "buttons"} @@ -141,6 +273,7 @@ value={option.value} icon={option.barIcon} title={option.barTitle || option.label} + {component} /> {/each} {:else} @@ -148,6 +281,7 @@ prop={setting.key} options={setting.options} label={setting.label} + {component} /> {/if} {:else if setting.type === "boolean"} @@ -156,9 +290,10 @@ icon={setting.barIcon} title={setting.barTitle || setting.label} bool + {component} /> {:else if setting.type === "color"} - + {/if} {#if setting.barSeparator !== false && (settings.length != idx + 1 || !isRoot)}
diff --git a/packages/client/src/components/preview/SettingsButton.svelte b/packages/client/src/components/preview/SettingsButton.svelte index 36009da821..a93ffb77af 100644 --- a/packages/client/src/components/preview/SettingsButton.svelte +++ b/packages/client/src/components/preview/SettingsButton.svelte @@ -1,17 +1,19 @@ @@ -19,7 +21,6 @@
{ if (prop) { @@ -49,7 +50,4 @@ background-color: rgba(13, 102, 208, 0.1); color: var(--spectrum-global-color-blue-600); } - .rotate { - transform: rotate(90deg); - } diff --git a/packages/client/src/components/preview/SettingsColorPicker.svelte b/packages/client/src/components/preview/SettingsColorPicker.svelte index b078d048d2..a292d7d838 100644 --- a/packages/client/src/components/preview/SettingsColorPicker.svelte +++ b/packages/client/src/components/preview/SettingsColorPicker.svelte @@ -1,10 +1,11 @@
diff --git a/packages/client/src/components/preview/SettingsPicker.svelte b/packages/client/src/components/preview/SettingsPicker.svelte index 8b83729fde..3900d065e8 100644 --- a/packages/client/src/components/preview/SettingsPicker.svelte +++ b/packages/client/src/components/preview/SettingsPicker.svelte @@ -1,12 +1,13 @@
diff --git a/packages/client/src/constants.js b/packages/client/src/constants.js index d3f1ab8be9..f7e3e86d40 100644 --- a/packages/client/src/constants.js +++ b/packages/client/src/constants.js @@ -15,3 +15,6 @@ export const ActionTypes = { export const DNDPlaceholderID = "dnd-placeholder" export const ScreenslotType = "screenslot" +export const GridRowHeight = 24 +export const GridColumns = 12 +export const GridSpacing = 4 diff --git a/packages/client/src/stores/builder.js b/packages/client/src/stores/builder.js index 5440fc3a79..faa37eddca 100644 --- a/packages/client/src/stores/builder.js +++ b/packages/client/src/stores/builder.js @@ -41,13 +41,20 @@ const createBuilderStore = () => { eventStore.actions.dispatchEvent("update-prop", { prop, value }) }, updateStyles: async (styles, id) => { - await eventStore.actions.dispatchEvent("update-styles", { styles, id }) + await eventStore.actions.dispatchEvent("update-styles", { + styles, + id, + }) }, keyDown: (key, ctrlKey) => { eventStore.actions.dispatchEvent("key-down", { key, ctrlKey }) }, - duplicateComponent: id => { - eventStore.actions.dispatchEvent("duplicate-component", { id }) + duplicateComponent: (id, mode = "below", selectComponent = true) => { + eventStore.actions.dispatchEvent("duplicate-component", { + id, + mode, + selectComponent, + }) }, deleteComponent: id => { eventStore.actions.dispatchEvent("delete-component", { id }) diff --git a/packages/client/src/stores/components.js b/packages/client/src/stores/components.js index b7f7d98197..d4afa6c7f1 100644 --- a/packages/client/src/stores/components.js +++ b/packages/client/src/stores/components.js @@ -142,9 +142,6 @@ const createComponentStore = () => { } const getComponentInstance = id => { - if (!id) { - return null - } return derived(store, $store => $store.mountedComponents[id]) } diff --git a/packages/client/src/stores/screens.js b/packages/client/src/stores/screens.js index 9cb0d536de..3c5ece0a6c 100644 --- a/packages/client/src/stores/screens.js +++ b/packages/client/src/stores/screens.js @@ -129,29 +129,30 @@ const createScreenStore = () => { // If we don't have a legacy custom layout, build a layout structure // from the screen navigation settings if (!activeLayout) { - let navigationSettings = { + let layoutSettings = { navigation: "None", pageWidth: activeScreen?.width || "Large", + embedded: $appStore.embedded, } if (activeScreen?.showNavigation) { - navigationSettings = { - ...navigationSettings, + layoutSettings = { + ...layoutSettings, ...($builderStore.navigation || $appStore.application?.navigation), } // Default navigation to top - if (!navigationSettings.navigation) { - navigationSettings.navigation = "Top" + if (!layoutSettings.navigation) { + layoutSettings.navigation = "Top" } // Default title to app name - if (!navigationSettings.title && !navigationSettings.hideTitle) { - navigationSettings.title = $appStore.application?.name + if (!layoutSettings.title && !layoutSettings.hideTitle) { + layoutSettings.title = $appStore.application?.name } // Default to the org logo - if (!navigationSettings.logoUrl) { - navigationSettings.logoUrl = $orgStore?.logoUrl + if (!layoutSettings.logoUrl) { + layoutSettings.logoUrl = $orgStore?.logoUrl } } activeLayout = { @@ -173,8 +174,7 @@ const createScreenStore = () => { }, }, ], - ...navigationSettings, - embedded: $appStore.embedded, + ...layoutSettings, }, } } diff --git a/packages/client/src/utils/domDebounce.js b/packages/client/src/utils/domDebounce.js deleted file mode 100644 index b15d2698b4..0000000000 --- a/packages/client/src/utils/domDebounce.js +++ /dev/null @@ -1,14 +0,0 @@ -export const domDebounce = (callback, extractParams = x => x) => { - let active = false - let lastParams - return (...params) => { - lastParams = extractParams(...params) - if (!active) { - active = true - requestAnimationFrame(() => { - callback(lastParams) - active = false - }) - } - } -} diff --git a/packages/client/src/utils/grid.js b/packages/client/src/utils/grid.js new file mode 100644 index 0000000000..1727b904ca --- /dev/null +++ b/packages/client/src/utils/grid.js @@ -0,0 +1,183 @@ +import { GridSpacing, GridRowHeight } from "constants" +import { builderStore } from "stores" +import { buildStyleString } from "utils/styleable.js" + +/** + * We use CSS variables on components to control positioning and layout of + * components inside grids. + * --grid-[mobile/desktop]-[row/col]-[start-end]: for positioning + * --grid-[mobile/desktop]-[h/v]-align: for layout of inner components within + * the components grid bounds + * + * Component definitions define their default layout preference via the + * `grid.hAlign` and `grid.vAlign` keys in the manifest. + * + * We also apply grid-[mobile/desktop]-grow CSS classes to component wrapper + * DOM nodes to use later in selectors, to control the sizing of children. + */ + +// Enum representing the different CSS variables we use for grid metadata +export const GridParams = { + HAlign: "h-align", + VAlign: "v-align", + ColStart: "col-start", + ColEnd: "col-end", + RowStart: "row-start", + RowEnd: "row-end", +} + +// Classes used in selectors inside grid containers to control child styles +export const GridClasses = { + DesktopFill: "grid-desktop-grow", + MobileFill: "grid-mobile-grow", +} + +// Enum for device preview type, included in grid CSS variables +export const Devices = { + Desktop: "desktop", + Mobile: "mobile", +} + +export const GridDragModes = { + Resize: "resize", + Move: "move", +} + +// Builds a CSS variable name for a certain piece of grid metadata +export const getGridVar = (device, param) => `--grid-${device}-${param}` + +// Determines whether a JS event originated from immediately within a grid +export const isGridEvent = e => { + return ( + e.target.dataset?.indicator === "true" || + e.target + .closest?.(".component") + ?.parentNode.closest(".component") + ?.childNodes[0]?.classList?.contains("grid") + ) +} + +// Svelte action to apply required class names and styles to our component +// wrappers +export const gridLayout = (node, metadata) => { + let selectComponent + + // Applies the required listeners, CSS and classes to a component DOM node + const applyMetadata = metadata => { + const { + id, + styles, + interactive, + errored, + definition, + draggable, + insideGrid, + ignoresLayout, + } = metadata + if (!insideGrid) { + return + } + + // If this component ignores layout, flag it as such so that we can avoid + // selecting it later + if (ignoresLayout) { + node.classList.add("ignores-layout") + return + } + + // Callback to select the component when clicking on the wrapper + selectComponent = e => { + e.stopPropagation() + builderStore.actions.selectComponent(id) + } + + // Determine default width and height of component + let width = errored ? 500 : definition?.size?.width || 200 + let height = errored ? 60 : definition?.size?.height || 200 + width += 2 * GridSpacing + height += 2 * GridSpacing + let vars = { + "--default-width": width, + "--default-height": height, + } + + // Generate defaults for all grid params + const defaults = { + [GridParams.HAlign]: definition?.grid?.hAlign || "stretch", + [GridParams.VAlign]: definition?.grid?.vAlign || "center", + [GridParams.ColStart]: 1, + [GridParams.ColEnd]: + "round(up, calc((var(--grid-spacing) * 2 + var(--default-width)) / var(--col-size) + 1))", + [GridParams.RowStart]: 1, + [GridParams.RowEnd]: Math.max(2, Math.ceil(height / GridRowHeight) + 1), + } + + // Specify values for all grid params for all devices, and strip these CSS + // variables from the styles being applied to the inner component, as we + // want to apply these to the wrapper instead + for (let param of Object.values(GridParams)) { + let dVar = getGridVar(Devices.Desktop, param) + let mVar = getGridVar(Devices.Mobile, param) + vars[dVar] = styles[dVar] ?? styles[mVar] ?? defaults[param] + vars[mVar] = styles[mVar] ?? styles[dVar] ?? defaults[param] + } + + // Apply some overrides depending on component state + if (errored) { + vars[getGridVar(Devices.Desktop, GridParams.HAlign)] = "stretch" + vars[getGridVar(Devices.Mobile, GridParams.HAlign)] = "stretch" + vars[getGridVar(Devices.Desktop, GridParams.VAlign)] = "stretch" + vars[getGridVar(Devices.Mobile, GridParams.VAlign)] = "stretch" + } + + // Apply some metadata to data attributes to speed up lookups + const addDataTag = (tagName, device, param) => { + const val = `${vars[getGridVar(device, param)]}` + if (node.dataset[tagName] !== val) { + node.dataset[tagName] = val + } + } + addDataTag("gridDesktopRowEnd", Devices.Desktop, GridParams.RowEnd) + addDataTag("gridMobileRowEnd", Devices.Mobile, GridParams.RowEnd) + addDataTag("gridDesktopHAlign", Devices.Desktop, GridParams.HAlign) + addDataTag("gridMobileHAlign", Devices.Mobile, GridParams.HAlign) + addDataTag("gridDesktopVAlign", Devices.Desktop, GridParams.VAlign) + addDataTag("gridMobileVAlign", Devices.Mobile, GridParams.VAlign) + if (node.dataset.insideGrid !== true) { + node.dataset.insideGrid = true + } + + // Apply all CSS variables to the wrapper + node.style = buildStyleString(vars) + + // Add a listener to select this node on click + if (interactive) { + node.addEventListener("click", selectComponent, false) + } + + // Add draggable attribute + node.setAttribute("draggable", !!draggable) + } + + // Removes the previously set up listeners + const removeListeners = () => { + // By checking if this is defined we can avoid trying to remove event + // listeners on every component + if (selectComponent) { + node.removeEventListener("click", selectComponent, false) + selectComponent = null + } + } + + applyMetadata(metadata) + + return { + update(newMetadata) { + removeListeners() + applyMetadata(newMetadata) + }, + destroy() { + removeListeners() + }, + } +} diff --git a/packages/client/src/utils/styleable.js b/packages/client/src/utils/styleable.js index 3fccae0be5..0f484a9ab9 100644 --- a/packages/client/src/utils/styleable.js +++ b/packages/client/src/utils/styleable.js @@ -3,13 +3,13 @@ import { builderStore } from "stores" /** * Helper to build a CSS string from a style object. */ -const buildStyleString = (styleObject, customStyles) => { +export const buildStyleString = (styleObject, customStyles) => { let str = "" - Object.entries(styleObject || {}).forEach(([style, value]) => { - if (style && value != null) { - str += `${style}: ${value}; ` + for (let key of Object.keys(styleObject || {})) { + if (styleObject[key] != null) { + str += `${key}:${styleObject[key]};` } - }) + } return str + (customStyles || "") } diff --git a/packages/frontend-core/src/components/ClientAppSkeleton.svelte b/packages/frontend-core/src/components/ClientAppSkeleton.svelte index a1c90d2db7..f867fccddb 100644 --- a/packages/frontend-core/src/components/ClientAppSkeleton.svelte +++ b/packages/frontend-core/src/components/ClientAppSkeleton.svelte @@ -58,7 +58,6 @@ height: 100%; display: flex; flex-direction: column; - border-radius: 4px; overflow: hidden; background-color: var(--spectrum-global-color-gray-200); } diff --git a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte index f3a7678cf2..8c66d9ecfc 100644 --- a/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte +++ b/packages/frontend-core/src/components/grid/controls/ColumnsSettingButton.svelte @@ -1,95 +1,17 @@
@@ -106,51 +28,5 @@
-
-
- {#each displayColumns as column} -
- -
- {column.label} -
-
- toggleColumn(column, e.detail)} - value={columnToPermissionOptions(column)} - options={column.options} - /> - {/each} -
-
+
- - diff --git a/packages/frontend-core/src/components/grid/controls/ColumnsSettingContent.svelte b/packages/frontend-core/src/components/grid/controls/ColumnsSettingContent.svelte new file mode 100644 index 0000000000..4f0e4424d4 --- /dev/null +++ b/packages/frontend-core/src/components/grid/controls/ColumnsSettingContent.svelte @@ -0,0 +1,134 @@ + + +
+
+ {#each displayColumns as column} +
+ +
+ {column.label} +
+
+ toggleColumn(column, e.detail)} + value={columnToPermissionOptions(column)} + options={column.options} + /> + {/each} +
+
+ + diff --git a/packages/frontend-core/src/utils/index.js b/packages/frontend-core/src/utils/index.js index 9eb7206012..5f21e7db99 100644 --- a/packages/frontend-core/src/utils/index.js +++ b/packages/frontend-core/src/utils/index.js @@ -9,3 +9,4 @@ export { memo, derivedMemo } from "./memo" export { createWebsocket } from "./websocket" export * from "./download" export * from "./theme" +export * from "./settings" diff --git a/packages/frontend-core/src/utils/memo.js b/packages/frontend-core/src/utils/memo.js index ba0e3f3490..8192be6790 100644 --- a/packages/frontend-core/src/utils/memo.js +++ b/packages/frontend-core/src/utils/memo.js @@ -4,32 +4,23 @@ import { writable, get, derived } from "svelte/store" // subscribed children will only fire when a new value is actually set export const memo = initialValue => { const store = writable(initialValue) + let currentJSON = JSON.stringify(initialValue) - const tryUpdateValue = (newValue, currentValue) => { - // Sanity check for primitive equality - if (currentValue === newValue) { - return - } - - // Otherwise deep compare via JSON stringify - const currentString = JSON.stringify(currentValue) - const newString = JSON.stringify(newValue) - if (currentString !== newString) { + const tryUpdateValue = newValue => { + const newJSON = JSON.stringify(newValue) + if (newJSON !== currentJSON) { store.set(newValue) + currentJSON = newJSON } } return { subscribe: store.subscribe, - set: newValue => { - const currentValue = get(store) - tryUpdateValue(newValue, currentValue) - }, + set: tryUpdateValue, update: updateFn => { - const currentValue = get(store) - let mutableCurrentValue = JSON.parse(JSON.stringify(currentValue)) + let mutableCurrentValue = JSON.parse(currentJSON) const newValue = updateFn(mutableCurrentValue) - tryUpdateValue(newValue, currentValue) + tryUpdateValue(newValue) }, } } diff --git a/packages/frontend-core/src/utils/settings.js b/packages/frontend-core/src/utils/settings.js new file mode 100644 index 0000000000..0e312c70e6 --- /dev/null +++ b/packages/frontend-core/src/utils/settings.js @@ -0,0 +1,43 @@ +import { helpers } from "@budibase/shared-core" + +// Util to check if a setting can be rendered for a certain instance, based on +// the "dependsOn" metadata in the manifest +export const shouldDisplaySetting = (instance, setting) => { + let dependsOn = setting.dependsOn + if (dependsOn && !Array.isArray(dependsOn)) { + dependsOn = [dependsOn] + } + if (!dependsOn?.length) { + return true + } + + // Ensure all conditions are met + return dependsOn.every(condition => { + let dependantSetting = condition + let dependantValues = null + let invert = !!condition.invert + if (typeof condition === "object") { + dependantSetting = condition.setting + dependantValues = condition.value + } + if (!dependantSetting) { + return false + } + + // Ensure values is an array + if (!Array.isArray(dependantValues)) { + dependantValues = [dependantValues] + } + + // If inverting, we want to ensure that we don't have any matches. + // If not inverting, we want to ensure that we do have any matches. + const currentVal = helpers.deepGet(instance, dependantSetting) + const anyMatches = dependantValues.some(dependantVal => { + if (dependantVal == null) { + return currentVal != null && currentVal !== false && currentVal !== "" + } + return dependantVal === currentVal + }) + return anyMatches !== invert + }) +} diff --git a/packages/frontend-core/src/utils/utils.js b/packages/frontend-core/src/utils/utils.js index 60752a35fe..6c9acb0f89 100644 --- a/packages/frontend-core/src/utils/utils.js +++ b/packages/frontend-core/src/utils/utils.js @@ -156,6 +156,7 @@ export const buildFormBlockButtonConfig = props => { providerId: formId, tableId: resourceId, notificationOverride, + confirm: null, }, }, { diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts index 5ee2d0fe2b..ac2d1e8c39 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -45,6 +45,7 @@ import { db as dbCore } from "@budibase/backend-core" import sdk from "../../../sdk" import env from "../../../environment" import { makeExternalQuery } from "../../../integrations/base/query" +import { dataFilters } from "@budibase/shared-core" export interface ManyRelationship { tableId?: string @@ -195,29 +196,33 @@ export class ExternalRequest { if (filters) { // need to map over the filters and make sure the _id field isn't present let prefix = 1 - for (const [operatorType, operator] of Object.entries(filters)) { - const isArrayOp = sdk.rows.utils.isArrayFilter(operatorType) - for (const field of Object.keys(operator || {})) { - if (dbCore.removeKeyNumbering(field) === "_id") { - if (primary) { - const parts = breakRowIdField(operator[field]) - if (primary.length > 1 && isArrayOp) { - operator[InternalSearchFilterOperator.COMPLEX_ID_OPERATOR] = { - id: primary, - values: parts[0], + const checkFilters = (innerFilters: SearchFilters): SearchFilters => { + for (const [operatorType, operator] of Object.entries(innerFilters)) { + const isArrayOp = sdk.rows.utils.isArrayFilter(operatorType) + for (const field of Object.keys(operator || {})) { + if (dbCore.removeKeyNumbering(field) === "_id") { + if (primary) { + const parts = breakRowIdField(operator[field]) + if (primary.length > 1 && isArrayOp) { + operator[InternalSearchFilterOperator.COMPLEX_ID_OPERATOR] = { + id: primary, + values: parts[0], + } + } else { + for (let field of primary) { + operator[`${prefix}:${field}`] = parts.shift() + } + prefix++ } - } else { - for (let field of primary) { - operator[`${prefix}:${field}`] = parts.shift() - } - prefix++ } + // make sure this field doesn't exist on any filter + delete operator[field] } - // make sure this field doesn't exist on any filter - delete operator[field] } } + return dataFilters.recurseLogicalOperators(innerFilters, checkFilters) } + checkFilters(filters) } // there is no id, just use the user provided filters if (!idCopy || !table) { diff --git a/packages/server/src/api/controllers/row/utils/sqlUtils.ts b/packages/server/src/api/controllers/row/utils/sqlUtils.ts index 32124fa79d..a24ec17c26 100644 --- a/packages/server/src/api/controllers/row/utils/sqlUtils.ts +++ b/packages/server/src/api/controllers/row/utils/sqlUtils.ts @@ -151,7 +151,10 @@ export function buildExternalRelationships( return relationships } -export function buildInternalRelationships(table: Table): RelationshipsJson[] { +export function buildInternalRelationships( + table: Table, + allTables: Table[] +): RelationshipsJson[] { const relationships: RelationshipsJson[] = [] const links = Object.values(table.schema).filter( column => column.type === FieldType.LINK @@ -164,6 +167,10 @@ export function buildInternalRelationships(table: Table): RelationshipsJson[] { const linkTableId = link.tableId! const junctionTableId = generateJunctionTableID(tableId, linkTableId) const isFirstTable = tableId > linkTableId + // skip relationships with missing table definitions + if (!allTables.find(table => table._id === linkTableId)) { + continue + } relationships.push({ through: junctionTableId, column: link.name, @@ -192,10 +199,10 @@ export function buildSqlFieldList( function extractRealFields(table: Table, existing: string[] = []) { return Object.entries(table.schema) .filter( - column => - column[1].type !== FieldType.LINK && - column[1].type !== FieldType.FORMULA && - !existing.find((field: string) => field === column[0]) + ([columnName, column]) => + column.type !== FieldType.LINK && + column.type !== FieldType.FORMULA && + !existing.find((field: string) => field === columnName) ) .map(column => `${table.name}.${column[0]}`) } diff --git a/packages/server/src/api/controllers/row/views.ts b/packages/server/src/api/controllers/row/views.ts index c38a415aa2..0b2bb8c203 100644 --- a/packages/server/src/api/controllers/row/views.ts +++ b/packages/server/src/api/controllers/row/views.ts @@ -38,7 +38,6 @@ export async function searchView( let query = dataFilters.buildQuery(view.query || []) if (body.query) { // Delete extraneous search params that cannot be overridden - delete body.query.allOr delete body.query.onEmptyFilter if (!isExternalTableID(view.tableId) && !db.isSqsEnabledForTenant()) { @@ -57,13 +56,12 @@ export async function searchView( } }) }) - } else { + } else query = { $and: { conditions: [query, body.query], }, } - } } await context.ensureSnippetContext(true) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 9351976d66..95e714fbd6 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -1664,7 +1664,7 @@ describe.each([ isInternal && describe("attachments and signatures", () => { const coreAttachmentEnrichment = async ( - schema: any, + schema: TableSchema, field: string, attachmentCfg: string | string[] ) => { @@ -1691,7 +1691,7 @@ describe.each([ await withEnv({ SELF_HOSTED: "true" }, async () => { return context.doInAppContext(config.getAppId(), async () => { - const enriched: Row[] = await outputProcessing(table, [row]) + const enriched: Row[] = await outputProcessing(testTable, [row]) const [targetRow] = enriched const attachmentEntries = Array.isArray(targetRow[field]) ? targetRow[field] diff --git a/packages/server/src/api/routes/tests/search.spec.ts b/packages/server/src/api/routes/tests/search.spec.ts index 9de97747e5..bac9b6f774 100644 --- a/packages/server/src/api/routes/tests/search.spec.ts +++ b/packages/server/src/api/routes/tests/search.spec.ts @@ -2762,6 +2762,57 @@ describe.each([ }) }) + isSql && + describe("primaryDisplay", () => { + beforeAll(async () => { + let toRelateTable = await createTable({ + name: { + name: "name", + type: FieldType.STRING, + }, + }) + table = await config.api.table.save( + tableForDatasource(datasource, { + schema: { + name: { + name: "name", + type: FieldType.STRING, + }, + link: { + name: "link", + type: FieldType.LINK, + relationshipType: RelationshipType.MANY_TO_ONE, + tableId: toRelateTable._id!, + fieldName: "link", + }, + }, + }) + ) + toRelateTable = await config.api.table.get(toRelateTable._id!) + await config.api.table.save({ + ...toRelateTable, + primaryDisplay: "link", + }) + const relatedRows = await Promise.all([ + config.api.row.save(toRelateTable._id!, { name: "test" }), + ]) + await Promise.all([ + config.api.row.save(table._id!, { + name: "test", + link: relatedRows.map(row => row._id), + }), + ]) + }) + + it("should be able to query, primary display on related table shouldn't be used", async () => { + // this test makes sure that if a relationship has been specified as the primary display on a table + // it is ignored and another column is used instead + await expectQuery({}).toContain([ + { name: "test", link: [{ primaryDisplay: "test" }] }, + ]) + }) + }) + !isLucene && describe("$and", () => { beforeAll(async () => { diff --git a/packages/server/src/api/routes/tests/viewV2.spec.ts b/packages/server/src/api/routes/tests/viewV2.spec.ts index e18cffeaa8..4401efc480 100644 --- a/packages/server/src/api/routes/tests/viewV2.spec.ts +++ b/packages/server/src/api/routes/tests/viewV2.spec.ts @@ -30,6 +30,7 @@ import { withEnv as withCoreEnv, setEnv as setCoreEnv, } from "@budibase/backend-core" +import sdk from "../../../sdk" describe.each([ ["lucene", undefined], @@ -120,6 +121,7 @@ describe.each([ }) beforeEach(() => { + jest.clearAllMocks() mocks.licenses.useCloudFree() }) @@ -1490,83 +1492,189 @@ describe.each([ ) }) - isLucene && - it("in lucene, cannot override a view filter", async () => { - await config.api.row.save(table._id!, { - one: "foo", - two: "bar", - }) - const two = await config.api.row.save(table._id!, { - one: "foo2", - two: "bar2", - }) - - const view = await config.api.viewV2.create({ - tableId: table._id!, - name: generator.guid(), - query: [ - { - operator: BasicOperator.EQUAL, - field: "two", - value: "bar2", - }, - ], - schema: { - id: { visible: true }, - one: { visible: false }, - two: { visible: true }, - }, - }) - - const response = await config.api.viewV2.search(view.id, { - query: { - equal: { - two: "bar", - }, - }, - }) - expect(response.rows).toHaveLength(1) - expect(response.rows).toEqual([ - expect.objectContaining({ _id: two._id }), - ]) + it("can query on top of the view filters", async () => { + await config.api.row.save(table._id!, { + one: "foo", + two: "bar", + }) + await config.api.row.save(table._id!, { + one: "foo2", + two: "bar2", + }) + const three = await config.api.row.save(table._id!, { + one: "foo3", + two: "bar3", }) - !isLucene && - it("can filter a view without a view filter", async () => { - const one = await config.api.row.save(table._id!, { - one: "foo", - two: "bar", - }) - await config.api.row.save(table._id!, { - one: "foo2", - two: "bar2", - }) - - const view = await config.api.viewV2.create({ - tableId: table._id!, - name: generator.guid(), - schema: { - id: { visible: true }, - one: { visible: false }, - two: { visible: true }, + const view = await config.api.viewV2.create({ + tableId: table._id!, + name: generator.guid(), + query: [ + { + operator: BasicOperator.NOT_EQUAL, + field: "one", + value: "foo2", }, - }) + ], + schema: { + id: { visible: true }, + one: { visible: true }, + two: { visible: true }, + }, + }) - const response = await config.api.viewV2.search(view.id, { - query: { - equal: { - two: "bar", - }, + const response = await config.api.viewV2.search(view.id, { + query: { + [BasicOperator.EQUAL]: { + two: "bar3", }, - }) - expect(response.rows).toHaveLength(1) - expect(response.rows).toEqual([ + [BasicOperator.NOT_EMPTY]: { + two: null, + }, + }, + }) + expect(response.rows).toHaveLength(1) + expect(response.rows).toEqual( + expect.arrayContaining([expect.objectContaining({ _id: three._id })]) + ) + }) + + it("can query on top of the view filters (using or filters)", async () => { + const one = await config.api.row.save(table._id!, { + one: "foo", + two: "bar", + }) + await config.api.row.save(table._id!, { + one: "foo2", + two: "bar2", + }) + const three = await config.api.row.save(table._id!, { + one: "foo3", + two: "bar3", + }) + + const view = await config.api.viewV2.create({ + tableId: table._id!, + name: generator.guid(), + query: [ + { + operator: BasicOperator.NOT_EQUAL, + field: "two", + value: "bar2", + }, + ], + schema: { + id: { visible: true }, + one: { visible: false }, + two: { visible: true }, + }, + }) + + const response = await config.api.viewV2.search(view.id, { + query: { + allOr: true, + [BasicOperator.NOT_EQUAL]: { + two: "bar", + }, + [BasicOperator.NOT_EMPTY]: { + two: null, + }, + }, + }) + expect(response.rows).toHaveLength(2) + expect(response.rows).toEqual( + expect.arrayContaining([ expect.objectContaining({ _id: one._id }), + expect.objectContaining({ _id: three._id }), ]) - }) + ) + }) + + isLucene && + it.each([true, false])( + "in lucene, cannot override a view filter", + async allOr => { + await config.api.row.save(table._id!, { + one: "foo", + two: "bar", + }) + const two = await config.api.row.save(table._id!, { + one: "foo2", + two: "bar2", + }) + + const view = await config.api.viewV2.create({ + tableId: table._id!, + name: generator.guid(), + query: [ + { + operator: BasicOperator.EQUAL, + field: "two", + value: "bar2", + }, + ], + schema: { + id: { visible: true }, + one: { visible: false }, + two: { visible: true }, + }, + }) + + const response = await config.api.viewV2.search(view.id, { + query: { + allOr, + equal: { + two: "bar", + }, + }, + }) + expect(response.rows).toHaveLength(1) + expect(response.rows).toEqual([ + expect.objectContaining({ _id: two._id }), + ]) + } + ) !isLucene && - it("cannot bypass a view filter", async () => { + it.each([true, false])( + "can filter a view without a view filter", + async allOr => { + const one = await config.api.row.save(table._id!, { + one: "foo", + two: "bar", + }) + await config.api.row.save(table._id!, { + one: "foo2", + two: "bar2", + }) + + const view = await config.api.viewV2.create({ + tableId: table._id!, + name: generator.guid(), + schema: { + id: { visible: true }, + one: { visible: false }, + two: { visible: true }, + }, + }) + + const response = await config.api.viewV2.search(view.id, { + query: { + allOr, + equal: { + two: "bar", + }, + }, + }) + expect(response.rows).toHaveLength(1) + expect(response.rows).toEqual([ + expect.objectContaining({ _id: one._id }), + ]) + } + ) + + !isLucene && + it.each([true, false])("cannot bypass a view filter", async allOr => { await config.api.row.save(table._id!, { one: "foo", two: "bar", @@ -1595,6 +1703,7 @@ describe.each([ const response = await config.api.viewV2.search(view.id, { query: { + allOr, equal: { two: "bar", }, @@ -1602,6 +1711,28 @@ describe.each([ }) expect(response.rows).toHaveLength(0) }) + + it("queries the row api passing the view fields only", async () => { + const searchSpy = jest.spyOn(sdk.rows, "search") + + const view = await config.api.viewV2.create({ + tableId: table._id!, + name: generator.guid(), + schema: { + id: { visible: true }, + one: { visible: false }, + }, + }) + + await config.api.viewV2.search(view.id, { query: {} }) + expect(searchSpy).toHaveBeenCalledTimes(1) + + expect(searchSpy).toHaveBeenCalledWith( + expect.objectContaining({ + fields: ["id"], + }) + ) + }) }) describe("permissions", () => { diff --git a/packages/server/src/db/linkedRows/index.ts b/packages/server/src/db/linkedRows/index.ts index 87f980600a..2da7e212b9 100644 --- a/packages/server/src/db/linkedRows/index.ts +++ b/packages/server/src/db/linkedRows/index.ts @@ -1,10 +1,10 @@ import LinkController from "./LinkController" import { getLinkDocuments, - getUniqueByProp, - getRelatedTableForField, - getLinkedTableIDs, getLinkedTable, + getLinkedTableIDs, + getRelatedTableForField, + getUniqueByProp, } from "./linkUtils" import flatten from "lodash/flatten" import { USER_METDATA_PREFIX } from "../utils" @@ -13,16 +13,25 @@ import { getGlobalUsersFromMetadata } from "../../utilities/global" import { processFormulas } from "../../utilities/rowProcessor" import { context } from "@budibase/backend-core" import { - Table, - Row, - LinkDocumentValue, - FieldType, ContextUser, + FieldType, + LinkDocumentValue, + Row, + Table, } from "@budibase/types" import sdk from "../../sdk" export { IncludeDocs, getLinkDocuments, createLinkView } from "./linkUtils" +const INVALID_DISPLAY_COLUMN_TYPE = [ + FieldType.LINK, + FieldType.ATTACHMENTS, + FieldType.ATTACHMENT_SINGLE, + FieldType.SIGNATURE_SINGLE, + FieldType.BB_REFERENCE, + FieldType.BB_REFERENCE_SINGLE, +] + /** * This functionality makes sure that when rows with links are created, updated or deleted they are processed * correctly - making sure that no stale links are left around and that all links have been made successfully. @@ -206,6 +215,31 @@ export async function attachFullLinkedDocs( return rows } +/** + * Finds a valid value for the primary display, avoiding columns which break things + * like relationships (can be circular). + * @param row The row to lift a value from for the primary display. + * @param table The related table to attempt to work out the primary display column from. + */ +function getPrimaryDisplayValue(row: Row, table?: Table) { + const primaryDisplay = table?.primaryDisplay + let invalid = true + if (primaryDisplay) { + const primaryDisplaySchema = table?.schema[primaryDisplay] + invalid = INVALID_DISPLAY_COLUMN_TYPE.includes(primaryDisplaySchema.type) + } + if (invalid || !primaryDisplay) { + const validKey = Object.keys(table?.schema || {}).find( + key => + table?.schema[key].type && + !INVALID_DISPLAY_COLUMN_TYPE.includes(table?.schema[key].type) + ) + return validKey ? row[validKey] : undefined + } else { + return row[primaryDisplay] + } +} + /** * This function will take the given enriched rows and squash the links to only contain the primary display field. * @param table The table from which the rows originated. @@ -232,9 +266,7 @@ export async function squashLinksToPrimaryDisplay( const linkTblId = link.tableId || getRelatedTableForField(table, column) const linkedTable = await getLinkedTable(linkTblId!, linkedTables) const obj: any = { _id: link._id } - if (linkedTable?.primaryDisplay && link[linkedTable.primaryDisplay]) { - obj.primaryDisplay = link[linkedTable.primaryDisplay] - } + obj.primaryDisplay = getPrimaryDisplayValue(link, linkedTable) newLinks.push(obj) } row[column] = newLinks diff --git a/packages/server/src/integrations/tests/sql.spec.ts b/packages/server/src/integrations/tests/sql.spec.ts index c4b2a69f7d..a6e63c434d 100644 --- a/packages/server/src/integrations/tests/sql.spec.ts +++ b/packages/server/src/integrations/tests/sql.spec.ts @@ -194,8 +194,8 @@ describe("SQL query builder", () => { }) ) expect(query).toEqual({ - bindings: ["john%", limit, 5000], - sql: `select * from (select * from (select * from (select * from "test" where LOWER("test"."name") LIKE :1 order by "test"."id" asc) where rownum <= :2) "test" order by "test"."id" asc) where rownum <= :3`, + bindings: ["john%", limit, "john%", 5000], + sql: `select * from (select * from (select * from (select * from "test" where LOWER("test"."name") LIKE :1 order by "test"."id" asc) where rownum <= :2) "test" where LOWER("test"."name") LIKE :3 order by "test"."id" asc) where rownum <= :4`, }) query = new Sql(SqlClient.ORACLE, limit)._query( @@ -208,9 +208,10 @@ describe("SQL query builder", () => { }, }) ) + const filterSet = [`%20%`, `%25%`, `%"john"%`, `%"mary"%`] expect(query).toEqual({ - bindings: ["%20%", "%25%", `%"john"%`, `%"mary"%`, limit, 5000], - sql: `select * from (select * from (select * from (select * from "test" where COALESCE(LOWER("test"."age"), '') LIKE :1 AND COALESCE(LOWER("test"."age"), '') LIKE :2 and COALESCE(LOWER("test"."name"), '') LIKE :3 AND COALESCE(LOWER("test"."name"), '') LIKE :4 order by "test"."id" asc) where rownum <= :5) "test" order by "test"."id" asc) where rownum <= :6`, + bindings: [...filterSet, limit, ...filterSet, 5000], + sql: `select * from (select * from (select * from (select * from "test" where COALESCE(LOWER("test"."age"), '') LIKE :1 AND COALESCE(LOWER("test"."age"), '') LIKE :2 and COALESCE(LOWER("test"."name"), '') LIKE :3 AND COALESCE(LOWER("test"."name"), '') LIKE :4 order by "test"."id" asc) where rownum <= :5) "test" where COALESCE(LOWER("test"."age"), '') LIKE :6 AND COALESCE(LOWER("test"."age"), '') LIKE :7 and COALESCE(LOWER("test"."name"), '') LIKE :8 AND COALESCE(LOWER("test"."name"), '') LIKE :9 order by "test"."id" asc) where rownum <= :10`, }) query = new Sql(SqlClient.ORACLE, limit)._query( @@ -223,8 +224,8 @@ describe("SQL query builder", () => { }) ) expect(query).toEqual({ - bindings: [`%jo%`, limit, 5000], - sql: `select * from (select * from (select * from (select * from "test" where LOWER("test"."name") LIKE :1 order by "test"."id" asc) where rownum <= :2) "test" order by "test"."id" asc) where rownum <= :3`, + bindings: [`%jo%`, limit, `%jo%`, 5000], + sql: `select * from (select * from (select * from (select * from "test" where LOWER("test"."name") LIKE :1 order by "test"."id" asc) where rownum <= :2) "test" where LOWER("test"."name") LIKE :3 order by "test"."id" asc) where rownum <= :4`, }) }) @@ -241,8 +242,8 @@ describe("SQL query builder", () => { ) expect(query).toEqual({ - bindings: ["John", limit, 5000], - sql: `select * from (select * from (select * from (select * from "test" where (to_char("test"."name") IS NOT NULL AND to_char("test"."name") = :1) order by "test"."id" asc) where rownum <= :2) "test" order by "test"."id" asc) where rownum <= :3`, + bindings: ["John", limit, "John", 5000], + sql: `select * from (select * from (select * from (select * from "test" where (to_char("test"."name") IS NOT NULL AND to_char("test"."name") = :1) order by "test"."id" asc) where rownum <= :2) "test" where (to_char("test"."name") IS NOT NULL AND to_char("test"."name") = :3) order by "test"."id" asc) where rownum <= :4`, }) }) @@ -259,8 +260,8 @@ describe("SQL query builder", () => { ) expect(query).toEqual({ - bindings: ["John", limit, 5000], - sql: `select * from (select * from (select * from (select * from "test" where (to_char("test"."name") IS NOT NULL AND to_char("test"."name") != :1) OR to_char("test"."name") IS NULL order by "test"."id" asc) where rownum <= :2) "test" order by "test"."id" asc) where rownum <= :3`, + bindings: ["John", limit, "John", 5000], + sql: `select * from (select * from (select * from (select * from "test" where (to_char("test"."name") IS NOT NULL AND to_char("test"."name") != :1) OR to_char("test"."name") IS NULL order by "test"."id" asc) where rownum <= :2) "test" where (to_char("test"."name") IS NOT NULL AND to_char("test"."name") != :3) OR to_char("test"."name") IS NULL order by "test"."id" asc) where rownum <= :4`, }) }) }) diff --git a/packages/server/src/integrations/tests/sqlAlias.spec.ts b/packages/server/src/integrations/tests/sqlAlias.spec.ts index 0b433896bf..6f34f4eb89 100644 --- a/packages/server/src/integrations/tests/sqlAlias.spec.ts +++ b/packages/server/src/integrations/tests/sqlAlias.spec.ts @@ -97,13 +97,14 @@ describe("Captures of real examples", () => { const filters = queryJson.filters?.oneOf?.taskid as number[] let query = new Sql(SqlClient.POSTGRES, limit)._query(queryJson) expect(query).toEqual({ - bindings: [...filters, limit, limit], - sql: multiline(`select "a"."executorid" as "a.executorid", "a"."taskname" as "a.taskname", - "a"."taskid" as "a.taskid", "a"."completed" as "a.completed", "a"."qaid" as "a.qaid", - "b"."productname" as "b.productname", "b"."productid" as "b.productid" - from (select * from "tasks" as "a" where "a"."taskid" in ($1, $2) order by "a"."taskid" asc limit $3) as "a" - left join "products_tasks" as "c" on "a"."taskid" = "c"."taskid" - left join "products" as "b" on "b"."productid" = "c"."productid" order by "a"."taskid" asc limit $4`), + bindings: [...filters, limit, ...filters, limit], + sql: multiline( + `select "a"."executorid" as "a.executorid", "a"."taskname" as "a.taskname", "a"."taskid" as "a.taskid", + "a"."completed" as "a.completed", "a"."qaid" as "a.qaid", "b"."productname" as "b.productname", "b"."productid" as "b.productid" + from (select * from "tasks" as "a" where "a"."taskid" in ($1, $2) order by "a"."taskid" asc limit $3) as "a" + left join "products_tasks" as "c" on "a"."taskid" = "c"."taskid" left join "products" as "b" on "b"."productid" = "c"."productid" + where "a"."taskid" in ($4, $5) order by "a"."taskid" asc limit $6` + ), }) }) @@ -123,6 +124,7 @@ describe("Captures of real examples", () => { rangeValue.low, rangeValue.high, equalValue, + true, limit, ], sql: expect.stringContaining( @@ -186,8 +188,9 @@ describe("Captures of real examples", () => { }, queryJson) expect(returningQuery).toEqual({ sql: multiline(`select top (@p0) * from (select top (@p1) * from [people] where CASE WHEN [people].[name] = @p2 - THEN 1 ELSE 0 END = 1 and CASE WHEN [people].[age] = @p3 THEN 1 ELSE 0 END = 1 order by [people].[name] asc) as [people]`), - bindings: [5000, 1, "Test", 22], + THEN 1 ELSE 0 END = 1 and CASE WHEN [people].[age] = @p3 THEN 1 ELSE 0 END = 1 order by [people].[name] asc) as [people] + where CASE WHEN [people].[name] = @p4 THEN 1 ELSE 0 END = 1 and CASE WHEN [people].[age] = @p5 THEN 1 ELSE 0 END = 1`), + bindings: [5000, 1, "Test", 22, "Test", 22], }) }) }) diff --git a/packages/server/src/sdk/app/rows/queryUtils.ts b/packages/server/src/sdk/app/rows/queryUtils.ts new file mode 100644 index 0000000000..65f400a1d9 --- /dev/null +++ b/packages/server/src/sdk/app/rows/queryUtils.ts @@ -0,0 +1,116 @@ +import { db } from "@budibase/backend-core" +import { + FieldType, + isLogicalSearchOperator, + SearchFilters, + Table, +} from "@budibase/types" +import { cloneDeep } from "lodash/fp" +import sdk from "../../../sdk" + +export const removeInvalidFilters = ( + filters: SearchFilters, + validFields: string[] +) => { + const result = cloneDeep(filters) + + validFields = validFields.map(f => f.toLowerCase()) + for (const filterKey of Object.keys(result) as (keyof SearchFilters)[]) { + const filter = result[filterKey] + if (!filter || typeof filter !== "object") { + continue + } + if (isLogicalSearchOperator(filterKey)) { + const resultingConditions: SearchFilters[] = [] + for (const condition of filter.conditions) { + const resultingCondition = removeInvalidFilters(condition, validFields) + if (Object.keys(resultingCondition).length) { + resultingConditions.push(resultingCondition) + } + } + if (resultingConditions.length) { + filter.conditions = resultingConditions + } else { + delete result[filterKey] + } + continue + } + + for (const columnKey of Object.keys(filter)) { + const possibleKeys = [columnKey, db.removeKeyNumbering(columnKey)].map( + c => c.toLowerCase() + ) + if (!validFields.some(f => possibleKeys.includes(f.toLowerCase()))) { + delete filter[columnKey as keyof typeof filter] + } + } + if (!Object.keys(filter).length) { + delete result[filterKey] + } + } + + return result +} + +export const getQueryableFields = async ( + fields: string[], + table: Table +): Promise => { + const extractTableFields = async ( + table: Table, + allowedFields: string[], + fromTables: string[], + opts?: { noRelationships?: boolean } + ): Promise => { + const result = [] + for (const field of Object.keys(table.schema).filter( + f => allowedFields.includes(f) && table.schema[f].visible !== false + )) { + const subSchema = table.schema[field] + const isRelationship = subSchema.type === FieldType.LINK + // avoid relationship loops + if ( + isRelationship && + (opts?.noRelationships || fromTables.includes(subSchema.tableId)) + ) { + continue + } + if (isRelationship) { + try { + const relatedTable = await sdk.tables.getTable(subSchema.tableId) + const relatedFields = await extractTableFields( + relatedTable, + Object.keys(relatedTable.schema), + [...fromTables, subSchema.tableId], + // don't let it recurse back and forth between relationships + { noRelationships: true } + ) + + result.push( + ...relatedFields.flatMap(f => [ + `${subSchema.name}.${f}`, + // should be able to filter by relationship using table name + `${relatedTable.name}.${f}`, + ]) + ) + } catch (err: any) { + // if related table is removed, ignore + if (err.status !== 404) { + throw err + } + } + } else { + result.push(field) + } + } + return result + } + + const result = [ + "_id", // Querying by _id is always allowed, even if it's never part of the schema + ] + + result.push(...(await extractTableFields(table, fields, [table._id!]))) + + return result +} diff --git a/packages/server/src/sdk/app/rows/search.ts b/packages/server/src/sdk/app/rows/search.ts index 1ccd89639b..6a4286814d 100644 --- a/packages/server/src/sdk/app/rows/search.ts +++ b/packages/server/src/sdk/app/rows/search.ts @@ -14,6 +14,7 @@ import sdk from "../../index" import { searchInputMapping } from "./search/utils" import { db as dbCore } from "@budibase/backend-core" import tracer from "dd-trace" +import { getQueryableFields, removeInvalidFilters } from "./queryUtils" export { isValidFilter } from "../../../integrations/utils" @@ -73,6 +74,18 @@ export async function search( const table = await sdk.tables.getTable(options.tableId) options = searchInputMapping(table, options) + if (options.query) { + const tableFields = Object.keys(table.schema).filter( + f => table.schema[f].visible !== false + ) + + const queriableFields = await getQueryableFields( + options.fields?.filter(f => tableFields.includes(f)) ?? tableFields, + table + ) + options.query = removeInvalidFilters(options.query, queriableFields) + } + let result: SearchResponse if (isExternalTable) { span?.addTags({ searchType: "external" }) diff --git a/packages/server/src/sdk/app/rows/search/filters.ts b/packages/server/src/sdk/app/rows/search/filters.ts index ccce0ab86a..4049fc5352 100644 --- a/packages/server/src/sdk/app/rows/search/filters.ts +++ b/packages/server/src/sdk/app/rows/search/filters.ts @@ -5,6 +5,7 @@ import { Table, } from "@budibase/types" import { isPlainObject } from "lodash" +import { dataFilters } from "@budibase/shared-core" export function getRelationshipColumns(table: Table): { name: string @@ -58,5 +59,7 @@ export function updateFilterKeys( } } } - return filters + return dataFilters.recurseLogicalOperators(filters, (f: SearchFilters) => { + return updateFilterKeys(f, updates) + }) } diff --git a/packages/server/src/sdk/app/rows/search/internal/sqs.ts b/packages/server/src/sdk/app/rows/search/internal/sqs.ts index 4bfa8f8fa5..6736ff6abf 100644 --- a/packages/server/src/sdk/app/rows/search/internal/sqs.ts +++ b/packages/server/src/sdk/app/rows/search/internal/sqs.ts @@ -297,7 +297,7 @@ export async function search( throw new Error("Unable to find table") } - const relationships = buildInternalRelationships(table) + const relationships = buildInternalRelationships(table, allTables) const searchFilters: SearchFilters = { ...cleanupFilters(query, table, allTables), diff --git a/packages/server/src/sdk/app/rows/search/tests/search.spec.ts b/packages/server/src/sdk/app/rows/search/tests/search.spec.ts index 252b9a6556..db1104bcf6 100644 --- a/packages/server/src/sdk/app/rows/search/tests/search.spec.ts +++ b/packages/server/src/sdk/app/rows/search/tests/search.spec.ts @@ -1,4 +1,11 @@ -import { Datasource, FieldType, Row, Table } from "@budibase/types" +import { + AutoColumnFieldMetadata, + AutoFieldSubType, + Datasource, + FieldType, + NumberFieldMetadata, + Table, +} from "@budibase/types" import TestConfiguration from "../../../../../tests/utilities/TestConfiguration" import { search } from "../../../../../sdk/app/rows/search" @@ -32,7 +39,6 @@ describe.each([ let envCleanup: (() => void) | undefined let datasource: Datasource | undefined let table: Table - let rows: Row[] beforeAll(async () => { await withCoreEnv({ SQS_SEARCH_ENABLE: isSqs ? "true" : "false" }, () => @@ -51,16 +57,28 @@ describe.each([ datasource: await dsProvider, }) } + }) + + beforeEach(async () => { + const idFieldSchema: NumberFieldMetadata | AutoColumnFieldMetadata = + isInternal + ? { + name: "id", + type: FieldType.AUTO, + subtype: AutoFieldSubType.AUTO_ID, + autocolumn: true, + } + : { + name: "id", + type: FieldType.NUMBER, + autocolumn: true, + } table = await config.api.table.save( tableForDatasource(datasource, { primary: ["id"], schema: { - id: { - name: "id", - type: FieldType.NUMBER, - autocolumn: true, - }, + id: idFieldSchema, name: { name: "name", type: FieldType.STRING, @@ -81,16 +99,13 @@ describe.each([ }) ) - rows = [] for (let i = 0; i < 10; i++) { - rows.push( - await config.api.row.save(table._id!, { - name: generator.first(), - surname: generator.last(), - age: generator.age(), - address: generator.address(), - }) - ) + await config.api.row.save(table._id!, { + name: generator.first(), + surname: generator.last(), + age: generator.age(), + address: generator.address(), + }) } }) @@ -138,4 +153,100 @@ describe.each([ ) }) }) + + it("does not allow accessing hidden fields", async () => { + await config.doInContext(config.appId, async () => { + await config.api.table.save({ + ...table, + schema: { + ...table.schema, + name: { + ...table.schema.name, + visible: true, + }, + age: { + ...table.schema.age, + visible: false, + }, + }, + }) + const result = await search({ + tableId: table._id!, + query: {}, + }) + expect(result.rows).toHaveLength(10) + for (const row of result.rows) { + const keys = Object.keys(row) + expect(keys).toContain("name") + expect(keys).toContain("surname") + expect(keys).toContain("address") + expect(keys).not.toContain("age") + } + }) + }) + + it("does not allow accessing hidden fields even if requested", async () => { + await config.doInContext(config.appId, async () => { + await config.api.table.save({ + ...table, + schema: { + ...table.schema, + name: { + ...table.schema.name, + visible: true, + }, + age: { + ...table.schema.age, + visible: false, + }, + }, + }) + const result = await search({ + tableId: table._id!, + query: {}, + fields: ["name", "age"], + }) + expect(result.rows).toHaveLength(10) + for (const row of result.rows) { + const keys = Object.keys(row) + expect(keys).toContain("name") + expect(keys).not.toContain("age") + expect(keys).not.toContain("surname") + expect(keys).not.toContain("address") + } + }) + }) + + !isLucene && + it.each([ + [["id", "name", "age"], 3], + [["name", "age"], 10], + ])( + "cannot query by non search fields (fields: %s)", + async (queryFields, expectedRows) => { + await config.doInContext(config.appId, async () => { + const { rows } = await search({ + tableId: table._id!, + query: { + $or: { + conditions: [ + { + $and: { + conditions: [ + { range: { id: { low: 2, high: 4 } } }, + { range: { id: { low: 3, high: 5 } } }, + ], + }, + }, + { equal: { id: 7 } }, + ], + }, + }, + fields: queryFields, + }) + + expect(rows).toHaveLength(expectedRows) + }) + } + ) }) diff --git a/packages/server/src/sdk/app/rows/sqlAlias.ts b/packages/server/src/sdk/app/rows/sqlAlias.ts index 664e64057b..535709791d 100644 --- a/packages/server/src/sdk/app/rows/sqlAlias.ts +++ b/packages/server/src/sdk/app/rows/sqlAlias.ts @@ -12,6 +12,7 @@ import { getSQLClient } from "./utils" import { cloneDeep } from "lodash" import datasources from "../datasources" import { BudibaseInternalDB } from "../../../db/utils" +import { dataFilters } from "@budibase/shared-core" type PerformQueryFunction = ( datasource: Datasource, @@ -199,16 +200,20 @@ export default class AliasTables { ) } if (json.filters) { - for (let [filterKey, filter] of Object.entries(json.filters)) { - if (typeof filter !== "object") { - continue + const aliasFilters = (filters: SearchFilters): SearchFilters => { + for (let [filterKey, filter] of Object.entries(filters)) { + if (typeof filter !== "object") { + continue + } + const aliasedFilters: typeof filter = {} + for (let key of Object.keys(filter)) { + aliasedFilters[this.aliasField(key)] = filter[key] + } + filters[filterKey as keyof SearchFilters] = aliasedFilters } - const aliasedFilters: typeof filter = {} - for (let key of Object.keys(filter)) { - aliasedFilters[this.aliasField(key)] = filter[key] - } - json.filters[filterKey as keyof SearchFilters] = aliasedFilters + return dataFilters.recurseLogicalOperators(filters, aliasFilters) } + json.filters = aliasFilters(json.filters) } if (json.meta?.table) { this.getAlias(json.meta.table.name) diff --git a/packages/server/src/sdk/app/rows/tests/queryUtils.spec.ts b/packages/server/src/sdk/app/rows/tests/queryUtils.spec.ts new file mode 100644 index 0000000000..aabc359484 --- /dev/null +++ b/packages/server/src/sdk/app/rows/tests/queryUtils.spec.ts @@ -0,0 +1,505 @@ +import { + FieldType, + RelationshipType, + SearchFilters, + Table, +} from "@budibase/types" +import { getQueryableFields, removeInvalidFilters } from "../queryUtils" +import { structures } from "../../../../api/routes/tests/utilities" +import TestConfiguration from "../../../../tests/utilities/TestConfiguration" + +describe("query utils", () => { + describe("removeInvalidFilters", () => { + const fullFilters: SearchFilters = { + equal: { one: "foo" }, + $or: { + conditions: [ + { + equal: { one: "foo2", two: "bar" }, + notEmpty: { one: null }, + $and: { + conditions: [ + { + equal: { three: "baz" }, + notEmpty: { forth: null }, + }, + ], + }, + }, + ], + }, + $and: { + conditions: [{ equal: { one: "foo2" }, notEmpty: { one: null } }], + }, + } + + it("can filter empty queries", () => { + const filters: SearchFilters = {} + const result = removeInvalidFilters(filters, []) + expect(result).toEqual({}) + }) + + it("does not trim any valid field", () => { + const result = removeInvalidFilters(fullFilters, [ + "one", + "two", + "three", + "forth", + ]) + expect(result).toEqual(fullFilters) + }) + + it("trims invalid field", () => { + const result = removeInvalidFilters(fullFilters, [ + "one", + "three", + "forth", + ]) + expect(result).toEqual({ + equal: { one: "foo" }, + $or: { + conditions: [ + { + equal: { one: "foo2" }, + notEmpty: { one: null }, + $and: { + conditions: [ + { + equal: { three: "baz" }, + notEmpty: { forth: null }, + }, + ], + }, + }, + ], + }, + $and: { + conditions: [{ equal: { one: "foo2" }, notEmpty: { one: null } }], + }, + }) + }) + + it("trims invalid field keeping a valid fields", () => { + const result = removeInvalidFilters(fullFilters, ["three", "forth"]) + const expected: SearchFilters = { + $or: { + conditions: [ + { + $and: { + conditions: [ + { + equal: { three: "baz" }, + notEmpty: { forth: null }, + }, + ], + }, + }, + ], + }, + } + expect(result).toEqual(expected) + }) + + it("keeps filter key numering", () => { + const prefixedFilters: SearchFilters = { + equal: { "1:one": "foo" }, + $or: { + conditions: [ + { + equal: { "2:one": "foo2", "3:two": "bar" }, + notEmpty: { "4:one": null }, + $and: { + conditions: [ + { + equal: { "5:three": "baz", two: "bar2" }, + notEmpty: { forth: null }, + }, + ], + }, + }, + ], + }, + $and: { + conditions: [{ equal: { "6:one": "foo2" }, notEmpty: { one: null } }], + }, + } + + const result = removeInvalidFilters(prefixedFilters, [ + "one", + "three", + "forth", + ]) + expect(result).toEqual({ + equal: { "1:one": "foo" }, + $or: { + conditions: [ + { + equal: { "2:one": "foo2" }, + notEmpty: { "4:one": null }, + $and: { + conditions: [ + { + equal: { "5:three": "baz" }, + notEmpty: { forth: null }, + }, + ], + }, + }, + ], + }, + $and: { + conditions: [{ equal: { "6:one": "foo2" }, notEmpty: { one: null } }], + }, + }) + }) + + it("handles relationship filters", () => { + const prefixedFilters: SearchFilters = { + $or: { + conditions: [ + { equal: { "1:other.one": "foo" } }, + { + equal: { + "2:other.one": "foo2", + "3:other.two": "bar", + "4:other.three": "baz", + }, + }, + { equal: { "another.three": "baz2" } }, + ], + }, + } + + const result = removeInvalidFilters(prefixedFilters, [ + "other.one", + "other.two", + "another.three", + ]) + expect(result).toEqual({ + $or: { + conditions: [ + { equal: { "1:other.one": "foo" } }, + { equal: { "2:other.one": "foo2", "3:other.two": "bar" } }, + { equal: { "another.three": "baz2" } }, + ], + }, + }) + }) + }) + + describe("getQueryableFields", () => { + const config = new TestConfiguration() + + beforeAll(async () => { + await config.init() + }) + + it("returns table schema fields and _id", async () => { + const table: Table = await config.api.table.save({ + ...structures.basicTable(), + schema: { + name: { name: "name", type: FieldType.STRING }, + age: { name: "age", type: FieldType.NUMBER }, + }, + }) + + const result = await getQueryableFields(Object.keys(table.schema), table) + expect(result).toEqual(["_id", "name", "age"]) + }) + + it("excludes hidden fields", async () => { + const table: Table = await config.api.table.save({ + ...structures.basicTable(), + schema: { + name: { name: "name", type: FieldType.STRING }, + age: { name: "age", type: FieldType.NUMBER, visible: false }, + }, + }) + + const result = await getQueryableFields(Object.keys(table.schema), table) + expect(result).toEqual(["_id", "name"]) + }) + + it("includes relationship fields", async () => { + const aux: Table = await config.api.table.save({ + ...structures.basicTable(), + name: "auxTable", + schema: { + title: { name: "title", type: FieldType.STRING }, + name: { name: "name", type: FieldType.STRING }, + }, + }) + + const table: Table = await config.api.table.save({ + ...structures.basicTable(), + schema: { + name: { name: "name", type: FieldType.STRING }, + aux: { + name: "aux", + type: FieldType.LINK, + tableId: aux._id!, + relationshipType: RelationshipType.ONE_TO_MANY, + fieldName: "table", + }, + }, + }) + + const result = await config.doInContext(config.appId, () => { + return getQueryableFields(Object.keys(table.schema), table) + }) + expect(result).toEqual([ + "_id", + "name", + "aux.title", + "auxTable.title", + "aux.name", + "auxTable.name", + ]) + }) + + it("excludes hidden relationship fields", async () => { + const aux: Table = await config.api.table.save({ + ...structures.basicTable(), + name: "auxTable", + schema: { + title: { name: "title", type: FieldType.STRING, visible: false }, + name: { name: "name", type: FieldType.STRING, visible: true }, + }, + }) + + const table: Table = await config.api.table.save({ + ...structures.basicTable(), + schema: { + name: { name: "name", type: FieldType.STRING }, + aux: { + name: "aux", + type: FieldType.LINK, + tableId: aux._id!, + relationshipType: RelationshipType.ONE_TO_MANY, + fieldName: "table", + }, + }, + }) + + const result = await config.doInContext(config.appId, () => { + return getQueryableFields(Object.keys(table.schema), table) + }) + expect(result).toEqual(["_id", "name", "aux.name", "auxTable.name"]) + }) + + it("excludes all relationship fields if hidden", async () => { + const aux: Table = await config.api.table.save({ + ...structures.basicTable(), + name: "auxTable", + schema: { + title: { name: "title", type: FieldType.STRING, visible: false }, + name: { name: "name", type: FieldType.STRING, visible: true }, + }, + }) + + const table: Table = await config.api.table.save({ + ...structures.basicTable(), + schema: { + name: { name: "name", type: FieldType.STRING }, + aux: { + name: "aux", + type: FieldType.LINK, + tableId: aux._id!, + relationshipType: RelationshipType.ONE_TO_MANY, + fieldName: "table", + visible: false, + }, + }, + }) + + const result = await config.doInContext(config.appId, () => { + return getQueryableFields(Object.keys(table.schema), table) + }) + expect(result).toEqual(["_id", "name"]) + }) + + describe("nested relationship", () => { + describe("one-to-many", () => { + let table: Table, aux1: Table, aux2: Table + + beforeAll(async () => { + const { _id: aux1Id } = await config.api.table.save({ + ...structures.basicTable(), + name: "aux1Table", + schema: { + name: { name: "name", type: FieldType.STRING }, + }, + }) + const { _id: aux2Id } = await config.api.table.save({ + ...structures.basicTable(), + name: "aux2Table", + schema: { + title: { name: "title", type: FieldType.STRING }, + aux1_1: { + name: "aux1_1", + type: FieldType.LINK, + tableId: aux1Id!, + relationshipType: RelationshipType.ONE_TO_MANY, + fieldName: "aux2_1", + }, + aux1_2: { + name: "aux1_2", + type: FieldType.LINK, + tableId: aux1Id!, + relationshipType: RelationshipType.ONE_TO_MANY, + fieldName: "aux2_2", + }, + }, + }) + + const { _id: tableId } = await config.api.table.save({ + ...structures.basicTable(), + schema: { + name: { name: "name", type: FieldType.STRING }, + aux1: { + name: "aux1", + type: FieldType.LINK, + tableId: aux1Id!, + relationshipType: RelationshipType.ONE_TO_MANY, + fieldName: "table", + }, + aux2: { + name: "aux2", + type: FieldType.LINK, + tableId: aux2Id!, + relationshipType: RelationshipType.ONE_TO_MANY, + fieldName: "table", + }, + }, + }) + + // We need to refech them to get the updated foreign keys + aux1 = await config.api.table.get(aux1Id!) + aux2 = await config.api.table.get(aux2Id!) + table = await config.api.table.get(tableId!) + }) + + it("includes nested relationship fields from main table", async () => { + const result = await config.doInContext(config.appId, () => { + return getQueryableFields(Object.keys(table.schema), table) + }) + expect(result).toEqual([ + "_id", + "name", + // aux1 primitive props + "aux1.name", + "aux1Table.name", + + // aux2 primitive props + "aux2.title", + "aux2Table.title", + ]) + }) + + it("includes nested relationship fields from aux 1 table", async () => { + const result = await config.doInContext(config.appId, () => { + return getQueryableFields(Object.keys(aux1.schema), aux1) + }) + expect(result).toEqual([ + "_id", + "name", + + // aux2_1 primitive props + "aux2_1.title", + "aux2Table.title", + + // aux2_2 primitive props + "aux2_2.title", + "aux2Table.title", + + // table primitive props + "table.name", + "TestTable.name", + ]) + }) + + it("includes nested relationship fields from aux 2 table", async () => { + const result = await config.doInContext(config.appId, () => { + return getQueryableFields(Object.keys(aux2.schema), aux2) + }) + expect(result).toEqual([ + "_id", + "title", + + // aux1_1 primitive props + "aux1_1.name", + "aux1Table.name", + + // aux1_2 primitive props + "aux1_2.name", + "aux1Table.name", + + // table primitive props + "table.name", + "TestTable.name", + ]) + }) + }) + + describe("many-to-many", () => { + let table: Table, aux: Table + + beforeAll(async () => { + const { _id: auxId } = await config.api.table.save({ + ...structures.basicTable(), + name: "auxTable", + schema: { + title: { name: "title", type: FieldType.STRING }, + }, + }) + + const { _id: tableId } = await config.api.table.save({ + ...structures.basicTable(), + schema: { + name: { name: "name", type: FieldType.STRING }, + aux: { + name: "aux", + type: FieldType.LINK, + tableId: auxId!, + relationshipType: RelationshipType.MANY_TO_MANY, + fieldName: "table", + }, + }, + }) + + // We need to refech them to get the updated foreign keys + aux = await config.api.table.get(auxId!) + table = await config.api.table.get(tableId!) + }) + + it("includes nested relationship fields from main table", async () => { + const result = await config.doInContext(config.appId, () => { + return getQueryableFields(Object.keys(table.schema), table) + }) + expect(result).toEqual([ + "_id", + "name", + + // deep 1 aux primitive props + "aux.title", + "auxTable.title", + ]) + }) + + it("includes nested relationship fields from aux table", async () => { + const result = await config.doInContext(config.appId, () => { + return getQueryableFields(Object.keys(aux.schema), aux) + }) + expect(result).toEqual([ + "_id", + "title", + + // deep 1 dependency primitive props + "table.name", + "TestTable.name", + ]) + }) + }) + }) + }) +}) diff --git a/packages/server/src/utilities/rowProcessor/index.ts b/packages/server/src/utilities/rowProcessor/index.ts index 62a3b2dd74..4b2fd83882 100644 --- a/packages/server/src/utilities/rowProcessor/index.ts +++ b/packages/server/src/utilities/rowProcessor/index.ts @@ -26,8 +26,13 @@ import { processOutputBBReferences, } from "./bbReferenceProcessor" import { isExternalTableID } from "../../integrations/utils" -import { helpers } from "@budibase/shared-core" +import { + helpers, + PROTECTED_EXTERNAL_COLUMNS, + PROTECTED_INTERNAL_COLUMNS, +} from "@budibase/shared-core" import { processString } from "@budibase/string-templates" +import { isUserMetadataTable } from "../../api/controllers/row/utils" export * from "./utils" export * from "./attachments" @@ -53,9 +58,9 @@ export async function processAutoColumn( row: Row, opts?: AutoColumnProcessingOpts ) { - let noUser = !userId - let isUserTable = table._id === InternalTables.USER_METADATA - let now = new Date().toISOString() + const noUser = !userId + const isUserTable = table._id === InternalTables.USER_METADATA + const now = new Date().toISOString() // if a row doesn't have a revision then it doesn't exist yet const creating = !row._rev // check its not user table, or whether any of the processing options have been disabled @@ -111,7 +116,7 @@ async function processDefaultValues(table: Table, row: Row) { ctx.user = user } - for (let [key, schema] of Object.entries(table.schema)) { + for (const [key, schema] of Object.entries(table.schema)) { if ("default" in schema && schema.default != null && row[key] == null) { const processed = await processString(schema.default, ctx) @@ -165,10 +170,10 @@ export async function inputProcessing( row: Row, opts?: AutoColumnProcessingOpts ) { - let clonedRow = cloneDeep(row) + const clonedRow = cloneDeep(row) const dontCleanseKeys = ["type", "_id", "_rev", "tableId"] - for (let [key, value] of Object.entries(clonedRow)) { + for (const [key, value] of Object.entries(clonedRow)) { const field = table.schema[key] // cleanse fields that aren't in the schema if (!field) { @@ -268,13 +273,13 @@ export async function outputProcessing( } // process complex types: attachments, bb references... - for (let [property, column] of Object.entries(table.schema)) { + for (const [property, column] of Object.entries(table.schema)) { if ( column.type === FieldType.ATTACHMENTS || column.type === FieldType.ATTACHMENT_SINGLE || column.type === FieldType.SIGNATURE_SINGLE ) { - for (let row of enriched) { + for (const row of enriched) { if (row[property] == null) { continue } @@ -299,7 +304,7 @@ export async function outputProcessing( !opts.skipBBReferences && column.type == FieldType.BB_REFERENCE ) { - for (let row of enriched) { + for (const row of enriched) { row[property] = await processOutputBBReferences( row[property], column.subtype @@ -309,14 +314,14 @@ export async function outputProcessing( !opts.skipBBReferences && column.type == FieldType.BB_REFERENCE_SINGLE ) { - for (let row of enriched) { + for (const row of enriched) { row[property] = await processOutputBBReference( row[property], column.subtype ) } } else if (column.type === FieldType.DATETIME && column.timeOnly) { - for (let row of enriched) { + for (const row of enriched) { if (row[property] instanceof Date) { const hours = row[property].getUTCHours().toString().padStart(2, "0") const minutes = row[property] @@ -343,14 +348,36 @@ export async function outputProcessing( )) as Row[] } // remove null properties to match internal API - if (isExternalTableID(table._id!)) { - for (let row of enriched) { - for (let key of Object.keys(row)) { + const isExternal = isExternalTableID(table._id!) + if (isExternal) { + for (const row of enriched) { + for (const key of Object.keys(row)) { if (row[key] === null) { delete row[key] } } } } + + if (!isUserMetadataTable(table._id!)) { + const protectedColumns = isExternal + ? PROTECTED_EXTERNAL_COLUMNS + : PROTECTED_INTERNAL_COLUMNS + + const tableFields = Object.keys(table.schema).filter( + f => table.schema[f].visible !== false + ) + const fields = [...tableFields, ...protectedColumns].map(f => + f.toLowerCase() + ) + for (const row of enriched) { + for (const key of Object.keys(row)) { + if (!fields.includes(key.toLowerCase())) { + delete row[key] + } + } + } + } + return (wasArray ? enriched : enriched[0]) as T } diff --git a/packages/shared-core/src/filters.ts b/packages/shared-core/src/filters.ts index d695859979..1cad32e790 100644 --- a/packages/shared-core/src/filters.ts +++ b/packages/shared-core/src/filters.ts @@ -116,6 +116,20 @@ export const NoEmptyFilterStrings = [ OperatorOptions.In.value, ] as (keyof SearchQueryFields)[] +export function recurseLogicalOperators( + filters: SearchFilters, + fn: (f: SearchFilters) => SearchFilters +) { + for (const logical of Object.values(LogicalOperator)) { + if (filters[logical]) { + filters[logical]!.conditions = filters[logical]!.conditions.map( + condition => fn(condition) + ) + } + } + return filters +} + /** * Removes any fields that contain empty strings that would cause inconsistent * behaviour with how backend tables are filtered (no value means no filter). @@ -148,6 +162,7 @@ export const cleanupQuery = (query: SearchFilters) => { } } } + query = recurseLogicalOperators(query, cleanupQuery) return query } @@ -562,6 +577,7 @@ export function fixupFilterArrays(filters: SearchFilters) { } } } + recurseLogicalOperators(filters, fixupFilterArrays) return filters } diff --git a/yarn.lock b/yarn.lock index a636a62c87..713a1845b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4208,160 +4208,160 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz#bbd0e616b2078cd2d68afc9824d1fadb2f2ffd27" integrity sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ== -"@rollup/rollup-android-arm-eabi@4.19.1": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.1.tgz#7746deb85e4a8fb54fbfda8ac5c102692f102476" - integrity sha512-XzqSg714++M+FXhHfXpS1tDnNZNpgxxuGZWlRG/jSj+VEPmZ0yg6jV4E0AL3uyBKxO8mO3xtOsP5mQ+XLfrlww== +"@rollup/rollup-android-arm-eabi@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.0.tgz#d941173f82f9b041c61b0dc1a2a91dcd06e4b31e" + integrity sha512-WTWD8PfoSAJ+qL87lE7votj3syLavxunWhzCnx3XFxFiI/BA/r3X7MUM8dVrH8rb2r4AiO8jJsr3ZjdaftmnfA== "@rollup/rollup-android-arm64@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz#97255ef6384c5f73f4800c0de91f5f6518e21203" integrity sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA== -"@rollup/rollup-android-arm64@4.19.1": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.1.tgz#93de4d867709d3313794723b5afd91e1e174f906" - integrity sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A== +"@rollup/rollup-android-arm64@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.0.tgz#7e7157c8543215245ceffc445134d9e843ba51c0" + integrity sha512-a1sR2zSK1B4eYkiZu17ZUZhmUQcKjk2/j9Me2IDjk1GHW7LB5Z35LEzj9iJch6gtUfsnvZs1ZNyDW2oZSThrkA== "@rollup/rollup-darwin-arm64@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz#b6dd74e117510dfe94541646067b0545b42ff096" integrity sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w== -"@rollup/rollup-darwin-arm64@4.19.1": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.1.tgz#e41e6a81673260ab196e0f59462b9940a6ac03cd" - integrity sha512-8o6eqeFZzVLia2hKPUZk4jdE3zW7LCcZr+MD18tXkgBBid3lssGVAYuox8x6YHoEPDdDa9ixTaStcmx88lio5Q== +"@rollup/rollup-darwin-arm64@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.0.tgz#f0a18a4fc8dc6eb1e94a51fa2adb22876f477947" + integrity sha512-zOnKWLgDld/svhKO5PD9ozmL6roy5OQ5T4ThvdYZLpiOhEGY+dp2NwUmxK0Ld91LrbjrvtNAE0ERBwjqhZTRAA== "@rollup/rollup-darwin-x64@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz#e07d76de1cec987673e7f3d48ccb8e106d42c05c" integrity sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA== -"@rollup/rollup-darwin-x64@4.19.1": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.1.tgz#2b0a0aef6e8c5317d494cfc9076d7a16b099bdcb" - integrity sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA== +"@rollup/rollup-darwin-x64@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.0.tgz#34b7867613e5cc42d2b85ddc0424228cc33b43f0" + integrity sha512-7doS8br0xAkg48SKE2QNtMSFPFUlRdw9+votl27MvT46vo44ATBmdZdGysOevNELmZlfd+NEa0UYOA8f01WSrg== "@rollup/rollup-linux-arm-gnueabihf@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz#9f1a6d218b560c9d75185af4b8bb42f9f24736b8" integrity sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA== -"@rollup/rollup-linux-arm-gnueabihf@4.19.1": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.1.tgz#e22319deb5367384ef315e66bc6de80d2bf2b3ae" - integrity sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q== +"@rollup/rollup-linux-arm-gnueabihf@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.0.tgz#422b19ff9ae02b05d3395183d1d43b38c7c8be0b" + integrity sha512-pWJsfQjNWNGsoCq53KjMtwdJDmh/6NubwQcz52aEwLEuvx08bzcy6tOUuawAOncPnxz/3siRtd8hiQ32G1y8VA== "@rollup/rollup-linux-arm-musleabihf@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz#53618b92e6ffb642c7b620e6e528446511330549" integrity sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A== -"@rollup/rollup-linux-arm-musleabihf@4.19.1": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.1.tgz#d5dd68f5d7ae21b345a5c87208c94e5c813f54b8" - integrity sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw== +"@rollup/rollup-linux-arm-musleabihf@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.0.tgz#568aa29195ef6fc57ec6ed3f518923764406a8ee" + integrity sha512-efRIANsz3UHZrnZXuEvxS9LoCOWMGD1rweciD6uJQIx2myN3a8Im1FafZBzh7zk1RJ6oKcR16dU3UPldaKd83w== "@rollup/rollup-linux-arm64-gnu@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz#99a7ba5e719d4f053761a698f7b52291cefba577" integrity sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw== -"@rollup/rollup-linux-arm64-gnu@4.19.1": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.1.tgz#1703d3a418d33f8f025acaf93f39ca1efcd5b645" - integrity sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw== +"@rollup/rollup-linux-arm64-gnu@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.0.tgz#22309c8bcba9a73114f69165c72bc94b2fbec085" + integrity sha512-ZrPhydkTVhyeGTW94WJ8pnl1uroqVHM3j3hjdquwAcWnmivjAwOYjTEAuEDeJvGX7xv3Z9GAvrBkEzCgHq9U1w== "@rollup/rollup-linux-arm64-musl@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz#f53db99a45d9bc00ce94db8a35efa7c3c144a58c" integrity sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ== -"@rollup/rollup-linux-arm64-musl@4.19.1": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.1.tgz#3f59c2c6e60f75ce8b1090bd841c555e3bb01f0e" - integrity sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw== +"@rollup/rollup-linux-arm64-musl@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.0.tgz#c93c388af6d33f082894b8a60839d7265b2b9bc5" + integrity sha512-cfaupqd+UEFeURmqNP2eEvXqgbSox/LHOyN9/d2pSdV8xTrjdg3NgOFJCtc1vQ/jEke1qD0IejbBfxleBPHnPw== "@rollup/rollup-linux-powerpc64le-gnu@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz#cbb0837408fe081ce3435cf3730e090febafc9bf" integrity sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA== -"@rollup/rollup-linux-powerpc64le-gnu@4.19.1": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.1.tgz#3f99a0921596a6f539121a312df29af52a205f15" - integrity sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ== +"@rollup/rollup-linux-powerpc64le-gnu@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.0.tgz#493c5e19e395cf3c6bd860c7139c8a903dea72b4" + integrity sha512-ZKPan1/RvAhrUylwBXC9t7B2hXdpb/ufeu22pG2psV7RN8roOfGurEghw1ySmX/CmDDHNTDDjY3lo9hRlgtaHg== "@rollup/rollup-linux-riscv64-gnu@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz#8ed09c1d1262ada4c38d791a28ae0fea28b80cc9" integrity sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg== -"@rollup/rollup-linux-riscv64-gnu@4.19.1": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.1.tgz#c08fb3e629d50d2eac31329347cfc559a1cf81d1" - integrity sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A== +"@rollup/rollup-linux-riscv64-gnu@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.0.tgz#a2eab4346fbe5909165ce99adb935ba30c9fb444" + integrity sha512-H1eRaCwd5E8eS8leiS+o/NqMdljkcb1d6r2h4fKSsCXQilLKArq6WS7XBLDu80Yz+nMqHVFDquwcVrQmGr28rg== "@rollup/rollup-linux-s390x-gnu@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz#938138d3c8e0c96f022252a28441dcfb17afd7ec" integrity sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg== -"@rollup/rollup-linux-s390x-gnu@4.19.1": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.1.tgz#173722cd745779d730d4b24d21386185e0e12de8" - integrity sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q== +"@rollup/rollup-linux-s390x-gnu@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.0.tgz#0bc49a79db4345d78d757bb1b05e73a1b42fa5c3" + integrity sha512-zJ4hA+3b5tu8u7L58CCSI0A9N1vkfwPhWd/puGXwtZlsB5bTkwDNW/+JCU84+3QYmKpLi+XvHdmrlwUwDA6kqw== "@rollup/rollup-linux-x64-gnu@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz#1a7481137a54740bee1ded4ae5752450f155d942" integrity sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w== -"@rollup/rollup-linux-x64-gnu@4.19.1": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.1.tgz#0af2b6541ab0f4954d2c4f96bcdc7947420dd28c" - integrity sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q== +"@rollup/rollup-linux-x64-gnu@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.0.tgz#4fd36a6a41f3406d8693321b13d4f9b7658dd4b9" + integrity sha512-e2hrvElFIh6kW/UNBQK/kzqMNY5mO+67YtEh9OA65RM5IJXYTWiXjX6fjIiPaqOkBthYF1EqgiZ6OXKcQsM0hg== "@rollup/rollup-linux-x64-musl@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz#f1186afc601ac4f4fc25fac4ca15ecbee3a1874d" integrity sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg== -"@rollup/rollup-linux-x64-musl@4.19.1": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.1.tgz#f973f9552744764b221128f7c3629222216ace69" - integrity sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q== +"@rollup/rollup-linux-x64-musl@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.0.tgz#10ebb13bd4469cbad1a5d9b073bd27ec8a886200" + integrity sha512-1vvmgDdUSebVGXWX2lIcgRebqfQSff0hMEkLJyakQ9JQUbLDkEaMsPTLOmyccyC6IJ/l3FZuJbmrBw/u0A0uCQ== "@rollup/rollup-win32-arm64-msvc@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz#ed6603e93636a96203c6915be4117245c1bd2daf" integrity sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA== -"@rollup/rollup-win32-arm64-msvc@4.19.1": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.1.tgz#21ac5ed84d914bc31821fec3dd909f7257cfb17b" - integrity sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA== +"@rollup/rollup-win32-arm64-msvc@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.0.tgz#2fef1a90f1402258ef915ae5a94cc91a5a1d5bfc" + integrity sha512-s5oFkZ/hFcrlAyBTONFY1TWndfyre1wOMwU+6KCpm/iatybvrRgmZVM+vCFwxmC5ZhdlgfE0N4XorsDpi7/4XQ== "@rollup/rollup-win32-ia32-msvc@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz#14e0b404b1c25ebe6157a15edb9c46959ba74c54" integrity sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg== -"@rollup/rollup-win32-ia32-msvc@4.19.1": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.1.tgz#0cfe740063b35dcd5a62c4e243226631a846ce11" - integrity sha512-LdxxcqRVSXi6k6JUrTah1rHuaupoeuiv38du8Mt4r4IPer3kwlTo+RuvfE8KzZ/tL6BhaPlzJ3835i6CxrFIRQ== +"@rollup/rollup-win32-ia32-msvc@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.0.tgz#a18ad47a95c5f264defb60acdd8c27569f816fc1" + integrity sha512-G9+TEqRnAA6nbpqyUqgTiopmnfgnMkR3kMukFBDsiyy23LZvUCpiUwjTRx6ezYCjJODXrh52rBR9oXvm+Fp5wg== "@rollup/rollup-win32-x64-msvc@4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz#5d694d345ce36b6ecf657349e03eb87297e68da4" integrity sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g== -"@rollup/rollup-win32-x64-msvc@4.19.1": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.1.tgz#5f2c40d3f1b53ede80fb4e6964f840c0f8936832" - integrity sha512-2bIrL28PcK3YCqD9anGxDxamxdiJAxA+l7fWIwM5o8UqNy1t3d1NdAweO2XhA0KTDJ5aH1FsuiT5+7VhtHliXg== +"@rollup/rollup-win32-x64-msvc@4.21.0": + version "4.21.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.0.tgz#20c09cf44dcb082140cc7f439dd679fe4bba3375" + integrity sha512-2jsCDZwtQvRhejHLfZ1JY6w6kEuEtfF9nzYsZxzSlNVKDX+DpsDJ+Rbjkm74nvg2rdx0gwBS+IMdvwJuq3S9pQ== "@roxi/routify@2.18.0": version "2.18.0" @@ -5127,6 +5127,11 @@ resolved "https://registry.yarnpkg.com/@spectrum-css/tabs/-/tabs-3.2.12.tgz#9b08f23d5aa881b3441af7757800c7173e5685ff" integrity sha512-rPFUW9SSW4+3/UJ3UrtY2/l3sQvlqB1fqxHLPDjgykvbfrnMejcCTNV4ZrFNHXpE/6+kGnk+yVViSPtWGwJzkA== +"@spectrum-css/tag@3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@spectrum-css/tag/-/tag-3.0.0.tgz#b2e335dc526713b83f3e995e8d1d4fc84a3fc4df" + integrity sha512-a9z7ZTAWPonkWRNY5kxVaO6bxu9de3qUZWJ9Bl1YBlwWc8Fy1L7XqT4Wq3pW+4sktUbUUqqPYPIXK9xEFDofEw== + "@spectrum-css/tags@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@spectrum-css/tags/-/tags-3.0.2.tgz#5bf35fb79c97cd9344de485bd4626ad5b9f07757" @@ -5874,11 +5879,11 @@ integrity sha512-7GgtHCs/QZrBrDzgIJnQtuSvhFSwhyYSI2uafSwZoNt1iOGhEN5fwNrQMjtONyHm9+/LoA4453jH0CMYcr06Pg== "@types/node@>=8.1.0": - version "22.0.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.0.0.tgz#04862a2a71e62264426083abe1e27e87cac05a30" - integrity sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw== + version "22.4.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.4.2.tgz#55fefb1c3dba2ecd7eb76738c6b80da75760523f" + integrity sha512-nAvM3Ey230/XzxtyDcJ+VjvlzpzoHwLsF7JaDRfoI0ytO0mVheerNmM45CtA0yOILXwXXxOrcUWH3wltX+7PSw== dependencies: - undici-types "~6.11.1" + undici-types "~6.19.2" "@types/node@^18.11.18": version "18.19.10" @@ -6878,39 +6883,34 @@ acorn-import-assertions@^1.9.0: resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== -acorn-jsx-walk@2.0.0: +acorn-jsx-walk@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/acorn-jsx-walk/-/acorn-jsx-walk-2.0.0.tgz#a5ed648264e68282d7c2aead80216bfdf232573a" integrity sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA== -acorn-jsx@5.3.2, acorn-jsx@^5.3.2: +acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-loose@8.4.0: +acorn-loose@^8.4.0: version "8.4.0" resolved "https://registry.yarnpkg.com/acorn-loose/-/acorn-loose-8.4.0.tgz#26d3e219756d1e180d006f5bcc8d261a28530f55" integrity sha512-M0EUka6rb+QC4l9Z3T0nJEzNOO7JcoJlYMrBlyBCiFSXRyxjLKayd4TbQs2FDRWQU1h9FR7QVNHt+PEaoNL5rQ== dependencies: acorn "^8.11.0" -acorn-walk@8.3.3, acorn-walk@^8.0.2, acorn-walk@^8.1.1, acorn-walk@^8.2.0: - version "8.3.3" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e" - integrity sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw== - dependencies: - acorn "^8.11.0" - acorn-walk@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn@8.12.1, acorn@^8.12.0: - version "8.12.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" - integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== +acorn-walk@^8.0.2, acorn-walk@^8.1.1, acorn-walk@^8.2.0, acorn-walk@^8.3.3: + version "8.3.3" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e" + integrity sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw== + dependencies: + acorn "^8.11.0" acorn@^5.2.1, acorn@^5.7.3: version "5.7.4" @@ -6927,6 +6927,11 @@ acorn@^8.1.0, acorn@^8.10.0, acorn@^8.11.0, acorn@^8.11.3, acorn@^8.2.4, acorn@^ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c" integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw== +acorn@^8.12.0, acorn@^8.12.1: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + add-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" @@ -6979,16 +6984,6 @@ ajv-formats@^2.0.2: dependencies: ajv "^8.0.0" -ajv@8.17.1: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" - integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== - dependencies: - fast-deep-equal "^3.1.3" - fast-uri "^3.0.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - ajv@^6.12.3, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -7009,6 +7004,16 @@ ajv@^8.0.0, ajv@^8.1.0, ajv@^8.4.0: require-from-string "^2.0.2" uri-js "^4.2.2" +ajv@^8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" @@ -8690,11 +8695,6 @@ combos@^0.2.0: resolved "https://registry.yarnpkg.com/combos/-/combos-0.2.0.tgz#dc31c5a899b42293d55fe19c064d3e6e207ba4f7" integrity sha512-Z6YfvgiTCERWJTj3wQiXamFhssdvz1n4ok447rS330lw3uL72WAx8IvrLU7xiE71uyb5WF8JEP+BWB5KhOoGeg== -commander@12.1.0: - version "12.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" - integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== - commander@6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.0.tgz#b990bfb8ac030aedc6d11bc04d1488ffef56db75" @@ -8710,6 +8710,11 @@ commander@^11.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-11.1.0.tgz#62fdce76006a68e5c1ab3314dc92e800eb83d906" integrity sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ== +commander@^12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + commander@^2.16.0, commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@^2.5.0, commander@^2.7.1, commander@^2.8.1: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -9440,9 +9445,9 @@ dayjs@^1.10.8: integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg== dayjs@^1.8.15: - version "1.11.12" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.12.tgz#5245226cc7f40a15bf52e0b99fd2a04669ccac1d" - integrity sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg== + version "1.11.13" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" + integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== dc-polyfill@^0.1.2: version "0.1.3" @@ -9780,32 +9785,32 @@ depd@^1.1.0, depd@~1.1.2: integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== dependency-cruiser@^16.3.7: - version "16.3.10" - resolved "https://registry.yarnpkg.com/dependency-cruiser/-/dependency-cruiser-16.3.10.tgz#fe26a50d5e10a4496bc2b70d027fca6ded48814f" - integrity sha512-WkCnibHBfvaiaQ+S46LZ6h4AR6oj42Vsf5/0Vgtrwdwn7ZekMJdZ/ALoTwNp/RaGlKW+MbV/fhSZOvmhAWVWzQ== + version "16.4.0" + resolved "https://registry.yarnpkg.com/dependency-cruiser/-/dependency-cruiser-16.4.0.tgz#a1b7d452acddf05045ae4f7942a2e9337aedad35" + integrity sha512-la/NnD23m6esCox8KMiZ/pcmtec6G/r7LgnJvkBepcErdzlGaxWnyaxtpoYB3fgODrU/7E2u81/nX5FNu5zfyw== dependencies: - acorn "8.12.1" - acorn-jsx "5.3.2" - acorn-jsx-walk "2.0.0" - acorn-loose "8.4.0" - acorn-walk "8.3.3" - ajv "8.17.1" - commander "12.1.0" - enhanced-resolve "5.17.1" - ignore "5.3.1" + acorn "^8.12.1" + acorn-jsx "^5.3.2" + acorn-jsx-walk "^2.0.0" + acorn-loose "^8.4.0" + acorn-walk "^8.3.3" + ajv "^8.17.1" + commander "^12.1.0" + enhanced-resolve "^5.17.1" + ignore "^5.3.2" interpret "^3.1.1" - is-installed-globally "1.0.0" - json5 "2.2.3" - memoize "10.0.0" - picocolors "1.0.1" - picomatch "4.0.2" - prompts "2.4.2" + is-installed-globally "^1.0.0" + json5 "^2.2.3" + memoize "^10.0.0" + picocolors "^1.0.1" + picomatch "^4.0.2" + prompts "^2.4.2" rechoir "^0.8.0" - safe-regex "2.1.1" + safe-regex "^2.1.1" semver "^7.6.3" - teamcity-service-messages "0.1.14" - tsconfig-paths-webpack-plugin "4.1.0" - watskeburt "4.1.0" + teamcity-service-messages "^0.1.14" + tsconfig-paths-webpack-plugin "^4.1.0" + watskeburt "^4.1.0" dependency-tree@^9.0.0: version "9.0.0" @@ -10358,23 +10363,10 @@ electron-to-chromium@^1.4.284: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.317.tgz#9a3d38a1a37f26a417d3d95dafe198ff11ed072b" integrity sha512-JhCRm9v30FMNzQSsjl4kXaygU+qHBD0Yh7mKxyjmF0V8VwYVB6qpBRX28GyAucrM9wDCpSUctT6FpMUQxbyKuA== -elliptic@^6.5.3: - version "6.5.4" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" - integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - -elliptic@^6.5.5: - version "6.5.5" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.5.tgz#c715e09f78b6923977610d4c2346d6ce22e6dded" - integrity sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw== +elliptic@^6.5.3, elliptic@^6.5.5: + version "6.5.7" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b" + integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q== dependencies: bn.js "^4.11.9" brorand "^1.1.0" @@ -10477,7 +10469,7 @@ engine.io@~6.5.2: engine.io-parser "~5.2.1" ws "~8.17.1" -enhanced-resolve@5.17.1, enhanced-resolve@^5.7.0: +enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0: version "5.17.1" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== @@ -12852,9 +12844,9 @@ husky@^8.0.3: integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== husky@^9.1.4: - version "9.1.4" - resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.4.tgz#926fd19c18d345add5eab0a42b2b6d9a80259b34" - integrity sha512-bho94YyReb4JV7LYWRWxZ/xr6TtOTt8cMfmQ39MQYJ7f/YE268s3GdghGwi+y4zAeqewE5zYLvuhV0M0ijsDEA== + version "9.1.5" + resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.5.tgz#2b6edede53ee1adbbd3a3da490628a23f5243b83" + integrity sha512-rowAVRUBfI0b4+niA4SJMhfQwc107VLkBUgEYYAOQAbqDCnra1nYh83hF/MDmhYs9t9n1E3DuKOrs2LYNC+0Ag== ical-generator@4.1.0: version "4.1.0" @@ -12928,17 +12920,12 @@ ignore-walk@^6.0.0: dependencies: minimatch "^7.4.2" -ignore@5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" - integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== - ignore@^5.0.4, ignore@^5.2.0, ignore@^5.2.4: version "5.3.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== -ignore@^5.3.1: +ignore@^5.3.1, ignore@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== @@ -13389,14 +13376,6 @@ is-immutable-type@^4.0.0: ts-api-utils "^1.3.0" ts-declaration-location "^1.0.0" -is-installed-globally@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-1.0.0.tgz#08952c43758c33d815692392f7f8437b9e436d5a" - integrity sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ== - dependencies: - global-directory "^4.0.1" - is-path-inside "^4.0.0" - is-installed-globally@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" @@ -13405,6 +13384,14 @@ is-installed-globally@^0.4.0: global-dirs "^3.0.0" is-path-inside "^3.0.2" +is-installed-globally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-1.0.0.tgz#08952c43758c33d815692392f7f8437b9e436d5a" + integrity sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ== + dependencies: + global-directory "^4.0.1" + is-path-inside "^4.0.0" + is-interactive@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" @@ -14516,11 +14503,6 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== -json5@2.2.3, json5@^2.2.1, json5@^2.2.2, json5@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - json5@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" @@ -14528,6 +14510,11 @@ json5@^1.0.2: dependencies: minimist "^1.2.0" +json5@^2.2.1, json5@^2.2.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + jsonc-parser@3.2.0, jsonc-parser@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" @@ -15872,7 +15859,7 @@ memdown@^5.1.0: ltgt "~2.2.0" safe-buffer "~5.2.0" -memoize@10.0.0: +memoize@^10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/memoize/-/memoize-10.0.0.tgz#43fa66b2022363c7c50cf5dfab732a808a3d7147" integrity sha512-H6cBLgsi6vMWOcCpvVCdFFnl3kerEXbrYh9q+lY6VXvQSmM6CkmV08VOwT+WE2tzIEqRPFfAq3fm4v/UIW6mSA== @@ -17860,26 +17847,26 @@ phin@^2.9.1: resolved "https://registry.yarnpkg.com/phin/-/phin-2.9.3.tgz#f9b6ac10a035636fb65dfc576aaaa17b8743125c" integrity sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA== -picocolors@1.0.1, picocolors@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" - integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== - picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" - integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== +picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" + integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== + pify@5.0.0, pify@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" @@ -18367,10 +18354,10 @@ postcss@^8.1.7, postcss@^8.2.9, postcss@^8.3.11, postcss@^8.4.12, postcss@^8.4.2 picocolors "^1.0.0" source-map-js "^1.2.0" -postcss@^8.4.39: - version "8.4.40" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.40.tgz#eb81f2a4dd7668ed869a6db25999e02e9ad909d8" - integrity sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q== +postcss@^8.4.41: + version "8.4.41" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.41.tgz#d6104d3ba272d882fe18fc07d15dc2da62fa2681" + integrity sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ== dependencies: nanoid "^3.3.7" picocolors "^1.0.1" @@ -18407,9 +18394,9 @@ posthog-js@^1.118.0: preact "^10.19.3" posthog-js@^1.13.4: - version "1.150.1" - resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.150.1.tgz#ce2e0aa0dc30369bf1b1b9a38b9fbf25e5c01ba0" - integrity sha512-jHSnqtAWkUQkiedQgHpD00+z8RUF0loDq7ORakBKfQjdntTJIEk16ewqTNRxnpE86guWDoy2J3iAqLgAYfFaLA== + version "1.157.2" + resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.157.2.tgz#dc2515818ead408aefb900e90c535fb57beb1f59" + integrity sha512-ATYKGs+Q51u26nHHhrhWNh1whqFm7j/rwQQYw+y6/YzNmRlo+YsqrGZji9nqXb9/4fo0ModDr+ZmuOI3hKkUXA== dependencies: fflate "^0.4.8" preact "^10.19.3" @@ -18837,7 +18824,7 @@ promise.series@^0.2.0: resolved "https://registry.yarnpkg.com/promise.series/-/promise.series-0.2.0.tgz#2cc7ebe959fc3a6619c04ab4dbdc9e452d864bbd" integrity sha512-VWQJyU2bcDTgZw8kpfBpB/ejZASlCrzwz5f2hjb/zlujOEB4oeiAhHygAWq8ubsX2GVkD4kCU5V2dwOTaCY5EQ== -prompts@2.4.2, prompts@^2.0.1: +prompts@^2.0.1, prompts@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== @@ -19003,9 +18990,9 @@ q@^1.1.2: integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== qs@^6.10.3: - version "6.12.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.3.tgz#e43ce03c8521b9c7fd7f1f13e514e5ca37727754" - integrity sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ== + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== dependencies: side-channel "^1.0.6" @@ -19661,9 +19648,9 @@ rimraf@^4.4.1: glob "^9.2.0" rimraf@^5.0.7: - version "5.0.9" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.9.tgz#c3baa1b886eadc2ec7981a06a593c3d01134ffe9" - integrity sha512-3i7b8OcswU6CpU8Ej89quJD4O98id7TtVM5U4Mybh84zQXdrFmDLouWBEEaD/QfO3gDDfH+AGFCGsR7kngzQnA== + version "5.0.10" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c" + integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== dependencies: glob "^10.3.7" @@ -19829,29 +19816,29 @@ rollup@^3.27.1: optionalDependencies: fsevents "~2.3.2" -rollup@^4.13.0: - version "4.19.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.19.1.tgz#21d865cd60d4a325172ce8b082e60caccd97b309" - integrity sha512-K5vziVlg7hTpYfFBI+91zHBEMo6jafYXpkMlqZjg7/zhIG9iHqazBf4xz9AVdjS9BruRn280ROqLI7G3OFRIlw== +rollup@^4.20.0: + version "4.21.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.21.0.tgz#28db5f5c556a5180361d35009979ccc749560b9d" + integrity sha512-vo+S/lfA2lMS7rZ2Qoubi6I5hwZwzXeUIctILZLbHI+laNtvhhOIon2S1JksA5UEDQ7l3vberd0fxK44lTYjbQ== dependencies: "@types/estree" "1.0.5" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.19.1" - "@rollup/rollup-android-arm64" "4.19.1" - "@rollup/rollup-darwin-arm64" "4.19.1" - "@rollup/rollup-darwin-x64" "4.19.1" - "@rollup/rollup-linux-arm-gnueabihf" "4.19.1" - "@rollup/rollup-linux-arm-musleabihf" "4.19.1" - "@rollup/rollup-linux-arm64-gnu" "4.19.1" - "@rollup/rollup-linux-arm64-musl" "4.19.1" - "@rollup/rollup-linux-powerpc64le-gnu" "4.19.1" - "@rollup/rollup-linux-riscv64-gnu" "4.19.1" - "@rollup/rollup-linux-s390x-gnu" "4.19.1" - "@rollup/rollup-linux-x64-gnu" "4.19.1" - "@rollup/rollup-linux-x64-musl" "4.19.1" - "@rollup/rollup-win32-arm64-msvc" "4.19.1" - "@rollup/rollup-win32-ia32-msvc" "4.19.1" - "@rollup/rollup-win32-x64-msvc" "4.19.1" + "@rollup/rollup-android-arm-eabi" "4.21.0" + "@rollup/rollup-android-arm64" "4.21.0" + "@rollup/rollup-darwin-arm64" "4.21.0" + "@rollup/rollup-darwin-x64" "4.21.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.21.0" + "@rollup/rollup-linux-arm-musleabihf" "4.21.0" + "@rollup/rollup-linux-arm64-gnu" "4.21.0" + "@rollup/rollup-linux-arm64-musl" "4.21.0" + "@rollup/rollup-linux-powerpc64le-gnu" "4.21.0" + "@rollup/rollup-linux-riscv64-gnu" "4.21.0" + "@rollup/rollup-linux-s390x-gnu" "4.21.0" + "@rollup/rollup-linux-x64-gnu" "4.21.0" + "@rollup/rollup-linux-x64-musl" "4.21.0" + "@rollup/rollup-win32-arm64-msvc" "4.21.0" + "@rollup/rollup-win32-ia32-msvc" "4.21.0" + "@rollup/rollup-win32-x64-msvc" "4.21.0" fsevents "~2.3.2" rollup@^4.9.4, rollup@^4.9.6: @@ -19954,7 +19941,7 @@ safe-regex-test@^1.0.3: es-errors "^1.3.0" is-regex "^1.1.4" -safe-regex@2.1.1: +safe-regex@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-2.1.1.tgz#f7128f00d056e2fe5c11e81a1324dd974aadced2" integrity sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A== @@ -21345,7 +21332,7 @@ tarn@^3.0.1, tarn@^3.0.2: resolved "https://registry.yarnpkg.com/tarn/-/tarn-3.0.2.tgz#73b6140fbb881b71559c4f8bfde3d9a4b3d27693" integrity sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ== -teamcity-service-messages@0.1.14: +teamcity-service-messages@^0.1.14: version "0.1.14" resolved "https://registry.yarnpkg.com/teamcity-service-messages/-/teamcity-service-messages-0.1.14.tgz#193d420a5e4aef8e5e50b8c39e7865e08fbb5d8a" integrity sha512-29aQwaHqm8RMX74u2o/h1KbMLP89FjNiMxD9wbF2BbWOnbM+q+d1sCEC+MqCc4QW3NJykn77OMpTFw/xTHIc0w== @@ -21557,9 +21544,9 @@ tinypool@^0.4.0: integrity sha512-2ksntHOKf893wSAH4z/+JbPpi92esw8Gn9N2deXX+B0EO92hexAVI9GIZZPx7P5aYo5KULfeOSt3kMOmSOy6uA== tinypool@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.0.tgz#a68965218e04f4ad9de037d2a1cd63cda9afb238" - integrity sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ== + version "1.0.1" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.1.tgz#c64233c4fac4304e109a64340178760116dbe1fe" + integrity sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA== tinyrainbow@^1.2.0: version "1.2.0" @@ -21787,7 +21774,7 @@ tsconfck@^3.0.3: resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.1.tgz#c7284913262c293b43b905b8b034f524de4a3162" integrity sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ== -tsconfig-paths-webpack-plugin@4.1.0: +tsconfig-paths-webpack-plugin@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz#3c6892c5e7319c146eee1e7302ed9e6f2be4f763" integrity sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA== @@ -22102,10 +22089,10 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -undici-types@~6.11.1: - version "6.11.1" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.11.1.tgz#432ea6e8efd54a48569705a699e62d8f4981b197" - integrity sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ== +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== undici@^4.14.1: version "4.16.0" @@ -22469,13 +22456,13 @@ vite-tsconfig-paths@^4.3.2: fsevents "~2.3.2" vite@^5.0.0: - version "5.3.5" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.5.tgz#b847f846fb2b6cb6f6f4ed50a830186138cb83d8" - integrity sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA== + version "5.4.2" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.2.tgz#8acb6ec4bfab823cdfc1cb2d6c53ed311bc4e47e" + integrity sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA== dependencies: esbuild "^0.21.3" - postcss "^8.4.39" - rollup "^4.13.0" + postcss "^8.4.41" + rollup "^4.20.0" optionalDependencies: fsevents "~2.3.3" @@ -22587,7 +22574,7 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -watskeburt@4.1.0: +watskeburt@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/watskeburt/-/watskeburt-4.1.0.tgz#3c0227669be646a97424b631164b1afe3d4d5344" integrity sha512-KkY5H51ajqy9HYYI+u9SIURcWnqeVVhdH0I+ab6aXPGHfZYxgRCwnR6Lm3+TYB6jJVt5jFqw4GAKmwf1zHmGQw== @@ -22610,9 +22597,9 @@ web-streams-polyfill@^3.2.1: integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== web-vitals@^4.0.1: - version "4.2.2" - resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.2.tgz#e883245180b95e175eb75a5ca8903b1a11597d7a" - integrity sha512-nYfoOqb4EmElljyXU2qdeE76KsvoHdftQKY4DzA9Aw8DervCg2bG634pHLrJ/d6+B4mE3nWTSJv8Mo7B2mbZkw== + version "4.2.3" + resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.3.tgz#270c4baecfbc6ec6fc15da1989e465e5f9b94fb7" + integrity sha512-/CFAm1mNxSmOj6i0Co+iGFJ58OS4NRGVP+AWS/l509uIK5a1bSoIVaHz/ZumpHTfHSZBpgrJ+wjfpAOrTHok5Q== webfinger@^0.4.2: version "0.4.2"