From d3ad0ccd6f81da7d30443c7d3392be8e2c17ec7b Mon Sep 17 00:00:00 2001 From: Sean Colsen Date: Mon, 29 Jul 2024 22:17:52 -0400 Subject: [PATCH 1/8] Transition frontend to use RPC API for columns.list --- mathesar_ui/src/api/rest/columns.ts | 11 - mathesar_ui/src/api/rest/types/queries.ts | 2 +- .../src/api/rest/types/tables/columns.ts | 162 ------------ .../api/rest/types/tables/joinable_tables.ts | 12 +- mathesar_ui/src/api/rpc/columns.ts | 241 ++++++++++++++++++ mathesar_ui/src/api/rpc/index.ts | 2 + .../number-input/number-formatter/options.ts | 6 +- .../AbstractTypeControl.svelte | 10 +- .../AbstractTypeSelector.svelte | 10 +- .../DurationConfiguration.svelte | 2 +- .../components/abstract-type-control/utils.ts | 16 +- .../cell-fabric/data-types/boolean.ts | 41 +-- .../data-types/components/typeDefinitions.ts | 2 +- .../components/cell-fabric/data-types/date.ts | 21 +- .../cell-fabric/data-types/datetime.ts | 24 +- .../cell-fabric/data-types/duration.ts | 20 +- .../cell-fabric/data-types/money.ts | 51 ++-- .../cell-fabric/data-types/number.ts | 50 ++-- .../cell-fabric/data-types/string.ts | 12 +- .../components/cell-fabric/data-types/time.ts | 22 +- .../src/components/cell-fabric/utils.ts | 2 +- .../packages/json-rpc-client-builder/index.ts | 1 + .../json-rpc-client-builder/requests.ts | 15 ++ .../preview/ImportPreviewContent.svelte | 15 +- .../import/preview/ImportPreviewSheet.svelte | 2 +- .../pages/import/preview/PreviewColumn.svelte | 2 +- .../import/preview/importPreviewPageUtils.ts | 2 +- .../src/pages/record/RecordPage.svelte | 4 +- .../src/pages/record/TableWidget.svelte | 7 +- mathesar_ui/src/pages/table/TablePage.svelte | 7 +- .../abstract-types/type-configs/boolean.ts | 45 ++-- .../type-configs/comboTypes/arrayFactory.ts | 20 +- .../abstract-types/type-configs/date.ts | 27 +- .../abstract-types/type-configs/datetime.ts | 26 +- .../abstract-types/type-configs/duration.ts | 23 +- .../abstract-types/type-configs/money.ts | 54 ++-- .../abstract-types/type-configs/number.ts | 41 ++- .../abstract-types/type-configs/text.ts | 4 +- .../abstract-types/type-configs/time.ts | 26 +- .../abstract-types/type-configs/utils.ts | 5 +- .../src/stores/abstract-types/types.ts | 2 +- .../src/stores/table-data/TableStructure.ts | 20 +- mathesar_ui/src/stores/table-data/columns.ts | 140 +++++----- .../src/stores/table-data/constraints.ts | 2 +- .../src/stores/table-data/constraintsUtils.ts | 2 +- .../src/stores/table-data/processedColumns.ts | 2 +- mathesar_ui/src/stores/table-data/records.ts | 2 +- .../src/stores/table-data/tabularData.ts | 23 +- mathesar_ui/src/stores/table-data/utils.ts | 2 +- mathesar_ui/src/stores/tables.ts | 7 +- .../data-explorer/urlSerializationUtils.ts | 2 +- .../src/systems/data-explorer/utils.ts | 20 +- .../RecordSelectorContent.svelte | 13 +- .../RecordSelectorController.ts | 2 +- .../RecordSelectorTable.svelte | 10 +- .../RecordSelectorWindow.svelte | 3 +- .../record-selector/recordSelectorUtils.ts | 2 +- .../src/systems/table-view/Body.svelte | 5 +- .../actions-pane/ActionsPane.svelte | 6 +- .../ForeignKeyConstraintDetails.svelte | 2 +- .../constraints/NewFkConstraint.svelte | 6 +- .../constraints/NewUniqueConstraint.svelte | 3 +- .../new-column-cell/NewColumnCell.svelte | 7 +- .../link-table/LinkTableForm.svelte | 5 +- .../column/ColumnFormatting.svelte | 8 +- .../table-inspector/column/ColumnType.svelte | 4 +- .../column/SetDefaultValue.svelte | 12 +- .../ExtractColumnsModal.svelte | 8 +- .../record-summary/AppendColumn.svelte | 2 +- .../FkRecordSummaryConfig.svelte | 5 +- .../record-summary/TemplateInput.svelte | 2 +- .../recordSummaryTemplateUtils.ts | 2 +- .../table-inspector/table/TableActions.svelte | 21 +- .../table/TableDescription.svelte | 12 +- .../table-inspector/table/TableName.svelte | 8 +- .../table/links/TableLinks.svelte | 3 +- .../utils/date-time/DateTimeSpecification.ts | 5 +- .../src/utils/duration/DurationFormatter.ts | 2 +- .../utils/duration/DurationSpecification.ts | 25 +- mathesar_ui/src/utils/objectUtils.ts | 14 + 80 files changed, 763 insertions(+), 703 deletions(-) delete mode 100644 mathesar_ui/src/api/rest/columns.ts delete mode 100644 mathesar_ui/src/api/rest/types/tables/columns.ts create mode 100644 mathesar_ui/src/api/rpc/columns.ts create mode 100644 mathesar_ui/src/utils/objectUtils.ts diff --git a/mathesar_ui/src/api/rest/columns.ts b/mathesar_ui/src/api/rest/columns.ts deleted file mode 100644 index 64e1baf5a9..0000000000 --- a/mathesar_ui/src/api/rest/columns.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Column } from './types/tables/columns'; -import { type PaginatedResponse, getAPI } from './utils/requestUtils'; - -function list(tableId: number) { - const url = `/api/db/v0/tables/${tableId}/columns/?limit=500`; - return getAPI>(url); -} - -export const columnsApi = { - list, -}; diff --git a/mathesar_ui/src/api/rest/types/queries.ts b/mathesar_ui/src/api/rest/types/queries.ts index 6517eef56a..6b5e72cea6 100644 --- a/mathesar_ui/src/api/rest/types/queries.ts +++ b/mathesar_ui/src/api/rest/types/queries.ts @@ -1,6 +1,6 @@ -import type { Column } from '@mathesar/api/rest/types/tables/columns'; import type { JpPath } from '@mathesar/api/rest/types/tables/joinable_tables'; import type { PaginatedResponse } from '@mathesar/api/rest/utils/requestUtils'; +import type { Column } from '@mathesar/api/rpc/columns'; import type { Schema } from '@mathesar/api/rpc/schemas'; export type QueryColumnAlias = string; diff --git a/mathesar_ui/src/api/rest/types/tables/columns.ts b/mathesar_ui/src/api/rest/types/tables/columns.ts deleted file mode 100644 index 763ba287fe..0000000000 --- a/mathesar_ui/src/api/rest/types/tables/columns.ts +++ /dev/null @@ -1,162 +0,0 @@ -import type { DbType } from '@mathesar/AppTypes'; - -/** - * | value | example locale | example format | - * | --------- | -------------- | -------------- | - * | 'english' | 'en' | '-123,456.7' | - * | 'german' | 'de' | '-123.456,7' | - * | 'french' | 'fr' | '-123 456,7' | - * | 'hindi' | 'hi' | '-1,23,456.7' | - * | 'swiss' | 'de-CH' | '-123'456.7' | - */ -export type NumberFormat = 'english' | 'german' | 'french' | 'hindi' | 'swiss'; - -/** - * This common for both Number and Money types - */ -interface FormattedNumberDisplayOptions { - /** When `null`, the browser's locale will be used. */ - number_format: NumberFormat | null; - - /** - * - "true": display grouping separators even if the locale prefers otherwise. - * - "false": do not display grouping separators. - */ - use_grouping: 'true' | 'false'; - - minimum_fraction_digits: number | null; - maximum_fraction_digits: number | null; -} - -export interface NumberDisplayOptions - extends Record, - FormattedNumberDisplayOptions {} - -/** - * See the [Postgres docs][1] for an explanation of `scale` and `precision`. - * - * [1]: https://www.postgresql.org/docs/current/datatype-numeric.html - */ -export interface NumberTypeOptions { - scale: number; - precision: number; -} - -export interface MoneyDisplayOptions extends FormattedNumberDisplayOptions { - /** - * e.g. "$", "€", "NZD", etc. - */ - currency_symbol: string; - - /** - * | value | formatting pattern | - * | ---------------- | -------------------------------------------- | - * | 'after-minus' | {minus_sign}{currency_symbol}{number} | - * | 'end-with-space' | {minus_sign}{number}{space}{currency_symbol} | - */ - currency_symbol_location: 'after-minus' | 'end-with-space'; - - /** - * PLANNED FOR FUTURE IMPLEMENTATION POST-ALPHA. - */ - // use_accounting_notation: boolean; -} - -export interface TextTypeOptions extends Record { - length: number | null; -} - -export interface BooleanDisplayOptions extends Record { - input: 'checkbox' | 'dropdown' | null; - custom_labels: { - TRUE: string; - FALSE: string; - } | null; -} - -export type DurationUnit = 'd' | 'h' | 'm' | 's' | 'ms'; - -export interface DurationDisplayOptions extends Record { - min: DurationUnit | null; - max: DurationUnit | null; - show_units: boolean | null; -} - -export type DateFormat = 'none' | 'us' | 'eu' | 'friendly' | 'iso'; - -export interface DateDisplayOptions extends Record { - format: DateFormat | null; -} - -export type TimeFormat = '24hr' | '12hr' | '24hrLong' | '12hrLong'; - -export interface TimeDisplayOptions extends Record { - format: TimeFormat | null; -} - -export interface TimeStampDisplayOptions extends Record { - date_format: DateDisplayOptions['format']; - time_format: TimeDisplayOptions['format']; -} - -export interface BaseColumn { - id: number; - name: string; - description: string | null; - type: DbType; - index: number; - nullable: boolean; - primary_key: boolean; - valid_target_types: DbType[]; - default: { - is_dynamic: boolean; - value: unknown; - } | null; -} - -/** - * TODO: - * - * Once we have all column types defined like `NumberColumn` is defined, then - * convert the `Column` type to a discriminated union of all possible specific - * column types. - */ -export interface Column extends BaseColumn { - type_options: Record | null; - display_options: Record | null; -} - -export interface MathesarMoneyColumn extends Column { - type: 'MATHESAR_TYPES.MATHESAR_MONEY'; - type_options: Partial | null; - display_options: Partial | null; -} - -export interface PostgresMoneyColumn extends Column { - type: 'MONEY'; - type_options: null; - display_options: Partial | null; -} - -export type MoneyColumn = MathesarMoneyColumn | PostgresMoneyColumn; - -// TODO: Remove specification of DB types here -export interface NumberColumn extends Column { - type: - | 'BIGINT' - | 'BIGSERIAL' - | 'DECIMAL' - | 'DOUBLE PRECISION' - | 'INTEGER' - | 'NUMERIC' - | 'REAL' - | 'SERIAL' - | 'SMALLINT' - | 'SMALLSERIAL'; - type_options: Partial | null; - display_options: Partial | null; -} - -export interface ArrayTypeOptions extends Record { - item_type: string; -} diff --git a/mathesar_ui/src/api/rest/types/tables/joinable_tables.ts b/mathesar_ui/src/api/rest/types/tables/joinable_tables.ts index 8b84483b2e..c53a5742f7 100644 --- a/mathesar_ui/src/api/rest/types/tables/joinable_tables.ts +++ b/mathesar_ui/src/api/rest/types/tables/joinable_tables.ts @@ -6,11 +6,11 @@ import type { Table } from '@mathesar/api/rpc/tables'; -import type { Column } from './columns'; - type ForeignKeyId = number; type IsLinkReversed = boolean; -export type JpPath = [Column['id'], Column['id']][]; + +/** [attnum, attnum][] */ +export type JpPath = [number, number][]; export interface JoinableTable { target: Table['oid']; // baseTableId @@ -26,14 +26,14 @@ export interface JoinableTablesResult { string, // tableId { name: Table['name']; - columns: Column['id'][]; + columns: number[]; } >; columns: Record< string, // columnId { - name: Column['name']; - type: Column['type']; + name: string; + type: string; } >; } diff --git a/mathesar_ui/src/api/rpc/columns.ts b/mathesar_ui/src/api/rpc/columns.ts new file mode 100644 index 0000000000..e4a3838ce6 --- /dev/null +++ b/mathesar_ui/src/api/rpc/columns.ts @@ -0,0 +1,241 @@ +import { rpcMethodTypeContainer } from '@mathesar/packages/json-rpc-client-builder'; + +export type BooleanInputType = 'checkbox' | 'dropdown'; + +/** + * | value | example locale | example format | + * | --------- | -------------- | -------------- | + * | 'english' | 'en' | '-123,456.7' | + * | 'german' | 'de' | '-123.456,7' | + * | 'french' | 'fr' | '-123 456,7' | + * | 'hindi' | 'hi' | '-1,23,456.7' | + * | 'swiss' | 'de-CH' | '-123'456.7' | + */ +export type NumberFormat = 'english' | 'german' | 'french' | 'hindi' | 'swiss'; + +/** + * Corresponds to the Intl.NumberFormat options `useGrouping`. + * + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#usegrouping + * + * - `"always"` - Display grouping separators even if the locale prefers + * otherwise. + * - `"auto"` - Display grouping separators based on the locale preference, + * which may also be dependent on the currency. + * - `"never"` - Do not display grouping separators. + */ +export type NumberGrouping = 'always' | 'auto' | 'never'; + +/** + * | value | formatting pattern | + * | ---------------- | -------------------------------------------- | + * | 'after-minus' | {minus_sign}{currency_symbol}{number} | + * | 'end-with-space' | {minus_sign}{number}{space}{currency_symbol} | + */ +export type CurrencyLocation = 'after-minus' | 'end-with-space'; + +export const allDurationUnits = ['d', 'h', 'm', 's', 'ms'] as const; + +export type DurationUnit = (typeof allDurationUnits)[number]; + +export type DateFormat = 'none' | 'us' | 'eu' | 'friendly' | 'iso'; + +export type TimeFormat = '24hr' | '12hr' | '24hrLong' | '12hrLong'; + +/** + * See the [Postgres docs][1] for an explanation of `scale` and `precision`. + * + * [1]: https://www.postgresql.org/docs/current/datatype-numeric.html + */ +interface ColumnTypeOptions { + /** + * For numeric types, the number of significant digits. For date/time types, + * the number of fractional digits. + */ + precision?: number | null; + + /** For numeric types, the number of fractional digits. */ + scale?: number | null; + + /** Which time fields are stored. See Postgres docs. */ + fields?: string | null; + + /** The maximum length of a character-type field. */ + length?: number | null; + + /** The member type for arrays. */ + item_type?: string | null; +} + +/** + * The column display options values, typed as we need in order to render them + * in the UI. + */ +export interface RequiredColumnDisplayOptions { + /** The type of input used for boolean values */ + bool_input: BooleanInputType; + + /** The text to display for a boolean `true` value */ + bool_true: string; + + /** The text to display for a boolean `false` value */ + bool_false: string; + + /** The minimum number of fraction digits to display for a number */ + num_min_frac_digits: number; + + /** The maximum number of fraction digits to display for a number */ + num_max_frac_digits: number; + + /** When `null`, the browser's locale will be used. */ + num_format: NumberFormat | null; + + /** + * - `null`: display grouping separators if the locale prefers it. + * - `true`: display grouping separators. + * - `false`: do not display grouping separators. + */ + num_grouping: NumberGrouping; + + /** The currency symbol to show for a money type e.g. "$", "€", "NZD", etc. */ + mon_currency_symbol: string; + + mon_currency_location: CurrencyLocation; + + time_format: TimeFormat; + + date_format: DateFormat; + + duration_min: DurationUnit; + + duration_max: DurationUnit; + + duration_show_units: boolean; +} + +/** The column display options values, types as we get them from the API. */ +type ColumnDisplayOptions = { + [K in keyof RequiredColumnDisplayOptions]?: + | RequiredColumnDisplayOptions[K] + | null; +}; + +export const defaultColumnDisplayOptions: RequiredColumnDisplayOptions = { + bool_input: 'checkbox', + bool_true: 'true', + bool_false: 'false', + num_min_frac_digits: 0, + num_max_frac_digits: 20, + num_format: null, + num_grouping: 'auto', + mon_currency_symbol: '$', + mon_currency_location: 'after-minus', + time_format: '24hr', + date_format: 'none', + duration_min: 's', + duration_max: 'm', + duration_show_units: true, +}; + +/** + * Gets a display option value from a column, if present. Otherwise returns the + * default value for that display option. + */ +export function getColumnDisplayOption< + Option extends keyof RequiredColumnDisplayOptions, +>(column: Pick, opt: Option) { + return column.display_options?.[opt] ?? defaultColumnDisplayOptions[opt]; +} + +interface ColumnDefault { + value: string; + is_dynamic: boolean; +} + +/** The raw column data, from the user database only */ +interface RawColumn { + /** The PostgreSQL attnum of the column */ + id: number; + name: string; + description: string | null; + /** The PostgreSQL data type */ + type: string; + type_options: ColumnTypeOptions | null; + nullable: boolean; + primary_key: boolean; + default: ColumnDefault | null; + has_dependents: boolean; + valid_target_types: string[]; +} + +/** + * The raw column data from the user database combined with Mathesar's metadata + */ +export interface Column extends RawColumn { + display_options: ColumnDisplayOptions | null; +} + +export interface ColumnCreationSpec { + name?: string; + type?: string; + description?: string; + type_options?: ColumnTypeOptions; + nullable?: boolean; + default?: ColumnDefault; +} + +export interface ColumnPatchSpec { + id: number; + name?: string; + type?: string | null; + description?: string | null; + type_options?: ColumnTypeOptions | null; + nullable?: boolean; + default?: ColumnDefault | null; +} + +export const columns = { + list: rpcMethodTypeContainer< + { + database_id: number; + table_oid: number; + }, + RawColumn[] + >(), + + list_with_metadata: rpcMethodTypeContainer< + { + database_id: number; + table_oid: number; + }, + Column[] + >(), + + /** Returns an array of the attnums of the newly-added columns */ + add: rpcMethodTypeContainer< + { + database_id: number; + table_oid: number; + column_data_list: ColumnCreationSpec[]; + }, + number[] + >(), + + patch: rpcMethodTypeContainer< + { + database_id: number; + table_oid: number; + column_data_list: ColumnPatchSpec[]; + }, + void + >(), + + delete: rpcMethodTypeContainer< + { + database_id: number; + table_oid: number; + column_attnums: number[]; + }, + void + >(), +}; diff --git a/mathesar_ui/src/api/rpc/index.ts b/mathesar_ui/src/api/rpc/index.ts index a3cefb1626..4a0a5a9697 100644 --- a/mathesar_ui/src/api/rpc/index.ts +++ b/mathesar_ui/src/api/rpc/index.ts @@ -2,6 +2,7 @@ import Cookies from 'js-cookie'; import { buildRpcApi } from '@mathesar/packages/json-rpc-client-builder'; +import { columns } from './columns'; import { configured_roles } from './configured_roles'; import { database_setup } from './database_setup'; import { databases } from './databases'; @@ -20,5 +21,6 @@ export const api = buildRpcApi({ schemas, servers, tables, + columns, }, }); diff --git a/mathesar_ui/src/component-library/number-input/number-formatter/options.ts b/mathesar_ui/src/component-library/number-input/number-formatter/options.ts index 2be88e9fea..04ba0d54cb 100644 --- a/mathesar_ui/src/component-library/number-input/number-formatter/options.ts +++ b/mathesar_ui/src/component-library/number-input/number-formatter/options.ts @@ -7,14 +7,10 @@ export interface Options { /** * Corresponds to the options of the [Intl.NumberFormat][1] API. * - * The MDN docs say that "true" and "false" are accepted as strings, but in my - * testing with Firefox and Chromium, I noticed that those values need to be - * passed as booleans to work correctly. - * * [1]: * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat */ - useGrouping: boolean; + useGrouping: 'always' | 'auto' | 'min2' | true | false; minimumFractionDigits: number; maximumFractionDigits: number; forceTrailingDecimal: boolean; diff --git a/mathesar_ui/src/components/abstract-type-control/AbstractTypeControl.svelte b/mathesar_ui/src/components/abstract-type-control/AbstractTypeControl.svelte index 5049f2185f..93301afd3e 100644 --- a/mathesar_ui/src/components/abstract-type-control/AbstractTypeControl.svelte +++ b/mathesar_ui/src/components/abstract-type-control/AbstractTypeControl.svelte @@ -4,6 +4,7 @@ import type { RequestStatus } from '@mathesar/api/rest/utils/requestUtils'; import { toast } from '@mathesar/stores/toast'; + import { objectsAreDeeplyEqual } from '@mathesar/utils/objectUtils'; import { CancelOrProceedButtonPair, createValidationContext, @@ -13,10 +14,9 @@ import AbstractTypeDBOptions from './AbstractTypeDBOptions.svelte'; import AbstractTypeSelector from './AbstractTypeSelector.svelte'; - import { - type ColumnTypeOptionsSaveArgs, - type ColumnWithAbstractType, - hasTypeOptionsChanged, + import type { + ColumnTypeOptionsSaveArgs, + ColumnWithAbstractType, } from './utils'; const dispatch = createEventDispatcher(); @@ -36,7 +36,7 @@ $: actionButtonsVisible = selectedAbstractType !== column.abstractType || selectedDbType !== column.type || - hasTypeOptionsChanged(column.type_options ?? {}, typeOptions ?? {}); + !objectsAreDeeplyEqual(column.type_options, typeOptions); let typeChangeState: RequestStatus; diff --git a/mathesar_ui/src/components/abstract-type-control/AbstractTypeSelector.svelte b/mathesar_ui/src/components/abstract-type-control/AbstractTypeSelector.svelte index 9984c0632a..1bd153be53 100644 --- a/mathesar_ui/src/components/abstract-type-control/AbstractTypeSelector.svelte +++ b/mathesar_ui/src/components/abstract-type-control/AbstractTypeSelector.svelte @@ -26,7 +26,15 @@ $: allowedTypeConversions = getAllowedAbstractTypesForDbTypeAndItsTargetTypes( column.type, - column.valid_target_types ?? [], + + // TODO_BETA + // + // We need to find another way to get the valid target types for a column + // since the RPC API no longer returns this. + + // column.valid_target_types ?? [], + [], + $currentDbAbstractTypes.data, ).filter((item) => !['jsonlist', 'map'].includes(item.identifier)); diff --git a/mathesar_ui/src/components/abstract-type-control/config-components/DurationConfiguration.svelte b/mathesar_ui/src/components/abstract-type-control/config-components/DurationConfiguration.svelte index 2771c8178b..0ffe9f0e46 100644 --- a/mathesar_ui/src/components/abstract-type-control/config-components/DurationConfiguration.svelte +++ b/mathesar_ui/src/components/abstract-type-control/config-components/DurationConfiguration.svelte @@ -2,7 +2,7 @@ import type { Writable } from 'svelte/store'; import { _ } from 'svelte-i18n'; - import type { DurationUnit } from '@mathesar/api/rest/types/tables/columns'; + import type { DurationUnit } from '@mathesar/api/rpc/columns'; import { RichText } from '@mathesar/components/rich-text'; import { DurationSpecification } from '@mathesar/utils/duration'; import type { DurationConfig } from '@mathesar/utils/duration/types'; diff --git a/mathesar_ui/src/components/abstract-type-control/utils.ts b/mathesar_ui/src/components/abstract-type-control/utils.ts index 822b90faa5..84d003c891 100644 --- a/mathesar_ui/src/components/abstract-type-control/utils.ts +++ b/mathesar_ui/src/components/abstract-type-control/utils.ts @@ -1,6 +1,6 @@ import { readable } from 'svelte/store'; -import type { Column } from '@mathesar/api/rest/types/tables/columns'; +import type { Column } from '@mathesar/api/rpc/columns'; import type { DbType } from '@mathesar/AppTypes'; import type { AbstractType, @@ -93,17 +93,3 @@ export function constructDisplayForm( displayFormValues, }; } - -export function hasTypeOptionsChanged( - previousTypeOptions: NonNullable, - currentTypeOptions: NonNullable, -): boolean { - for (const key in currentTypeOptions) { - if (Object.hasOwn(currentTypeOptions, key)) { - if (currentTypeOptions[key] !== previousTypeOptions[key]) { - return true; - } - } - } - return false; -} diff --git a/mathesar_ui/src/components/cell-fabric/data-types/boolean.ts b/mathesar_ui/src/components/cell-fabric/data-types/boolean.ts index 00a3435bce..112f966071 100644 --- a/mathesar_ui/src/components/cell-fabric/data-types/boolean.ts +++ b/mathesar_ui/src/components/cell-fabric/data-types/boolean.ts @@ -1,4 +1,4 @@ -import type { BooleanDisplayOptions } from '@mathesar/api/rest/types/tables/columns'; +import { type Column, getColumnDisplayOption } from '@mathesar/api/rpc/columns'; import { Select, isDefinedNonNullable } from '@mathesar-component-library'; import type { ComponentAndProps, @@ -11,37 +11,38 @@ import type { CheckBoxCellExternalProps, SingleSelectCellExternalProps, } from './components/typeDefinitions'; -import type { CellColumnLike, CellComponentFactory } from './typeDefinitions'; - -export interface BooleanLikeColumn extends CellColumnLike { - display_options: Partial | null; -} +import type { CellComponentFactory } from './typeDefinitions'; type Props = | CheckBoxCellExternalProps | SingleSelectCellExternalProps; -function getLabels( - displayOptions?: BooleanLikeColumn['display_options'], -): [string, string] { - const customLabels = displayOptions?.custom_labels ?? undefined; - return [customLabels?.TRUE ?? 'true', customLabels?.FALSE ?? 'false']; +interface BooleanLabels { + true: string; + false: string; +} + +function getLabels(column: Column): BooleanLabels { + return { + true: getColumnDisplayOption(column, 'bool_true'), + false: getColumnDisplayOption(column, 'bool_false'), + }; } function getFormattedValue( - labels: [string, string], + labels: BooleanLabels, value?: boolean | null, ): string { if (isDefinedNonNullable(value)) { - return value ? labels[0] : labels[1]; + return value ? labels.true : labels.false; } return ''; } function getProps( - column: BooleanLikeColumn, + column: Column, ): SingleSelectCellExternalProps { - const labels = getLabels(column.display_options); + const labels = getLabels(column); return { options: [null, true, false], getLabel: (value?: boolean | null) => getFormattedValue(labels, value), @@ -50,9 +51,9 @@ function getProps( const booleanType: CellComponentFactory = { initialInputValue: null, - get: (column: BooleanLikeColumn): ComponentAndProps => { + get: (column: Column): ComponentAndProps => { const displayOptions = column.display_options ?? undefined; - if (displayOptions && displayOptions.input === 'dropdown') { + if (displayOptions && displayOptions.bool_input === 'dropdown') { return { component: SingleSelectCell, props: getProps(column), @@ -61,13 +62,13 @@ const booleanType: CellComponentFactory = { return { component: CheckboxCell, props: {} }; }, getInput: ( - column: BooleanLikeColumn, + column: Column, ): ComponentAndProps> => ({ component: Select, props: getProps(column), }), - getDisplayFormatter(column: BooleanLikeColumn) { - const labels = getLabels(column.display_options); + getDisplayFormatter(column: Column) { + const labels = getLabels(column); return (value: unknown) => { if (value === null || value === undefined || typeof value === 'boolean') { return getFormattedValue(labels, value); diff --git a/mathesar_ui/src/components/cell-fabric/data-types/components/typeDefinitions.ts b/mathesar_ui/src/components/cell-fabric/data-types/components/typeDefinitions.ts index 9897637c48..2c07cdac98 100644 --- a/mathesar_ui/src/components/cell-fabric/data-types/components/typeDefinitions.ts +++ b/mathesar_ui/src/components/cell-fabric/data-types/components/typeDefinitions.ts @@ -1,5 +1,5 @@ -import type { Column } from '@mathesar/api/rest/types/tables/columns'; import type { FkConstraint } from '@mathesar/api/rest/types/tables/constraints'; +import type { Column } from '@mathesar/api/rpc/columns'; import type { DBObjectEntry } from '@mathesar/AppTypes'; import type { DateTimeFormatter } from '@mathesar/utils/date-time/types'; import type { diff --git a/mathesar_ui/src/components/cell-fabric/data-types/date.ts b/mathesar_ui/src/components/cell-fabric/data-types/date.ts index a754b40b06..66c130800b 100644 --- a/mathesar_ui/src/components/cell-fabric/data-types/date.ts +++ b/mathesar_ui/src/components/cell-fabric/data-types/date.ts @@ -1,4 +1,4 @@ -import type { DateDisplayOptions } from '@mathesar/api/rest/types/tables/columns'; +import { type Column, getColumnDisplayOption } from '@mathesar/api/rpc/columns'; import { DateTimeFormatter, DateTimeSpecification, @@ -9,15 +9,10 @@ import type { ComponentAndProps } from '@mathesar-component-library/types'; import DateTimeCell from './components/date-time/DateTimeCell.svelte'; import DateTimeInput from './components/date-time/DateTimeInput.svelte'; import type { DateTimeCellExternalProps } from './components/typeDefinitions'; -import type { CellColumnLike, CellComponentFactory } from './typeDefinitions'; +import type { CellComponentFactory } from './typeDefinitions'; -export interface DateLikeColumn extends CellColumnLike { - display_options: Partial | null; -} - -function getProps(column: DateLikeColumn): DateTimeCellExternalProps { - const displayOptions = column.display_options ?? {}; - const format = displayOptions.format ?? 'none'; +function getProps(column: Column): DateTimeCellExternalProps { + const format = getColumnDisplayOption(column, 'date_format'); const specification = new DateTimeSpecification({ type: 'date', dateFormat: format, @@ -39,14 +34,12 @@ function getProps(column: DateLikeColumn): DateTimeCellExternalProps { } const stringType: CellComponentFactory = { - get: ( - column: DateLikeColumn, - ): ComponentAndProps => ({ + get: (column: Column): ComponentAndProps => ({ component: DateTimeCell, props: getProps(column), }), getInput: ( - column: DateLikeColumn, + column: Column, ): ComponentAndProps< Omit > => ({ @@ -56,7 +49,7 @@ const stringType: CellComponentFactory = { allowRelativePresets: true, }, }), - getDisplayFormatter(column: DateLikeColumn) { + getDisplayFormatter(column: Column) { return (v) => getProps(column).formatForDisplay(String(v)); }, }; diff --git a/mathesar_ui/src/components/cell-fabric/data-types/datetime.ts b/mathesar_ui/src/components/cell-fabric/data-types/datetime.ts index d214e45060..a5ef36949c 100644 --- a/mathesar_ui/src/components/cell-fabric/data-types/datetime.ts +++ b/mathesar_ui/src/components/cell-fabric/data-types/datetime.ts @@ -1,4 +1,4 @@ -import type { TimeStampDisplayOptions } from '@mathesar/api/rest/types/tables/columns'; +import { type Column, getColumnDisplayOption } from '@mathesar/api/rpc/columns'; import { DateTimeFormatter, DateTimeSpecification, @@ -9,19 +9,14 @@ import type { ComponentAndProps } from '@mathesar-component-library/types'; import DateTimeCell from './components/date-time/DateTimeCell.svelte'; import DateTimeInput from './components/date-time/DateTimeInput.svelte'; import type { DateTimeCellExternalProps } from './components/typeDefinitions'; -import type { CellColumnLike, CellComponentFactory } from './typeDefinitions'; - -export interface DateLikeColumn extends CellColumnLike { - display_options: Partial | null; -} +import type { CellComponentFactory } from './typeDefinitions'; function getProps( - column: DateLikeColumn, + column: Column, supportTimeZone: boolean, ): DateTimeCellExternalProps { - const displayOptions = column.display_options ?? {}; - const dateFormat = displayOptions.date_format ?? 'none'; - const timeFormat = displayOptions.time_format ?? '24hr'; + const dateFormat = getColumnDisplayOption(column, 'date_format'); + const timeFormat = getColumnDisplayOption(column, 'time_format'); const specification = new DateTimeSpecification({ type: supportTimeZone ? 'timestampWithTZ' : 'timestamp', dateFormat, @@ -47,14 +42,14 @@ function getProps( const datetimeType: CellComponentFactory = { get: ( - column: DateLikeColumn, + column: Column, config?: { supportTimeZone?: boolean }, ): ComponentAndProps => ({ component: DateTimeCell, props: getProps(column, config?.supportTimeZone ?? false), }), getInput: ( - column: DateLikeColumn, + column: Column, config?: { supportTimeZone?: boolean }, ): ComponentAndProps< Omit @@ -65,10 +60,7 @@ const datetimeType: CellComponentFactory = { allowRelativePresets: true, }, }), - getDisplayFormatter( - column: DateLikeColumn, - config?: { supportTimeZone?: boolean }, - ) { + getDisplayFormatter(column: Column, config?: { supportTimeZone?: boolean }) { return (v) => getProps(column, config?.supportTimeZone ?? false).formatForDisplay( String(v), diff --git a/mathesar_ui/src/components/cell-fabric/data-types/duration.ts b/mathesar_ui/src/components/cell-fabric/data-types/duration.ts index 5f7af38409..2864958f0c 100644 --- a/mathesar_ui/src/components/cell-fabric/data-types/duration.ts +++ b/mathesar_ui/src/components/cell-fabric/data-types/duration.ts @@ -1,4 +1,4 @@ -import type { DurationDisplayOptions } from '@mathesar/api/rest/types/tables/columns'; +import type { Column } from '@mathesar/api/rpc/columns'; import { DurationFormatter, DurationSpecification, @@ -14,16 +14,12 @@ import type { import FormattedInputCell from './components/formatted-input/FormattedInputCell.svelte'; import type { FormattedInputCellExternalProps } from './components/typeDefinitions'; -import type { CellColumnLike, CellComponentFactory } from './typeDefinitions'; +import type { CellComponentFactory } from './typeDefinitions'; -export interface DurationLikeColumn extends CellColumnLike { - display_options: Partial | null; -} - -function getProps(column: DurationLikeColumn): FormattedInputCellExternalProps { +function getProps(column: Column): FormattedInputCellExternalProps { const defaults = DurationSpecification.getDefaults(); - const max = column.display_options?.max ?? defaults.max; - const min = column.display_options?.min ?? defaults.min; + const max = column.display_options?.duration_max ?? defaults.max; + const min = column.display_options?.duration_min ?? defaults.min; const durationSpecification = new DurationSpecification({ max, min }); const formatter = new DurationFormatter(durationSpecification); return { @@ -42,18 +38,18 @@ function getProps(column: DurationLikeColumn): FormattedInputCellExternalProps { const durationType: CellComponentFactory = { get: ( - column: DurationLikeColumn, + column: Column, ): ComponentAndProps => ({ component: FormattedInputCell, props: getProps(column), }), getInput: ( - column: DurationLikeColumn, + column: Column, ): ComponentAndProps> => ({ component: FormattedInput, props: getProps(column), }), - getDisplayFormatter(column: DurationLikeColumn) { + getDisplayFormatter(column: Column) { return (v) => getProps(column).formatForDisplay(String(v)); }, }; diff --git a/mathesar_ui/src/components/cell-fabric/data-types/money.ts b/mathesar_ui/src/components/cell-fabric/data-types/money.ts index 028f50ac60..8666d4fc49 100644 --- a/mathesar_ui/src/components/cell-fabric/data-types/money.ts +++ b/mathesar_ui/src/components/cell-fabric/data-types/money.ts @@ -1,7 +1,8 @@ -import type { - MoneyColumn, - NumberFormat, -} from '@mathesar/api/rest/types/tables/columns'; +import { + type Column, + type NumberFormat, + getColumnDisplayOption, +} from '@mathesar/api/rpc/columns'; import { StringifiedNumberFormatter, isDefinedNonNullable, @@ -14,10 +15,6 @@ import type { MoneyCellExternalProps } from './components/typeDefinitions'; import { getUseGrouping } from './number'; import type { CellComponentFactory } from './typeDefinitions'; -// Values to use if for some reason we don't get them from the API. -const FALLBACK_CURRENCY_SYMBOL = '$'; -const FALLBACK_CURRENCY_SYMBOL_LOCATION = 'after-minus'; - // prettier-ignore const localeMap = new Map([ ['english' , 'en' ], @@ -27,30 +24,36 @@ const localeMap = new Map([ ['swiss' , 'de-CH' ], ]); -function moneyColumnIsInteger(column: MoneyColumn): boolean { +function ColumnIsInteger(column: Column): boolean { return (column.type_options?.scale ?? Infinity) === 0; } function getFormatterOptions( - column: MoneyColumn, + column: Column, ): MoneyCellExternalProps['formatterOptions'] { - const displayOptions = column.display_options; - const format = displayOptions?.number_format ?? null; + const format = getColumnDisplayOption(column, 'num_format'); return { locale: (format && localeMap.get(format)) ?? undefined, - useGrouping: getUseGrouping(displayOptions?.use_grouping ?? 'true'), - allowFloat: !moneyColumnIsInteger(column), + useGrouping: getUseGrouping(column), + allowFloat: !ColumnIsInteger(column), allowNegative: true, - minimumFractionDigits: displayOptions?.minimum_fraction_digits ?? undefined, - maximumFractionDigits: displayOptions?.maximum_fraction_digits ?? undefined, - currencySymbol: displayOptions?.currency_symbol ?? FALLBACK_CURRENCY_SYMBOL, - currencySymbolLocation: - displayOptions?.currency_symbol_location ?? - FALLBACK_CURRENCY_SYMBOL_LOCATION, + minimumFractionDigits: getColumnDisplayOption( + column, + 'num_min_frac_digits', + ), + maximumFractionDigits: getColumnDisplayOption( + column, + 'num_max_frac_digits', + ), + currencySymbol: getColumnDisplayOption(column, 'mon_currency_symbol'), + currencySymbolLocation: getColumnDisplayOption( + column, + 'mon_currency_location', + ), }; } -function getProps(column: MoneyColumn): MoneyCellExternalProps { +function getProps(column: Column): MoneyCellExternalProps { const formatterOptions = getFormatterOptions(column); const displayFormatter = new StringifiedNumberFormatter(formatterOptions); const insertCurrencySymbol = (() => { @@ -78,7 +81,7 @@ function getProps(column: MoneyColumn): MoneyCellExternalProps { } const moneyType: CellComponentFactory = { - get(column: MoneyColumn): ComponentAndProps { + get(column: Column): ComponentAndProps { return { component: MoneyCell, props: getProps(column), @@ -86,7 +89,7 @@ const moneyType: CellComponentFactory = { }, getInput( - column: MoneyColumn, + column: Column, ): ComponentAndProps { return { component: MoneyCellInput, @@ -97,7 +100,7 @@ const moneyType: CellComponentFactory = { }; }, - getDisplayFormatter(column: MoneyColumn) { + getDisplayFormatter(column: Column) { return (v) => getProps(column).formatForDisplay(String(v)); }, }; diff --git a/mathesar_ui/src/components/cell-fabric/data-types/number.ts b/mathesar_ui/src/components/cell-fabric/data-types/number.ts index eff54ce072..d1d93eadd0 100644 --- a/mathesar_ui/src/components/cell-fabric/data-types/number.ts +++ b/mathesar_ui/src/components/cell-fabric/data-types/number.ts @@ -1,10 +1,11 @@ -import type { - NumberColumn, - NumberDisplayOptions, - NumberFormat, -} from '@mathesar/api/rest/types/tables/columns'; +import { + type Column, + type NumberFormat, + getColumnDisplayOption, +} from '@mathesar/api/rpc/columns'; import { StringifiedNumberFormatter, + assertExhaustive, isDefinedNonNullable, } from '@mathesar-component-library'; import type { ComponentAndProps } from '@mathesar-component-library/types'; @@ -34,7 +35,7 @@ interface Config extends Record { } function getAllowFloat( - column: NumberColumn, + column: Column, floatAllowanceStrategy?: FloatAllowanceStrategy, ): boolean { if (floatAllowanceStrategy === 'scale-based') { @@ -47,29 +48,33 @@ function getAllowFloat( } export function getUseGrouping( - apiUseGrouping: NumberDisplayOptions['use_grouping'], + column: Column, ): NumberCellExternalProps['formatterOptions']['useGrouping'] { - switch (apiUseGrouping) { - case 'true': - return true; - case 'false': - default: + const grouping = getColumnDisplayOption(column, 'num_grouping'); + switch (grouping) { + case 'always': + return 'always'; + case 'auto': + return 'auto'; + case 'never': return false; + default: + return assertExhaustive(grouping); } } function getFormatterOptions( - column: NumberColumn, + column: Column, config?: Config, ): NumberCellExternalProps['formatterOptions'] { const displayOptions = column.display_options; - const format = displayOptions?.number_format ?? null; + const format = displayOptions?.num_format ?? null; const locale = (format && localeMap.get(format)) ?? undefined; - const useGrouping = getUseGrouping(displayOptions?.use_grouping ?? 'false'); + const useGrouping = getUseGrouping(column); const allowFloat = getAllowFloat(column, config?.floatAllowanceStrategy); const allowNegative = true; const minimumFractionDigits = - displayOptions?.minimum_fraction_digits ?? undefined; + displayOptions?.num_min_frac_digits ?? undefined; return { locale, allowFloat, @@ -79,14 +84,11 @@ function getFormatterOptions( }; } -function getProps( - column: NumberColumn, - config?: Config, -): NumberCellExternalProps { +function getProps(column: Column, config?: Config): NumberCellExternalProps { const basicFormatterOptions = getFormatterOptions(column, config); const displayOptions = column.display_options; const maximumFractionDigits = - displayOptions?.maximum_fraction_digits ?? undefined; + displayOptions?.num_max_frac_digits ?? undefined; const formatterOptions = { ...basicFormatterOptions, // We only want to apply `maximumFractionDigits` during display. We don't @@ -109,7 +111,7 @@ function getProps( const numberType: CellComponentFactory = { get( - column: NumberColumn, + column: Column, config?: Config, ): ComponentAndProps { return { @@ -119,7 +121,7 @@ const numberType: CellComponentFactory = { }, getInput( - column: NumberColumn, + column: Column, config?: Config, ): ComponentAndProps { return { @@ -128,7 +130,7 @@ const numberType: CellComponentFactory = { }; }, - getDisplayFormatter(column: NumberColumn, config?: Config) { + getDisplayFormatter(column: Column, config?: Config) { return (v) => getProps(column, config).formatForDisplay(String(v)); }, }; diff --git a/mathesar_ui/src/components/cell-fabric/data-types/string.ts b/mathesar_ui/src/components/cell-fabric/data-types/string.ts index 79ab7c20cd..3711fa67c8 100644 --- a/mathesar_ui/src/components/cell-fabric/data-types/string.ts +++ b/mathesar_ui/src/components/cell-fabric/data-types/string.ts @@ -1,4 +1,4 @@ -import type { TextTypeOptions } from '@mathesar/api/rest/types/tables/columns'; +import type { Column } from '@mathesar/api/rpc/columns'; import GrowableTextArea from '@mathesar/components/GrowableTextArea.svelte'; import { TextInput, optionalNonNullable } from '@mathesar-component-library'; import type { @@ -12,16 +12,12 @@ import type { TextAreaCellExternalProps, TextBoxCellExternalProps, } from './components/typeDefinitions'; -import type { CellColumnLike, CellComponentFactory } from './typeDefinitions'; - -export interface StringLikeColumn extends CellColumnLike { - type_options: Partial | null; -} +import type { CellComponentFactory } from './typeDefinitions'; const stringType: CellComponentFactory = { initialInputValue: '', get: ( - column: StringLikeColumn, + column: Column, config?: { multiLine?: boolean }, ): ComponentAndProps< TextBoxCellExternalProps | TextAreaCellExternalProps @@ -31,7 +27,7 @@ const stringType: CellComponentFactory = { return { component, props: typeOptions }; }, getInput: ( - column: StringLikeColumn, + column: Column, config?: { multiLine?: boolean }, ): ComponentAndProps => { const component = config?.multiLine ? GrowableTextArea : TextInput; diff --git a/mathesar_ui/src/components/cell-fabric/data-types/time.ts b/mathesar_ui/src/components/cell-fabric/data-types/time.ts index a6c1ed331f..f4301ceae0 100644 --- a/mathesar_ui/src/components/cell-fabric/data-types/time.ts +++ b/mathesar_ui/src/components/cell-fabric/data-types/time.ts @@ -1,4 +1,4 @@ -import type { TimeDisplayOptions } from '@mathesar/api/rest/types/tables/columns'; +import { type Column, getColumnDisplayOption } from '@mathesar/api/rpc/columns'; import { DateTimeFormatter, DateTimeSpecification, @@ -9,18 +9,13 @@ import type { ComponentAndProps } from '@mathesar-component-library/types'; import DateTimeCell from './components/date-time/DateTimeCell.svelte'; import DateTimeInput from './components/date-time/DateTimeInput.svelte'; import type { DateTimeCellExternalProps } from './components/typeDefinitions'; -import type { CellColumnLike, CellComponentFactory } from './typeDefinitions'; - -export interface TimeLikeColumn extends CellColumnLike { - display_options: Partial | null; -} +import type { CellComponentFactory } from './typeDefinitions'; function getProps( - column: TimeLikeColumn, + column: Column, supportTimeZone: boolean, ): DateTimeCellExternalProps { - const displayOptions = column.display_options ?? {}; - const format = displayOptions.format ?? '24hr'; + const format = getColumnDisplayOption(column, 'time_format'); const specification = new DateTimeSpecification({ type: supportTimeZone ? 'timeWithTZ' : 'time', timeFormat: format, @@ -45,14 +40,14 @@ function getProps( const timeType: CellComponentFactory = { get: ( - column: TimeLikeColumn, + column: Column, config?: { supportTimeZone?: boolean }, ): ComponentAndProps => ({ component: DateTimeCell, props: getProps(column, config?.supportTimeZone ?? false), }), getInput: ( - column: TimeLikeColumn, + column: Column, config?: { supportTimeZone?: boolean }, ): ComponentAndProps< Omit @@ -63,10 +58,7 @@ const timeType: CellComponentFactory = { allowRelativePresets: true, }, }), - getDisplayFormatter( - column: TimeLikeColumn, - config?: { supportTimeZone?: boolean }, - ) { + getDisplayFormatter(column: Column, config?: { supportTimeZone?: boolean }) { const supportTimeZone = config?.supportTimeZone ?? false; return (v) => getProps(column, supportTimeZone).formatForDisplay(String(v)); }, diff --git a/mathesar_ui/src/components/cell-fabric/utils.ts b/mathesar_ui/src/components/cell-fabric/utils.ts index a97e119ab0..ddcff2edf7 100644 --- a/mathesar_ui/src/components/cell-fabric/utils.ts +++ b/mathesar_ui/src/components/cell-fabric/utils.ts @@ -1,4 +1,4 @@ -import type { Column } from '@mathesar/api/rest/types/tables/columns'; +import type { Column } from '@mathesar/api/rpc/columns'; import type { Table } from '@mathesar/api/rpc/tables'; import type { CellInfo } from '@mathesar/stores/abstract-types/types'; import type { RecordSummariesForSheet } from '@mathesar/stores/table-data/record-summaries/recordSummaryUtils'; diff --git a/mathesar_ui/src/packages/json-rpc-client-builder/index.ts b/mathesar_ui/src/packages/json-rpc-client-builder/index.ts index 066c9206d9..d8eb45d709 100644 --- a/mathesar_ui/src/packages/json-rpc-client-builder/index.ts +++ b/mathesar_ui/src/packages/json-rpc-client-builder/index.ts @@ -2,6 +2,7 @@ export { type RpcError } from './RpcError'; export { buildRpcApi, rpcMethodTypeContainer } from './builder'; export { batchSend, + runner, type RpcBatchResponse, type RpcRequest, type RpcResponse, diff --git a/mathesar_ui/src/packages/json-rpc-client-builder/requests.ts b/mathesar_ui/src/packages/json-rpc-client-builder/requests.ts index 4609dffa6f..0ad5cfd337 100644 --- a/mathesar_ui/src/packages/json-rpc-client-builder/requests.ts +++ b/mathesar_ui/src/packages/json-rpc-client-builder/requests.ts @@ -137,3 +137,18 @@ export function batchSend[]>( // TODO implement batch sending throw new Error('Not implemented'); } + +/** + * A factory function to builds a function that directly runs a specific RPC + * request. The built function will then accept all the props of the RPC method + * and run the request without needing to call `.run()` on it. + * + * This utility is useful when you want to define a function that runs a + * specific RPC method without having to spell out the type of the method + * parameters. + */ +export function runner( + method: (props: P) => RpcRequest, +): (props: P) => CancellablePromise { + return (props) => method(props).run(); +} diff --git a/mathesar_ui/src/pages/import/preview/ImportPreviewContent.svelte b/mathesar_ui/src/pages/import/preview/ImportPreviewContent.svelte index cefec83868..3ce4b725a3 100644 --- a/mathesar_ui/src/pages/import/preview/ImportPreviewContent.svelte +++ b/mathesar_ui/src/pages/import/preview/ImportPreviewContent.svelte @@ -2,10 +2,10 @@ import { _ } from 'svelte-i18n'; import { router } from 'tinro'; - import { columnsApi } from '@mathesar/api/rest/columns'; import type { DataFile } from '@mathesar/api/rest/types/dataFiles'; - import type { Column } from '@mathesar/api/rest/types/tables/columns'; import { getAPI, postAPI } from '@mathesar/api/rest/utils/requestUtils'; + import { api } from '@mathesar/api/rpc'; + import type { Column } from '@mathesar/api/rpc/columns'; import type { Database } from '@mathesar/api/rpc/databases'; import type { Schema } from '@mathesar/api/rpc/schemas'; import type { Table } from '@mathesar/api/rpc/tables'; @@ -18,6 +18,7 @@ } from '@mathesar/components/form'; import InfoBox from '@mathesar/components/message-boxes/InfoBox.svelte'; import { iconDeleteMajor } from '@mathesar/icons'; + import { runner } from '@mathesar/packages/json-rpc-client-builder'; import { getImportPreviewPageUrl, getSchemaPageUrl, @@ -25,6 +26,7 @@ } from '@mathesar/routes/urls'; import { currentDbAbstractTypes } from '@mathesar/stores/abstract-types'; import AsyncStore from '@mathesar/stores/AsyncStore'; + import { currentDatabase } from '@mathesar/stores/databases'; import { currentTables } from '@mathesar/stores/tables'; import { toast } from '@mathesar/stores/toast'; import { @@ -76,7 +78,7 @@ return postAPI(`/api/db/v0/tables/${table.oid}/previews/`, { columns }); } - const columnsFetch = new AsyncStore(columnsApi.list); + const columnsFetch = new AsyncStore(runner(api.columns.list_with_metadata)); const previewRequest = new AsyncStore(generateTablePreview); const typeSuggestionsRequest = new AsyncStore(getTypeSuggestionsForTable); const headerUpdate = makeHeaderUpdateRequest(); @@ -104,8 +106,11 @@ $: processedColumns = processColumns(columns, $currentDbAbstractTypes.data); async function init() { - const columnsResponse = await columnsFetch.run(table.oid); - const fetchedColumns = columnsResponse?.resolvedValue?.results; + const columnsResponse = await columnsFetch.run({ + database_id: $currentDatabase.id, + table_oid: table.oid, + }); + const fetchedColumns = columnsResponse?.resolvedValue; if (!fetchedColumns) { return; } diff --git a/mathesar_ui/src/pages/import/preview/ImportPreviewSheet.svelte b/mathesar_ui/src/pages/import/preview/ImportPreviewSheet.svelte index e2689764f4..724b44f34b 100644 --- a/mathesar_ui/src/pages/import/preview/ImportPreviewSheet.svelte +++ b/mathesar_ui/src/pages/import/preview/ImportPreviewSheet.svelte @@ -1,5 +1,5 @@ diff --git a/mathesar_ui/src/pages/table/TablePage.svelte b/mathesar_ui/src/pages/table/TablePage.svelte index da4603c51e..865735b000 100644 --- a/mathesar_ui/src/pages/table/TablePage.svelte +++ b/mathesar_ui/src/pages/table/TablePage.svelte @@ -7,6 +7,7 @@ import LayoutWithHeader from '@mathesar/layouts/LayoutWithHeader.svelte'; import { makeSimplePageTitle } from '@mathesar/pages/pageTitleUtils'; import { currentDbAbstractTypes } from '@mathesar/stores/abstract-types'; + import { currentDatabase } from '@mathesar/stores/databases'; import { Meta, TabularData, @@ -36,10 +37,10 @@ $: ({ query } = $router); $: meta = Meta.fromSerialization(query[metaSerializationQueryKey] ?? ''); $: tabularData = new TabularData({ - id: table.oid, + database: $currentDatabase, + table, abstractTypesMap, meta, - table, shareConsumer, }); $: ({ isLoading, selection } = tabularData); @@ -73,7 +74,7 @@
- +
diff --git a/mathesar_ui/src/stores/abstract-types/type-configs/boolean.ts b/mathesar_ui/src/stores/abstract-types/type-configs/boolean.ts index 730490ecc8..b42c7c2ceb 100644 --- a/mathesar_ui/src/stores/abstract-types/type-configs/boolean.ts +++ b/mathesar_ui/src/stores/abstract-types/type-configs/boolean.ts @@ -1,7 +1,8 @@ -import type { - BooleanDisplayOptions, - Column, -} from '@mathesar/api/rest/types/tables/columns'; +import { + type BooleanInputType, + type Column, + getColumnDisplayOption, +} from '@mathesar/api/rpc/columns'; import { iconUiTypeBoolean } from '@mathesar/icons'; import type { FormValues } from '@mathesar-component-library/types'; @@ -84,39 +85,31 @@ const displayForm: AbstractTypeConfigForm = { }; function determineDisplayOptions( - dispFormValues: FormValues, + formValues: FormValues, ): Column['display_options'] { const displayOptions: Column['display_options'] = { - input: dispFormValues.displayAs, + bool_input: formValues.displayAs as BooleanInputType, }; - if ( - dispFormValues.displayAs === 'dropdown' && - dispFormValues.useCustomLabels - ) { - displayOptions.custom_labels = { - TRUE: dispFormValues.trueLabel, - FALSE: dispFormValues.falseLabel, - }; + if (formValues.displayAs === 'dropdown' && formValues.useCustomLabels) { + displayOptions.bool_true = formValues.trueLabel as string; + displayOptions.bool_false = formValues.falseLabel as string; } return displayOptions; } function constructDisplayFormValuesFromDisplayOptions( - columnDisplayOpts: Column['display_options'], + displayOptions: Column['display_options'], ): FormValues { - const displayOptions = columnDisplayOpts as BooleanDisplayOptions | null; - const dispFormValues: FormValues = { - displayAs: displayOptions?.input ?? 'checkbox', + const column = { display_options: displayOptions }; + const formValues: FormValues = { + displayAs: getColumnDisplayOption(column, 'bool_input'), }; - if ( - typeof displayOptions?.custom_labels === 'object' && - displayOptions.custom_labels !== null - ) { - dispFormValues.useCustomLabels = true; - dispFormValues.trueLabel = displayOptions.custom_labels.TRUE; - dispFormValues.falseLabel = displayOptions.custom_labels.FALSE; + if (displayOptions?.bool_true || displayOptions?.bool_false) { + formValues.useCustomLabels = true; + formValues.trueLabel = getColumnDisplayOption(column, 'bool_true'); + formValues.falseLabel = getColumnDisplayOption(column, 'bool_false'); } - return dispFormValues; + return formValues; } const booleanType: AbstractTypeConfiguration = { diff --git a/mathesar_ui/src/stores/abstract-types/type-configs/comboTypes/arrayFactory.ts b/mathesar_ui/src/stores/abstract-types/type-configs/comboTypes/arrayFactory.ts index 3238fdfdc3..6f1ef295de 100644 --- a/mathesar_ui/src/stores/abstract-types/type-configs/comboTypes/arrayFactory.ts +++ b/mathesar_ui/src/stores/abstract-types/type-configs/comboTypes/arrayFactory.ts @@ -1,4 +1,3 @@ -import type { ArrayTypeOptions } from '@mathesar/api/rest/types/tables/columns'; import { iconUiTypeArray } from '@mathesar/icons'; import type { AbstractTypeConfigurationFactory } from '../../types'; @@ -7,19 +6,12 @@ import { getAbstractTypeForDbType } from '../../utils'; const arrayFactory: AbstractTypeConfigurationFactory = (map) => ({ getIcon: (args) => { const arrayIcon = { ...iconUiTypeArray, label: 'Array' }; - if (args && args.typeOptions) { - const typeOpts = args.typeOptions as ArrayTypeOptions; - const innerAbstractType = getAbstractTypeForDbType( - typeOpts.item_type, - map, - ); - if (innerAbstractType) { - const innerIcon = innerAbstractType.getIcon(); - const innerIcons = Array.isArray(innerIcon) ? innerIcon : [innerIcon]; - return [{ ...iconUiTypeArray, label: 'Array' }, ...innerIcons]; - } - } - return arrayIcon; + const itemType = args?.typeOptions?.item_type ?? undefined; + if (!itemType) return arrayIcon; + const innerAbstractType = getAbstractTypeForDbType(itemType, map); + const innerIcon = innerAbstractType.getIcon(); + const innerIcons = Array.isArray(innerIcon) ? innerIcon : [innerIcon]; + return [arrayIcon, ...innerIcons]; }, cellInfo: { type: 'array', diff --git a/mathesar_ui/src/stores/abstract-types/type-configs/date.ts b/mathesar_ui/src/stores/abstract-types/type-configs/date.ts index b31d33175e..9e1fa9ee47 100644 --- a/mathesar_ui/src/stores/abstract-types/type-configs/date.ts +++ b/mathesar_ui/src/stores/abstract-types/type-configs/date.ts @@ -1,8 +1,8 @@ -import type { - Column, - DateDisplayOptions, - DateFormat, -} from '@mathesar/api/rest/types/tables/columns'; +import { + type Column, + type DateFormat, + getColumnDisplayOption, +} from '@mathesar/api/rpc/columns'; import { iconUiTypeDate } from '@mathesar/icons'; import type { FormValues } from '@mathesar-component-library/types'; @@ -35,22 +35,21 @@ const displayForm: AbstractTypeConfigForm = { }; function determineDisplayOptions( - dispFormValues: FormValues, + formValues: FormValues, ): Column['display_options'] { - const displayOptions: DateDisplayOptions = { - format: dispFormValues.format as DateFormat, + return { + date_format: formValues.format as DateFormat, }; - return displayOptions; } function constructDisplayFormValuesFromDisplayOptions( - columnDisplayOpts: Column['display_options'], + displayOptions: Column['display_options'], ): FormValues { - const displayOptions = columnDisplayOpts as DateDisplayOptions | null; - const dispFormValues: FormValues = { - format: displayOptions?.format ?? 'none', + const column = { display_options: displayOptions }; + const formValues: FormValues = { + format: getColumnDisplayOption(column, 'date_format'), }; - return dispFormValues; + return formValues; } const dateType: AbstractTypeConfiguration = { diff --git a/mathesar_ui/src/stores/abstract-types/type-configs/datetime.ts b/mathesar_ui/src/stores/abstract-types/type-configs/datetime.ts index 43e1771aa6..1f366a2740 100644 --- a/mathesar_ui/src/stores/abstract-types/type-configs/datetime.ts +++ b/mathesar_ui/src/stores/abstract-types/type-configs/datetime.ts @@ -1,9 +1,9 @@ -import type { - Column, - DateFormat, - TimeFormat, - TimeStampDisplayOptions, -} from '@mathesar/api/rest/types/tables/columns'; +import { + type Column, + type DateFormat, + type TimeFormat, + getColumnDisplayOption, +} from '@mathesar/api/rpc/columns'; import { iconUiTypeDateTime } from '@mathesar/icons'; import type { FormValues } from '@mathesar-component-library/types'; @@ -94,7 +94,7 @@ const displayForm: AbstractTypeConfigForm = { function determineDisplayOptions( dispFormValues: FormValues, ): Column['display_options'] { - const displayOptions: TimeStampDisplayOptions = { + const displayOptions: Column['display_options'] = { date_format: dispFormValues.dateFormat as DateFormat, time_format: dispFormValues.timeFormat as TimeFormat, }; @@ -102,14 +102,14 @@ function determineDisplayOptions( } function constructDisplayFormValuesFromDisplayOptions( - columnDisplayOpts: Column['display_options'], + displayOptions: Column['display_options'], ): FormValues { - const displayOptions = columnDisplayOpts as TimeStampDisplayOptions | null; - const dispFormValues: FormValues = { - dateFormat: displayOptions?.date_format ?? 'none', - timeFormat: displayOptions?.time_format ?? '24hr', + const column = { display_options: displayOptions }; + const formValues: FormValues = { + dateFormat: getColumnDisplayOption(column, 'date_format'), + timeFormat: getColumnDisplayOption(column, 'time_format'), }; - return dispFormValues; + return formValues; } const dateTimeType: AbstractTypeConfiguration = { diff --git a/mathesar_ui/src/stores/abstract-types/type-configs/duration.ts b/mathesar_ui/src/stores/abstract-types/type-configs/duration.ts index fc546fa12e..da7d4fb81f 100644 --- a/mathesar_ui/src/stores/abstract-types/type-configs/duration.ts +++ b/mathesar_ui/src/stores/abstract-types/type-configs/duration.ts @@ -1,7 +1,4 @@ -import type { - Column, - DurationDisplayOptions, -} from '@mathesar/api/rest/types/tables/columns'; +import { type Column, getColumnDisplayOption } from '@mathesar/api/rpc/columns'; import { iconUiTypeDuration } from '@mathesar/icons'; import { DurationSpecification } from '@mathesar/utils/duration'; import type { FormValues } from '@mathesar-component-library/types'; @@ -43,26 +40,26 @@ const displayForm: AbstractTypeConfigForm = { }; function determineDisplayOptions( - dispFormValues: FormValues, + formValues: FormValues, ): Column['display_options'] { const displayOptions: Column['display_options'] = { - ...(dispFormValues.durationConfig as Record), - show_units: false, + ...(formValues.durationConfig as Record), + duration_show_units: false, }; return displayOptions; } function constructDisplayFormValuesFromDisplayOptions( - columnDisplayOpts: Column['display_options'], + displayOptions: Column['display_options'], ): FormValues { - const displayOptions = columnDisplayOpts as DurationDisplayOptions | null; - const dispFormValues: FormValues = { + const column = { display_options: displayOptions }; + const formValues: FormValues = { durationConfig: { - max: displayOptions?.max ?? durationDefaults.max, - min: displayOptions?.min ?? durationDefaults.min, + max: getColumnDisplayOption(column, 'duration_max'), + min: getColumnDisplayOption(column, 'duration_min'), }, }; - return dispFormValues; + return formValues; } const durationType: AbstractTypeConfiguration = { diff --git a/mathesar_ui/src/stores/abstract-types/type-configs/money.ts b/mathesar_ui/src/stores/abstract-types/type-configs/money.ts index 83da2fb45b..789dbea426 100644 --- a/mathesar_ui/src/stores/abstract-types/type-configs/money.ts +++ b/mathesar_ui/src/stores/abstract-types/type-configs/money.ts @@ -1,8 +1,10 @@ -import type { - Column, - MoneyDisplayOptions, - NumberFormat, -} from '@mathesar/api/rest/types/tables/columns'; +import { + type Column, + type CurrencyLocation, + type NumberFormat, + type NumberGrouping, + getColumnDisplayOption, +} from '@mathesar/api/rpc/columns'; import { iconUiTypeMoney } from '@mathesar/icons'; import type { FormValues } from '@mathesar-component-library/types'; @@ -93,41 +95,43 @@ const displayForm: AbstractTypeConfigForm = { }; interface MoneyFormValues extends Record { - currencySymbol: MoneyDisplayOptions['currency_symbol']; - decimalPlaces: MoneyDisplayOptions['minimum_fraction_digits']; - currencySymbolLocation: MoneyDisplayOptions['currency_symbol_location']; + currencySymbol: string; + decimalPlaces: number | null; + currencySymbolLocation: CurrencyLocation; numberFormat: NumberFormat | 'none'; - useGrouping: MoneyDisplayOptions['use_grouping']; + useGrouping: NumberGrouping; } function determineDisplayOptions(form: FormValues): Column['display_options'] { const f = form as MoneyFormValues; - const opts: Partial = { - currency_symbol: f.currencySymbol, - currency_symbol_location: f.currencySymbolLocation, - number_format: f.numberFormat === 'none' ? null : f.numberFormat, - use_grouping: f.useGrouping, - minimum_fraction_digits: f.decimalPlaces ?? undefined, - maximum_fraction_digits: f.decimalPlaces ?? undefined, + const opts: Partial = { + mon_currency_symbol: f.currencySymbol, + mon_currency_location: f.currencySymbolLocation, + num_format: f.numberFormat === 'none' ? null : f.numberFormat, + num_grouping: f.useGrouping, + num_min_frac_digits: f.decimalPlaces ?? undefined, + num_max_frac_digits: f.decimalPlaces ?? undefined, }; return opts; } function constructDisplayFormValuesFromDisplayOptions( - columnDisplayOpts: Column['display_options'], + displayOptions: Column['display_options'], ): MoneyFormValues { - const displayOptions = columnDisplayOpts as MoneyDisplayOptions | null; + const column = { display_options: displayOptions }; const decimalPlaces = getDecimalPlaces( - displayOptions?.minimum_fraction_digits ?? null, - displayOptions?.maximum_fraction_digits ?? null, + displayOptions?.num_min_frac_digits ?? null, + displayOptions?.num_max_frac_digits ?? null, ); const displayFormValues: MoneyFormValues = { - numberFormat: displayOptions?.number_format ?? 'none', - currencySymbol: displayOptions?.currency_symbol ?? '', + numberFormat: getColumnDisplayOption(column, 'num_format') ?? 'none', + currencySymbol: getColumnDisplayOption(column, 'mon_currency_symbol') ?? '', decimalPlaces, - currencySymbolLocation: - displayOptions?.currency_symbol_location ?? 'after-minus', - useGrouping: displayOptions?.use_grouping ?? 'true', + currencySymbolLocation: getColumnDisplayOption( + column, + 'mon_currency_location', + ), + useGrouping: getColumnDisplayOption(column, 'num_grouping'), }; return displayFormValues; } diff --git a/mathesar_ui/src/stores/abstract-types/type-configs/number.ts b/mathesar_ui/src/stores/abstract-types/type-configs/number.ts index 423b344e25..0986c592ce 100644 --- a/mathesar_ui/src/stores/abstract-types/type-configs/number.ts +++ b/mathesar_ui/src/stores/abstract-types/type-configs/number.ts @@ -1,8 +1,9 @@ -import type { - Column, - NumberDisplayOptions, - NumberFormat, -} from '@mathesar/api/rest/types/tables/columns'; +import { + type Column, + type NumberFormat, + type NumberGrouping, + getColumnDisplayOption, +} from '@mathesar/api/rpc/columns'; import type { DbType } from '@mathesar/AppTypes'; import { iconUiTypeNumber } from '@mathesar/icons'; import type { FormValues } from '@mathesar-component-library/types'; @@ -148,10 +149,10 @@ function determineDbTypeAndOptions( if (dbType === DB_TYPES.DECIMAL || dbType === DB_TYPES.NUMERIC) { if (dbFormValues.maxDigits !== null) { - typeOptions.precision = dbFormValues.maxDigits; + typeOptions.precision = Number(dbFormValues.maxDigits); } if (dbFormValues.decimalPlaces !== null) { - typeOptions.scale = dbFormValues.decimalPlaces; + typeOptions.scale = Number(dbFormValues.decimalPlaces); } } @@ -257,17 +258,15 @@ function determineDisplayOptions( formValues: FormValues, ): Column['display_options'] { const decimalPlaces = formValues.decimalPlaces as number | null; - const opts: Partial = { - number_format: + const opts: Partial = { + num_format: formValues.numberFormat === 'none' ? undefined : (formValues.numberFormat as NumberFormat), - use_grouping: - (formValues.useGrouping as - | NumberDisplayOptions['use_grouping'] - | undefined) ?? 'false', - minimum_fraction_digits: decimalPlaces ?? undefined, - maximum_fraction_digits: decimalPlaces ?? undefined, + num_grouping: + (formValues.useGrouping as NumberGrouping | undefined) ?? 'auto', + num_min_frac_digits: decimalPlaces ?? undefined, + num_max_frac_digits: decimalPlaces ?? undefined, }; return opts; } @@ -289,16 +288,16 @@ export function getDecimalPlaces( } function constructDisplayFormValuesFromDisplayOptions( - columnDisplayOpts: Column['display_options'], + displayOptions: Column['display_options'], ): FormValues { - const displayOptions = columnDisplayOpts as NumberDisplayOptions | null; + const column = { display_options: displayOptions }; const decimalPlaces = getDecimalPlaces( - displayOptions?.minimum_fraction_digits ?? null, - displayOptions?.maximum_fraction_digits ?? null, + displayOptions?.num_min_frac_digits ?? null, + displayOptions?.num_max_frac_digits ?? null, ); const formValues: FormValues = { - numberFormat: displayOptions?.number_format ?? 'none', - useGrouping: displayOptions?.use_grouping ?? 'false', + numberFormat: getColumnDisplayOption(column, 'num_format'), + useGrouping: getColumnDisplayOption(column, 'num_grouping'), decimalPlaces, }; return formValues; diff --git a/mathesar_ui/src/stores/abstract-types/type-configs/text.ts b/mathesar_ui/src/stores/abstract-types/type-configs/text.ts index fecf3c0b87..5dbd4fdb56 100644 --- a/mathesar_ui/src/stores/abstract-types/type-configs/text.ts +++ b/mathesar_ui/src/stores/abstract-types/type-configs/text.ts @@ -1,4 +1,4 @@ -import type { Column } from '@mathesar/api/rest/types/tables/columns'; +import type { Column } from '@mathesar/api/rpc/columns'; import type { DbType } from '@mathesar/AppTypes'; import { iconUiTypeText } from '@mathesar/icons'; import type { FormValues } from '@mathesar-component-library/types'; @@ -76,7 +76,7 @@ function determineDbTypeAndOptions( const dbType = determineDbType(dbFormValues, columnType); const typeOptions: Column['type_options'] = {}; if (dbType === DB_TYPES.CHAR || dbType === DB_TYPES.VARCHAR) { - typeOptions.length = dbFormValues.length; + typeOptions.length = Number(dbFormValues.length); } return { dbType, diff --git a/mathesar_ui/src/stores/abstract-types/type-configs/time.ts b/mathesar_ui/src/stores/abstract-types/type-configs/time.ts index 8c0905dc44..42e739c7c3 100644 --- a/mathesar_ui/src/stores/abstract-types/type-configs/time.ts +++ b/mathesar_ui/src/stores/abstract-types/type-configs/time.ts @@ -1,8 +1,8 @@ -import type { - Column, - TimeDisplayOptions, - TimeFormat, -} from '@mathesar/api/rest/types/tables/columns'; +import { + type Column, + type TimeFormat, + getColumnDisplayOption, +} from '@mathesar/api/rpc/columns'; import { iconUiTypeTime } from '@mathesar/icons'; import type { FormValues } from '@mathesar-component-library/types'; @@ -80,22 +80,20 @@ const displayForm: AbstractTypeConfigForm = { }; function determineDisplayOptions( - dispFormValues: FormValues, + formValues: FormValues, ): Column['display_options'] { - const displayOptions: TimeDisplayOptions = { - format: dispFormValues.format as TimeFormat, + return { + time_format: formValues.format as TimeFormat, }; - return displayOptions; } function constructDisplayFormValuesFromDisplayOptions( - columnDisplayOpts: Column['display_options'], + displayOptions: Column['display_options'], ): FormValues { - const displayOptions = columnDisplayOpts as TimeDisplayOptions | null; - const dispFormValues: FormValues = { - format: displayOptions?.format ?? '24hr', + const column = { display_options: displayOptions }; + return { + format: getColumnDisplayOption(column, 'time_format'), }; - return dispFormValues; } const timeType: AbstractTypeConfiguration = { diff --git a/mathesar_ui/src/stores/abstract-types/type-configs/utils.ts b/mathesar_ui/src/stores/abstract-types/type-configs/utils.ts index bb3774db89..ab126c1d0e 100644 --- a/mathesar_ui/src/stores/abstract-types/type-configs/utils.ts +++ b/mathesar_ui/src/stores/abstract-types/type-configs/utils.ts @@ -1,7 +1,4 @@ -import type { - DateFormat, - TimeFormat, -} from '@mathesar/api/rest/types/tables/columns'; +import type { DateFormat, TimeFormat } from '@mathesar/api/rpc/columns'; import { DateTimeSpecification } from '@mathesar/utils/date-time'; import { dayjs } from '@mathesar-component-library'; diff --git a/mathesar_ui/src/stores/abstract-types/types.ts b/mathesar_ui/src/stores/abstract-types/types.ts index c05a43ed8b..6f04eaf055 100644 --- a/mathesar_ui/src/stores/abstract-types/types.ts +++ b/mathesar_ui/src/stores/abstract-types/types.ts @@ -1,6 +1,6 @@ import type { QuerySummarizationFunctionId } from '@mathesar/api/rest/types/queries'; -import type { Column } from '@mathesar/api/rest/types/tables/columns'; import type { States } from '@mathesar/api/rest/utils/requestUtils'; +import type { Column } from '@mathesar/api/rpc/columns'; import type { DbType } from '@mathesar/AppTypes'; import type { CellDataType } from '@mathesar/components/cell-fabric/data-types/typeDefinitions'; import type { diff --git a/mathesar_ui/src/stores/table-data/TableStructure.ts b/mathesar_ui/src/stores/table-data/TableStructure.ts index 0289a340c2..12444c7fa6 100644 --- a/mathesar_ui/src/stores/table-data/TableStructure.ts +++ b/mathesar_ui/src/stores/table-data/TableStructure.ts @@ -1,8 +1,10 @@ import type { Readable } from 'svelte/store'; import { derived } from 'svelte/store'; -import type { Column } from '@mathesar/api/rest/types/tables/columns'; import { States } from '@mathesar/api/rest/utils/requestUtils'; +import type { Column } from '@mathesar/api/rpc/columns'; +import type { Database } from '@mathesar/api/rpc/databases'; +import type { Table } from '@mathesar/api/rpc/tables'; import type { DBObjectEntry } from '@mathesar/AppTypes'; import type { AbstractTypesMap } from '@mathesar/stores/abstract-types/types'; @@ -13,12 +15,13 @@ import type { ProcessedColumnsStore } from './processedColumns'; import { processColumn } from './processedColumns'; export interface TableStructureProps { - id: DBObjectEntry['id']; + database: Pick; + table: Pick; abstractTypesMap: AbstractTypesMap; } export class TableStructure { - id: DBObjectEntry['id']; + oid: DBObjectEntry['id']; columnsDataStore: ColumnsDataStore; @@ -29,9 +32,12 @@ export class TableStructure { isLoading: Readable; constructor(props: TableStructureProps) { - this.id = props.id; - this.columnsDataStore = new ColumnsDataStore({ tableId: this.id }); - this.constraintsDataStore = new ConstraintsDataStore({ tableId: this.id }); + this.oid = props.table.oid; + this.columnsDataStore = new ColumnsDataStore({ + database: props.database, + tableOid: this.oid, + }); + this.constraintsDataStore = new ConstraintsDataStore({ tableId: this.oid }); this.processedColumns = derived( [this.columnsDataStore.columns, this.constraintsDataStore], ([columns, constraintsData]) => @@ -39,7 +45,7 @@ export class TableStructure { columns.map((column, columnIndex) => [ column.id, processColumn({ - tableId: this.id, + tableId: this.oid, column, columnIndex, constraints: constraintsData.constraints, diff --git a/mathesar_ui/src/stores/table-data/columns.ts b/mathesar_ui/src/stores/table-data/columns.ts index b76dded669..58b9bdfdc8 100644 --- a/mathesar_ui/src/stores/table-data/columns.ts +++ b/mathesar_ui/src/stores/table-data/columns.ts @@ -1,17 +1,13 @@ import { type Readable, derived, writable } from 'svelte/store'; -import type { Column } from '@mathesar/api/rest/types/tables/columns'; +import type { RequestStatus } from '@mathesar/api/rest/utils/requestUtils'; +import { api } from '@mathesar/api/rpc'; import type { - PaginatedResponse, - RequestStatus, -} from '@mathesar/api/rest/utils/requestUtils'; -import { - addQueryParamsToUrl, - deleteAPI, - getAPI, - patchAPI, - postAPI, -} from '@mathesar/api/rest/utils/requestUtils'; + Column, + ColumnCreationSpec, + ColumnPatchSpec, +} from '@mathesar/api/rpc/columns'; +import type { Database } from '@mathesar/api/rpc/databases'; import type { Table } from '@mathesar/api/rpc/tables'; import { getErrorMessage } from '@mathesar/utils/errors'; import type { ShareConsumer } from '@mathesar/utils/shares'; @@ -21,36 +17,18 @@ import { WritableSet, } from '@mathesar-component-library'; -function api(url: string) { - return { - get(queryParams: Record) { - const requestUrl = addQueryParamsToUrl(url, queryParams); - return getAPI>(requestUrl); - }, - add(columnDetails: Partial) { - return postAPI>(url, columnDetails); - }, - remove(id: Column['id']) { - return deleteAPI(`${url}${id}/`); - }, - update(id: Column['id'], data: Partial) { - return patchAPI>(`${url}${id}/`, data); - }, - }; -} - export class ColumnsDataStore extends EventHandler<{ - columnRenamed: number; - columnAdded: Partial; - columnDeleted: number; - columnPatched: Partial; - columnsFetched: Column[]; + columnRenamed: void; + columnAdded: void; + columnDeleted: Column['id']; + columnPatched: void; }> { - private tableId: Table['oid']; - - private promise: CancellablePromise> | undefined; + private apiContext: { + database_id: number; + table_oid: Table['oid']; + }; - private api: ReturnType; + private promise: CancellablePromise | undefined; private fetchedColumns = writable([]); @@ -66,19 +44,20 @@ export class ColumnsDataStore extends EventHandler<{ readonly shareConsumer?: ShareConsumer; constructor({ - tableId, + database, + tableOid, hiddenColumns, shareConsumer, }: { - tableId: Table['oid']; + database: Pick; + tableOid: Table['oid']; /** Values are column ids */ hiddenColumns?: Iterable; shareConsumer?: ShareConsumer; }) { super(); - this.tableId = tableId; + this.apiContext = { database_id: database.id, table_oid: tableOid }; this.shareConsumer = shareConsumer; - this.api = api(`/api/db/v0/tables/${this.tableId}/columns/`); this.hiddenColumns = new WritableSet(hiddenColumns); this.columns = derived( [this.fetchedColumns, this.hiddenColumns], @@ -94,15 +73,16 @@ export class ColumnsDataStore extends EventHandler<{ try { this.fetchStatus.set({ state: 'processing' }); this.promise?.cancel(); - this.promise = this.api.get({ - limit: 500, - ...this.shareConsumer?.getQueryParams(), - }); - const response = await this.promise; - const columns = response.results; + // TODO_BETA: For some reason `...this.shareConsumer?.getQueryParams()` + // was getting passed into the API call when it was REST. I don't know + // why. We need to figure out if this is necessary to replicate for the + // RPC call. + this.promise = api.columns + .list_with_metadata({ ...this.apiContext }) + .run(); + const columns = await this.promise; this.fetchedColumns.set(columns); this.fetchStatus.set({ state: 'success' }); - await this.dispatch('columnsFetched', columns); return columns; } catch (e) { this.fetchStatus.set({ state: 'failure', errors: [getErrorMessage(e)] }); @@ -112,33 +92,30 @@ export class ColumnsDataStore extends EventHandler<{ } } - async add(columnDetails: Partial): Promise> { - const column = await this.api.add(columnDetails); - await this.dispatch('columnAdded', column); + async add(columnDetails: ColumnCreationSpec): Promise { + await api.columns + .add({ ...this.apiContext, column_data_list: [columnDetails] }) + .run(); + await this.dispatch('columnAdded'); await this.fetch(); - return column; } - async rename(id: Column['id'], newName: string): Promise { - await this.api.update(id, { name: newName }); - await this.dispatch('columnRenamed', id); + async rename(id: Column['id'], name: string): Promise { + await api.columns + .patch({ ...this.apiContext, column_data_list: [{ id, name }] }) + .run(); + await this.dispatch('columnRenamed'); } async updateDescription( id: Column['id'], description: string | null, ): Promise { - await this.api.update(id, { description }); + await api.columns + .patch({ ...this.apiContext, column_data_list: [{ id, description }] }) + .run(); this.fetchedColumns.update((columns) => - columns.map((column) => { - if (column.id === id) { - return { - ...column, - description, - }; - } - return column; - }), + columns.map((c) => (c.id === id ? { ...c, description } : c)), ); } @@ -151,23 +128,24 @@ export class ColumnsDataStore extends EventHandler<{ `Column "${column.name}" cannot allow NULL because it is a primary key.`, ); } - await this.api.update(column.id, { nullable }); + await api.columns + .patch({ + ...this.apiContext, + column_data_list: [{ id: column.id, nullable }], + }) + .run(); await this.fetch(); } - // TODO: Analyze: Might be cleaner to move following functions as a property of Column class - // but are the object instantiations worth it? - - async patch( - columnId: Column['id'], - properties: Omit, 'id'>, - ): Promise> { - const column = await this.api.update(columnId, { - ...properties, - }); + async patch(patchSpec: ColumnPatchSpec): Promise { + await api.columns + .patch({ + ...this.apiContext, + column_data_list: [patchSpec], + }) + .run(); await this.fetch(); - await this.dispatch('columnPatched', column); - return column; + await this.dispatch('columnPatched'); } destroy(): void { @@ -177,7 +155,9 @@ export class ColumnsDataStore extends EventHandler<{ } async deleteColumn(columnId: Column['id']): Promise { - await this.api.remove(columnId); + await api.columns + .delete({ ...this.apiContext, column_attnums: [columnId] }) + .run(); await this.dispatch('columnDeleted', columnId); await this.fetch(); } diff --git a/mathesar_ui/src/stores/table-data/constraints.ts b/mathesar_ui/src/stores/table-data/constraints.ts index ffb5de065f..64b9a05a7b 100644 --- a/mathesar_ui/src/stores/table-data/constraints.ts +++ b/mathesar_ui/src/stores/table-data/constraints.ts @@ -7,7 +7,6 @@ import type { } from 'svelte/store'; import { derived, get as getStoreValue, writable } from 'svelte/store'; -import type { Column } from '@mathesar/api/rest/types/tables/columns'; import type { Constraint as ApiConstraint } from '@mathesar/api/rest/types/tables/constraints'; import type { PaginatedResponse } from '@mathesar/api/rest/utils/requestUtils'; import { @@ -17,6 +16,7 @@ import { getAPI, postAPI, } from '@mathesar/api/rest/utils/requestUtils'; +import type { Column } from '@mathesar/api/rpc/columns'; import type { Table } from '@mathesar/api/rpc/tables'; import type { ShareConsumer } from '@mathesar/utils/shares'; import type { CancellablePromise } from '@mathesar-component-library'; diff --git a/mathesar_ui/src/stores/table-data/constraintsUtils.ts b/mathesar_ui/src/stores/table-data/constraintsUtils.ts index 31f616a838..c5a033b823 100644 --- a/mathesar_ui/src/stores/table-data/constraintsUtils.ts +++ b/mathesar_ui/src/stores/table-data/constraintsUtils.ts @@ -1,8 +1,8 @@ -import type { Column } from '@mathesar/api/rest/types/tables/columns'; import type { Constraint, FkConstraint, } from '@mathesar/api/rest/types/tables/constraints'; +import type { Column } from '@mathesar/api/rpc/columns'; export function constraintIsFk(c: Constraint): c is FkConstraint { return c.type === 'foreignkey'; diff --git a/mathesar_ui/src/stores/table-data/processedColumns.ts b/mathesar_ui/src/stores/table-data/processedColumns.ts index ab1ccad0c5..036aae5b8f 100644 --- a/mathesar_ui/src/stores/table-data/processedColumns.ts +++ b/mathesar_ui/src/stores/table-data/processedColumns.ts @@ -1,7 +1,7 @@ import type { Readable } from 'svelte/store'; -import type { Column } from '@mathesar/api/rest/types/tables/columns'; import type { Constraint } from '@mathesar/api/rest/types/tables/constraints'; +import type { Column } from '@mathesar/api/rpc/columns'; import type { Table } from '@mathesar/api/rpc/tables'; import type { CellColumnFabric } from '@mathesar/components/cell-fabric/types'; import { diff --git a/mathesar_ui/src/stores/table-data/records.ts b/mathesar_ui/src/stores/table-data/records.ts index fbc4443993..f536769732 100644 --- a/mathesar_ui/src/stores/table-data/records.ts +++ b/mathesar_ui/src/stores/table-data/records.ts @@ -7,7 +7,6 @@ import { writable, } from 'svelte/store'; -import type { Column } from '@mathesar/api/rest/types/tables/columns'; import type { GetRequestParams as ApiGetRequestParams, Group as ApiGroup, @@ -24,6 +23,7 @@ import { patchAPI, postAPI, } from '@mathesar/api/rest/utils/requestUtils'; +import type { Column } from '@mathesar/api/rpc/columns'; import type { Table } from '@mathesar/api/rpc/tables'; import { getErrorMessage } from '@mathesar/utils/errors'; import { pluralize } from '@mathesar/utils/languageUtils'; diff --git a/mathesar_ui/src/stores/table-data/tabularData.ts b/mathesar_ui/src/stores/table-data/tabularData.ts index cead9d366b..c8c719f750 100644 --- a/mathesar_ui/src/stores/table-data/tabularData.ts +++ b/mathesar_ui/src/stores/table-data/tabularData.ts @@ -1,10 +1,10 @@ import { getContext, setContext } from 'svelte'; import { type Readable, type Writable, derived, writable } from 'svelte/store'; -import type { Column } from '@mathesar/api/rest/types/tables/columns'; import { States } from '@mathesar/api/rest/utils/requestUtils'; +import type { Column } from '@mathesar/api/rpc/columns'; +import type { Database } from '@mathesar/api/rpc/databases'; import type { Table } from '@mathesar/api/rpc/tables'; -import type { DBObjectEntry } from '@mathesar/AppTypes'; import Plane from '@mathesar/components/sheet/selection/Plane'; import Series from '@mathesar/components/sheet/selection/Series'; import SheetSelectionStore from '@mathesar/components/sheet/selection/SheetSelectionStore'; @@ -23,9 +23,9 @@ import type { TableRecordsData } from './records'; import { RecordsData } from './records'; export interface TabularDataProps { - id: DBObjectEntry['id']; - abstractTypesMap: AbstractTypesMap; + database: Pick; table: Table; + abstractTypesMap: AbstractTypesMap; meta?: Meta; shareConsumer?: ShareConsumer; /** @@ -42,7 +42,7 @@ export interface TabularDataProps { } export class TabularData { - id: DBObjectEntry['id']; + table: Table; meta: Meta; @@ -60,27 +60,26 @@ export class TabularData { selection: SheetSelectionStore; - table: Table; - shareConsumer?: ShareConsumer; constructor(props: TabularDataProps) { const contextualFilters = props.contextualFilters ?? new Map(); - this.id = props.id; + this.table = props.table; this.meta = props.meta ?? new Meta(); this.shareConsumer = props.shareConsumer; this.columnsDataStore = new ColumnsDataStore({ - tableId: this.id, + database: props.database, + tableOid: this.table.oid, hiddenColumns: contextualFilters.keys(), shareConsumer: this.shareConsumer, }); this.constraintsDataStore = new ConstraintsDataStore({ - tableId: this.id, + tableId: this.table.oid, shareConsumer: this.shareConsumer, }); this.recordsData = new RecordsData({ - tableId: this.id, + tableId: this.table.oid, meta: this.meta, columnsDataStore: this.columnsDataStore, contextualFilters, @@ -102,7 +101,7 @@ export class TabularData { columns.map((column, columnIndex) => [ column.id, processColumn({ - tableId: this.id, + tableId: this.table.oid, column, columnIndex, constraints: constraintsData.constraints, diff --git a/mathesar_ui/src/stores/table-data/utils.ts b/mathesar_ui/src/stores/table-data/utils.ts index 7557f7f9c8..7eac7ed9fd 100644 --- a/mathesar_ui/src/stores/table-data/utils.ts +++ b/mathesar_ui/src/stores/table-data/utils.ts @@ -1,8 +1,8 @@ import { concat } from 'iter-tools'; -import type { Column } from '@mathesar/api/rest/types/tables/columns'; import type { RequestStatus } from '@mathesar/api/rest/utils/requestUtils'; import { getMostImportantRequestStatusState } from '@mathesar/api/rest/utils/requestUtils'; +import type { Column } from '@mathesar/api/rpc/columns'; import { ImmutableMap, ImmutableSet, diff --git a/mathesar_ui/src/stores/tables.ts b/mathesar_ui/src/stores/tables.ts index e09222aae2..92261d8eca 100644 --- a/mathesar_ui/src/stores/tables.ts +++ b/mathesar_ui/src/stores/tables.ts @@ -446,7 +446,12 @@ export function getJoinableTablesResult( tableId: number, maxDepth = 1, ): Promise { - throw new Error('Not implemented'); // TODO_BETA + return Promise.resolve({ + joinable_tables: [], + tables: {}, + columns: {}, + }); + // TODO_BETA: re-implement this with the RPC API. // return getAPI( // `/api/db/v0/tables/${tableId}/joinable_tables/?max_depth=${maxDepth}`, diff --git a/mathesar_ui/src/systems/data-explorer/urlSerializationUtils.ts b/mathesar_ui/src/systems/data-explorer/urlSerializationUtils.ts index 3551b2675e..a077f35d08 100644 --- a/mathesar_ui/src/systems/data-explorer/urlSerializationUtils.ts +++ b/mathesar_ui/src/systems/data-explorer/urlSerializationUtils.ts @@ -1,5 +1,5 @@ import type { QueryInstanceSummarizationTransformation } from '@mathesar/api/rest/types/queries'; -import type { Column } from '@mathesar/api/rest/types/tables/columns'; +import type { Column } from '@mathesar/api/rpc/columns'; import type { Table } from '@mathesar/api/rpc/tables'; import { getDataExplorerPageUrl } from '@mathesar/routes/urls'; import type { UnsavedQueryInstance } from '@mathesar/stores/queries'; diff --git a/mathesar_ui/src/systems/data-explorer/utils.ts b/mathesar_ui/src/systems/data-explorer/utils.ts index 7a3c77f1fd..b1e187f51c 100644 --- a/mathesar_ui/src/systems/data-explorer/utils.ts +++ b/mathesar_ui/src/systems/data-explorer/utils.ts @@ -1,3 +1,6 @@ +import type { Simplify } from 'type-fest'; +import type { SimplifyDeep } from 'type-fest/source/merge-deep'; + import type { QueryColumnMetaData, QueryGeneratedColumnSource, @@ -5,11 +8,11 @@ import type { QueryResultColumn, QueryRunResponse, } from '@mathesar/api/rest/types/queries'; -import type { Column } from '@mathesar/api/rest/types/tables/columns'; import type { JoinableTablesResult, JpPath, } from '@mathesar/api/rest/types/tables/joinable_tables'; +import type { Column } from '@mathesar/api/rpc/columns'; import type { Table } from '@mathesar/api/rpc/tables'; import type { CellColumnFabric } from '@mathesar/components/cell-fabric/types'; import { @@ -305,6 +308,10 @@ export function getTablesThatReferenceBaseTable( return references; } +// type T = SimplifyDeep & +// ProcessedQueryResultColumnSource & +// Partial>; + function processColumn( columnInfo: Pick & ProcessedQueryResultColumnSource & @@ -468,9 +475,14 @@ export function speculateColumnMetaData({ type_options: aggregation.function === 'distinct_aggregate_to_array' ? { - type: - updatedColumnsMetaData.get(aggregation.inputAlias) - ?.column.type ?? 'unknown', + // TODO_3704: Ask Pavish. + // `Column['type_options']` was previously typed loosely + // as `Record | null`. Now it's more + // strict and it doesn't have a `type` property. + // + // type: + // updatedColumnsMetaData.get(aggregation.inputAlias) + // ?.column.type ?? 'unknown', } : null, display_options: null, diff --git a/mathesar_ui/src/systems/record-selector/RecordSelectorContent.svelte b/mathesar_ui/src/systems/record-selector/RecordSelectorContent.svelte index b30cd4b009..2305433c06 100644 --- a/mathesar_ui/src/systems/record-selector/RecordSelectorContent.svelte +++ b/mathesar_ui/src/systems/record-selector/RecordSelectorContent.svelte @@ -13,7 +13,6 @@ renderTransitiveRecordSummary, } from '@mathesar/stores/table-data/record-summaries/recordSummaryUtils'; import { getPkValueInRecord } from '@mathesar/stores/table-data/records'; - import { currentTablesData } from '@mathesar/stores/tables'; import { toast } from '@mathesar/stores/toast'; import { getErrorMessage } from '@mathesar/utils/errors'; import { Button, Icon, Spinner } from '@mathesar-component-library'; @@ -39,7 +38,7 @@ isLoading, columnsDataStore, recordsData, - id: tableId, + table, } = tabularData); $: ({ purpose: rowType } = controller); $: ({ columns, fetchStatus } = columnsDataStore); @@ -56,7 +55,10 @@ controller.submit(result); } else if ($rowType === 'navigation') { const { recordId } = result; - const recordPageUrl = $storeToGetRecordPageUrl({ tableId, recordId }); + const recordPageUrl = $storeToGetRecordPageUrl({ + tableId: table.oid, + recordId, + }); if (recordPageUrl) { router.goto(recordPageUrl); controller.cancel(); @@ -70,7 +72,7 @@ } async function submitNewRecord() { - const url = `/api/db/v0/tables/${tableId}/records/`; + const url = `/api/db/v0/tables/${table.oid}/records/`; const body = getDataForNewRecord(); try { isSubmittingNewRecord = true; @@ -78,8 +80,7 @@ const record = response.results[0]; const recordId = getPkValueInRecord(record, $columns); const previewData = response.preview_data ?? []; - const table = $currentTablesData.tablesMap.get(tableId); - const template = table?.metadata?.record_summary_template; + const template = table.metadata?.record_summary_template; // TODO_RS_TEMPLATE // // We need to change the logic here to account for the fact that sometimes diff --git a/mathesar_ui/src/systems/record-selector/RecordSelectorController.ts b/mathesar_ui/src/systems/record-selector/RecordSelectorController.ts index 933e5879df..6944a8e64a 100644 --- a/mathesar_ui/src/systems/record-selector/RecordSelectorController.ts +++ b/mathesar_ui/src/systems/record-selector/RecordSelectorController.ts @@ -1,8 +1,8 @@ import { getContext, setContext } from 'svelte'; import { writable } from 'svelte/store'; -import type { Column } from '@mathesar/api/rest/types/tables/columns'; import type { Result as ApiRecord } from '@mathesar/api/rest/types/tables/records'; +import type { Column } from '@mathesar/api/rpc/columns'; import type { DBObjectEntry } from '@mathesar/AppTypes'; import type { RecordSelectorPurpose } from './recordSelectorUtils'; diff --git a/mathesar_ui/src/systems/record-selector/RecordSelectorTable.svelte b/mathesar_ui/src/systems/record-selector/RecordSelectorTable.svelte index cf9a36368d..299d786088 100644 --- a/mathesar_ui/src/systems/record-selector/RecordSelectorTable.svelte +++ b/mathesar_ui/src/systems/record-selector/RecordSelectorTable.svelte @@ -1,8 +1,8 @@ -{#key id} +{#key oid} {#if usesVirtualList} import { _ } from 'svelte-i18n'; - import type { Table } from '@mathesar/api/rpc/tables'; import EntityPageHeader from '@mathesar/components/EntityPageHeader.svelte'; import ModificationStatus from '@mathesar/components/ModificationStatus.svelte'; import { iconInspector, iconTable } from '@mathesar/icons'; @@ -18,9 +17,8 @@ const tabularData = getTabularDataStoreFromContext(); export let context: TableActionsContext = 'page'; - export let table: Pick; - $: ({ id, meta, isLoading, display } = $tabularData); + $: ({ table, meta, isLoading, display } = $tabularData); $: ({ filtering, sorting, grouping, sheetState } = meta); $: ({ isTableInspectorVisible } = display); @@ -50,7 +48,7 @@
{#if context === 'page'} - +