Merge pull request #14778 from Budibase/view-calculation-ui
Calculation Views UI
This commit is contained in:
commit
7bbe1c2ec8
|
@ -4,11 +4,10 @@ packages/server/runtime_apps/
|
||||||
.idea/
|
.idea/
|
||||||
bb-airgapped.tar.gz
|
bb-airgapped.tar.gz
|
||||||
*.iml
|
*.iml
|
||||||
|
|
||||||
packages/server/build/oldClientVersions/**/*
|
packages/server/build/oldClientVersions/**/*
|
||||||
packages/builder/src/components/deploy/clientVersions.json
|
packages/builder/src/components/deploy/clientVersions.json
|
||||||
|
|
||||||
packages/server/src/integrations/tests/utils/*.lock
|
packages/server/src/integrations/tests/utils/*.lock
|
||||||
|
packages/builder/vite.config.mjs.timestamp*
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
|
|
|
@ -16,14 +16,17 @@
|
||||||
href={url}
|
href={url}
|
||||||
class="list-item"
|
class="list-item"
|
||||||
class:hoverable={hoverable || url != null}
|
class:hoverable={hoverable || url != null}
|
||||||
|
class:large={!!subtitle}
|
||||||
on:click
|
on:click
|
||||||
class:selected
|
class:selected
|
||||||
>
|
>
|
||||||
<div class="left">
|
<div class="list-item__left">
|
||||||
{#if icon === "StatusLight"}
|
{#if icon === "StatusLight"}
|
||||||
<StatusLight square size="L" color={iconColor} />
|
<StatusLight square size="L" color={iconColor} />
|
||||||
{:else if icon}
|
{:else if icon}
|
||||||
<Icon name={icon} color={iconColor} />
|
<div class="list-item__icon">
|
||||||
|
<Icon name={icon} color={iconColor} size={subtitle ? "XL" : "M"} />
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="list-item__text">
|
<div class="list-item__text">
|
||||||
{#if title}
|
{#if title}
|
||||||
|
@ -38,7 +41,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="list-item__right">
|
||||||
<slot name="right" />
|
<slot name="right" />
|
||||||
{#if showArrow}
|
{#if showArrow}
|
||||||
<Icon name="ChevronRight" />
|
<Icon name="ChevronRight" />
|
||||||
|
@ -54,9 +57,12 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
transition: background 130ms ease-out;
|
transition: background 130ms ease-out, border-color 130ms ease-out;
|
||||||
gap: var(--spacing-m);
|
gap: var(--spacing-m);
|
||||||
color: var(--spectrum-global-color-gray-800);
|
color: var(--spectrum-global-color-gray-800);
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.list-item:not(:first-child) {
|
.list-item:not(:first-child) {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
|
@ -74,27 +80,72 @@
|
||||||
}
|
}
|
||||||
.hoverable:not(.selected):hover {
|
.hoverable:not(.selected):hover {
|
||||||
background: var(--spectrum-global-color-gray-200);
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
border-color: var(--spectrum-global-color-gray-400);
|
||||||
}
|
}
|
||||||
.selected {
|
.selected {
|
||||||
background: var(--spectrum-global-color-blue-100);
|
background: var(--spectrum-global-color-blue-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.left,
|
/* Selection is only meant for standalone list items (non stacked) so we just set a fixed border radius */
|
||||||
.right {
|
.list-item.selected {
|
||||||
|
background-color: var(--spectrum-global-color-blue-100);
|
||||||
|
border-color: var(--spectrum-global-color-blue-100);
|
||||||
|
}
|
||||||
|
.list-item.selected:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--spectrum-global-color-blue-400);
|
||||||
|
pointer-events: none;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 1;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Large icons */
|
||||||
|
.list-item.large .list-item__icon {
|
||||||
|
background-color: var(--spectrum-global-color-gray-200);
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
transition: background-color 130ms ease-out, border-color 130ms ease-out,
|
||||||
|
color 130ms ease-out;
|
||||||
|
}
|
||||||
|
.list-item.large.hoverable:not(.selected):hover .list-item__icon {
|
||||||
|
background-color: var(--spectrum-global-color-gray-300);
|
||||||
|
}
|
||||||
|
.list-item.large.selected .list-item__icon {
|
||||||
|
background-color: var(--spectrum-global-color-blue-400);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--spectrum-global-color-blue-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Internal layout */
|
||||||
|
.list-item__left,
|
||||||
|
.list-item__right {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-m);
|
gap: var(--spacing-m);
|
||||||
}
|
}
|
||||||
.left {
|
.list-item.large .list-item__left,
|
||||||
|
.list-item.large .list-item__right {
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.list-item__left {
|
||||||
width: 0;
|
width: 0;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
.right {
|
.list-item__right {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
color: var(--spectrum-global-color-gray-600);
|
color: var(--spectrum-global-color-gray-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Text */
|
||||||
.list-item__text {
|
.list-item__text {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
@ -106,6 +157,7 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.list-item__subtitle {
|
.list-item__subtitle {
|
||||||
color: var(--spectrum-global-color-gray-600);
|
color: var(--spectrum-global-color-gray-700);
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -147,6 +147,9 @@
|
||||||
.spectrum-Dialog--extraLarge {
|
.spectrum-Dialog--extraLarge {
|
||||||
width: 1000px;
|
width: 1000px;
|
||||||
}
|
}
|
||||||
|
.spectrum-Dialog--medium {
|
||||||
|
width: 540px;
|
||||||
|
}
|
||||||
|
|
||||||
.content-grid {
|
.content-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
|
@ -9,13 +9,11 @@
|
||||||
let anchor
|
let anchor
|
||||||
|
|
||||||
$: columnOptions = $columns
|
$: columnOptions = $columns
|
||||||
|
.filter(col => canBeSortColumn(col.schema))
|
||||||
.map(col => ({
|
.map(col => ({
|
||||||
label: col.label || col.name,
|
label: col.label || col.name,
|
||||||
value: col.name,
|
value: col.name,
|
||||||
type: col.schema?.type,
|
|
||||||
related: col.related,
|
|
||||||
}))
|
}))
|
||||||
.filter(col => canBeSortColumn(col))
|
|
||||||
$: orderOptions = getOrderOptions($sort.column, columnOptions)
|
$: orderOptions = getOrderOptions($sort.column, columnOptions)
|
||||||
|
|
||||||
const getOrderOptions = (column, columnOptions) => {
|
const getOrderOptions = (column, columnOptions) => {
|
||||||
|
|
|
@ -0,0 +1,259 @@
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
ActionButton,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
Select,
|
||||||
|
Icon,
|
||||||
|
Multiselect,
|
||||||
|
} from "@budibase/bbui"
|
||||||
|
import { CalculationType, canGroupBy, FieldType } from "@budibase/types"
|
||||||
|
import InfoDisplay from "pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/InfoDisplay.svelte"
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
|
||||||
|
const { definition, datasource, rows } = getContext("grid")
|
||||||
|
const calculationTypeOptions = [
|
||||||
|
{
|
||||||
|
label: "Average",
|
||||||
|
value: CalculationType.AVG,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Sum",
|
||||||
|
value: CalculationType.SUM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Minimum",
|
||||||
|
value: CalculationType.MIN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Maximum",
|
||||||
|
value: CalculationType.MAX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Count",
|
||||||
|
value: CalculationType.COUNT,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
let modal
|
||||||
|
let calculations = []
|
||||||
|
let groupBy = []
|
||||||
|
let schema = {}
|
||||||
|
|
||||||
|
$: schema = $definition?.schema || {}
|
||||||
|
$: count = extractCalculations($definition?.schema || {}).length
|
||||||
|
$: groupByOptions = getGroupByOptions(schema)
|
||||||
|
|
||||||
|
const open = () => {
|
||||||
|
calculations = extractCalculations(schema)
|
||||||
|
groupBy = calculations.length ? extractGroupBy(schema) : []
|
||||||
|
modal?.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractCalculations = schema => {
|
||||||
|
if (!schema) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return Object.keys(schema)
|
||||||
|
.filter(field => {
|
||||||
|
return schema[field].calculationType != null
|
||||||
|
})
|
||||||
|
.map(field => ({
|
||||||
|
type: schema[field].calculationType,
|
||||||
|
field: schema[field].field,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractGroupBy = schema => {
|
||||||
|
if (!schema) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return Object.keys(schema).filter(field => {
|
||||||
|
return schema[field].calculationType == null && schema[field].visible
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the available types for a given calculation
|
||||||
|
const getTypeOptions = (self, calculations) => {
|
||||||
|
return calculationTypeOptions.filter(option => {
|
||||||
|
return !calculations.some(
|
||||||
|
calc =>
|
||||||
|
calc !== self &&
|
||||||
|
calc.field === self.field &&
|
||||||
|
calc.type === option.value
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the available fields for a given calculation
|
||||||
|
const getFieldOptions = (self, calculations, schema) => {
|
||||||
|
return Object.entries(schema)
|
||||||
|
.filter(([field, fieldSchema]) => {
|
||||||
|
// Only allow numeric fields that are not calculations themselves
|
||||||
|
if (
|
||||||
|
fieldSchema.calculationType ||
|
||||||
|
fieldSchema.type !== FieldType.NUMBER
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Don't allow duplicates
|
||||||
|
return !calculations.some(calc => {
|
||||||
|
return (
|
||||||
|
calc !== self && calc.type === self.type && calc.field === field
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map(([field]) => field)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets the available fields to group by
|
||||||
|
const getGroupByOptions = schema => {
|
||||||
|
return Object.entries(schema)
|
||||||
|
.filter(([_, fieldSchema]) => {
|
||||||
|
// Don't allow grouping by calculations
|
||||||
|
if (fieldSchema.calculationType) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Don't allow complex types
|
||||||
|
return canGroupBy(fieldSchema.type)
|
||||||
|
})
|
||||||
|
.map(([field]) => field)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addCalc = () => {
|
||||||
|
calculations = [...calculations, { type: CalculationType.AVG }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteCalc = idx => {
|
||||||
|
calculations = calculations.toSpliced(idx, 1)
|
||||||
|
|
||||||
|
// Remove any grouping if clearing the last calculation
|
||||||
|
if (!calculations.length) {
|
||||||
|
groupBy = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
let newSchema = {}
|
||||||
|
|
||||||
|
// Add calculations
|
||||||
|
for (let calc of calculations) {
|
||||||
|
if (!calc.type || !calc.field) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const typeOption = calculationTypeOptions.find(x => x.value === calc.type)
|
||||||
|
const name = `${typeOption.label} ${calc.field}`
|
||||||
|
newSchema[name] = {
|
||||||
|
calculationType: calc.type,
|
||||||
|
field: calc.field,
|
||||||
|
visible: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add other fields
|
||||||
|
for (let field of Object.keys(schema)) {
|
||||||
|
if (schema[field].calculationType) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newSchema[field] = {
|
||||||
|
...schema[field],
|
||||||
|
visible: groupBy.includes(field),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure primary display is valid
|
||||||
|
let primaryDisplay = $definition.primaryDisplay
|
||||||
|
if (!primaryDisplay || !newSchema[primaryDisplay]?.visible) {
|
||||||
|
primaryDisplay = groupBy[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save changes
|
||||||
|
await datasource.actions.saveDefinition({
|
||||||
|
...$definition,
|
||||||
|
primaryDisplay,
|
||||||
|
schema: newSchema,
|
||||||
|
})
|
||||||
|
await rows.actions.refreshData()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ActionButton icon="WebPage" quiet on:click={open}>
|
||||||
|
Configure calculations{count ? `: ${count}` : ""}
|
||||||
|
</ActionButton>
|
||||||
|
|
||||||
|
<Modal bind:this={modal}>
|
||||||
|
<ModalContent
|
||||||
|
title="Calculations"
|
||||||
|
confirmText="Save"
|
||||||
|
size="M"
|
||||||
|
onConfirm={save}
|
||||||
|
>
|
||||||
|
{#if calculations.length}
|
||||||
|
<div class="calculations">
|
||||||
|
{#each calculations as calc, idx}
|
||||||
|
<span>{idx === 0 ? "Calculate" : "and"} the</span>
|
||||||
|
<Select
|
||||||
|
options={getTypeOptions(calc, calculations)}
|
||||||
|
bind:value={calc.type}
|
||||||
|
placeholder={false}
|
||||||
|
/>
|
||||||
|
<span>of</span>
|
||||||
|
<Select
|
||||||
|
options={getFieldOptions(calc, calculations, schema)}
|
||||||
|
bind:value={calc.field}
|
||||||
|
placeholder="Column"
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
hoverable
|
||||||
|
name="Delete"
|
||||||
|
size="S"
|
||||||
|
on:click={() => deleteCalc(idx)}
|
||||||
|
color="var(--spectrum-global-color-gray-700)"
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
<span>Group by</span>
|
||||||
|
<div class="group-by">
|
||||||
|
<Multiselect
|
||||||
|
options={groupByOptions}
|
||||||
|
bind:value={groupBy}
|
||||||
|
placeholder="None"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="buttons">
|
||||||
|
<ActionButton
|
||||||
|
quiet
|
||||||
|
icon="Add"
|
||||||
|
on:click={addCalc}
|
||||||
|
disabled={calculations.length >= 5}
|
||||||
|
>
|
||||||
|
Add calculation
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
<InfoDisplay
|
||||||
|
icon="Help"
|
||||||
|
quiet
|
||||||
|
body="Calculations only work with numeric columns and a maximum of 5 calculations can be added at once."
|
||||||
|
/>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.calculations {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr auto 1fr auto;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: var(--spacing-m);
|
||||||
|
row-gap: var(--spacing-m);
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.group-by {
|
||||||
|
grid-column: 2 / 5;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -4,6 +4,7 @@
|
||||||
export let title
|
export let title
|
||||||
export let align = "left"
|
export let align = "left"
|
||||||
export let showPopover
|
export let showPopover
|
||||||
|
export let width
|
||||||
|
|
||||||
let popover
|
let popover
|
||||||
let anchor
|
let anchor
|
||||||
|
@ -22,8 +23,8 @@
|
||||||
<Popover
|
<Popover
|
||||||
bind:this={popover}
|
bind:this={popover}
|
||||||
bind:open
|
bind:open
|
||||||
minWidth={400}
|
minWidth={width || 400}
|
||||||
maxWidth={400}
|
maxWidth={width || 400}
|
||||||
{anchor}
|
{anchor}
|
||||||
{align}
|
{align}
|
||||||
{showPopover}
|
{showPopover}
|
||||||
|
|
|
@ -13,14 +13,18 @@
|
||||||
import GridGenerateButton from "components/backend/DataTable/buttons/grid/GridGenerateButton.svelte"
|
import GridGenerateButton from "components/backend/DataTable/buttons/grid/GridGenerateButton.svelte"
|
||||||
import GridScreensButton from "components/backend/DataTable/buttons/grid/GridScreensButton.svelte"
|
import GridScreensButton from "components/backend/DataTable/buttons/grid/GridScreensButton.svelte"
|
||||||
import GridRowActionsButton from "components/backend/DataTable/buttons/grid/GridRowActionsButton.svelte"
|
import GridRowActionsButton from "components/backend/DataTable/buttons/grid/GridRowActionsButton.svelte"
|
||||||
|
import GridViewCalculationButton from "components/backend/DataTable/buttons/grid/GridViewCalculationButton.svelte"
|
||||||
|
import { ViewV2Type } from "@budibase/types"
|
||||||
|
|
||||||
let generateButton
|
let generateButton
|
||||||
|
|
||||||
$: id = $viewsV2.selected?.id
|
$: view = $viewsV2.selected
|
||||||
|
$: calculation = view?.type === ViewV2Type.CALCULATION
|
||||||
|
$: id = view?.id
|
||||||
$: datasource = {
|
$: datasource = {
|
||||||
type: "viewV2",
|
type: "viewV2",
|
||||||
id,
|
id,
|
||||||
tableId: $viewsV2.selected?.tableId,
|
tableId: view?.tableId,
|
||||||
}
|
}
|
||||||
$: buttons = makeRowActionButtons($rowActions[id])
|
$: buttons = makeRowActionButtons($rowActions[id])
|
||||||
$: rowActions.refreshRowActions(id)
|
$: rowActions.refreshRowActions(id)
|
||||||
|
@ -56,13 +60,18 @@
|
||||||
buttonsCollapsed
|
buttonsCollapsed
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="controls">
|
<svelte:fragment slot="controls">
|
||||||
|
{#if calculation}
|
||||||
|
<GridViewCalculationButton />
|
||||||
|
{/if}
|
||||||
<GridManageAccessButton />
|
<GridManageAccessButton />
|
||||||
<GridFilterButton />
|
<GridFilterButton />
|
||||||
<GridSortButton />
|
<GridSortButton />
|
||||||
<GridSizeButton />
|
<GridSizeButton />
|
||||||
<GridColumnsSettingButton />
|
{#if !calculation}
|
||||||
<GridRowActionsButton />
|
<GridColumnsSettingButton />
|
||||||
<GridScreensButton on:generate={() => generateButton?.show()} />
|
<GridRowActionsButton />
|
||||||
|
<GridScreensButton on:generate={() => generateButton?.show()} />
|
||||||
|
{/if}
|
||||||
<GridGenerateButton bind:this={generateButton} />
|
<GridGenerateButton bind:this={generateButton} />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<GridCreateEditRowModal />
|
<GridCreateEditRowModal />
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import DetailPopover from "components/common/DetailPopover.svelte"
|
import DetailPopover from "components/common/DetailPopover.svelte"
|
||||||
import { Input, notifications, Button, Icon } from "@budibase/bbui"
|
import { Input, notifications, Button, Icon, ListItem } from "@budibase/bbui"
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { viewsV2 } from "stores/builder"
|
import { viewsV2 } from "stores/builder"
|
||||||
|
import { ViewV2Type } from "@budibase/types"
|
||||||
|
|
||||||
export let table
|
export let table
|
||||||
export let firstView = false
|
export let firstView = false
|
||||||
|
|
||||||
|
let calculation = false
|
||||||
let name
|
let name
|
||||||
let popover
|
let popover
|
||||||
|
|
||||||
|
@ -37,8 +39,9 @@
|
||||||
const newView = await viewsV2.create({
|
const newView = await viewsV2.create({
|
||||||
name: trimmedName,
|
name: trimmedName,
|
||||||
tableId: table._id,
|
tableId: table._id,
|
||||||
schema: enrichSchema(table.schema),
|
schema: calculation ? {} : enrichSchema(table.schema),
|
||||||
primaryDisplay: table.primaryDisplay,
|
primaryDisplay: calculation ? undefined : table.primaryDisplay,
|
||||||
|
type: calculation ? ViewV2Type.CALCULATION : undefined,
|
||||||
})
|
})
|
||||||
notifications.success(`View ${name} created`)
|
notifications.success(`View ${name} created`)
|
||||||
$goto(`./${newView.id}`)
|
$goto(`./${newView.id}`)
|
||||||
|
@ -52,6 +55,7 @@
|
||||||
title="Create view"
|
title="Create view"
|
||||||
bind:this={popover}
|
bind:this={popover}
|
||||||
on:open={() => (name = null)}
|
on:open={() => (name = null)}
|
||||||
|
width={540}
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="anchor" let:open>
|
<svelte:fragment slot="anchor" let:open>
|
||||||
{#if firstView}
|
{#if firstView}
|
||||||
|
@ -66,6 +70,28 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
<div class="options">
|
||||||
|
<div>
|
||||||
|
<ListItem
|
||||||
|
title="Table"
|
||||||
|
subtitle="Create a subset of your data"
|
||||||
|
hoverable
|
||||||
|
on:click={() => (calculation = false)}
|
||||||
|
selected={!calculation}
|
||||||
|
icon="Rail"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<ListItem
|
||||||
|
title="Calculation"
|
||||||
|
subtitle="Calculate groups of rows"
|
||||||
|
hoverable
|
||||||
|
on:click={() => (calculation = true)}
|
||||||
|
selected={calculation}
|
||||||
|
icon="123"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<Input
|
<Input
|
||||||
label="Name"
|
label="Name"
|
||||||
thin
|
thin
|
||||||
|
@ -81,6 +107,11 @@
|
||||||
</DetailPopover>
|
</DetailPopover>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.options {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
}
|
||||||
.icon {
|
.icon {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
|
|
|
@ -4,11 +4,12 @@
|
||||||
export let title
|
export let title
|
||||||
export let body
|
export let body
|
||||||
export let icon = "HelpOutline"
|
export let icon = "HelpOutline"
|
||||||
|
export let quiet = false
|
||||||
export let warning = false
|
export let warning = false
|
||||||
export let error = false
|
export let error = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="info" class:noTitle={!title} class:warning class:error>
|
<div class="info" class:noTitle={!title} class:warning class:error class:quiet>
|
||||||
{#if title}
|
{#if title}
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<Icon name={icon} />
|
<Icon name={icon} />
|
||||||
|
@ -58,7 +59,22 @@
|
||||||
.icon {
|
.icon {
|
||||||
color: var(--spectrum-global-color-gray-600);
|
color: var(--spectrum-global-color-gray-600);
|
||||||
}
|
}
|
||||||
|
.info {
|
||||||
|
background-color: var(--background-alt);
|
||||||
|
padding: var(--spacing-m) var(--spacing-l) var(--spacing-m) var(--spacing-l);
|
||||||
|
border-radius: var(--border-radius-s);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.quiet {
|
||||||
|
background: none;
|
||||||
|
color: var(--spectrum-global-color-gray-700);
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.noTitle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-l);
|
||||||
|
}
|
||||||
.info :global(a) {
|
.info :global(a) {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
transition: color 130ms ease-out;
|
transition: color 130ms ease-out;
|
||||||
|
|
|
@ -79,10 +79,12 @@
|
||||||
|
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
|
|
||||||
$: fieldOptions = (schemaFields || []).map(field => ({
|
$: fieldOptions = (schemaFields || [])
|
||||||
label: field.displayName || field.name,
|
.filter(field => !field.calculationType)
|
||||||
value: field.name,
|
.map(field => ({
|
||||||
}))
|
label: field.displayName || field.name,
|
||||||
|
value: field.name,
|
||||||
|
}))
|
||||||
|
|
||||||
const onFieldChange = filter => {
|
const onFieldChange = filter => {
|
||||||
const previousType = filter.type
|
const previousType = filter.type
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import { getColumnIcon } from "../../../utils/schema"
|
import { getColumnIcon } from "../../../utils/schema"
|
||||||
import MigrationModal from "../controls/MigrationModal.svelte"
|
import MigrationModal from "../controls/MigrationModal.svelte"
|
||||||
import { debounce } from "../../../utils/utils"
|
import { debounce } from "../../../utils/utils"
|
||||||
import { FieldType, FormulaType } from "@budibase/types"
|
import { FieldType, FormulaType, SortOrder } from "@budibase/types"
|
||||||
import { TableNames } from "../../../constants"
|
import { TableNames } from "../../../constants"
|
||||||
import GridPopover from "../overlays/GridPopover.svelte"
|
import GridPopover from "../overlays/GridPopover.svelte"
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
$: sortedBy = column.name === $sort.column
|
$: sortedBy = column.name === $sort.column
|
||||||
$: canMoveLeft = orderable && idx > 0
|
$: canMoveLeft = orderable && idx > 0
|
||||||
$: canMoveRight = orderable && idx < $scrollableColumns.length - 1
|
$: canMoveRight = orderable && idx < $scrollableColumns.length - 1
|
||||||
$: sortingLabels = getSortingLabels(column.schema?.type)
|
$: sortingLabels = getSortingLabels(column)
|
||||||
$: searchable = isColumnSearchable(column)
|
$: searchable = isColumnSearchable(column)
|
||||||
$: resetSearchValue(column.name)
|
$: resetSearchValue(column.name)
|
||||||
$: searching = searchValue != null
|
$: searching = searchValue != null
|
||||||
|
@ -66,8 +66,14 @@
|
||||||
editIsOpen = false
|
editIsOpen = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSortingLabels = type => {
|
const getSortingLabels = column => {
|
||||||
switch (type) {
|
if (column.calculationType) {
|
||||||
|
return {
|
||||||
|
ascending: "low-high",
|
||||||
|
descending: "high-low",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (column?.schema?.type) {
|
||||||
case FieldType.NUMBER:
|
case FieldType.NUMBER:
|
||||||
case FieldType.BIGINT:
|
case FieldType.BIGINT:
|
||||||
return {
|
return {
|
||||||
|
@ -137,7 +143,7 @@
|
||||||
const sortAscending = () => {
|
const sortAscending = () => {
|
||||||
sort.set({
|
sort.set({
|
||||||
column: column.name,
|
column: column.name,
|
||||||
order: "ascending",
|
order: SortOrder.ASCENDING,
|
||||||
})
|
})
|
||||||
open = false
|
open = false
|
||||||
}
|
}
|
||||||
|
@ -145,7 +151,7 @@
|
||||||
const sortDescending = () => {
|
const sortDescending = () => {
|
||||||
sort.set({
|
sort.set({
|
||||||
column: column.name,
|
column: column.name,
|
||||||
order: "descending",
|
order: SortOrder.DESCENDING,
|
||||||
})
|
})
|
||||||
open = false
|
open = false
|
||||||
}
|
}
|
||||||
|
@ -318,7 +324,7 @@
|
||||||
<Icon
|
<Icon
|
||||||
hoverable
|
hoverable
|
||||||
size="S"
|
size="S"
|
||||||
name={$sort.order === "descending"
|
name={$sort.order === SortOrder.DESCENDING
|
||||||
? "SortOrderDown"
|
? "SortOrderDown"
|
||||||
: "SortOrderUp"}
|
: "SortOrderUp"}
|
||||||
/>
|
/>
|
||||||
|
@ -366,7 +372,8 @@
|
||||||
icon="SortOrderUp"
|
icon="SortOrderUp"
|
||||||
on:click={sortAscending}
|
on:click={sortAscending}
|
||||||
disabled={!canBeSortColumn(column.schema) ||
|
disabled={!canBeSortColumn(column.schema) ||
|
||||||
(column.name === $sort.column && $sort.order === "ascending")}
|
(column.name === $sort.column &&
|
||||||
|
$sort.order === SortOrder.ASCENDING)}
|
||||||
>
|
>
|
||||||
Sort {sortingLabels.ascending}
|
Sort {sortingLabels.ascending}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
@ -374,7 +381,8 @@
|
||||||
icon="SortOrderDown"
|
icon="SortOrderDown"
|
||||||
on:click={sortDescending}
|
on:click={sortDescending}
|
||||||
disabled={!canBeSortColumn(column.schema) ||
|
disabled={!canBeSortColumn(column.schema) ||
|
||||||
(column.name === $sort.column && $sort.order === "descending")}
|
(column.name === $sort.column &&
|
||||||
|
$sort.order === SortOrder.DESCENDING)}
|
||||||
>
|
>
|
||||||
Sort {sortingLabels.descending}
|
Sort {sortingLabels.descending}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
|
@ -41,6 +41,9 @@ const TypeComponentMap = {
|
||||||
role: RoleCell,
|
role: RoleCell,
|
||||||
}
|
}
|
||||||
export const getCellRenderer = column => {
|
export const getCellRenderer = column => {
|
||||||
|
if (column.calculationType) {
|
||||||
|
return NumberCell
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
TypeComponentMap[column?.schema?.cellRenderType] ||
|
TypeComponentMap[column?.schema?.cellRenderType] ||
|
||||||
TypeComponentMap[column?.schema?.type] ||
|
TypeComponentMap[column?.schema?.type] ||
|
||||||
|
|
|
@ -161,10 +161,10 @@ export const initialise = context => {
|
||||||
order: fieldSchema.order ?? oldColumn?.order,
|
order: fieldSchema.order ?? oldColumn?.order,
|
||||||
conditions: fieldSchema.conditions,
|
conditions: fieldSchema.conditions,
|
||||||
related: fieldSchema.related,
|
related: fieldSchema.related,
|
||||||
|
calculationType: fieldSchema.calculationType,
|
||||||
}
|
}
|
||||||
// Override a few properties for primary display
|
// Override a few properties for primary display
|
||||||
if (field === primaryDisplay) {
|
if (field === primaryDisplay) {
|
||||||
column.visible = true
|
|
||||||
column.order = 0
|
column.order = 0
|
||||||
column.primaryDisplay = true
|
column.primaryDisplay = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { derivedMemo } from "../../../utils"
|
import { derivedMemo } from "../../../utils"
|
||||||
import { derived } from "svelte/store"
|
import { derived } from "svelte/store"
|
||||||
|
import { ViewV2Type } from "@budibase/types"
|
||||||
|
|
||||||
export const createStores = context => {
|
export const createStores = context => {
|
||||||
const { props } = context
|
const { props } = context
|
||||||
|
@ -30,18 +31,26 @@ export const createStores = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = context => {
|
||||||
const { props, hasNonAutoColumn } = context
|
const { props, definition, hasNonAutoColumn } = context
|
||||||
|
|
||||||
// Derive features
|
// Derive features
|
||||||
const config = derived(
|
const config = derived(
|
||||||
[props, hasNonAutoColumn],
|
[props, definition, hasNonAutoColumn],
|
||||||
([$props, $hasNonAutoColumn]) => {
|
([$props, $definition, $hasNonAutoColumn]) => {
|
||||||
let config = { ...$props }
|
let config = { ...$props }
|
||||||
const type = $props.datasource?.type
|
const type = $props.datasource?.type
|
||||||
|
|
||||||
// Disable some features if we're editing a view
|
// Disable some features if we're editing a view
|
||||||
if (type === "viewV2") {
|
if (type === "viewV2") {
|
||||||
config.canEditColumns = false
|
config.canEditColumns = false
|
||||||
|
|
||||||
|
// Disable features for calculation views
|
||||||
|
if ($definition?.type === ViewV2Type.CALCULATION) {
|
||||||
|
config.canAddRows = false
|
||||||
|
config.canEditRows = false
|
||||||
|
config.canDeleteRows = false
|
||||||
|
config.canExpandRows = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable adding rows if we don't have any valid columns
|
// Disable adding rows if we don't have any valid columns
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { derived, get } from "svelte/store"
|
||||||
import { getDatasourceDefinition, getDatasourceSchema } from "../../../fetch"
|
import { getDatasourceDefinition, getDatasourceSchema } from "../../../fetch"
|
||||||
import { enrichSchemaWithRelColumns, memo } from "../../../utils"
|
import { enrichSchemaWithRelColumns, memo } from "../../../utils"
|
||||||
import { cloneDeep } from "lodash"
|
import { cloneDeep } from "lodash"
|
||||||
|
import { ViewV2Type } from "@budibase/types"
|
||||||
|
|
||||||
export const createStores = () => {
|
export const createStores = () => {
|
||||||
const definition = memo(null)
|
const definition = memo(null)
|
||||||
|
@ -81,13 +82,20 @@ export const deriveStores = context => {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const hasBudibaseIdentifiers = derived(datasource, $datasource => {
|
const hasBudibaseIdentifiers = derived(
|
||||||
let type = $datasource?.type
|
[datasource, definition],
|
||||||
if (type === "provider") {
|
([$datasource, $definition]) => {
|
||||||
type = $datasource.value?.datasource?.type
|
let type = $datasource?.type
|
||||||
|
if (type === "provider") {
|
||||||
|
type = $datasource.value?.datasource?.type
|
||||||
|
}
|
||||||
|
// Handle calculation views
|
||||||
|
if (type === "viewV2" && $definition?.type === ViewV2Type.CALCULATION) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return ["table", "viewV2", "link"].includes(type)
|
||||||
}
|
}
|
||||||
return ["table", "viewV2", "link"].includes(type)
|
)
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
schema,
|
schema,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { SortOrder } from "@budibase/types"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
|
||||||
export const createActions = context => {
|
export const createActions = context => {
|
||||||
|
@ -84,7 +85,7 @@ export const initialise = context => {
|
||||||
inlineFilters.set([])
|
inlineFilters.set([])
|
||||||
sort.set({
|
sort.set({
|
||||||
column: get(initialSortColumn),
|
column: get(initialSortColumn),
|
||||||
order: get(initialSortOrder) || "ascending",
|
order: get(initialSortOrder) || SortOrder.ASCENDING,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Update fetch when filter changes
|
// Update fetch when filter changes
|
||||||
|
@ -110,7 +111,7 @@ export const initialise = context => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
$fetch.update({
|
$fetch.update({
|
||||||
sortOrder: $sort.order || "ascending",
|
sortOrder: $sort.order || SortOrder.ASCENDING,
|
||||||
sortColumn: $sort.column,
|
sortColumn: $sort.column,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { SortOrder } from "@budibase/types"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
|
||||||
const SuppressErrors = true
|
const SuppressErrors = true
|
||||||
|
@ -93,7 +94,7 @@ export const initialise = context => {
|
||||||
inlineFilters.set([])
|
inlineFilters.set([])
|
||||||
sort.set({
|
sort.set({
|
||||||
column: get(initialSortColumn),
|
column: get(initialSortColumn),
|
||||||
order: get(initialSortOrder) || "ascending",
|
order: get(initialSortOrder) || SortOrder.ASCENDING,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Update fetch when filter changes
|
// Update fetch when filter changes
|
||||||
|
@ -119,7 +120,7 @@ export const initialise = context => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
$fetch.update({
|
$fetch.update({
|
||||||
sortOrder: $sort.order || "ascending",
|
sortOrder: $sort.order || SortOrder.ASCENDING,
|
||||||
sortColumn: $sort.column,
|
sortColumn: $sort.column,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
import { SortOrder } from "@budibase/types"
|
||||||
|
|
||||||
const SuppressErrors = true
|
const SuppressErrors = true
|
||||||
|
|
||||||
|
@ -104,7 +105,7 @@ export const initialise = context => {
|
||||||
inlineFilters.set([])
|
inlineFilters.set([])
|
||||||
sort.set({
|
sort.set({
|
||||||
column: get(initialSortColumn),
|
column: get(initialSortColumn),
|
||||||
order: get(initialSortOrder) || "ascending",
|
order: get(initialSortOrder) || SortOrder.ASCENDING,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Keep sort and filter state in line with the view definition when in builder
|
// Keep sort and filter state in line with the view definition when in builder
|
||||||
|
@ -120,7 +121,7 @@ export const initialise = context => {
|
||||||
if (!get(initialSortColumn)) {
|
if (!get(initialSortColumn)) {
|
||||||
sort.set({
|
sort.set({
|
||||||
column: $definition.sort?.field,
|
column: $definition.sort?.field,
|
||||||
order: $definition.sort?.order || "ascending",
|
order: $definition.sort?.order || SortOrder.ASCENDING,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// Only override filter state if we don't have an initial filter
|
// Only override filter state if we don't have an initial filter
|
||||||
|
@ -153,7 +154,7 @@ export const initialise = context => {
|
||||||
...$view,
|
...$view,
|
||||||
sort: {
|
sort: {
|
||||||
field: $sort.column,
|
field: $sort.column,
|
||||||
order: $sort.order || "ascending",
|
order: $sort.order || SortOrder.ASCENDING,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { derived, get } from "svelte/store"
|
import { derived, get } from "svelte/store"
|
||||||
import { memo } from "../../../utils"
|
import { memo } from "../../../utils"
|
||||||
|
import { SortOrder } from "@budibase/types"
|
||||||
|
|
||||||
export const createStores = context => {
|
export const createStores = context => {
|
||||||
const { props } = context
|
const { props } = context
|
||||||
|
@ -8,7 +9,7 @@ export const createStores = context => {
|
||||||
// Initialise to default props
|
// Initialise to default props
|
||||||
const sort = memo({
|
const sort = memo({
|
||||||
column: $props.initialSortColumn,
|
column: $props.initialSortColumn,
|
||||||
order: $props.initialSortOrder || "ascending",
|
order: $props.initialSortOrder || SortOrder.ASCENDING,
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -24,7 +25,10 @@ export const initialise = context => {
|
||||||
sort.update(state => ({ ...state, column: newSortColumn }))
|
sort.update(state => ({ ...state, column: newSortColumn }))
|
||||||
})
|
})
|
||||||
initialSortOrder.subscribe(newSortOrder => {
|
initialSortOrder.subscribe(newSortOrder => {
|
||||||
sort.update(state => ({ ...state, order: newSortOrder || "ascending" }))
|
sort.update(state => ({
|
||||||
|
...state,
|
||||||
|
order: newSortOrder || SortOrder.ASCENDING,
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Derive if the current sort column exists in the schema
|
// Derive if the current sort column exists in the schema
|
||||||
|
@ -40,7 +44,7 @@ export const initialise = context => {
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
sort.set({
|
sort.set({
|
||||||
column: null,
|
column: null,
|
||||||
order: "ascending",
|
order: SortOrder.ASCENDING,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { writable, derived, get } from "svelte/store"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { QueryUtils } from "../utils"
|
import { QueryUtils } from "../utils"
|
||||||
import { convertJSONSchemaToTableSchema } from "../utils/json"
|
import { convertJSONSchemaToTableSchema } from "../utils/json"
|
||||||
|
import { FieldType, SortOrder, SortType } from "@budibase/types"
|
||||||
|
|
||||||
const { buildQuery, limit: queryLimit, runQuery, sort } = QueryUtils
|
const { buildQuery, limit: queryLimit, runQuery, sort } = QueryUtils
|
||||||
|
|
||||||
|
@ -37,7 +38,7 @@ export default class DataFetch {
|
||||||
|
|
||||||
// Sorting config
|
// Sorting config
|
||||||
sortColumn: null,
|
sortColumn: null,
|
||||||
sortOrder: "ascending",
|
sortOrder: SortOrder.ASCENDING,
|
||||||
sortType: null,
|
sortType: null,
|
||||||
|
|
||||||
// Pagination config
|
// Pagination config
|
||||||
|
@ -162,17 +163,22 @@ export default class DataFetch {
|
||||||
// If we don't have a sort column specified then just ensure we don't set
|
// If we don't have a sort column specified then just ensure we don't set
|
||||||
// any sorting params
|
// any sorting params
|
||||||
if (!this.options.sortColumn) {
|
if (!this.options.sortColumn) {
|
||||||
this.options.sortOrder = "ascending"
|
this.options.sortOrder = SortOrder.ASCENDING
|
||||||
this.options.sortType = null
|
this.options.sortType = null
|
||||||
} else {
|
} else {
|
||||||
// Otherwise determine what sort type to use base on sort column
|
// Otherwise determine what sort type to use base on sort column
|
||||||
const type = schema?.[this.options.sortColumn]?.type
|
this.options.sortType = SortType.STRING
|
||||||
this.options.sortType =
|
const fieldSchema = schema?.[this.options.sortColumn]
|
||||||
type === "number" || type === "bigint" ? "number" : "string"
|
if (
|
||||||
|
fieldSchema?.type === FieldType.NUMBER ||
|
||||||
|
fieldSchema?.type === FieldType.BIGINT ||
|
||||||
|
fieldSchema?.calculationType
|
||||||
|
) {
|
||||||
|
this.options.sortType = SortType.NUMBER
|
||||||
|
}
|
||||||
// If no sort order, default to ascending
|
// If no sort order, default to ascending
|
||||||
if (!this.options.sortOrder) {
|
if (!this.options.sortOrder) {
|
||||||
this.options.sortOrder = "ascending"
|
this.options.sortOrder = SortOrder.ASCENDING
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,7 +316,7 @@ export default class DataFetch {
|
||||||
let jsonAdditions = {}
|
let jsonAdditions = {}
|
||||||
Object.keys(schema).forEach(fieldKey => {
|
Object.keys(schema).forEach(fieldKey => {
|
||||||
const fieldSchema = schema[fieldKey]
|
const fieldSchema = schema[fieldKey]
|
||||||
if (fieldSchema?.type === "json") {
|
if (fieldSchema?.type === FieldType.JSON) {
|
||||||
const jsonSchema = convertJSONSchemaToTableSchema(fieldSchema, {
|
const jsonSchema = convertJSONSchemaToTableSchema(fieldSchema, {
|
||||||
squashObjects: true,
|
squashObjects: true,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import DataFetch from "./DataFetch.js"
|
import DataFetch from "./DataFetch.js"
|
||||||
|
import { SortOrder } from "@budibase/types"
|
||||||
|
|
||||||
export default class TableFetch extends DataFetch {
|
export default class TableFetch extends DataFetch {
|
||||||
determineFeatureFlags() {
|
determineFeatureFlags() {
|
||||||
|
@ -23,7 +24,7 @@ export default class TableFetch extends DataFetch {
|
||||||
query,
|
query,
|
||||||
limit,
|
limit,
|
||||||
sort: sortColumn,
|
sort: sortColumn,
|
||||||
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
|
sortOrder: sortOrder?.toLowerCase() ?? SortOrder.ASCENDING,
|
||||||
sortType,
|
sortType,
|
||||||
paginate,
|
paginate,
|
||||||
bookmark: cursor,
|
bookmark: cursor,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { ViewV2Type } from "@budibase/types"
|
||||||
import DataFetch from "./DataFetch.js"
|
import DataFetch from "./DataFetch.js"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
|
|
||||||
|
@ -39,6 +40,19 @@ export default class ViewV2Fetch extends DataFetch {
|
||||||
this.options
|
this.options
|
||||||
const { cursor, query, definition } = get(this.store)
|
const { cursor, query, definition } = get(this.store)
|
||||||
|
|
||||||
|
// If this is a calculation view and we have no calculations, return nothing
|
||||||
|
if (
|
||||||
|
definition.type === ViewV2Type.CALCULATION &&
|
||||||
|
!Object.values(definition.schema || {}).some(x => x.calculationType)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
rows: [],
|
||||||
|
hasNextPage: false,
|
||||||
|
cursor: null,
|
||||||
|
error: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If sort/filter params are not defined, update options to store the
|
// If sort/filter params are not defined, update options to store the
|
||||||
// params built in to this view. This ensures that we can accurately
|
// params built in to this view. This ensures that we can accurately
|
||||||
// compare old and new params and skip a redundant API call.
|
// compare old and new params and skip a redundant API call.
|
||||||
|
@ -67,6 +81,7 @@ export default class ViewV2Fetch extends DataFetch {
|
||||||
return {
|
return {
|
||||||
rows: [],
|
rows: [],
|
||||||
hasNextPage: false,
|
hasNextPage: false,
|
||||||
|
cursor: null,
|
||||||
error,
|
error,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,15 +5,15 @@ export const getColumnIcon = column => {
|
||||||
if (column.schema.icon) {
|
if (column.schema.icon) {
|
||||||
return column.schema.icon
|
return column.schema.icon
|
||||||
}
|
}
|
||||||
|
if (column.calculationType) {
|
||||||
|
return "Calculator"
|
||||||
|
}
|
||||||
if (column.schema.autocolumn) {
|
if (column.schema.autocolumn) {
|
||||||
return "MagicWand"
|
return "MagicWand"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (helpers.schema.isDeprecatedSingleUserColumn(column.schema)) {
|
if (helpers.schema.isDeprecatedSingleUserColumn(column.schema)) {
|
||||||
return "User"
|
return "User"
|
||||||
}
|
}
|
||||||
|
|
||||||
const { type, subtype } = column.schema
|
const { type, subtype } = column.schema
|
||||||
const result =
|
const result =
|
||||||
typeof TypeIconMap[type] === "object" && subtype
|
typeof TypeIconMap[type] === "object" && subtype
|
||||||
|
|
|
@ -4,24 +4,24 @@ export function canBeDisplayColumn(column) {
|
||||||
if (!sharedCore.canBeDisplayColumn(column.type)) {
|
if (!sharedCore.canBeDisplayColumn(column.type)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
// If it's a related column (only available in the frontend), don't allow using it as display column
|
||||||
if (column.related) {
|
if (column.related) {
|
||||||
// If it's a related column (only available in the frontend), don't allow using it as display column
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canBeSortColumn(column) {
|
export function canBeSortColumn(column) {
|
||||||
|
// Allow sorting by calculation columns
|
||||||
|
if (column.calculationType) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if (!sharedCore.canBeSortColumn(column.type)) {
|
if (!sharedCore.canBeSortColumn(column.type)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
// If it's a related column (only available in the frontend), don't allow using it as display column
|
||||||
if (column.related) {
|
if (column.related) {
|
||||||
// If it's a related column (only available in the frontend), don't allow using it as display column
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue