Relationship picker incorrectly renders selections (#13175)
* Ensure _id is decoded for external search * Fetch initial value for 'Update' type forms * test didn't run locally - might run on github workflow * Tested and appears to be as before * Null-pointer fix * undo type change * update modules * add test * update modules
This commit is contained in:
parent
de0cdd25d1
commit
8694b8d772
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { CoreSelect, CoreMultiselect } from "@budibase/bbui"
|
import { CoreSelect, CoreMultiselect } from "@budibase/bbui"
|
||||||
import { fetchData, Utils } from "@budibase/frontend-core"
|
import { fetchData, Utils } from "@budibase/frontend-core"
|
||||||
import { getContext } from "svelte"
|
import { getContext, onMount } from "svelte"
|
||||||
import Field from "./Field.svelte"
|
import Field from "./Field.svelte"
|
||||||
import { FieldTypes } from "../../../constants"
|
import { FieldTypes } from "../../../constants"
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@
|
||||||
let tableDefinition
|
let tableDefinition
|
||||||
let searchTerm
|
let searchTerm
|
||||||
let open
|
let open
|
||||||
|
let initialValue
|
||||||
|
|
||||||
$: type =
|
$: type =
|
||||||
datasourceType === "table" ? FieldTypes.LINK : FieldTypes.BB_REFERENCE
|
datasourceType === "table" ? FieldTypes.LINK : FieldTypes.BB_REFERENCE
|
||||||
|
@ -109,7 +110,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$: forceFetchRows(filter)
|
$: forceFetchRows(filter)
|
||||||
$: debouncedFetchRows(searchTerm, primaryDisplay, defaultValue)
|
$: debouncedFetchRows(
|
||||||
|
searchTerm,
|
||||||
|
primaryDisplay,
|
||||||
|
initialValue || defaultValue
|
||||||
|
)
|
||||||
|
|
||||||
const forceFetchRows = async () => {
|
const forceFetchRows = async () => {
|
||||||
// if the filter has changed, then we need to reset the options, clear the selection, and re-fetch
|
// if the filter has changed, then we need to reset the options, clear the selection, and re-fetch
|
||||||
|
@ -127,9 +132,13 @@
|
||||||
if (allRowsFetched || !primaryDisplay) {
|
if (allRowsFetched || !primaryDisplay) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (defaultVal && !optionsObj[defaultVal]) {
|
// must be an array
|
||||||
|
if (defaultVal && !Array.isArray(defaultVal)) {
|
||||||
|
defaultVal = defaultVal.split(",")
|
||||||
|
}
|
||||||
|
if (defaultVal && defaultVal.some(val => !optionsObj[val])) {
|
||||||
await fetch.update({
|
await fetch.update({
|
||||||
query: { equal: { _id: defaultVal } },
|
query: { oneOf: { _id: defaultVal } },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,6 +211,16 @@
|
||||||
fetch.nextPage()
|
fetch.nextPage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
// if the form is in 'Update' mode, then we need to fetch the matching row so that the value is correctly set
|
||||||
|
if (fieldState?.value) {
|
||||||
|
initialValue =
|
||||||
|
fieldSchema?.relationshipType !== "one-to-many"
|
||||||
|
? flatten(fieldState?.value) ?? []
|
||||||
|
: flatten(fieldState?.value)?.[0]
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
|
|
|
@ -11,7 +11,10 @@ import {
|
||||||
import * as exporters from "../../../../api/controllers/view/exporters"
|
import * as exporters from "../../../../api/controllers/view/exporters"
|
||||||
import sdk from "../../../../sdk"
|
import sdk from "../../../../sdk"
|
||||||
import { handleRequest } from "../../../../api/controllers/row/external"
|
import { handleRequest } from "../../../../api/controllers/row/external"
|
||||||
import { breakExternalTableId } from "../../../../integrations/utils"
|
import {
|
||||||
|
breakExternalTableId,
|
||||||
|
breakRowIdField,
|
||||||
|
} from "../../../../integrations/utils"
|
||||||
import { cleanExportRows } from "../utils"
|
import { cleanExportRows } from "../utils"
|
||||||
import { utils } from "@budibase/shared-core"
|
import { utils } from "@budibase/shared-core"
|
||||||
import { ExportRowsParams, ExportRowsResult } from "../search"
|
import { ExportRowsParams, ExportRowsResult } from "../search"
|
||||||
|
@ -52,6 +55,15 @@ export async function search(options: SearchParams) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure oneOf _id queries decode the Row IDs
|
||||||
|
if (query?.oneOf?._id) {
|
||||||
|
const rowIds = query.oneOf._id
|
||||||
|
query.oneOf._id = rowIds.map((row: string) => {
|
||||||
|
const ids = breakRowIdField(row)
|
||||||
|
return ids[0]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const table = await sdk.tables.getTable(tableId)
|
const table = await sdk.tables.getTable(tableId)
|
||||||
options = searchInputMapping(table, options)
|
options = searchInputMapping(table, options)
|
||||||
|
@ -119,9 +131,7 @@ export async function exportRows(
|
||||||
requestQuery = {
|
requestQuery = {
|
||||||
oneOf: {
|
oneOf: {
|
||||||
_id: rowIds.map((row: string) => {
|
_id: rowIds.map((row: string) => {
|
||||||
const ids = JSON.parse(
|
const ids = breakRowIdField(row)
|
||||||
decodeURI(row).replace(/'/g, `"`).replace(/%2C/g, ",")
|
|
||||||
)
|
|
||||||
if (ids.length > 1) {
|
if (ids.length > 1) {
|
||||||
throw new HTTPError(
|
throw new HTTPError(
|
||||||
"Export data does not support composite keys.",
|
"Export data does not support composite keys.",
|
||||||
|
|
|
@ -21,10 +21,11 @@ jest.unmock("mysql2/promise")
|
||||||
|
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
describe.skip("external", () => {
|
describe("external search", () => {
|
||||||
const config = new TestConfiguration()
|
const config = new TestConfiguration()
|
||||||
|
|
||||||
let externalDatasource: Datasource, tableData: Table
|
let externalDatasource: Datasource, tableData: Table
|
||||||
|
const rows: Row[] = []
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const container = await new GenericContainer("mysql")
|
const container = await new GenericContainer("mysql")
|
||||||
|
@ -89,67 +90,81 @@ describe.skip("external", () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const table = await config.createExternalTable({
|
||||||
|
...tableData,
|
||||||
|
sourceId: externalDatasource._id,
|
||||||
|
})
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
rows.push(
|
||||||
|
await config.createRow({
|
||||||
|
tableId: table._id,
|
||||||
|
name: generator.first(),
|
||||||
|
surname: generator.last(),
|
||||||
|
age: generator.age(),
|
||||||
|
address: generator.address(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("search", () => {
|
it("default search returns all the data", async () => {
|
||||||
const rows: Row[] = []
|
await config.doInContext(config.appId, async () => {
|
||||||
beforeAll(async () => {
|
const tableId = config.table!._id!
|
||||||
const table = await config.createExternalTable({
|
|
||||||
...tableData,
|
const searchParams: SearchParams = {
|
||||||
sourceId: externalDatasource._id,
|
tableId,
|
||||||
})
|
query: {},
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
rows.push(
|
|
||||||
await config.createRow({
|
|
||||||
tableId: table._id,
|
|
||||||
name: generator.first(),
|
|
||||||
surname: generator.last(),
|
|
||||||
age: generator.age(),
|
|
||||||
address: generator.address(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
const result = await search(searchParams)
|
||||||
|
|
||||||
|
expect(result.rows).toHaveLength(10)
|
||||||
|
expect(result.rows).toEqual(
|
||||||
|
expect.arrayContaining(rows.map(r => expect.objectContaining(r)))
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it("default search returns all the data", async () => {
|
it("querying by fields will always return data attribute columns", async () => {
|
||||||
await config.doInContext(config.appId, async () => {
|
await config.doInContext(config.appId, async () => {
|
||||||
const tableId = config.table!._id!
|
const tableId = config.table!._id!
|
||||||
|
|
||||||
const searchParams: SearchParams = {
|
const searchParams: SearchParams = {
|
||||||
tableId,
|
tableId,
|
||||||
query: {},
|
query: {},
|
||||||
}
|
fields: ["name", "age"],
|
||||||
const result = await search(searchParams)
|
}
|
||||||
|
const result = await search(searchParams)
|
||||||
|
|
||||||
expect(result.rows).toHaveLength(10)
|
expect(result.rows).toHaveLength(10)
|
||||||
expect(result.rows).toEqual(
|
expect(result.rows).toEqual(
|
||||||
expect.arrayContaining(rows.map(r => expect.objectContaining(r)))
|
expect.arrayContaining(
|
||||||
|
rows.map(r => ({
|
||||||
|
...expectAnyExternalColsAttributes,
|
||||||
|
name: r.name,
|
||||||
|
age: r.age,
|
||||||
|
}))
|
||||||
)
|
)
|
||||||
})
|
)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it("querying by fields will always return data attribute columns", async () => {
|
it("will decode _id in oneOf query", async () => {
|
||||||
await config.doInContext(config.appId, async () => {
|
await config.doInContext(config.appId, async () => {
|
||||||
const tableId = config.table!._id!
|
const tableId = config.table!._id!
|
||||||
|
|
||||||
const searchParams: SearchParams = {
|
const searchParams: SearchParams = {
|
||||||
tableId,
|
tableId,
|
||||||
query: {},
|
query: {
|
||||||
fields: ["name", "age"],
|
oneOf: {
|
||||||
}
|
_id: ["%5B1%5D", "%5B4%5D", "%5B8%5D"],
|
||||||
const result = await search(searchParams)
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const result = await search(searchParams)
|
||||||
|
|
||||||
expect(result.rows).toHaveLength(10)
|
expect(result.rows).toHaveLength(3)
|
||||||
expect(result.rows).toEqual(
|
expect(result.rows.map(row => row.id)).toEqual([1, 4, 8])
|
||||||
expect.arrayContaining(
|
|
||||||
rows.map(r => ({
|
|
||||||
...expectAnyExternalColsAttributes,
|
|
||||||
name: r.name,
|
|
||||||
age: r.age,
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import {
|
import {
|
||||||
FieldType,
|
FieldType,
|
||||||
FieldTypeSubtypes,
|
|
||||||
SearchParams,
|
SearchParams,
|
||||||
Table,
|
Table,
|
||||||
DocumentType,
|
DocumentType,
|
||||||
|
|
Loading…
Reference in New Issue