1
0
Fork 0
mirror of synced 2024-08-23 05:51:29 +12:00

Merge branch 'master' into fix/grid-null-dereference-error

This commit is contained in:
Martin McKeaveney 2024-05-13 16:53:00 +01:00 committed by GitHub
commit 0fccca6d76
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 263 additions and 1274 deletions

View file

@ -1,5 +1,5 @@
{
"version": "2.25.0",
"version": "2.26.1",
"npmClient": "yarn",
"packages": [
"packages/*",

View file

@ -73,7 +73,6 @@
"chance": "1.1.8",
"ioredis-mock": "8.9.0",
"jest": "29.7.0",
"jest-environment-node": "29.7.0",
"jest-serial-runner": "1.2.1",
"pino-pretty": "10.0.0",
"pouchdb-adapter-memory": "7.2.2",

View file

@ -93,7 +93,6 @@
"identity-obj-proxy": "^3.0.0",
"jest": "29.7.0",
"jsdom": "^21.1.1",
"ncp": "^2.0.0",
"svelte-jester": "^1.3.2",
"vite": "^4.5.0",
"vite-plugin-static-copy": "^0.17.0",

View file

@ -23,6 +23,7 @@
faQuestionCircle,
faCircleCheck,
faGear,
faRectangleList,
} from "@fortawesome/free-solid-svg-icons"
import { faGithub, faDiscord } from "@fortawesome/free-brands-svg-icons"
@ -37,6 +38,7 @@
faFileArrowUp,
faChevronLeft,
faCircleInfo,
faRectangleList,
// -- Required for easyMDE use in the builder.
faBold,

View file

@ -4,6 +4,7 @@
import { isEnabled, TENANT_FEATURE_FLAGS } from "helpers/featureFlags"
import { licensing } from "stores/portal"
import { isPremiumOrAbove } from "helpers/planTitle"
import { ChangelogURL } from "constants"
$: premiumOrAboveLicense = isPremiumOrAbove($licensing?.license?.plan?.type)
@ -30,6 +31,13 @@
<Body size="S">Help docs</Body>
</a>
<div class="divider" />
<a target="_blank" href={ChangelogURL}>
<div class="icon">
<FontAwesomeIcon name="fa-solid fa-rectangle-list" />
</div>
<Body size="S">Changelog</Body>
</a>
<div class="divider" />
<a
target="_blank"
href="https://github.com/Budibase/budibase/discussions"

View file

@ -7,10 +7,12 @@
Body,
Button,
StatusLight,
Link,
} from "@budibase/bbui"
import { appStore, initialise } from "stores/builder"
import { API } from "api"
import RevertModalVersionSelect from "./RevertModalVersionSelect.svelte"
import { ChangelogURL } from "constants"
export function show() {
updateModal.show()
@ -106,6 +108,10 @@
latest version available.
</Body>
{/if}
<Body size="S">
Find the changelog for the latest release
<Link href={ChangelogURL} target="_blank">here</Link>
</Body>
{#if revertAvailable}
<Body size="S">
You can revert this app to version

View file

@ -49,17 +49,20 @@
},
]
$: tables = findAllMatchingComponents($selectedScreen?.props, component =>
component._component.endsWith("table")
)
$: tableBlocks = findAllMatchingComponents(
$: components = findAllMatchingComponents(
$selectedScreen?.props,
component => component._component.endsWith("tableblock")
component => {
const type = component._component
return (
type.endsWith("/table") ||
type.endsWith("/tableblock") ||
type.endsWith("/gridblock")
)
}
)
$: components = tables.concat(tableBlocks)
$: componentOptions = components.map(table => ({
label: table._instanceName,
value: table._component.includes("tableblock")
value: table._component.endsWith("/tableblock")
? `${table._id}-table`
: table._id,
}))
@ -69,6 +72,7 @@
$: selectedTable = components.find(
component => component._id === selectedTableId
)
$: parameters.rows = `{{ literal [${parameters.tableComponentId}].[selectedRows] }}`
onMount(() => {
if (!parameters.type) {

View file

@ -70,3 +70,5 @@ export const PlanModel = {
PER_USER: "perUser",
DAY_PASS: "dayPass",
}
export const ChangelogURL = "https://docs.budibase.com/changelog"

View file

@ -166,10 +166,16 @@ const automationActions = store => ({
await store.actions.save(newAutomation)
},
test: async (automation, testData) => {
const result = await API.testAutomation({
automationId: automation?._id,
testData,
})
let result
try {
result = await API.testAutomation({
automationId: automation?._id,
testData,
})
} catch (err) {
const message = err.message || err.status || JSON.stringify(err)
throw `Automation test failed - ${message}`
}
if (!result?.trigger && !result?.steps?.length) {
if (result?.err?.code === "usage_limit_exceeded") {
throw "You have exceeded your automation quota"

View file

@ -7017,10 +7017,22 @@
]
}
],
"context": {
"type": "schema",
"scope": "local"
},
"context": [
{
"type": "schema",
"scope": "local"
},
{
"type": "static",
"values": [
{
"label": "Selected rows",
"key": "selectedRows",
"type": "array"
}
]
}
],
"actions": ["RefreshDatasource"]
},
"bbreferencefield": {

View file

@ -1,8 +1,8 @@
<script>
// NOTE: this is not a block - it's just named as such to avoid confusing users,
// because it functions similarly to one
import { getContext } from "svelte"
import { get } from "svelte/store"
import { getContext, onMount } from "svelte"
import { get, derived, readable } from "svelte/store"
import { Grid } from "@budibase/frontend-core"
// table is actually any datasource, but called table for legacy compatibility
@ -19,7 +19,6 @@
export let columns = null
export let onRowClick = null
export let buttons = null
export let repeat = null
const context = getContext("context")
const component = getContext("component")
@ -36,17 +35,18 @@
} = getContext("sdk")
let grid
let gridContext
$: columnWhitelist = parsedColumns
?.filter(col => col.active)
?.map(col => col.field)
$: parsedColumns = getParsedColumns(columns)
$: columnWhitelist = parsedColumns.filter(x => x.active).map(x => x.field)
$: schemaOverrides = getSchemaOverrides(parsedColumns)
$: enrichedButtons = enrichButtons(buttons)
$: parsedColumns = getParsedColumns(columns)
$: selectedRows = deriveSelectedRows(gridContext)
$: data = { selectedRows: $selectedRows }
$: actions = [
{
type: ActionTypes.RefreshDatasource,
callback: () => grid?.getContext()?.rows.actions.refreshData(),
callback: () => gridContext?.rows.actions.refreshData(),
metadata: { dataSource: table },
},
]
@ -68,12 +68,14 @@
// Parses columns to fix older formats
const getParsedColumns = columns => {
if (!columns?.length) {
return []
}
// If the first element has an active key all elements should be in the new format
if (columns?.length && columns[0]?.active !== undefined) {
if (columns[0].active !== undefined) {
return columns
}
return columns?.map(column => ({
return columns.map(column => ({
label: column.displayName || column.name,
field: column.name,
active: true,
@ -82,7 +84,7 @@
const getSchemaOverrides = columns => {
let overrides = {}
columns?.forEach(column => {
columns.forEach(column => {
overrides[column.field] = {
displayName: column.label,
}
@ -109,6 +111,23 @@
}))
}
const deriveSelectedRows = gridContext => {
if (!gridContext) {
return readable([])
}
return derived(
[gridContext.selectedRows, gridContext.rowLookupMap, gridContext.rows],
([$selectedRows, $rowLookupMap, $rows]) => {
return Object.entries($selectedRows || {})
.filter(([_, selected]) => selected)
.map(([rowId]) => {
const idx = $rowLookupMap[rowId]
return gridContext.rows.actions.cleanRow($rows[idx])
})
}
)
}
const getSanitisedStyles = styles => {
return {
...styles,
@ -118,40 +137,44 @@
},
}
}
onMount(() => {
gridContext = grid.getContext()
})
</script>
<div use:styleable={styles} class:in-builder={$builderStore.inBuilder}>
<span style="--height:{height};">
<Provider {actions}>
<Grid
bind:this={grid}
datasource={table}
{API}
{stripeRows}
{quiet}
{initialFilter}
{initialSortColumn}
{initialSortOrder}
{fixedRowHeight}
{columnWhitelist}
{schemaOverrides}
{repeat}
canAddRows={allowAddRows}
canEditRows={allowEditRows}
canDeleteRows={allowDeleteRows}
canEditColumns={false}
canExpandRows={false}
canSaveSchema={false}
showControls={false}
notifySuccess={notificationStore.actions.success}
notifyError={notificationStore.actions.error}
buttons={enrichedButtons}
on:rowclick={e => onRowClick?.({ row: e.detail })}
/>
</Provider>
<Grid
bind:this={grid}
datasource={table}
{API}
{stripeRows}
{quiet}
{initialFilter}
{initialSortColumn}
{initialSortOrder}
{fixedRowHeight}
{columnWhitelist}
{schemaOverrides}
canAddRows={allowAddRows}
canEditRows={allowEditRows}
canDeleteRows={allowDeleteRows}
canEditColumns={false}
canExpandRows={false}
canSaveSchema={false}
canSelectRows={true}
showControls={false}
notifySuccess={notificationStore.actions.success}
notifyError={notificationStore.actions.error}
buttons={enrichedButtons}
on:rowclick={e => onRowClick?.({ row: e.detail })}
/>
</span>
</div>
<Provider {data} {actions} />
<style>
div {
display: flex;

View file

@ -15,7 +15,7 @@ const createRowSelectionStore = () => {
const componentId = Object.keys(selection).find(
componentId => componentId === tableComponentId
)
return selection[componentId] || {}
return selection[componentId]
}
return {

View file

@ -333,31 +333,59 @@ const s3UploadHandler = async action => {
}
}
/**
* For new configs, "rows" is defined and enriched to be the array of rows to
* export. For old configs it will be undefined and we need to use the legacy
* row selection store in combination with the tableComponentId parameter.
*/
const exportDataHandler = async action => {
let selection = rowSelectionStore.actions.getSelection(
action.parameters.tableComponentId
)
if (selection.selectedRows && selection.selectedRows.length > 0) {
let { tableComponentId, rows, type, columns, delimiter, customHeaders } =
action.parameters
let tableId
// Handle legacy configs using the row selection store
if (!rows?.length) {
const selection = rowSelectionStore.actions.getSelection(tableComponentId)
if (selection?.selectedRows?.length) {
rows = selection.selectedRows
tableId = selection.tableId
}
}
// Get table ID from first row if needed
if (!tableId) {
tableId = rows?.[0]?.tableId
}
// Handle no rows selected
if (!rows?.length) {
notificationStore.actions.error("Please select at least one row")
}
// Handle case where we're not using a DS+
else if (!tableId) {
notificationStore.actions.error(
"You can only export data from table datasources"
)
}
// Happy path when we have both rows and table ID
else {
try {
// Flatten rows if required
if (typeof rows[0] !== "string") {
rows = rows.map(row => row._id)
}
const data = await API.exportRows({
tableId: selection.tableId,
rows: selection.selectedRows,
format: action.parameters.type,
columns: action.parameters.columns?.map(
column => column.name || column
),
delimiter: action.parameters.delimiter,
customHeaders: action.parameters.customHeaders,
tableId,
rows,
format: type,
columns: columns?.map(column => column.name || column),
delimiter,
customHeaders,
})
download(
new Blob([data], { type: "text/plain" }),
`${selection.tableId}.${action.parameters.type}`
)
download(new Blob([data], { type: "text/plain" }), `${tableId}.${type}`)
} catch (error) {
notificationStore.actions.error("There was an error exporting the data")
}
} else {
notificationStore.actions.error("Please select at least one row")
}
}

View file

@ -16,6 +16,8 @@
const { config, dispatch, selectedRows } = getContext("grid")
const svelteDispatch = createEventDispatcher()
$: selectionEnabled = $config.canSelectRows || $config.canDeleteRows
const select = e => {
e.stopPropagation()
svelteDispatch("select")
@ -52,7 +54,7 @@
<div
on:click={select}
class="checkbox"
class:visible={$config.canDeleteRows &&
class:visible={selectionEnabled &&
(disableNumber || rowSelected || rowHovered || rowFocused)}
>
<Checkbox value={rowSelected} {disabled} />
@ -60,7 +62,7 @@
{#if !disableNumber}
<div
class="number"
class:visible={!$config.canDeleteRows ||
class:visible={!selectionEnabled ||
!(rowSelected || rowHovered || rowFocused)}
>
{row.__idx + 1}
@ -117,19 +119,11 @@
.expand {
margin-right: 4px;
}
.expand {
.expand:not(.visible),
.expand:not(.visible) :global(*) {
opacity: 0;
pointer-events: none !important;
}
.expand :global(.spectrum-Icon) {
pointer-events: none;
}
.expand.visible {
opacity: 1;
}
.expand.visible :global(.spectrum-Icon) {
pointer-events: all;
}
.delete:hover {
cursor: pointer;
}

View file

@ -41,6 +41,7 @@
export let canDeleteRows = true
export let canEditColumns = true
export let canSaveSchema = true
export let canSelectRows = false
export let stripeRows = false
export let quiet = false
export let collaboration = true
@ -94,6 +95,7 @@
canDeleteRows,
canEditColumns,
canSaveSchema,
canSelectRows,
stripeRows,
quiet,
collaboration,

View file

@ -110,12 +110,11 @@ export const deriveStores = context => {
}
export const createActions = context => {
const { focusedCellId, selectedRows, hoveredRowId } = context
const { focusedCellId, hoveredRowId } = context
// Callback when leaving the grid, deselecting all focussed or selected items
const blur = () => {
focusedCellId.set(null)
selectedRows.set({})
hoveredRowId.set(null)
}

@ -1 +1 @@
Subproject commit ff397e5454ad3361b25efdf14746c36dcbd3f409
Subproject commit d3c3077011a8e20ed3c48dcd6301caca4120b6ac

View file

@ -101,7 +101,6 @@
"mysql2": "3.9.7",
"node-fetch": "2.6.7",
"object-sizeof": "2.6.1",
"open": "8.4.0",
"openai": "^3.2.1",
"openapi-types": "9.3.1",
"pg": "8.10.0",
@ -113,12 +112,8 @@
"server-destroy": "1.0.1",
"snowflake-promise": "^4.5.0",
"socket.io": "4.6.1",
"sqlite3": "5.1.6",
"swagger-parser": "10.0.3",
"tar": "6.1.15",
"to-json-schema": "0.2.5",
"undici": "^6.0.1",
"undici-types": "^6.0.1",
"uuid": "^8.3.2",
"validate.js": "0.13.1",
"worker-farm": "1.7.0",
@ -144,16 +139,13 @@
"@types/supertest": "2.0.14",
"@types/tar": "6.1.5",
"@types/uuid": "8.3.4",
"apidoc": "0.50.4",
"copyfiles": "2.4.1",
"docker-compose": "0.23.17",
"jest": "29.7.0",
"jest-openapi": "0.14.2",
"jest-runner": "29.7.0",
"nock": "13.5.4",
"nodemon": "2.0.15",
"openapi-typescript": "5.2.0",
"path-to-regexp": "6.2.0",
"rimraf": "3.0.2",
"supertest": "6.3.3",
"swagger-jsdoc": "6.1.0",

View file

@ -279,8 +279,7 @@ export async function trigger(ctx: UserCtx) {
{
fields: ctx.request.body.fields,
timeout:
ctx.request.body.timeout * 1000 ||
env.getDefaults().AUTOMATION_SYNC_TIMEOUT,
ctx.request.body.timeout * 1000 || env.AUTOMATION_THREAD_TIMEOUT,
},
{ getResponses: true }
)

View file

@ -20,7 +20,7 @@ function parseIntSafe(number?: string) {
const DEFAULTS = {
QUERY_THREAD_TIMEOUT: 15000,
AUTOMATION_THREAD_TIMEOUT: 12000,
AUTOMATION_THREAD_TIMEOUT: 15000,
AUTOMATION_SYNC_TIMEOUT: 120000,
AUTOMATION_MAX_ITERATIONS: 200,
JS_PER_EXECUTION_TIME_LIMIT_MS: 1500,
@ -34,6 +34,10 @@ const DEFAULTS = {
const QUERY_THREAD_TIMEOUT =
parseIntSafe(process.env.QUERY_THREAD_TIMEOUT) ||
DEFAULTS.QUERY_THREAD_TIMEOUT
const DEFAULT_AUTOMATION_TIMEOUT =
QUERY_THREAD_TIMEOUT > DEFAULTS.AUTOMATION_THREAD_TIMEOUT
? QUERY_THREAD_TIMEOUT
: DEFAULTS.AUTOMATION_THREAD_TIMEOUT
const environment = {
// features
APP_FEATURES: process.env.APP_FEATURES,
@ -75,9 +79,7 @@ const environment = {
QUERY_THREAD_TIMEOUT: QUERY_THREAD_TIMEOUT,
AUTOMATION_THREAD_TIMEOUT:
parseIntSafe(process.env.AUTOMATION_THREAD_TIMEOUT) ||
DEFAULTS.AUTOMATION_THREAD_TIMEOUT > QUERY_THREAD_TIMEOUT
? DEFAULTS.AUTOMATION_THREAD_TIMEOUT
: QUERY_THREAD_TIMEOUT,
DEFAULT_AUTOMATION_TIMEOUT,
BB_ADMIN_USER_EMAIL: process.env.BB_ADMIN_USER_EMAIL,
BB_ADMIN_USER_PASSWORD: process.env.BB_ADMIN_USER_PASSWORD,
PLUGINS_DIR: process.env.PLUGINS_DIR || DEFAULTS.PLUGINS_DIR,

1248
yarn.lock

File diff suppressed because it is too large Load diff