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..9affd4cabf 100644 --- a/mathesar_ui/src/api/rest/types/queries.ts +++ b/mathesar_ui/src/api/rest/types/queries.ts @@ -1,7 +1,7 @@ -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'; +import type { JoinPath } from '@mathesar/api/rpc/tables'; export type QueryColumnAlias = string; @@ -12,7 +12,7 @@ export type QueryColumnAlias = string; export interface QueryInstanceInitialColumn { alias: QueryColumnAlias; id: Column['id']; - jp_path?: JpPath; + jp_path?: JoinPath; } // TODO: Extend this to support more complicated filters 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/constraints.ts b/mathesar_ui/src/api/rest/types/tables/constraints.ts deleted file mode 100644 index 490b62163f..0000000000 --- a/mathesar_ui/src/api/rest/types/tables/constraints.ts +++ /dev/null @@ -1,40 +0,0 @@ -export interface BaseConstraint { - id: number; - name: string; - /** - * Each number is a column id. - */ - columns: number[]; -} - -export interface BasicConstraint extends BaseConstraint { - type: 'primary' | 'unique' | 'check' | 'exclude'; -} - -export interface FkConstraint extends BaseConstraint { - type: 'foreignkey'; - /** The ids of the columns in the table which this FK references */ - referent_columns: number[]; - /** The id of the table which this FK references */ - referent_table: number; - onupdate: - | 'RESTRICT' - | 'CASCADE' - | 'SET NULL' - | 'NO ACTION' - | 'SET DEFAULT' - | null; - ondelete: - | 'RESTRICT' - | 'CASCADE' - | 'SET NULL' - | 'NO ACTION' - | 'SET DEFAULT' - | null; - deferrable: boolean | null; - match: 'SIMPLE' | 'PARTIAL' | 'FULL' | null; -} - -export type Constraint = BasicConstraint | FkConstraint; - -export type ConstraintType = Constraint['type']; diff --git a/mathesar_ui/src/api/rest/types/tables/joinable_tables.ts b/mathesar_ui/src/api/rest/types/tables/joinable_tables.ts deleted file mode 100644 index 8b84483b2e..0000000000 --- a/mathesar_ui/src/api/rest/types/tables/joinable_tables.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @file - * - * endpoint: /api/db/v0/tables//joinable_tables/ - */ - -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']][]; - -export interface JoinableTable { - target: Table['oid']; // baseTableId - jp_path: JpPath; - fk_path: [ForeignKeyId, IsLinkReversed][]; - depth: number; - multiple_results: boolean; -} - -export interface JoinableTablesResult { - joinable_tables: JoinableTable[]; - tables: Record< - string, // tableId - { - name: Table['name']; - columns: Column['id'][]; - } - >; - columns: Record< - string, // columnId - { - name: Column['name']; - type: Column['type']; - } - >; -} diff --git a/mathesar_ui/src/api/rpc/columns.ts b/mathesar_ui/src/api/rpc/columns.ts new file mode 100644 index 0000000000..3cfd02c2c6 --- /dev/null +++ b/mathesar_ui/src/api/rpc/columns.ts @@ -0,0 +1,236 @@ +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; + + 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/constraints.ts b/mathesar_ui/src/api/rpc/constraints.ts new file mode 100644 index 0000000000..5c6aa2ee36 --- /dev/null +++ b/mathesar_ui/src/api/rpc/constraints.ts @@ -0,0 +1,86 @@ +import { rpcMethodTypeContainer } from '@mathesar/packages/json-rpc-client-builder'; + +interface BaseConstraint { + oid: number; + name: string; + /** Each number is a column attnum */ + columns: number[]; +} + +export interface PkConstraint extends BaseConstraint { + type: 'primary'; +} + +export interface UniqueConstraint extends BaseConstraint { + type: 'unique'; +} + +export interface FkConstraint extends BaseConstraint { + type: 'foreignkey'; + /** The ids of the columns in the table which this FK references */ + referent_columns: number[]; + /** The id of the table which this FK references */ + referent_table_oid: number; +} + +export interface CheckConstraint extends BaseConstraint { + type: 'check'; +} + +export interface ExcludeConstraint extends BaseConstraint { + type: 'exclude'; +} + +export type Constraint = + | PkConstraint + | UniqueConstraint + | FkConstraint + | CheckConstraint + | ExcludeConstraint; + +export type ConstraintType = Constraint['type']; + +export interface UniqueConstraintRecipe { + type: 'u'; + name?: string | null; + /** Values are column attnums */ + columns: number[]; +} + +export interface FkConstraintRecipe { + type: 'f'; + name?: string | null; + columns: number[]; + fkey_relation_id: number; + fkey_columns: number[]; +} + +export type ConstraintRecipe = UniqueConstraintRecipe | FkConstraintRecipe; + +export const constraints = { + list: rpcMethodTypeContainer< + { + database_id: number; + table_oid: number; + }, + Constraint[] + >(), + + add: rpcMethodTypeContainer< + { + database_id: number; + table_oid: number; + constraint_def_list: ConstraintRecipe[]; + }, + void + >(), + + delete: rpcMethodTypeContainer< + { + database_id: number; + table_oid: number; + constraint_oid: number; + }, + void + >(), +}; diff --git a/mathesar_ui/src/api/rpc/index.ts b/mathesar_ui/src/api/rpc/index.ts index a3cefb1626..9e12fd779a 100644 --- a/mathesar_ui/src/api/rpc/index.ts +++ b/mathesar_ui/src/api/rpc/index.ts @@ -2,9 +2,12 @@ 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 { constraints } from './constraints'; import { database_setup } from './database_setup'; import { databases } from './databases'; +import { records } from './records'; import { schemas } from './schemas'; import { servers } from './servers'; import { tables } from './tables'; @@ -17,8 +20,11 @@ export const api = buildRpcApi({ configured_roles, database_setup, databases, + records, schemas, servers, tables, + columns, + constraints, }, }); diff --git a/mathesar_ui/src/api/rest/types/tables/records.ts b/mathesar_ui/src/api/rpc/records.ts similarity index 89% rename from mathesar_ui/src/api/rest/types/tables/records.ts rename to mathesar_ui/src/api/rpc/records.ts index fdbfafdd0e..5389b42989 100644 --- a/mathesar_ui/src/api/rest/types/tables/records.ts +++ b/mathesar_ui/src/api/rpc/records.ts @@ -1,3 +1,5 @@ +import { rpcMethodTypeContainer } from '@mathesar/packages/json-rpc-client-builder'; + export interface Grouping { /** Each string is a column id */ columns: number[]; @@ -22,7 +24,7 @@ export interface Grouping { export type SortDirection = 'asc' | 'desc'; export interface SortingEntry { /** column id */ - field: number; + attnum: number; direction: SortDirection; } export type FilterCombination = 'and' | 'or'; @@ -34,15 +36,15 @@ export type FilterCondition = Record; type MakeFilteringOption = U extends string ? { [k in U]: FilterRequest[] } : never; -export type FilterRequest = - | FilterCondition - | MakeFilteringOption; +type FilterRequest = FilterCondition | MakeFilteringOption; -export interface GetRequestParams { +export interface RecordsListParams { + database_id: number; + table_oid: number; limit?: number; offset?: number; - order_by?: SortingEntry[]; - grouping?: Pick; + order?: SortingEntry[]; + group?: Pick; filter?: FilterRequest; search_fuzzy?: Record[]; } @@ -117,7 +119,7 @@ export interface ApiDataForRecordSummariesInFkColumn { data: Record; } -export interface Response { +export interface RecordsResponse { count: number; grouping: Grouping | null; results: Result[]; @@ -126,3 +128,7 @@ export interface Response { */ preview_data: ApiDataForRecordSummariesInFkColumn[] | null; } + +export const records = { + list: rpcMethodTypeContainer(), +}; diff --git a/mathesar_ui/src/api/rpc/tables.ts b/mathesar_ui/src/api/rpc/tables.ts index 11c269cd40..da27409597 100644 --- a/mathesar_ui/src/api/rpc/tables.ts +++ b/mathesar_ui/src/api/rpc/tables.ts @@ -19,6 +19,48 @@ export interface Table extends RawTable { metadata: TableMetadata | null; } +/** [table oid, column attnum][] */ +export type JoinPath = [number, number][]; + +export interface JoinableTable { + target: Table['oid']; + join_path: JoinPath; + /** + * [Constraint OID, is_reversed] + * + * When is_reversed is `false`, following the join path constitutes following + * a foreign key relationship from the base table to the target table. For + * example, following a join from `books` to `authors` when one book has one + * author. + * + * When is_reversed is `true`, following the join path constitutes following + * a foreign key relationship from the target table to the base table. For + * example, following a join from `authors` to `books` when one author has + * many books. + */ + fkey_path: [number, boolean][]; + depth: number; + multiple_results: boolean; +} + +export interface JoinableTablesResult { + joinable_tables: JoinableTable[]; + tables: Record< + string, // stringified table_oid + { + name: Table['name']; + columns: number[]; + } + >; + columns: Record< + string, // stringified column_oid + { + name: string; + type: string; + } + >; +} + export const tables = { list: rpcMethodTypeContainer< { @@ -87,4 +129,12 @@ export const tables = { }, void >(), + + list_joinable: rpcMethodTypeContainer< + { + database_id: number; + schema_oid: number; + }, + JoinableTablesResult + >(), }; 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..e32705b26c 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 { FkConstraint } from '@mathesar/api/rpc/constraints'; 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/components/column/types.ts b/mathesar_ui/src/components/column/types.ts index 2b5d4023cb..b7e2140cbd 100644 --- a/mathesar_ui/src/components/column/types.ts +++ b/mathesar_ui/src/components/column/types.ts @@ -1,4 +1,4 @@ -import type { ConstraintType } from '@mathesar/api/rest/types/tables/constraints'; +import type { ConstraintType } from '@mathesar/api/rpc/constraints'; import type { CellColumnLike } from '@mathesar/components/cell-fabric/data-types/typeDefinitions'; // Since the ColumnName component is being used diff --git a/mathesar_ui/src/components/filter-entry/FilterEntry.svelte b/mathesar_ui/src/components/filter-entry/FilterEntry.svelte index a3d64c2743..74494a5fa0 100644 --- a/mathesar_ui/src/components/filter-entry/FilterEntry.svelte +++ b/mathesar_ui/src/components/filter-entry/FilterEntry.svelte @@ -2,7 +2,7 @@ import { createEventDispatcher, onDestroy } from 'svelte'; import { readable } from 'svelte/store'; - import type { ConstraintType } from '@mathesar/api/rest/types/tables/constraints'; + import type { ConstraintType } from '@mathesar/api/rpc/constraints'; import DynamicInput from '@mathesar/components/cell-fabric/DynamicInput.svelte'; import { getDbTypeBasedInputCap } from '@mathesar/components/cell-fabric/utils'; import ColumnName from '@mathesar/components/column/ColumnName.svelte'; diff --git a/mathesar_ui/src/components/filter-entry/utils.ts b/mathesar_ui/src/components/filter-entry/utils.ts index 179018cf85..90c48a8b64 100644 --- a/mathesar_ui/src/components/filter-entry/utils.ts +++ b/mathesar_ui/src/components/filter-entry/utils.ts @@ -1,4 +1,4 @@ -import type { FkConstraint } from '@mathesar/api/rest/types/tables/constraints'; +import type { FkConstraint } from '@mathesar/api/rpc/constraints'; import { getEqualityFiltersForAbstractType, getFiltersForAbstractType, diff --git a/mathesar_ui/src/components/group-entry/GroupEntry.svelte b/mathesar_ui/src/components/group-entry/GroupEntry.svelte index b86e28fd9d..f852cd3c9e 100644 --- a/mathesar_ui/src/components/group-entry/GroupEntry.svelte +++ b/mathesar_ui/src/components/group-entry/GroupEntry.svelte @@ -2,7 +2,7 @@ import { createEventDispatcher, tick } from 'svelte'; import { _ } from 'svelte-i18n'; - import type { ConstraintType } from '@mathesar/api/rest/types/tables/constraints'; + import type { ConstraintType } from '@mathesar/api/rpc/constraints'; import ColumnName from '@mathesar/components/column/ColumnName.svelte'; import { iconDeleteMajor } from '@mathesar/icons'; import type { ReadableMapLike } from '@mathesar/typeUtils'; diff --git a/mathesar_ui/src/components/sort-entry/SortEntry.svelte b/mathesar_ui/src/components/sort-entry/SortEntry.svelte index 485d4931a7..4f872694e0 100644 --- a/mathesar_ui/src/components/sort-entry/SortEntry.svelte +++ b/mathesar_ui/src/components/sort-entry/SortEntry.svelte @@ -2,7 +2,7 @@ import { createEventDispatcher } from 'svelte'; import { _ } from 'svelte-i18n'; - import type { ConstraintType } from '@mathesar/api/rest/types/tables/constraints'; + import type { ConstraintType } from '@mathesar/api/rpc/constraints'; import ColumnName from '@mathesar/components/column/ColumnName.svelte'; import { iconDeleteMajor } from '@mathesar/icons'; import type { ReadableMapLike } from '@mathesar/typeUtils'; 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/record/Widgets.svelte b/mathesar_ui/src/pages/record/Widgets.svelte index 21f23fd65f..34906815fd 100644 --- a/mathesar_ui/src/pages/record/Widgets.svelte +++ b/mathesar_ui/src/pages/record/Widgets.svelte @@ -4,7 +4,7 @@ import type { JoinableTable, JoinableTablesResult, - } from '@mathesar/api/rest/types/tables/joinable_tables'; + } from '@mathesar/api/rpc/tables'; import NameWithIcon from '@mathesar/components/NameWithIcon.svelte'; import { RichText } from '@mathesar/components/rich-text'; import { iconRecord } from '@mathesar/icons'; @@ -27,7 +27,7 @@ function buildWidgetInput(joinableTable: JoinableTable) { const table = $currentTablesData.tablesMap.get(joinableTable.target); if (!table) return undefined; - const id = joinableTable.jp_path[0].slice(-1)[0]; + const id = joinableTable.join_path[0].slice(-1)[0]; const name = columnNameMap.get(id) ?? `(${$_('unknown_column')})`; return { table, fkColumn: { id, name } }; } 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..72d95070c3 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,9 @@ 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(props); + this.constraintsDataStore = new ConstraintsDataStore(props); this.processedColumns = derived( [this.columnsDataStore.columns, this.constraintsDataStore], ([columns, constraintsData]) => @@ -39,7 +42,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..9e7530002b 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, + table, hiddenColumns, shareConsumer, }: { - tableId: Table['oid']; + database: Pick; + table: Pick; /** Values are column ids */ hiddenColumns?: Iterable; shareConsumer?: ShareConsumer; }) { super(); - this.tableId = tableId; + this.apiContext = { database_id: database.id, table_oid: table.oid }; 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..0542009b3a 100644 --- a/mathesar_ui/src/stores/table-data/constraints.ts +++ b/mathesar_ui/src/stores/table-data/constraints.ts @@ -7,31 +7,18 @@ 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 { - States, - addQueryParamsToUrl, - deleteAPI, - getAPI, - postAPI, -} from '@mathesar/api/rest/utils/requestUtils'; +import { States } from '@mathesar/api/rest/utils/requestUtils'; +import { api } from '@mathesar/api/rpc'; +import type { Column } from '@mathesar/api/rpc/columns'; +import type { + Constraint, + ConstraintRecipe, +} from '@mathesar/api/rpc/constraints'; +import type { Database } from '@mathesar/api/rpc/databases'; import type { Table } from '@mathesar/api/rpc/tables'; import type { ShareConsumer } from '@mathesar/utils/shares'; import type { CancellablePromise } from '@mathesar-component-library'; -/** - * When representing a constraint on the front end, we directly use the object - * schema from the API. - * - * In https://github.com/centerofci/mathesar/pull/776#issuecomment-963514261 we - * had some discussion about converting the `columns` field to a Set instead of - * an Array, but we chose to keep it as an Array because we didn't need the - * performance gains from a Set here. - */ -export type Constraint = ApiConstraint; - export interface ConstraintsData { state: States; error?: string; @@ -74,31 +61,15 @@ function uniqueColumns( ); } -function api(url: string) { - return { - get(queryParams: Record) { - const requestUrl = addQueryParamsToUrl(url, queryParams); - return getAPI>(requestUrl); - }, - add(constraintDetails: Partial) { - return postAPI>(url, constraintDetails); - }, - remove(constraintId: Constraint['id']) { - return deleteAPI(`${url}${constraintId}/`); - }, - }; -} - export class ConstraintsDataStore implements Writable { - private tableId: Table['oid']; + private apiContext: { + database_id: number; + table_oid: Table['oid']; + }; private store: Writable; - private promise: - | CancellablePromise> - | undefined; - - private api: ReturnType; + private promise: CancellablePromise | undefined; readonly shareConsumer?: ShareConsumer; @@ -112,20 +83,21 @@ export class ConstraintsDataStore implements Writable { uniqueColumns: Readable>; constructor({ - tableId, + database, + table, shareConsumer, }: { - tableId: Table['oid']; + database: Pick; + table: Pick; shareConsumer?: ShareConsumer; }) { - this.tableId = tableId; + this.apiContext = { database_id: database.id, table_oid: table.oid }; this.shareConsumer = shareConsumer; this.store = writable({ state: States.Loading, constraints: [], }); this.uniqueColumns = uniqueColumns(this.store); - this.api = api(`/api/db/v0/tables/${this.tableId}/constraints/`); void this.fetch(); } @@ -153,17 +125,18 @@ export class ConstraintsDataStore implements Writable { try { this.promise?.cancel(); - this.promise = this.api.get({ - limit: 500, - ...this.shareConsumer?.getQueryParams(), - }); - const response = await this.promise; + // TODO_BETA Do we need shareConsumer here? Previously we had been + // passing: + // + // ``` + // ...this.shareConsumer?.getQueryParams() + // ``` + this.promise = api.constraints.list(this.apiContext).run(); + + const constraints = await this.promise; - const storeData: ConstraintsData = { - state: States.Done, - constraints: response.results, - }; + const storeData: ConstraintsData = { state: States.Done, constraints }; this.set(storeData); return storeData; } catch (err) { @@ -178,16 +151,17 @@ export class ConstraintsDataStore implements Writable { return undefined; } - async add( - constraintDetails: Partial, - ): Promise> { - const constraint = await this.api.add(constraintDetails); + async add(recipe: ConstraintRecipe): Promise { + await api.constraints + .add({ ...this.apiContext, constraint_def_list: [recipe] }) + .run(); await this.fetch(); - return constraint; } async remove(constraintId: number): Promise { - await this.api.remove(constraintId); + await api.constraints + .delete({ ...this.apiContext, constraint_oid: constraintId }) + .run(); await this.fetch(); } @@ -209,7 +183,7 @@ export class ConstraintsDataStore implements Writable { return; } if (shouldBeUnique) { - await this.add({ type: 'unique', columns: [column.id] }); + await this.add({ type: 'u', columns: [column.id] }); return; } // Technically, one column can have two unique constraints applied on it, @@ -220,8 +194,10 @@ export class ConstraintsDataStore implements Writable { [column.id], ).filter((c) => c.type === 'unique'); await Promise.all( - uniqueConstraintsForColumn.map((constraint) => - this.api.remove(constraint.id), + uniqueConstraintsForColumn.map((c) => + api.constraints + .delete({ ...this.apiContext, constraint_oid: c.oid }) + .run(), ), ); await this.fetch(); diff --git a/mathesar_ui/src/stores/table-data/constraintsUtils.ts b/mathesar_ui/src/stores/table-data/constraintsUtils.ts index 31f616a838..4aeed78408 100644 --- a/mathesar_ui/src/stores/table-data/constraintsUtils.ts +++ b/mathesar_ui/src/stores/table-data/constraintsUtils.ts @@ -1,8 +1,5 @@ -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'; +import type { Constraint, FkConstraint } from '@mathesar/api/rpc/constraints'; export function constraintIsFk(c: Constraint): c is FkConstraint { return c.type === 'foreignkey'; diff --git a/mathesar_ui/src/stores/table-data/filtering.ts b/mathesar_ui/src/stores/table-data/filtering.ts index e19881229f..7bbbb184ee 100644 --- a/mathesar_ui/src/stores/table-data/filtering.ts +++ b/mathesar_ui/src/stores/table-data/filtering.ts @@ -2,8 +2,8 @@ import type { FilterCombination, FilterCondition, FilterConditionParams, - GetRequestParams, -} from '@mathesar/api/rest/types/tables/records'; + RecordsListParams, +} from '@mathesar/api/rpc/records'; export interface FilterEntry { readonly columnId: number; @@ -117,7 +117,7 @@ export class Filtering { return true; } - recordsRequestParams(): Pick { + recordsRequestParams(): Pick { if (!this.entries.length) { return {}; } diff --git a/mathesar_ui/src/stores/table-data/grouping.ts b/mathesar_ui/src/stores/table-data/grouping.ts index 193cb8f810..4dbafb7511 100644 --- a/mathesar_ui/src/stores/table-data/grouping.ts +++ b/mathesar_ui/src/stores/table-data/grouping.ts @@ -1,4 +1,4 @@ -import type { GetRequestParams } from '@mathesar/api/rest/types/tables/records'; +import type { RecordsListParams } from '@mathesar/api/rpc/records'; import { isDefinedNonNullable } from '@mathesar/component-library'; export interface GroupEntry { @@ -75,11 +75,11 @@ export class Grouping { }); } - recordsRequestParams(): Pick { + recordsRequestParams(): Pick { if (!this.entries.length) { return {}; } - const request: GetRequestParams['grouping'] = { + const request: RecordsListParams['group'] = { columns: [], preproc: [], }; @@ -91,7 +91,7 @@ export class Grouping { request.preproc = null; } return { - grouping: request, + group: request, }; } diff --git a/mathesar_ui/src/stores/table-data/index.ts b/mathesar_ui/src/stores/table-data/index.ts index 2d68b467ac..8b25c28daa 100644 --- a/mathesar_ui/src/stores/table-data/index.ts +++ b/mathesar_ui/src/stores/table-data/index.ts @@ -48,11 +48,7 @@ export { type ProcessedColumns, type ProcessedColumnsStore, } from './processedColumns'; -export { - type Constraint, - type ConstraintsData, - type ConstraintsDataStore, -} from './constraints'; +export { type ConstraintsData, type ConstraintsDataStore } from './constraints'; export { TableStructure } from './TableStructure'; export { SearchFuzzy } from './searchFuzzy'; export { constraintIsFk, findFkConstraintsForColumn } from './constraintsUtils'; diff --git a/mathesar_ui/src/stores/table-data/processedColumns.ts b/mathesar_ui/src/stores/table-data/processedColumns.ts index ab1ccad0c5..4d6a8faa2a 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 { Constraint } from '@mathesar/api/rpc/constraints'; import type { Table } from '@mathesar/api/rpc/tables'; import type { CellColumnFabric } from '@mathesar/components/cell-fabric/types'; import { @@ -87,7 +87,7 @@ export function processColumn({ ); const linkFk = findFkConstraintsForColumn(exclusiveConstraints, column.id)[0]; const isPk = (hasEnhancedPrimaryKeyCell ?? true) && column.primary_key; - const fkTargetTableId = linkFk ? linkFk.referent_table : undefined; + const fkTargetTableId = linkFk ? linkFk.referent_table_oid : undefined; return { id: column.id, column, diff --git a/mathesar_ui/src/stores/table-data/record-summaries/recordSummaryUtils.ts b/mathesar_ui/src/stores/table-data/record-summaries/recordSummaryUtils.ts index 445c2ac371..fb8745ea6d 100644 --- a/mathesar_ui/src/stores/table-data/record-summaries/recordSummaryUtils.ts +++ b/mathesar_ui/src/stores/table-data/record-summaries/recordSummaryUtils.ts @@ -1,7 +1,7 @@ import type { ApiDataForRecordSummariesInFkColumn, ApiRecordSummaryInputData, -} from '@mathesar/api/rest/types/tables/records'; +} from '@mathesar/api/rpc/records'; import { escapeHtml } from '@mathesar/utils/stringUtils'; import { ImmutableMap } from '@mathesar-component-library'; diff --git a/mathesar_ui/src/stores/table-data/records.ts b/mathesar_ui/src/stores/table-data/records.ts index fbc4443993..4c8221ef58 100644 --- a/mathesar_ui/src/stores/table-data/records.ts +++ b/mathesar_ui/src/stores/table-data/records.ts @@ -7,30 +7,25 @@ import { writable, } from 'svelte/store'; -import type { Column } from '@mathesar/api/rest/types/tables/columns'; +import { States } 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 { - GetRequestParams as ApiGetRequestParams, Group as ApiGroup, Grouping as ApiGrouping, Result as ApiRecord, - Response as ApiRecordsResponse, GroupingMode, -} from '@mathesar/api/rest/types/tables/records'; -import { - States, - deleteAPI, - getAPI, - getQueryStringFromParams, - patchAPI, - postAPI, -} from '@mathesar/api/rest/utils/requestUtils'; + RecordsListParams, + RecordsResponse, +} from '@mathesar/api/rpc/records'; import type { Table } from '@mathesar/api/rpc/tables'; import { getErrorMessage } from '@mathesar/utils/errors'; import { pluralize } from '@mathesar/utils/languageUtils'; import type Pagination from '@mathesar/utils/Pagination'; import type { ShareConsumer } from '@mathesar/utils/shares'; import { - type CancellablePromise, + CancellablePromise, getGloballyUniqueId, isDefinedNonNullable, } from '@mathesar-component-library'; @@ -54,25 +49,6 @@ export interface RecordsRequestParamsData { searchFuzzy: SearchFuzzy; } -interface RecordsFetchQueryParamsData extends RecordsRequestParamsData { - shareConsumer?: ShareConsumer; -} - -function buildFetchQueryString(data: RecordsFetchQueryParamsData): string { - const params: ApiGetRequestParams = { - ...data.pagination.recordsRequestParams(), - ...data.sorting.recordsRequestParamsIncludingGrouping(data.grouping), - ...data.grouping.recordsRequestParams(), - ...data.filtering.recordsRequestParams(), - ...data.searchFuzzy.recordsRequestParams(), - }; - const paramsWithShareConsumer = { - ...params, - ...data.shareConsumer?.getQueryParams(), - }; - return getQueryStringFromParams(paramsWithShareConsumer); -} - export interface RecordGroup { count: number; eqValue: ApiGroup['eq_value']; @@ -279,9 +255,10 @@ function preprocessRecords({ } export class RecordsData { - private tableId: Table['oid']; - - private url: string; + private apiContext: { + database_id: number; + table_oid: Table['oid']; + }; private meta: Meta; @@ -306,7 +283,7 @@ export class RecordsData { /** Keys are row selection ids */ selectableRowsMap: Readable>; - private promise: CancellablePromise | undefined; + private promise: CancellablePromise | undefined; // @ts-ignore: https://github.com/centerofci/mathesar/issues/1055 private createPromises: Map>; @@ -327,19 +304,21 @@ export class RecordsData { readonly shareConsumer?: ShareConsumer; constructor({ - tableId, + database, + table, meta, columnsDataStore, contextualFilters, shareConsumer, }: { - tableId: Table['oid']; + database: Pick; + table: Pick; meta: Meta; columnsDataStore: ColumnsDataStore; contextualFilters: Map; shareConsumer?: ShareConsumer; }) { - this.tableId = tableId; + this.apiContext = { database_id: database.id, table_oid: table.oid }; this.shareConsumer = shareConsumer; this.state = writable(States.Loading); this.savedRecordRowsWithGroupHeaders = writable([]); @@ -358,7 +337,6 @@ export class RecordsData { this.meta = meta; this.columnsDataStore = columnsDataStore; this.contextualFilters = contextualFilters; - this.url = `/api/db/v0/tables/${this.tableId}/records/`; void this.fetch(); this.selectableRowsMap = derived( @@ -410,12 +388,24 @@ export class RecordsData { const contextualFilterEntries = [...this.contextualFilters].map( ([columnId, value]) => ({ columnId, conditionId: 'equal', value }), ); - const queryString = buildFetchQueryString({ - ...params, - filtering: params.filtering.withEntries(contextualFilterEntries), - shareConsumer: this.shareConsumer, - }); - this.promise = getAPI(`${this.url}?${queryString}`); + + const recordsListParams: RecordsListParams = { + ...this.apiContext, + ...params.pagination.recordsRequestParams(), + ...params.sorting.recordsRequestParamsIncludingGrouping( + params.grouping, + ), + ...params.grouping.recordsRequestParams(), + ...params.filtering + .withEntries(contextualFilterEntries) + .recordsRequestParams(), + ...params.searchFuzzy.recordsRequestParams(), + // TODO_BETA Do we need shareConsumer here? Previously we had been + // passing `...this.shareConsumer?.getQueryParams()` + }; + + this.promise = api.records.list(recordsListParams).run(); + const response = await this.promise; const totalCount = response.count || 0; const grouping = response.grouping @@ -494,9 +484,9 @@ export class RecordsData { let shouldReFetchRecords = successRowKeys.size > 0; if (keysToDelete.length > 0) { const recordIds = [...keysToDelete]; - const bulkDeleteURL = `/api/ui/v0/tables/${this.tableId}/records/delete/`; try { - await deleteAPI(bulkDeleteURL, { pks: recordIds }); + throw new Error('Not implemented'); // TODO_BETA + // await deleteAPI(bulkDeleteURL, { pks: recordIds }); keysToDelete.forEach((key) => successRowKeys.add(key)); } catch (error) { failures.set(keysToDelete.join(','), getErrorMessage(error)); @@ -580,33 +570,36 @@ export class RecordsData { const cellKey = getCellKey(rowKey, column.id); this.meta.cellModificationStatus.set(cellKey, { state: 'processing' }); this.updatePromises?.get(cellKey)?.cancel(); - const promise = patchAPI( - `${this.url}${String(primaryKeyValue)}/`, - { [column.id]: record[column.id] }, - ); - if (!this.updatePromises) { - this.updatePromises = new Map(); - } - this.updatePromises.set(cellKey, promise); - try { - const result = await promise; - this.meta.cellModificationStatus.set(cellKey, { state: 'success' }); - return { - ...row, - record: result.results[0], - }; - } catch (err) { - this.meta.cellModificationStatus.set(cellKey, { - state: 'failure', - errors: [`Unable to save cell. ${getErrorMessage(err)}`], - }); - } finally { - if (this.updatePromises.get(cellKey) === promise) { - this.updatePromises.delete(cellKey); - } - } - return row; + throw new Error('Not implemented'); // TODO_BETA + // const promise = patchAPI( + // `${this.url}${String(primaryKeyValue)}/`, + // { [column.id]: record[column.id] }, + // ); + + // if (!this.updatePromises) { + // this.updatePromises = new Map(); + // } + // this.updatePromises.set(cellKey, promise); + + // try { + // const result = await promise; + // this.meta.cellModificationStatus.set(cellKey, { state: 'success' }); + // return { + // ...row, + // record: result.results[0], + // }; + // } catch (err) { + // this.meta.cellModificationStatus.set(cellKey, { + // state: 'failure', + // errors: [`Unable to save cell. ${getErrorMessage(err)}`], + // }); + // } finally { + // if (this.updatePromises.get(cellKey) === promise) { + // this.updatePromises.delete(cellKey); + // } + // } + // return row; } getNewEmptyRecord(): NewRecordRow { @@ -654,48 +647,51 @@ export class RecordsData { ...Object.fromEntries(this.contextualFilters), ...row.record, }; - const promise = postAPI(this.url, requestRecord); - if (!this.createPromises) { - this.createPromises = new Map(); - } - this.createPromises.set(rowKeyOfBlankRow, promise); - - try { - const response = await promise; - const record = response.results[0]; - let newRow: NewRecordRow = { - ...row, - record, - }; - if (isPlaceholderRow(newRow)) { - const { isAddPlaceholder, ...newRecordRow } = newRow; - newRow = newRecordRow; - } - const rowKeyWithRecord = getRowKey(newRow, pkColumn?.id); - this.meta.rowCreationStatus.delete(rowKeyOfBlankRow); - this.meta.rowCreationStatus.set(rowKeyWithRecord, { state: 'success' }); - this.newRecords.update((existing) => - existing.map((entry) => { - if (entry.identifier === row.identifier) { - return newRow; - } - return entry; - }), - ); - this.totalCount.update((count) => (count ?? 0) + 1); - return newRow; - } catch (err) { - this.meta.rowCreationStatus.set(rowKeyOfBlankRow, { - state: 'failure', - errors: [getErrorMessage(err)], - }); - } finally { - if (this.createPromises.get(rowKeyOfBlankRow) === promise) { - this.createPromises.delete(rowKeyOfBlankRow); - } - } - return row; + throw new Error('Not implemented'); // TODO_BETA + + // const promise = postAPI(this.url, requestRecord); + // if (!this.createPromises) { + // this.createPromises = new Map(); + // } + // this.createPromises.set(rowKeyOfBlankRow, promise); + + // try { + // const response = await promise; + // const record = response.results[0]; + // let newRow: NewRecordRow = { + // ...row, + // record, + // }; + // if (isPlaceholderRow(newRow)) { + // const { isAddPlaceholder, ...newRecordRow } = newRow; + // newRow = newRecordRow; + // } + + // const rowKeyWithRecord = getRowKey(newRow, pkColumn?.id); + // this.meta.rowCreationStatus.delete(rowKeyOfBlankRow); + // this.meta.rowCreationStatus.set(rowKeyWithRecord, { state: 'success' }); + // this.newRecords.update((existing) => + // existing.map((entry) => { + // if (entry.identifier === row.identifier) { + // return newRow; + // } + // return entry; + // }), + // ); + // this.totalCount.update((count) => (count ?? 0) + 1); + // return newRow; + // } catch (err) { + // this.meta.rowCreationStatus.set(rowKeyOfBlankRow, { + // state: 'failure', + // errors: [getErrorMessage(err)], + // }); + // } finally { + // if (this.createPromises.get(rowKeyOfBlankRow) === promise) { + // this.createPromises.delete(rowKeyOfBlankRow); + // } + // } + // return row; } async createOrUpdateRecord( diff --git a/mathesar_ui/src/stores/table-data/searchFuzzy.ts b/mathesar_ui/src/stores/table-data/searchFuzzy.ts index 411f9b6f5c..92040e486e 100644 --- a/mathesar_ui/src/stores/table-data/searchFuzzy.ts +++ b/mathesar_ui/src/stores/table-data/searchFuzzy.ts @@ -1,4 +1,4 @@ -import type { GetRequestParams } from '@mathesar/api/rest/types/tables/records'; +import type { RecordsListParams } from '@mathesar/api/rpc/records'; import { ImmutableMap } from '@mathesar/component-library'; /** @@ -18,7 +18,7 @@ export class SearchFuzzy extends ImmutableMap { this.valueIsValid = valueIsSearchable; } - recordsRequestParams(): Pick { + recordsRequestParams(): Pick { if (this.size === 0) { return {}; } diff --git a/mathesar_ui/src/stores/table-data/sorting.ts b/mathesar_ui/src/stores/table-data/sorting.ts index f133a5a8f2..f5b23f8b5b 100644 --- a/mathesar_ui/src/stores/table-data/sorting.ts +++ b/mathesar_ui/src/stores/table-data/sorting.ts @@ -1,8 +1,8 @@ import type { SortDirection as ApiSortDirection, SortingEntry as ApiSortingEntry, - GetRequestParams, -} from '@mathesar/api/rest/types/tables/records'; + RecordsListParams, +} from '@mathesar/api/rpc/records'; import { type SortDirection, allowedSortDirections, @@ -45,17 +45,17 @@ export class Sorting extends ImmutableMap { super(entries); } - private recordsRequestParams(): Pick { + private recordsRequestParams(): Pick { const sortingEntries: ApiSortingEntry[] = [...this].map( ([columnId, sortDirection]) => ({ - field: columnId, + attnum: columnId, direction: getApiSortDirection(sortDirection), }), ); if (!sortingEntries.length) { return {}; } - return { order_by: sortingEntries }; + return { order: sortingEntries }; } /** @@ -65,7 +65,7 @@ export class Sorting extends ImmutableMap { */ recordsRequestParamsIncludingGrouping( grouping: Grouping, - ): Pick { + ): Pick { const sortingFromGrouping = new Sorting( grouping.entries.map((g) => [g.columnId, 'ASCENDING']), ); diff --git a/mathesar_ui/src/stores/table-data/tabularData.ts b/mathesar_ui/src/stores/table-data/tabularData.ts index cead9d366b..8fc01d60af 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,28 @@ 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, + table: this.table, hiddenColumns: contextualFilters.keys(), shareConsumer: this.shareConsumer, }); this.constraintsDataStore = new ConstraintsDataStore({ - tableId: this.id, + database: props.database, + table: props.table, shareConsumer: this.shareConsumer, }); this.recordsData = new RecordsData({ - tableId: this.id, + database: props.database, + table: props.table, meta: this.meta, columnsDataStore: this.columnsDataStore, contextualFilters, @@ -102,7 +103,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..d9179497a2 100644 --- a/mathesar_ui/src/stores/tables.ts +++ b/mathesar_ui/src/stores/tables.ts @@ -14,13 +14,12 @@ import type { Readable, Writable } from 'svelte/store'; import { derived, get, readable, writable } from 'svelte/store'; import { _ } from 'svelte-i18n'; -import type { JoinableTablesResult } from '@mathesar/api/rest/types/tables/joinable_tables'; import type { SplitTableResponse } from '@mathesar/api/rest/types/tables/split_table'; import type { RequestStatus } from '@mathesar/api/rest/utils/requestUtils'; import { api } from '@mathesar/api/rpc'; import type { Database } from '@mathesar/api/rpc/databases'; import type { Schema } from '@mathesar/api/rpc/schemas'; -import type { Table } from '@mathesar/api/rpc/tables'; +import type { JoinableTablesResult, Table } from '@mathesar/api/rpc/tables'; import { invalidIf } from '@mathesar/components/form'; import { TupleMap } from '@mathesar/packages/tuple-map'; import { preloadCommonData } from '@mathesar/utils/preloadData'; @@ -446,7 +445,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/QueryManager.ts b/mathesar_ui/src/systems/data-explorer/QueryManager.ts index dbd73e9d64..9d2901d051 100644 --- a/mathesar_ui/src/systems/data-explorer/QueryManager.ts +++ b/mathesar_ui/src/systems/data-explorer/QueryManager.ts @@ -6,11 +6,10 @@ import type { QueryInstance, QueryRunResponse, } from '@mathesar/api/rest/types/queries'; -import type { JoinableTablesResult } from '@mathesar/api/rest/types/tables/joinable_tables'; import type { RequestStatus } from '@mathesar/api/rest/utils/requestUtils'; import { getAPI } from '@mathesar/api/rest/utils/requestUtils'; import { api } from '@mathesar/api/rpc'; -import type { Table } from '@mathesar/api/rpc/tables'; +import type { JoinableTablesResult, Table } from '@mathesar/api/rpc/tables'; import type { AbstractTypesMap } from '@mathesar/stores/abstract-types/types'; import { currentDatabase } from '@mathesar/stores/databases'; import { createQuery, putQuery } from '@mathesar/stores/queries'; diff --git a/mathesar_ui/src/systems/data-explorer/__tests__/mockData.ts b/mathesar_ui/src/systems/data-explorer/__tests__/mockData.ts index 6d66195cf3..092f0511c2 100644 --- a/mathesar_ui/src/systems/data-explorer/__tests__/mockData.ts +++ b/mathesar_ui/src/systems/data-explorer/__tests__/mockData.ts @@ -1,4 +1,4 @@ -import type { JoinableTablesResult } from '@mathesar/api/rest/types/tables/joinable_tables'; +import type { JoinableTablesResult } from '@mathesar/api/rpc/tables'; export const ItemsTableId = 75; export const CheckoutsPkColumnId = 216; @@ -8,25 +8,25 @@ export const checkoutsJoinableTables: JoinableTablesResult = { joinable_tables: [ { target: ItemsTableId, - jp_path: [[CheckoutsPkColumnId, ItemsPkColumnId]], - fk_path: [[188, false]], + join_path: [[CheckoutsPkColumnId, ItemsPkColumnId]], + fkey_path: [[188, false]], depth: 1, multiple_results: false, }, { target: 72, - jp_path: [[217, 228]], - fk_path: [[189, false]], + join_path: [[217, 228]], + fkey_path: [[189, false]], depth: 1, multiple_results: false, }, { target: 78, - jp_path: [ + join_path: [ [CheckoutsPkColumnId, ItemsPkColumnId], [222, 229], ], - fk_path: [ + fkey_path: [ [188, false], [192, false], ], @@ -35,12 +35,12 @@ export const checkoutsJoinableTables: JoinableTablesResult = { }, { target: 74, - jp_path: [ + join_path: [ [CheckoutsPkColumnId, ItemsPkColumnId], [222, 229], [231, 235], ], - fk_path: [ + fkey_path: [ [188, false], [192, false], [196, false], @@ -50,12 +50,12 @@ export const checkoutsJoinableTables: JoinableTablesResult = { }, { target: 77, - jp_path: [ + join_path: [ [CheckoutsPkColumnId, ItemsPkColumnId], [222, 229], [232, 211], ], - fk_path: [ + fkey_path: [ [188, false], [192, false], [195, false], @@ -196,25 +196,25 @@ export const selfRefTableJoinableTables: JoinableTablesResult = { joinable_tables: [ { target: SelfRefTableId, - jp_path: [[SelfRefTableFkColumnId, SelfRefTablePkColumnId]], - fk_path: [[2529, false]], + join_path: [[SelfRefTableFkColumnId, SelfRefTablePkColumnId]], + fkey_path: [[2529, false]], depth: 1, multiple_results: false, }, { target: SelfRefTableId, - jp_path: [[SelfRefTablePkColumnId, SelfRefTableFkColumnId]], - fk_path: [[2529, true]], + join_path: [[SelfRefTablePkColumnId, SelfRefTableFkColumnId]], + fkey_path: [[2529, true]], depth: 1, multiple_results: true, }, { target: SelfRefTableId, - jp_path: [ + join_path: [ [SelfRefTableFkColumnId, SelfRefTablePkColumnId], [SelfRefTableFkColumnId, SelfRefTablePkColumnId], ], - fk_path: [ + fkey_path: [ [2529, false], [2529, false], ], @@ -223,11 +223,11 @@ export const selfRefTableJoinableTables: JoinableTablesResult = { }, { target: SelfRefTableId, - jp_path: [ + join_path: [ [SelfRefTablePkColumnId, SelfRefTableFkColumnId], [SelfRefTablePkColumnId, SelfRefTableFkColumnId], ], - fk_path: [ + fkey_path: [ [2529, true], [2529, true], ], @@ -236,12 +236,12 @@ export const selfRefTableJoinableTables: JoinableTablesResult = { }, { target: SelfRefTableId, - jp_path: [ + join_path: [ [SelfRefTableFkColumnId, SelfRefTablePkColumnId], [SelfRefTableFkColumnId, SelfRefTablePkColumnId], [SelfRefTableFkColumnId, SelfRefTablePkColumnId], ], - fk_path: [ + fkey_path: [ [2529, false], [2529, false], [2529, false], @@ -251,12 +251,12 @@ export const selfRefTableJoinableTables: JoinableTablesResult = { }, { target: SelfRefTableId, - jp_path: [ + join_path: [ [SelfRefTablePkColumnId, SelfRefTableFkColumnId], [SelfRefTablePkColumnId, SelfRefTableFkColumnId], [SelfRefTablePkColumnId, SelfRefTableFkColumnId], ], - fk_path: [ + fkey_path: [ [2529, true], [2529, true], [2529, true], @@ -376,25 +376,25 @@ export const groupTable: JoinableTablesResult = { joinable_tables: [ { target: personTableId, - jp_path: [[groupTablePerson1ColumnId, personTablePkColumnId]], - fk_path: [[2765, false]], + join_path: [[groupTablePerson1ColumnId, personTablePkColumnId]], + fkey_path: [[2765, false]], depth: 1, multiple_results: false, }, { target: personTableId, - jp_path: [[groupTablePerson2ColumnId, personTablePkColumnId]], - fk_path: [[2801, false]], + join_path: [[groupTablePerson2ColumnId, personTablePkColumnId]], + fkey_path: [[2801, false]], depth: 1, multiple_results: false, }, { target: 1295, - jp_path: [ + join_path: [ [groupTablePerson1ColumnId, personTablePkColumnId], [personTablePkColumnId, groupTablePerson2ColumnId], ], - fk_path: [ + fkey_path: [ [2765, false], [2801, true], ], @@ -403,11 +403,11 @@ export const groupTable: JoinableTablesResult = { }, { target: 1295, - jp_path: [ + join_path: [ [groupTablePerson2ColumnId, personTablePkColumnId], [personTablePkColumnId, groupTablePerson1ColumnId], ], - fk_path: [ + fkey_path: [ [2801, false], [2765, true], ], @@ -416,12 +416,12 @@ export const groupTable: JoinableTablesResult = { }, { target: personTableId, - jp_path: [ + join_path: [ [groupTablePerson1ColumnId, personTablePkColumnId], [personTablePkColumnId, groupTablePerson2ColumnId], [groupTablePerson1ColumnId, personTablePkColumnId], ], - fk_path: [ + fkey_path: [ [2765, false], [2801, true], [2765, false], @@ -431,12 +431,12 @@ export const groupTable: JoinableTablesResult = { }, { target: personTableId, - jp_path: [ + join_path: [ [groupTablePerson2ColumnId, personTablePkColumnId], [personTablePkColumnId, groupTablePerson1ColumnId], [groupTablePerson2ColumnId, personTablePkColumnId], ], - fk_path: [ + fkey_path: [ [2801, false], [2765, true], [2801, false], 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..24ef474430 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,12 +8,12 @@ import type { QueryResultColumn, QueryRunResponse, } 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 { + JoinPath, JoinableTablesResult, - JpPath, -} from '@mathesar/api/rest/types/tables/joinable_tables'; -import type { Table } from '@mathesar/api/rpc/tables'; + Table, +} from '@mathesar/api/rpc/tables'; import type { CellColumnFabric } from '@mathesar/components/cell-fabric/types'; import { getCellCap, @@ -71,7 +74,7 @@ export interface InputColumn { id: Column['id']; name: Column['name']; tableName: Table['name']; - jpPath?: JpPath; + jpPath?: JoinPath; type: Column['type']; tableId: Table['oid']; } @@ -129,9 +132,9 @@ export function getLinkFromColumn( const allLinksFromColumn = result.joinable_tables.filter( (entry) => entry.depth === depth && - entry.fk_path[depth - 1][1] === false && - entry.jp_path[depth - 1][0] === columnId && - entry.jp_path.join(',').indexOf(parentPath) === 0, + entry.fkey_path[depth - 1][1] === false && + entry.join_path[depth - 1][0] === columnId && + entry.join_path.join(',').indexOf(parentPath) === 0, ); if (allLinksFromColumn.length === 0) { return undefined; @@ -146,7 +149,7 @@ export function getLinkFromColumn( id: link.target, name: toTableInfo.name, }; - const toColumnId = link.jp_path[depth - 1][1]; + const toColumnId = link.join_path[depth - 1][1]; const toColumn = { id: toColumnId, name: result.columns[toColumnId].name, @@ -165,9 +168,9 @@ export function getLinkFromColumn( result, columnIdInLinkedTable, depth + 1, - link.jp_path.join(','), + link.join_path.join(','), ), - jpPath: link.jp_path, + jpPath: link.join_path, producesMultipleResults: link.multiple_results, }, ]; @@ -245,15 +248,15 @@ export function getTablesThatReferenceBaseTable( baseTable: Pick, ): ReferencedByTable[] { const referenceLinks = result.joinable_tables.filter( - (entry) => entry.depth === 1 && entry.fk_path[0][1] === true, + (entry) => entry.depth === 1 && entry.fkey_path[0][1] === true, ); const references: ReferencedByTable[] = []; referenceLinks.forEach((reference) => { const tableId = reference.target; const table = result.tables[tableId]; - const baseTableColumnId = reference.jp_path[0][0]; - const referenceTableColumnId = reference.jp_path[0][1]; + const baseTableColumnId = reference.join_path[0][0]; + const referenceTableColumnId = reference.join_path[0][1]; // TODO_BETA: figure out how to deal with the fact that our `Table` type no // longer has a `columns` field. @@ -282,9 +285,9 @@ export function getTablesThatReferenceBaseTable( result, columnIdInTable, 2, - reference.jp_path.join(','), + reference.join_path.join(','), ), - jpPath: reference.jp_path, + jpPath: reference.join_path, producesMultipleResults: reference.multiple_results, }, ]; @@ -305,6 +308,10 @@ export function getTablesThatReferenceBaseTable( return references; } +// type T = SimplifyDeep & +// ProcessedQueryResultColumnSource & +// Partial>; + function processColumn( columnInfo: Pick & ProcessedQueryResultColumnSource & @@ -468,9 +475,15 @@ export function speculateColumnMetaData({ type_options: aggregation.function === 'distinct_aggregate_to_array' ? { - type: - updatedColumnsMetaData.get(aggregation.inputAlias) - ?.column.type ?? 'unknown', + // TODO_BETA: 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..12e15c77bc 100644 --- a/mathesar_ui/src/systems/record-selector/RecordSelectorContent.svelte +++ b/mathesar_ui/src/systems/record-selector/RecordSelectorContent.svelte @@ -2,8 +2,8 @@ import { _ } from 'svelte-i18n'; import { router } from 'tinro'; - import type { Response as ApiRecordsResponse } from '@mathesar/api/rest/types/tables/records'; import { States, postAPI } from '@mathesar/api/rest/utils/requestUtils'; + import type { RecordsResponse } from '@mathesar/api/rpc/records'; import { iconAddNew } from '@mathesar/icons'; import { storeToGetRecordPageUrl } from '@mathesar/stores/storeBasedUrls'; import type { TabularData } from '@mathesar/stores/table-data'; @@ -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,16 +72,15 @@ } 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; - const response = await postAPI(url, body); + const response = await postAPI(url, body); 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..947ef816a6 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 { Result as ApiRecord } from '@mathesar/api/rpc/records'; 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'} - +