Merge remote-tracking branch 'origin/master' into feature/monolith-js-refactor

This commit is contained in:
Dean 2023-11-03 11:30:05 +00:00
commit b9541d3b57
28 changed files with 372 additions and 245 deletions

View File

@ -11,10 +11,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: peter-evans/repository-dispatch@v2
env:
PAYLOAD_VERSION: ${{ github.sha }}
REF_NAME: ${{ github.ref_name}}
with:
repository: budibase/budibase-deploys
event-type: budicloud-qa-deploy
token: ${{ secrets.GH_ACCESS_TOKEN }}
client-payload: |-
{
"VERSION": "${{ github.sha }}",
"REF_NAME": "${{ github.ref_name}}"
}

View File

@ -165,17 +165,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Get the current budibase release version
id: version
run: |
release_version=$(cat lerna.json | jq -r '.version')
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
- uses: passeidireto/trigger-external-workflow-action@main
env:
PAYLOAD_VERSION: ${{ env.RELEASE_VERSION }}
REF_NAME: ${{ github.ref_name}}
- uses: peter-evans/repository-dispatch@v2
with:
repository: budibase/budibase-deploys
event: budicloud-qa-deploy
github_pat: ${{ secrets.GH_ACCESS_TOKEN }}
event-type: budicloud-qa-deploy
token: ${{ secrets.GH_ACCESS_TOKEN }}
client-payload: |-
{
"VERSION": "${{ github.ref_name }}",
"REF_NAME": "${{ github.ref_name}}"
}

View File

@ -66,7 +66,7 @@ jobs:
context: .
push: true
platforms: linux/amd64,linux/arm64
build-args: BUDIBASE_VERSION=$BUDIBASE_VERSION
build-args: BUDIBASE_VERSION=${{ env.BUDIBASE_VERSION }}
tags: budibase/budibase,budibase/budibase:${{ env.RELEASE_VERSION }}
file: ./hosting/single/Dockerfile.v2
env:
@ -79,7 +79,7 @@ jobs:
platforms: linux/amd64
build-args: |
TARGETBUILD=aas
BUDIBASE_VERSION=$BUDIBASE_VERSION
BUDIBASE_VERSION=${{ env.BUDIBASE_VERSION }}
tags: budibase/budibase-aas,budibase/budibase-aas:${{ env.RELEASE_VERSION }}
file: ./hosting/single/Dockerfile.v2
env:

View File

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

View File

@ -27,7 +27,7 @@
"scripts": {
"preinstall": "node scripts/syncProPackage.js",
"setup": "git config submodule.recurse true && git submodule update && node ./hosting/scripts/setup.js && yarn && yarn build && yarn dev",
"build": "lerna run build --stream",
"build": "NODE_OPTIONS=--max-old-space-size=1500 lerna run build --stream",
"build:dev": "lerna run --stream prebuild && yarn nx run-many --target=build --output-style=dynamic --watch --preserveWatchOutput",
"check:types": "lerna run check:types",
"build:sdk": "lerna run --stream build:sdk",

View File

@ -30,6 +30,7 @@ export * as timers from "./timers"
export { default as env } from "./environment"
export * as blacklist from "./blacklist"
export * as docUpdates from "./docUpdates"
export * from "./utils/Duration"
export { SearchParams } from "./db"
// Add context to tenancy for backwards compatibility
// only do this for external usages to prevent internal

View File

@ -36,7 +36,7 @@ class InMemoryQueue {
* @param opts This is not used by the in memory queue as there is no real use
* case when in memory, but is the same API as Bull
*/
constructor(name: string, opts = null) {
constructor(name: string, opts?: any) {
this._name = name
this._opts = opts
this._messages = []

View File

@ -2,11 +2,18 @@ import env from "../environment"
import { getRedisOptions } from "../redis/utils"
import { JobQueue } from "./constants"
import InMemoryQueue from "./inMemoryQueue"
import BullQueue from "bull"
import BullQueue, { QueueOptions } from "bull"
import { addListeners, StalledFn } from "./listeners"
import { Duration } from "../utils"
import * as timers from "../timers"
import * as Redis from "ioredis"
const CLEANUP_PERIOD_MS = 60 * 1000
// the queue lock is held for 5 minutes
const QUEUE_LOCK_MS = Duration.fromMinutes(5).toMs()
// queue lock is refreshed every 30 seconds
const QUEUE_LOCK_RENEW_INTERNAL_MS = Duration.fromSeconds(30).toMs()
// cleanup the queue every 60 seconds
const CLEANUP_PERIOD_MS = Duration.fromSeconds(60).toMs()
let QUEUES: BullQueue.Queue[] | InMemoryQueue[] = []
let cleanupInterval: NodeJS.Timeout
@ -21,7 +28,14 @@ export function createQueue<T>(
opts: { removeStalledCb?: StalledFn } = {}
): BullQueue.Queue<T> {
const { opts: redisOpts, redisProtocolUrl } = getRedisOptions()
const queueConfig: any = redisProtocolUrl || { redis: redisOpts }
const queueConfig: QueueOptions = {
redis: redisProtocolUrl! || (redisOpts as Redis.RedisOptions),
settings: {
maxStalledCount: 0,
lockDuration: QUEUE_LOCK_MS,
lockRenewTime: QUEUE_LOCK_RENEW_INTERNAL_MS,
},
}
let queue: any
if (!env.isTest()) {
queue = new BullQueue(jobQueue, queueConfig)

View File

@ -0,0 +1,49 @@
export enum DurationType {
MILLISECONDS = "milliseconds",
SECONDS = "seconds",
MINUTES = "minutes",
HOURS = "hours",
DAYS = "days",
}
const conversion: Record<DurationType, number> = {
milliseconds: 1,
seconds: 1000,
minutes: 60 * 1000,
hours: 60 * 60 * 1000,
days: 24 * 60 * 60 * 1000,
}
export class Duration {
static convert(from: DurationType, to: DurationType, duration: number) {
const milliseconds = duration * conversion[from]
return milliseconds / conversion[to]
}
static from(from: DurationType, duration: number) {
return {
to: (to: DurationType) => {
return Duration.convert(from, to, duration)
},
toMs: () => {
return Duration.convert(from, DurationType.MILLISECONDS, duration)
},
}
}
static fromSeconds(duration: number) {
return Duration.from(DurationType.SECONDS, duration)
}
static fromMinutes(duration: number) {
return Duration.from(DurationType.MINUTES, duration)
}
static fromHours(duration: number) {
return Duration.from(DurationType.HOURS, duration)
}
static fromDays(duration: number) {
return Duration.from(DurationType.DAYS, duration)
}
}

View File

@ -1,3 +1,4 @@
export * from "./hashing"
export * from "./utils"
export * from "./stringUtils"
export * from "./Duration"

View File

@ -0,0 +1,19 @@
import { Duration, DurationType } from "../Duration"
describe("duration", () => {
it("should convert minutes to milliseconds", () => {
expect(Duration.fromMinutes(5).toMs()).toBe(300000)
})
it("should convert seconds to milliseconds", () => {
expect(Duration.fromSeconds(30).toMs()).toBe(30000)
})
it("should convert days to milliseconds", () => {
expect(Duration.fromDays(1).toMs()).toBe(86400000)
})
it("should convert minutes to days", () => {
expect(Duration.fromMinutes(1440).to(DurationType.DAYS)).toBe(1)
})
})

View File

@ -30,15 +30,15 @@
part2: PrettyRelationshipDefinitions.MANY,
},
[RelationshipType.MANY_TO_ONE]: {
part1: PrettyRelationshipDefinitions.ONE,
part2: PrettyRelationshipDefinitions.MANY,
part1: PrettyRelationshipDefinitions.MANY,
part2: PrettyRelationshipDefinitions.ONE,
},
}
let relationshipOpts1 = Object.values(PrettyRelationshipDefinitions)
let relationshipOpts2 = Object.values(PrettyRelationshipDefinitions)
let relationshipPart1 = PrettyRelationshipDefinitions.MANY
let relationshipPart2 = PrettyRelationshipDefinitions.ONE
let relationshipPart1 = PrettyRelationshipDefinitions.ONE
let relationshipPart2 = PrettyRelationshipDefinitions.MANY
let originalFromColumnName = toRelationship.name,
originalToColumnName = fromRelationship.name

View File

@ -1,5 +1,5 @@
import { Checkbox, Select, RadioGroup, Stepper, Input } from "@budibase/bbui"
import DataSourceSelect from "./controls/DataSourceSelect.svelte"
import DataSourceSelect from "./controls/DataSourceSelect/DataSourceSelect.svelte"
import S3DataSourceSelect from "./controls/S3DataSourceSelect.svelte"
import DataProviderSelect from "./controls/DataProviderSelect.svelte"
import ButtonActionEditor from "./controls/ButtonActionEditor/ButtonActionEditor.svelte"

View File

@ -0,0 +1,55 @@
<script>
import { Divider, Heading } from "@budibase/bbui"
export let dividerState
export let heading
export let dataSet
export let value
export let onSelect
</script>
{#if dividerState}
<Divider />
{/if}
{#if heading}
<div class="title">
<Heading size="XS">{heading}</Heading>
</div>
{/if}
<ul class="spectrum-Menu" role="listbox">
{#each dataSet as data}
<li
class="spectrum-Menu-item"
class:is-selected={value?.label === data.label &&
value?.type === data.type}
role="option"
aria-selected="true"
tabindex="0"
on:click={() => onSelect(data)}
>
<span class="spectrum-Menu-itemLabel">
{data.label}
</span>
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
focusable="false"
aria-hidden="true"
>
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
</li>
{/each}
</ul>
<style>
.title {
padding: 0 var(--spacing-m) var(--spacing-s) var(--spacing-m);
}
ul {
list-style: none;
padding-left: 0px;
margin: 0px;
width: 100%;
}
</style>

View File

@ -7,10 +7,8 @@
import {
Button,
Popover,
Divider,
Select,
Layout,
Heading,
Drawer,
DrawerContent,
Icon,
@ -32,6 +30,7 @@
import IntegrationQueryEditor from "components/integration/index.svelte"
import { makePropSafe as safe } from "@budibase/string-templates"
import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte"
import DataSourceCategory from "components/design/settings/controls/DataSourceSelect/DataSourceCategory.svelte"
import { API } from "api"
export let value = {}
@ -279,102 +278,81 @@
</div>
<Popover bind:this={dropdownRight} anchor={anchorRight}>
<div class="dropdown">
<div class="title">
<Heading size="XS">Tables</Heading>
</div>
<ul>
{#each tables as table}
<li on:click={() => handleSelected(table)}>{table.label}</li>
{/each}
</ul>
<DataSourceCategory
heading="Tables"
dataSet={tables}
{value}
onSelect={handleSelected}
/>
{#if views?.length}
<Divider />
<div class="title">
<Heading size="XS">Views</Heading>
</div>
<ul>
{#each views as view}
<li on:click={() => handleSelected(view)}>{view.label}</li>
{/each}
</ul>
<DataSourceCategory
dividerState={true}
heading="Views"
dataSet={views}
{value}
onSelect={handleSelected}
/>
{/if}
{#if queries?.length}
<Divider />
<div class="title">
<Heading size="XS">Queries</Heading>
</div>
<ul>
{#each queries as query}
<li
class:selected={value === query}
on:click={() => handleSelected(query)}
>
{query.label}
</li>
{/each}
</ul>
<DataSourceCategory
dividerState={true}
heading="Queries"
dataSet={queries}
{value}
onSelect={handleSelected}
/>
{/if}
{#if links?.length}
<Divider />
<div class="title">
<Heading size="XS">Relationships</Heading>
</div>
<ul>
{#each links as link}
<li on:click={() => handleSelected(link)}>{link.label}</li>
{/each}
</ul>
<DataSourceCategory
dividerState={true}
heading="Links"
dataSet={links}
{value}
onSelect={handleSelected}
/>
{/if}
{#if fields?.length}
<Divider />
<div class="title">
<Heading size="XS">Fields</Heading>
</div>
<ul>
{#each fields as field}
<li on:click={() => handleSelected(field)}>{field.label}</li>
{/each}
</ul>
<DataSourceCategory
dividerState={true}
heading="Fields"
dataSet={fields}
{value}
onSelect={handleSelected}
/>
{/if}
{#if jsonArrays?.length}
<Divider />
<div class="title">
<Heading size="XS">JSON Arrays</Heading>
</div>
<ul>
{#each jsonArrays as field}
<li on:click={() => handleSelected(field)}>{field.label}</li>
{/each}
</ul>
<DataSourceCategory
dividerState={true}
heading="JSON Arrays"
dataSet={jsonArrays}
{value}
onSelect={handleSelected}
/>
{/if}
{#if showDataProviders && dataProviders?.length}
<Divider />
<div class="title">
<Heading size="XS">Data Providers</Heading>
</div>
<ul>
{#each dataProviders as provider}
<li
class:selected={value === provider}
on:click={() => handleSelected(provider)}
>
{provider.label}
</li>
{/each}
</ul>
<DataSourceCategory
dividerState={true}
heading="Data Providers"
dataSet={dataProviders}
{value}
onSelect={handleSelected}
/>
{/if}
<DataSourceCategory
dividerState={true}
heading="Other"
dataSet={[custom]}
{value}
onSelect={handleSelected}
/>
{#if otherSources?.length}
<DataSourceCategory
dividerState={false}
dataSet={otherSources}
{value}
onSelect={handleSelected}
/>
{/if}
<Divider />
<div class="title">
<Heading size="XS">Other</Heading>
</div>
<ul>
<li on:click={() => handleSelected(custom)}>{custom.label}</li>
{#if otherSources?.length}
{#each otherSources as source}
<li on:click={() => handleSelected(source)}>{source.label}</li>
{/each}
{/if}
</ul>
</div>
</Popover>
@ -398,31 +376,6 @@
.dropdown {
padding: var(--spacing-m) 0;
z-index: 99999999;
overflow-y: scroll;
}
.title {
padding: 0 var(--spacing-m) var(--spacing-s) var(--spacing-m);
}
ul {
list-style: none;
padding-left: 0px;
margin: 0px;
}
li {
cursor: pointer;
margin: 0px;
padding: var(--spacing-s) var(--spacing-m);
font-size: var(--font-size-m);
}
.selected {
color: var(--spectrum-global-color-blue-600);
}
li:hover {
background-color: var(--spectrum-global-color-gray-200);
}
.icon {

View File

@ -1,5 +1,5 @@
<script>
import DataSourceSelect from "./DataSourceSelect.svelte"
import DataSourceSelect from "./DataSourceSelect/DataSourceSelect.svelte"
const otherSources = [{ name: "Custom", label: "Custom" }]
</script>

View File

@ -1,27 +1,10 @@
<script context="module">
// We can create a module level cache for all relationship cells to avoid
// having to fetch the table definition one time for each cell
let primaryDisplayCache = {}
const getPrimaryDisplayForTableId = async (API, tableId) => {
if (primaryDisplayCache[tableId]) {
return primaryDisplayCache[tableId]
}
const definition = await API.fetchTableDefinition(tableId)
const primaryDisplay =
definition?.primaryDisplay || definition?.schema?.[0]?.name
primaryDisplayCache[tableId] = primaryDisplay
return primaryDisplay
}
</script>
<script>
import { getColor } from "../lib/utils"
import { onMount, getContext } from "svelte"
import { Icon, Input, ProgressCircle, clickOutside } from "@budibase/bbui"
import { debounce } from "../../../utils/utils"
const { API, dispatch } = getContext("grid")
const { API, dispatch, cache } = getContext("grid")
export let value
export let api
@ -147,7 +130,9 @@
// Find the primary display for the related table
if (!primaryDisplay) {
searching = true
primaryDisplay = await getPrimaryDisplayForTableId(API, schema.tableId)
primaryDisplay = await cache.actions.getPrimaryDisplayForTableId(
schema.tableId
)
}
// Show initial list of results
@ -195,7 +180,7 @@
const toggleRow = async row => {
if (value?.some(x => x._id === row._id)) {
// If the row is already included, remove it and update the candidate
// row to be the the same position if possible
// row to be the same position if possible
if (oneRowOnly) {
await onChange([])
} else {
@ -260,31 +245,29 @@
class:wrap={editable || contentLines > 1}
on:wheel={e => (focused ? e.stopPropagation() : null)}
>
{#if Array.isArray(value) && value.length}
{#each value as relationship}
{#if relationship[primaryDisplay] || relationship.primaryDisplay}
<div class="badge">
<span
on:click={editable
? () => showRelationship(relationship._id)
: null}
>
{readable(
relationship[primaryDisplay] || relationship.primaryDisplay
)}
</span>
{#if editable}
<Icon
name="Close"
size="XS"
hoverable
on:click={() => toggleRow(relationship)}
/>
{/if}
</div>
{/if}
{/each}
{/if}
{#each value || [] as relationship}
{#if relationship[primaryDisplay] || relationship.primaryDisplay}
<div class="badge">
<span
on:click={editable
? () => showRelationship(relationship._id)
: null}
>
{readable(
relationship[primaryDisplay] || relationship.primaryDisplay
)}
</span>
{#if editable}
<Icon
name="Close"
size="XS"
hoverable
on:click={() => toggleRow(relationship)}
/>
{/if}
</div>
{/if}
{/each}
{#if editable}
<div class="add" on:click={open}>
<Icon name="Add" size="S" />
@ -320,7 +303,7 @@
<div class="searching">
<ProgressCircle size="S" />
</div>
{:else if Array.isArray(searchResults) && searchResults.length}
{:else if searchResults?.length}
<div class="results">
{#each searchResults as row, idx}
<div

View File

@ -0,0 +1,47 @@
export const createActions = context => {
const { API } = context
// Cache for the primary display columns of different tables.
// If we ever need to cache table definitions for other purposes then we can
// expand this to be a more generic cache.
let primaryDisplayCache = {}
const resetPrimaryDisplayCache = () => {
primaryDisplayCache = {}
}
const getPrimaryDisplayForTableId = async tableId => {
// If we've never encountered this tableId before then store a promise that
// resolves to the primary display so that subsequent invocations before the
// promise completes can reuse this promise
if (!primaryDisplayCache[tableId]) {
primaryDisplayCache[tableId] = new Promise(resolve => {
API.fetchTableDefinition(tableId).then(def => {
const display = def?.primaryDisplay || def?.schema?.[0]?.name
primaryDisplayCache[tableId] = display
resolve(display)
})
})
}
// We await the result so that we account for both promises and primitives
return await primaryDisplayCache[tableId]
}
return {
cache: {
actions: {
getPrimaryDisplayForTableId,
resetPrimaryDisplayCache,
},
},
}
}
export const initialise = context => {
const { datasource, cache } = context
// Wipe the caches whenever the datasource changes to ensure we aren't
// storing any stale information
datasource.subscribe(cache.actions.resetPrimaryDisplayCache)
}

View File

@ -160,11 +160,6 @@ export const createActions = context => {
return getAPI()?.actions.canUseColumn(name)
}
// Gets the default number of rows for a single page
const getFeatures = () => {
return getAPI()?.actions.getFeatures()
}
return {
datasource: {
...datasource,
@ -177,7 +172,6 @@ export const createActions = context => {
getRow,
isDatasourceValid,
canUseColumn,
getFeatures,
},
},
}

View File

@ -35,11 +35,6 @@ export const createActions = context => {
return $columns.some(col => col.name === name) || $sticky?.name === name
}
const getFeatures = () => {
// We don't support any features
return {}
}
return {
nonPlus: {
actions: {
@ -50,7 +45,6 @@ export const createActions = context => {
getRow,
isDatasourceValid,
canUseColumn,
getFeatures,
},
},
}

View File

@ -1,5 +1,4 @@
import { get } from "svelte/store"
import TableFetch from "../../../../fetch/TableFetch"
const SuppressErrors = true
@ -46,10 +45,6 @@ export const createActions = context => {
return $columns.some(col => col.name === name) || $sticky?.name === name
}
const getFeatures = () => {
return new TableFetch({ API }).determineFeatureFlags()
}
return {
table: {
actions: {
@ -60,7 +55,6 @@ export const createActions = context => {
getRow,
isDatasourceValid,
canUseColumn,
getFeatures,
},
},
}

View File

@ -1,5 +1,4 @@
import { get } from "svelte/store"
import ViewV2Fetch from "../../../../fetch/ViewV2Fetch"
const SuppressErrors = true
@ -46,10 +45,6 @@ export const createActions = context => {
)
}
const getFeatures = () => {
return new ViewV2Fetch({ API }).determineFeatureFlags()
}
return {
viewV2: {
actions: {
@ -60,7 +55,6 @@ export const createActions = context => {
getRow,
isDatasourceValid,
canUseColumn,
getFeatures,
},
},
}

View File

@ -19,6 +19,7 @@ import * as Datasource from "./datasource"
import * as Table from "./datasources/table"
import * as ViewV2 from "./datasources/viewV2"
import * as NonPlus from "./datasources/nonPlus"
import * as Cache from "./cache"
const DependencyOrderedStores = [
Sort,
@ -42,6 +43,7 @@ const DependencyOrderedStores = [
Clipboard,
Config,
Notifications,
Cache,
]
export const attachStores = context => {

View File

@ -114,10 +114,6 @@ export const createActions = context => {
const $allFilters = get(allFilters)
const $sort = get(sort)
// Determine how many rows to fetch per page
const features = datasource.actions.getFeatures()
const limit = features?.supportsPagination ? RowPageSize : null
// Create new fetch model
const newFetch = fetchData({
API,
@ -126,8 +122,12 @@ export const createActions = context => {
filter: $allFilters,
sortColumn: $sort.column,
sortOrder: $sort.order,
limit,
limit: RowPageSize,
paginate: true,
// Disable client side limiting, so that for queries and custom data
// sources we don't impose fake row limits. We want all the data.
clientSideLimiting: false,
},
})

View File

@ -43,6 +43,11 @@ export default class DataFetch {
// Pagination config
paginate: true,
// Client side feature customisation
clientSideSearching: true,
clientSideSorting: true,
clientSideLimiting: true,
}
// State of the fetch
@ -208,24 +213,32 @@ export default class DataFetch {
* Fetches some filtered, sorted and paginated data
*/
async getPage() {
const { sortColumn, sortOrder, sortType, limit } = this.options
const {
sortColumn,
sortOrder,
sortType,
limit,
clientSideSearching,
clientSideSorting,
clientSideLimiting,
} = this.options
const { query } = get(this.store)
// Get the actual data
let { rows, info, hasNextPage, cursor, error } = await this.getData()
// If we don't support searching, do a client search
if (!this.features.supportsSearch) {
if (!this.features.supportsSearch && clientSideSearching) {
rows = runLuceneQuery(rows, query)
}
// If we don't support sorting, do a client-side sort
if (!this.features.supportsSort) {
if (!this.features.supportsSort && clientSideSorting) {
rows = luceneSort(rows, sortColumn, sortOrder, sortType)
}
// If we don't support pagination, do a client-side limit
if (!this.features.supportsPagination) {
if (!this.features.supportsPagination && clientSideLimiting) {
rows = luceneLimit(rows, limit)
}

View File

@ -7,6 +7,7 @@ import {
isBBReferenceField,
isRelationshipField,
LinkDocument,
LinkInfo,
RelationshipFieldMetadata,
RelationshipType,
Row,
@ -125,7 +126,23 @@ abstract class UserColumnMigrator implements ColumnMigrator {
protected newColumn: BBReferenceFieldMetadata
) {}
abstract updateRow(row: Row, link: LinkDocument): void
abstract updateRow(row: Row, linkInfo: LinkInfo): void
pickUserTableLinkSide(link: LinkDocument): LinkInfo {
if (link.doc1.tableId === InternalTable.USER_METADATA) {
return link.doc1
} else {
return link.doc2
}
}
pickOtherTableLinkSide(link: LinkDocument): LinkInfo {
if (link.doc1.tableId === InternalTable.USER_METADATA) {
return link.doc2
} else {
return link.doc1
}
}
async doMigration(): Promise<MigrationResult> {
let oldTable = cloneDeep(this.table)
@ -137,15 +154,17 @@ abstract class UserColumnMigrator implements ColumnMigrator {
let links = await sdk.links.fetchWithDocument(this.table._id!)
for (let link of links) {
const userSide = this.pickUserTableLinkSide(link)
const otherSide = this.pickOtherTableLinkSide(link)
if (
link.doc1.tableId !== this.table._id ||
link.doc1.fieldName !== this.oldColumn.name ||
link.doc2.tableId !== InternalTable.USER_METADATA
otherSide.tableId !== this.table._id ||
otherSide.fieldName !== this.oldColumn.name ||
userSide.tableId !== InternalTable.USER_METADATA
) {
continue
}
let row = rowsById[link.doc1.rowId]
let row = rowsById[otherSide.rowId]
if (!row) {
// This can happen if the row has been deleted but the link hasn't,
// which was a state that was found during the initial testing of this
@ -153,7 +172,7 @@ abstract class UserColumnMigrator implements ColumnMigrator {
continue
}
this.updateRow(row, link)
this.updateRow(row, userSide)
}
let db = context.getAppDB()
@ -175,20 +194,20 @@ abstract class UserColumnMigrator implements ColumnMigrator {
}
class SingleUserColumnMigrator extends UserColumnMigrator {
updateRow(row: Row, link: LinkDocument): void {
updateRow(row: Row, linkInfo: LinkInfo): void {
row[this.newColumn.name] = dbCore.getGlobalIDFromUserMetadataID(
link.doc2.rowId
linkInfo.rowId
)
}
}
class MultiUserColumnMigrator extends UserColumnMigrator {
updateRow(row: Row, link: LinkDocument): void {
updateRow(row: Row, linkInfo: LinkInfo): void {
if (!row[this.newColumn.name]) {
row[this.newColumn.name] = []
}
row[this.newColumn.name].push(
dbCore.getGlobalIDFromUserMetadataID(link.doc2.rowId)
dbCore.getGlobalIDFromUserMetadataID(linkInfo.rowId)
)
}
}

View File

@ -1,17 +1,15 @@
import { Document } from "../document"
export interface LinkInfo {
rowId: string
fieldName: string
tableId: string
}
export interface LinkDocument extends Document {
type: string
doc1: {
rowId: string
fieldName: string
tableId: string
}
doc2: {
rowId: string
fieldName: string
tableId: string
}
doc1: LinkInfo
doc2: LinkInfo
}
export interface LinkDocumentValue {

View File

@ -1,18 +1,16 @@
## Description
_Describe the problem or feature in addition to a link to the relevant github issues._
Addresses:
Addresses:
- `<Enter the Link to the issue(s) this PR addresses>`
- ...more if required
## App Export
- If possible, attach an app export file along with your request template to make QA testing easier, with minimal setup.
## Screenshots
_If a UI facing feature, a short video of the happy path, and some screenshots of the new functionality._
## Documentation
- [ ] I have reviewed the budibase documentatation to verify if this feature requires any changes. If changes or new docs are required I have written them.