mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-15 16:03:02 +00:00
* feat: replace ag-grid in the db manager table viewer with a native grid Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: add user-managed data, diagram and sql editor tabs to the db manager Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: add schema autocomplete to the db manager sql editor and polish its layout Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: add joined foreign key columns and draggable tabs to the db manager Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: move db manager tabs on drop instead of mid-drag Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: open followed foreign keys in a new db manager tab Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: scroll large tables, drop stale joins and return to the last tab on close Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: tolerate the db manager tabs going away while switching data table Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: keep saved joined columns while metadata loads, test joins in every dialect Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: put the db manager grid on the input surface in dark mode Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: address db manager review nits on joins, boolean keys and sql quoting Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: keep the dark border color on the left pinned column edge Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: tone down the db manager tree menu icons Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: write json and jsonb values from the db manager through a text cast Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: dim unrelated diagram tables less on hover Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: match json columns as text when deleting a db manager row Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: compare postgres json columns as text in exact db manager filters Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: pick the columns the db manager grid shows Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: toggle every db manager column from one checkbox Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: word joined columns as a view in the db manager Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: keep data table roles and access visible when unavailable, with the reason Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: keep data table and instance roles visible in settings when unavailable Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: hide a db manager column from its header menu Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: format db manager columns with a unit, significant digits and color rules Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: drop the before/after hints from the db manager unit picker Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: write the euro after the amount and keep units off non-numeric values Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: color rule presets, bold and italic, layered and reorderable rules in the db manager Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: decimals, thousands separator, compact notation and alignment in db manager column formats Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * feat: compact db manager format controls, a notation toggle group and a reset button Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix: db manager format and filter nits Keep the value's scale when decimals are auto, read boolean color-rule conditions as booleans, let a rule's text color reach foreign-key links, close the formatter when the columns picker opens, and filter BigQuery complex columns through TO_JSON_STRING. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
144 lines
5.8 KiB
TypeScript
144 lines
5.8 KiB
TypeScript
import { dbColumnKind, matchesColumnFilter } from './dbTableFilters'
|
|
|
|
/** How a column's values are displayed. Display only: filters, sorting, edits and copies keep
|
|
* the raw value. */
|
|
export type ColumnFormat = {
|
|
unit?: { symbol: string; position: 'before' | 'after' }
|
|
/** Digits after the decimal point; undefined keeps those of the value. */
|
|
decimals?: number
|
|
/** Grouped by thousands (1,234,567) or shortened (1.2M); undefined writes 1234567. */
|
|
notation?: 'thousands' | 'compact'
|
|
/** Undefined aligns numbers right and anything else left. */
|
|
align?: 'left' | 'right'
|
|
/** Every rule whose condition matches styles the cell, a later one overriding an earlier one. */
|
|
rules?: ColorRule[]
|
|
}
|
|
|
|
export type ColorRule = {
|
|
/** In the search bar's filter syntax: `=paid`, `>= 4`, `pend`. Empty matches every cell. */
|
|
condition: string
|
|
bg?: string
|
|
text?: string
|
|
bold?: boolean
|
|
italic?: boolean
|
|
}
|
|
|
|
/** Picked when adding a rule, so that one click sets a background and a text color that read
|
|
* well together. */
|
|
export const RULE_PRESETS: { bg?: string; text: string }[] = [
|
|
{ bg: '#fee2e2', text: '#991b1b' },
|
|
{ bg: '#ffedd5', text: '#9a3412' },
|
|
{ bg: '#fef3c7', text: '#92400e' },
|
|
{ bg: '#dcfce7', text: '#166534' },
|
|
{ bg: '#ccfbf1', text: '#115e59' },
|
|
{ bg: '#dbeafe', text: '#1e40af' },
|
|
{ bg: '#ede9fe', text: '#5b21b6' },
|
|
{ bg: '#fce7f3', text: '#9d174d' },
|
|
{ bg: '#f3f4f6', text: '#374151' },
|
|
{ text: '#dc2626' },
|
|
{ text: '#ea580c' },
|
|
{ text: '#d97706' },
|
|
{ text: '#16a34a' },
|
|
{ text: '#0d9488' },
|
|
{ text: '#2563eb' },
|
|
{ text: '#7c3aed' },
|
|
{ text: '#db2777' },
|
|
{ text: '#9ca3af' }
|
|
]
|
|
|
|
// Each where it is usually written: 12.5%, $5, 5 €, £5, ¥5.
|
|
export const UNIT_PRESETS: { symbol: string; position: 'before' | 'after' }[] = [
|
|
{ symbol: '%', position: 'after' },
|
|
{ symbol: '$', position: 'before' },
|
|
{ symbol: '\u00A0€', position: 'after' },
|
|
{ symbol: '£', position: 'before' },
|
|
{ symbol: '¥', position: 'before' }
|
|
]
|
|
|
|
const NUMERIC = /^-?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?$/i
|
|
/** Previews read most values as text, so a number may arrive as a string. */
|
|
export function asNumber(value: unknown): number | undefined {
|
|
if (typeof value === 'number') return Number.isFinite(value) ? value : undefined
|
|
if (typeof value === 'string' && NUMERIC.test(value.trim())) return Number(value.trim())
|
|
return undefined
|
|
}
|
|
|
|
/** Whether the numbers of the column are formatted at all. */
|
|
export function formatsNumbers(format: ColumnFormat | undefined): boolean {
|
|
return !!format && (format.decimals !== undefined || !!format.notation || !!format.unit?.symbol)
|
|
}
|
|
|
|
/** The text a cell shows for `value`, or undefined when the format leaves it as is. */
|
|
export function formatValue(value: unknown, format: ColumnFormat | undefined): string | undefined {
|
|
if (!format || value === null || value === undefined) return undefined
|
|
const n = asNumber(value)
|
|
// A unit on text would read as a quantity: `N/A` must not become `$N/A`.
|
|
if (n === undefined) return undefined
|
|
if (!formatsNumbers(format)) return undefined
|
|
// A string keeps the digits a float would lose: numeric columns are read as text.
|
|
const source = typeof value === 'string' ? value.trim() : String(n)
|
|
const compact = format.notation === 'compact'
|
|
// Auto keeps the value's own scale, trailing zeros included: numeric(10,2) shows 1234.50.
|
|
const scale = /e/i.test(source) ? 0 : Math.min(source.split('.')[1]?.length ?? 0, 100)
|
|
const text = new Intl.NumberFormat('en-US', {
|
|
useGrouping: format.notation === 'thousands',
|
|
notation: compact ? 'compact' : 'standard',
|
|
minimumFractionDigits: format.decimals ?? (compact ? undefined : scale),
|
|
// Left to the value, or to the compact notation's own rounding.
|
|
maximumFractionDigits: format.decimals ?? (compact ? undefined : 100)
|
|
}).format(typeof value === 'string' ? (source as Intl.StringNumericLiteral) : n)
|
|
if (!format.unit?.symbol) return text
|
|
const { symbol, position } = format.unit
|
|
if (position === 'after') return `${text}${symbol}`
|
|
// A sign reads before a leading unit: -$5, not $-5.
|
|
return text.startsWith('-') ? `-${symbol}${text.slice(1)}` : `${symbol}${text}`
|
|
}
|
|
|
|
const TRUE_TEXT = ['true', 't', '1', 'yes', 'y']
|
|
const FALSE_TEXT = ['false', 'f', '0', 'no', 'n']
|
|
function asBoolean(value: unknown): boolean | undefined {
|
|
if (typeof value === 'boolean') return value
|
|
const text = String(value).trim().toLowerCase()
|
|
return TRUE_TEXT.includes(text) ? true : FALSE_TEXT.includes(text) ? false : undefined
|
|
}
|
|
|
|
/** The search bar filters a boolean column with a toggle, but a rule's condition is typed:
|
|
* `true`, `=false`, `t`… are read as the boolean they name. */
|
|
function matchesCondition(value: unknown, datatype: string | undefined, condition: string) {
|
|
if (dbColumnKind(datatype) !== 'boolean') return matchesColumnFilter(value, datatype, condition)
|
|
const expected = asBoolean(condition.replace(/^=/, ''))
|
|
return expected !== undefined && value !== null && asBoolean(value) === expected
|
|
}
|
|
|
|
export type CellStyle = Pick<ColorRule, 'bg' | 'text' | 'bold' | 'italic'>
|
|
|
|
/** Every rule `value` matches, layered in order: a later rule overrides only what it sets. */
|
|
export function cellStyle(
|
|
value: unknown,
|
|
datatype: string | undefined,
|
|
format: ColumnFormat | undefined
|
|
): CellStyle | undefined {
|
|
let style: CellStyle | undefined
|
|
for (const rule of format?.rules ?? []) {
|
|
const condition = rule.condition.trim()
|
|
// No condition styles every cell.
|
|
if (condition && !matchesCondition(value, datatype, condition)) continue
|
|
style = { ...style }
|
|
if (rule.bg) style.bg = rule.bg
|
|
if (rule.text) style.text = rule.text
|
|
if (rule.bold) style.bold = true
|
|
if (rule.italic) style.italic = true
|
|
}
|
|
return style
|
|
}
|
|
|
|
export function isEmptyFormat(format: ColumnFormat): boolean {
|
|
return (
|
|
!format.unit?.symbol &&
|
|
format.decimals === undefined &&
|
|
!format.notation &&
|
|
!format.align &&
|
|
!format.rules?.length
|
|
)
|
|
}
|