mirror of https://github.com/nocodb/nocodb
Browse Source
* feat: created inferTypes.ts * chore: switch to sdk types * feat: convert string to given type * feat: convert string to rating * fix: handle the case with multiple . * feat: numeric decimal type conversion in postgres * feat: add cast for non text fields * refactor: move type casts to separate file * feat: add casts for date, date-time and time * doc: added function docs * feat: added cast for year and rating * feat: added cast for duration * fix: cast for multi-select * fix: cast for multi-select * fix: cast for year * feat: date conversion on best effort basis * fix: single line text to select * fix: any field to select * lint: simplified expressions * fix: user conversion * fix: user conversion * fix: date time conversion * test: added test cases for type casts * fix: SLT to User field * fix: SLT to Long text, single select and multiselect * chore: handle True/False & TRUE/FALSE in checkbox * lint: fixed eslint issues * chore: remove system fields as destination type when converting a field * feat: show warning when changing column type * fix: toned down edit modal * test: click on update button during warning popup * test: update selector * test: fix type change flag * fix: handle date format * chore: auto focus update button * fix: parameterize columnName and other values * chore: removed number of digits limit for hour * test: fix add-edit modal label * fix: fixed missing column reference * fix: handle missing date format * fix: handle missing date format * test: fix save routine mux * test: fix barCode & QRCode save * refactor: combined uiType filters * fix: sanitise column name * refactor: switch to some instead of find * feat: created inferTypes.ts * chore: switch to sdk types * feat: convert string to given type * feat: numeric decimal type conversion in postgres * feat: add cast for non text fields * refactor: move type casts to separate file * feat: add casts for date, date-time and time * doc: added function docs * feat: added cast for year and rating * feat: added cast for duration * fix: cast for multi-select * fix: cast for multi-select * fix: cast for year * feat: date conversion on best effort basis * fix: single line text to select * fix: user conversion * fix: date time conversion * fix: SLT to User field * fix: SLT to Long text, single select and multiselect * chore: handle True/False & TRUE/FALSE in checkbox * lint: fixed eslint issues * feat: show warning when changing column type * fix: toned down edit modal * test: click on update button during warning popup * fix: handle date format * chore: auto focus update button * fix: parameterize columnName and other values * chore: removed number of digits limit for hour * fix: handle missing date format * fix: handle missing date format * test: fix save routine mux * fix: revert removing verify * fix: sanitise column name * fix: pass context * tests: remove duplicate statement * fix: add context bypass for list method * fix: disable type conversion for Formula, BarCode, QrCode * fix: render confirm modal sing useDialog to avoid accidental closing * refactor: construct context using column while getting colOptions data --------- Co-authored-by: rohittp <tprohit9@gmail.com> Co-authored-by: Pranav C <pranavxc@gmail.com>pull/8706/head
Raju Udava
4 months ago
committed by
GitHub
18 changed files with 753 additions and 153 deletions
@ -0,0 +1,46 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
const props = defineProps<{ |
||||||
|
visible?: boolean |
||||||
|
saving?: boolean |
||||||
|
}>() |
||||||
|
|
||||||
|
const emit = defineEmits(['submit', 'cancel', 'update:visible']) |
||||||
|
|
||||||
|
const visible = useVModel(props, 'visible', emit) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<GeneralModal v-model:visible="visible" size="small"> |
||||||
|
<div class="flex flex-col p-6" @click.stop> |
||||||
|
<div class="flex flex-row pb-2 mb-4 font-medium text-lg border-b-1 border-gray-50 text-gray-800">Field Type Change</div> |
||||||
|
|
||||||
|
<div class="mb-3 text-gray-800"> |
||||||
|
<div class="flex item-center gap-2 mb-4"> |
||||||
|
<component :is="iconMap.warning" id="nc-selected-item-icon" class="text-yellow-500 w-10 h-10" /> |
||||||
|
This action cannot be undone. Converting data types may result in data loss. Proceed with caution! |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<slot name="entity-preview"></slot> |
||||||
|
|
||||||
|
<div class="flex flex-row gap-x-2 mt-2.5 pt-2.5 justify-end"> |
||||||
|
<NcButton type="secondary" @click="visible = false"> |
||||||
|
{{ $t('general.cancel') }} |
||||||
|
</NcButton> |
||||||
|
|
||||||
|
<NcButton |
||||||
|
key="submit" |
||||||
|
autofocus |
||||||
|
type="primary" |
||||||
|
html-type="submit" |
||||||
|
:loading="saving" |
||||||
|
data-testid="nc-delete-modal-delete-btn" |
||||||
|
@click="emit('submit')" |
||||||
|
> |
||||||
|
Update |
||||||
|
<template #loading> Saving... </template> |
||||||
|
</NcButton> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</GeneralModal> |
||||||
|
</template> |
@ -0,0 +1,72 @@ |
|||||||
|
import { UITypes } from 'nocodb-sdk' |
||||||
|
import { getCheckboxValue } from './parsers/parserHelpers' |
||||||
|
|
||||||
|
/* |
||||||
|
* @param {string} str - string with numbers |
||||||
|
* @returns {number} - number extracted from string |
||||||
|
* |
||||||
|
* @example abc123 -> 123 |
||||||
|
* @example 12.3abc -> 12.3 |
||||||
|
* @example 12.3.2 -> 12.32 |
||||||
|
*/ |
||||||
|
function extractNumbers(str: string): number { |
||||||
|
const parts = str.replace(/[^\d.]/g, '').split('.') |
||||||
|
if (parts.length > 1) parts[0] += '.' |
||||||
|
|
||||||
|
return parseFloat(parts.join('')) |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* @param {string} value - string value of duration |
||||||
|
* @returns {number} - duration in seconds |
||||||
|
* |
||||||
|
* @example 1:30:00 -> 5400 |
||||||
|
* @example 1:30 -> 5400 |
||||||
|
* @example 90 -> 90 |
||||||
|
*/ |
||||||
|
function toDuration(value: string) { |
||||||
|
if (value.includes(':')) { |
||||||
|
const [hours, minutes, seconds] = value.split(':').map((v) => parseInt(v) || 0) |
||||||
|
return hours * 3600 + minutes * 60 + seconds |
||||||
|
} |
||||||
|
|
||||||
|
return Math.floor(extractNumbers(value)) |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* @param {string} value - string value to convert |
||||||
|
* @param {string} type - type of the field to convert to |
||||||
|
* @returns {number|string|boolean|array} - converted value |
||||||
|
* |
||||||
|
* @example convert('12.3', 'Number') -> 12 |
||||||
|
* @example convert('1a23', 'SingleLineText') -> '1a23' |
||||||
|
* @example convert('1', 'Checkbox') -> true |
||||||
|
*/ |
||||||
|
export function convert(value: string, type: string, limit = 100): unknown { |
||||||
|
switch (type) { |
||||||
|
case UITypes.SingleLineText: |
||||||
|
case UITypes.SingleSelect: |
||||||
|
case UITypes.LongText: |
||||||
|
case UITypes.Email: |
||||||
|
case UITypes.URL: |
||||||
|
return value |
||||||
|
case UITypes.Number: |
||||||
|
return Math.floor(extractNumbers(value)) |
||||||
|
case UITypes.Decimal: |
||||||
|
case UITypes.Currency: |
||||||
|
return extractNumbers(value) |
||||||
|
case UITypes.Percent: |
||||||
|
case UITypes.Rating: |
||||||
|
return Math.min(limit, Math.max(0, extractNumbers(value))) |
||||||
|
case UITypes.Checkbox: |
||||||
|
return getCheckboxValue(value) |
||||||
|
case UITypes.Date: |
||||||
|
case UITypes.DateTime: |
||||||
|
case UITypes.Time: |
||||||
|
return new Date(value) |
||||||
|
case UITypes.Duration: |
||||||
|
return toDuration(value) |
||||||
|
case UITypes.MultiSelect: |
||||||
|
return value.split(',').map((v) => v.trim()) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,79 @@ |
|||||||
|
const MONTHS = [ |
||||||
|
'January', |
||||||
|
'February', |
||||||
|
'March', |
||||||
|
'April', |
||||||
|
'May', |
||||||
|
'June', |
||||||
|
'July', |
||||||
|
'August', |
||||||
|
'September', |
||||||
|
'October', |
||||||
|
'November', |
||||||
|
'December', |
||||||
|
]; |
||||||
|
const MONTHS_SHORT = MONTHS.map((m) => m.slice(0, 3)); |
||||||
|
const MONTHS_SHORT_LOWER = MONTHS_SHORT.map((m) => m.toLowerCase()); |
||||||
|
const MONTHS_SHORT_UPPER = MONTHS_SHORT.map((m) => m.toUpperCase()); |
||||||
|
|
||||||
|
const MONTHS_LOWER = MONTHS.map((m) => m.toLowerCase()); |
||||||
|
const MONTHS_UPPER = MONTHS.map((m) => m.toUpperCase()); |
||||||
|
|
||||||
|
const MONTH_FORMATS = { |
||||||
|
MM: '[0-9]{1,2}', |
||||||
|
Mon: '(' + MONTHS_SHORT.join('|') + ')', |
||||||
|
MON: '(' + MONTHS_SHORT_UPPER.join('|') + ')', |
||||||
|
mon: '(' + MONTHS_SHORT_LOWER.join('|') + ')', |
||||||
|
Month: '(' + MONTHS.join('|') + ')', |
||||||
|
MONTH: '(' + MONTHS_UPPER.join('|') + ')', |
||||||
|
month: '(' + MONTHS_LOWER.join('|') + ')', |
||||||
|
}; |
||||||
|
/* |
||||||
|
* Map of date formats to their respective regex patterns. |
||||||
|
*/ |
||||||
|
export const DATE_FORMATS = { |
||||||
|
ymd: Object.keys(MONTH_FORMATS).map((format) => [ |
||||||
|
`Y-${format}-DD`, |
||||||
|
`^\\d{1,4}[:\\- /]+${MONTH_FORMATS[format]}[:\\- /]+\\d{1,2}$`, |
||||||
|
]), |
||||||
|
dmy: Object.keys(MONTH_FORMATS).map((format) => [ |
||||||
|
`DD-${format}-Y`, |
||||||
|
`^\\d{1,2}[:\\- /]+${MONTH_FORMATS[format]}[:\\- /]+\\d{1,4}$`, |
||||||
|
]), |
||||||
|
mdy: Object.keys(MONTH_FORMATS).map((format) => [ |
||||||
|
`${format}-DD-Y`, |
||||||
|
`^${MONTH_FORMATS[format]}[:\\- /]+\\d{1,2}[:\\- /]+\\d{1,4}$`, |
||||||
|
]), |
||||||
|
empty: [['', '^.*$']], |
||||||
|
}; |
||||||
|
|
||||||
|
/* |
||||||
|
* Map of date time formats to their respective regex patterns. |
||||||
|
*/ |
||||||
|
export const TIME_FORMATS = [ |
||||||
|
[ |
||||||
|
'HH24:MI:SS:MS:US', |
||||||
|
'^\\d{1,2}[:\\- /]+\\d{1,2}[:\\- /]+\\d{1,2}[:\\- /]+\\d{1,3}[:\\- /]+\\d*$', |
||||||
|
], |
||||||
|
[ |
||||||
|
'HH24:MI:SS:MS', |
||||||
|
'^\\d{1,2}[:\\- /]+\\d{1,2}[:\\- /]+\\d{1,2}[:\\- /]+\\d{1,3}$', |
||||||
|
], |
||||||
|
['HH24:MI:SS', '^\\d{1,2}[:\\- /]+\\d{1,2}[:\\- /]+\\d{1,2}$'], |
||||||
|
['HH24:MI', '^\\d{1,2}[:\\- /]+\\d{1,2}$'], |
||||||
|
['HH24', '^\\d{1,2}$'], |
||||||
|
[ |
||||||
|
'HH12:MI:SS:MS:US (AM|PM)', |
||||||
|
'^\\d{1,2}[:\\- /]+\\d{1,2}[:\\- /]+\\d{1,2}[:\\- /]+\\d{1,3}[:\\- /]+\\d* (AM|PM)$', |
||||||
|
], |
||||||
|
[ |
||||||
|
'HH12:MI:SS:MS (AM|PM)', |
||||||
|
'^\\d{1,2}[:\\- /]+\\d{1,2}[:\\- /]+\\d{1,2}[:\\- /]+\\d{1,3} (AM|PM)$', |
||||||
|
], |
||||||
|
[ |
||||||
|
'HH12:MI:SS (AM|PM)', |
||||||
|
'^\\d{1,2}[:\\- /]+\\d{1,2}[:\\- /]+\\d{1,2} (AM|PM)$', |
||||||
|
], |
||||||
|
['HH12:MI (AM|PM)', '^\\d{1,2}[:\\- /]+\\d{1,2} (AM|PM)$'], |
||||||
|
['HH12 (AM|PM)', '^\\d{1,2} (AM|PM)$'], |
||||||
|
]; |
@ -0,0 +1,227 @@ |
|||||||
|
import { UITypes } from 'nocodb-sdk'; |
||||||
|
import { DATE_FORMATS, TIME_FORMATS } from '~/db/sql-client/lib/pg/constants'; |
||||||
|
|
||||||
|
/* |
||||||
|
* Generate query to extract number from a string. The number is extracted by |
||||||
|
* removing all non-numeric characters from the string. Decimal point is allowed. |
||||||
|
* If there are more than one decimal points, only the first one is considered, the rest are ignored. |
||||||
|
* |
||||||
|
* @param {String} source - source column name |
||||||
|
* @returns {String} - query to extract number from a string |
||||||
|
*/ |
||||||
|
function extractNumberQuery(source: string) { |
||||||
|
return ` |
||||||
|
CAST( |
||||||
|
NULLIF( |
||||||
|
REPLACE( |
||||||
|
REPLACE( |
||||||
|
REGEXP_REPLACE( |
||||||
|
REGEXP_REPLACE(${source}, '[^0-9.]', '', 'g'),
|
||||||
|
'(\\d)\\.', '\\1-' |
||||||
|
),
|
||||||
|
'.', '' |
||||||
|
),
|
||||||
|
'-', '.' |
||||||
|
), '' |
||||||
|
) AS DECIMAL |
||||||
|
) |
||||||
|
`;
|
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* Generate query to cast a value to boolean. The boolean value is determined based on the given mappings. |
||||||
|
* |
||||||
|
* @param {String} columnName - Source column name |
||||||
|
* @returns {String} - query to cast value to boolean |
||||||
|
*/ |
||||||
|
function generateBooleanCastQuery(columnName: string): string { |
||||||
|
return ` |
||||||
|
CASE |
||||||
|
WHEN LOWER(${columnName}) IN ('checked', 'x', 'yes', 'y', '1', '[x]', '☑', '✅', '✓', '✔', 'enabled', 'on', 'done', 'true') THEN true |
||||||
|
WHEN LOWER(${columnName}) IN ('unchecked', '', 'no', 'n', '0', '[]', '[ ]', 'disabled', 'off', 'false') THEN false |
||||||
|
ELSE null |
||||||
|
END; |
||||||
|
`;
|
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* Generate query to cast a value to date time based on the given date and time formats. |
||||||
|
* |
||||||
|
* @param {String} source - Source column name |
||||||
|
* @param {String} dateFormat - Date format |
||||||
|
* @param {String} timeFormat - Time format |
||||||
|
* @param {String} functionName - Function name to cast value to date time |
||||||
|
* @returns {String} - query to cast value to date time |
||||||
|
*/ |
||||||
|
function generateDateTimeCastQuery(source: string, dateFormat: string) { |
||||||
|
if (!(dateFormat in DATE_FORMATS)) { |
||||||
|
throw new Error(`Invalid date format: ${dateFormat}`); |
||||||
|
} |
||||||
|
|
||||||
|
const timeFormats = |
||||||
|
dateFormat === 'empty' ? TIME_FORMATS : [...TIME_FORMATS, ['', '^$']]; |
||||||
|
|
||||||
|
const cases = DATE_FORMATS[dateFormat].map(([format, regex]) => |
||||||
|
timeFormats |
||||||
|
.map( |
||||||
|
([timeFormat, timeRegex]) => |
||||||
|
`WHEN ${source} ~ '${regex.slice(0, -1)}\\s*${timeRegex.slice( |
||||||
|
1, |
||||||
|
)}' THEN to_date_time_safe(${source}, '${format} ${timeFormat}')`,
|
||||||
|
) |
||||||
|
.join('\n'), |
||||||
|
); |
||||||
|
|
||||||
|
return `CASE
|
||||||
|
${cases.join('\n')} |
||||||
|
ELSE NULL |
||||||
|
END;`;
|
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* Generate SQL query to extract a number from a string and make out-of-bounds values NULL. |
||||||
|
* |
||||||
|
* @param {String} source - Source column name. |
||||||
|
* @param {Number} minValue - Minimum allowed value. |
||||||
|
* @param {Number} maxValue - Maximum allowed value. |
||||||
|
* @returns {String} - SQL query to extract number and handle out-of-bounds values. |
||||||
|
*/ |
||||||
|
function generateNumberBoundingQuery( |
||||||
|
source: string, |
||||||
|
minValue: number, |
||||||
|
maxValue: number, |
||||||
|
) { |
||||||
|
return ` |
||||||
|
NULLIF( |
||||||
|
NULLIF( |
||||||
|
LEAST( |
||||||
|
${maxValue + 1}, GREATEST(${minValue - 1}, ${source}) |
||||||
|
), ${minValue - 1} |
||||||
|
), ${maxValue + 1} |
||||||
|
); |
||||||
|
`;
|
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* Generate query to cast a value to duration. |
||||||
|
* |
||||||
|
* @param {String} source - Source column name |
||||||
|
* @returns {String} - query to cast value to duration |
||||||
|
*/ |
||||||
|
function generateToDurationQuery(source: string) { |
||||||
|
return ` |
||||||
|
CASE |
||||||
|
WHEN ${source} ~ '^\\d+:\\d{1,2}$' THEN 60 * CAST(SPLIT_PART(${source}, ':', 1) AS INT) + CAST(SPLIT_PART(${source}, ':', 2) AS INT) |
||||||
|
ELSE ${extractNumberQuery(source)} |
||||||
|
END; |
||||||
|
`;
|
||||||
|
} |
||||||
|
|
||||||
|
function getDateFormat(format: string) { |
||||||
|
const y = format.indexOf('Y'); |
||||||
|
const m = format.indexOf('M'); |
||||||
|
const d = format.indexOf('D'); |
||||||
|
|
||||||
|
if (y < m) { |
||||||
|
if (m < d) return 'ymd'; |
||||||
|
else if (y < d) return 'ydm'; |
||||||
|
else return 'dym'; |
||||||
|
} else if (y < d) return 'myd'; |
||||||
|
else if (m < d) return 'mdy'; |
||||||
|
else return 'dmy'; |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* Generate query to cast a column to a specific data type based on the UI data type. |
||||||
|
* |
||||||
|
* @param {UITypes} uidt - UI data type |
||||||
|
* @param {String} dt - DB Data type |
||||||
|
* @param {String} source - Source column name |
||||||
|
* @param {Number} limit - Limit for the data type |
||||||
|
* @param {String} dateFormat - Date format |
||||||
|
* @param {String} timeFormat - Time format |
||||||
|
* @returns {String} - query to cast column to a specific data type |
||||||
|
*/ |
||||||
|
export function generateCastQuery( |
||||||
|
uidt: UITypes, |
||||||
|
dt: string, |
||||||
|
source: string, |
||||||
|
limit: number, |
||||||
|
format: string, |
||||||
|
) { |
||||||
|
switch (uidt) { |
||||||
|
case UITypes.SingleLineText: |
||||||
|
case UITypes.MultiSelect: |
||||||
|
case UITypes.SingleSelect: |
||||||
|
case UITypes.Email: |
||||||
|
case UITypes.PhoneNumber: |
||||||
|
case UITypes.URL: |
||||||
|
return `${source}::VARCHAR(${limit || 255});`; |
||||||
|
case UITypes.LongText: |
||||||
|
return `${source}::TEXT;`; |
||||||
|
case UITypes.Number: |
||||||
|
return `CAST(${extractNumberQuery(source)} AS BIGINT);`; |
||||||
|
case UITypes.Year: |
||||||
|
return generateNumberBoundingQuery( |
||||||
|
extractNumberQuery(source), |
||||||
|
1000, |
||||||
|
9999, |
||||||
|
); |
||||||
|
case UITypes.Decimal: |
||||||
|
case UITypes.Currency: |
||||||
|
return `${extractNumberQuery(source)};`; |
||||||
|
case UITypes.Percent: |
||||||
|
return `LEAST(100, GREATEST(0, ${extractNumberQuery(source)}));`; |
||||||
|
case UITypes.Rating: |
||||||
|
return `LEAST(${limit || 5}, GREATEST(0, ${extractNumberQuery( |
||||||
|
source, |
||||||
|
)}));`;
|
||||||
|
case UITypes.Checkbox: |
||||||
|
return generateBooleanCastQuery(source); |
||||||
|
case UITypes.Date: |
||||||
|
return `CAST(${generateDateTimeCastQuery( |
||||||
|
source, |
||||||
|
getDateFormat(format), |
||||||
|
).slice(0, -1)} AS DATE);`;
|
||||||
|
case UITypes.DateTime: |
||||||
|
return generateDateTimeCastQuery(source, getDateFormat(format)); |
||||||
|
case UITypes.Time: |
||||||
|
return generateDateTimeCastQuery(source, 'empty'); |
||||||
|
case UITypes.Duration: |
||||||
|
return generateToDurationQuery(source); |
||||||
|
default: |
||||||
|
return `null::${dt};`; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* Generate query to format a column based on the UI data type. |
||||||
|
* |
||||||
|
* @param {String} columnName - Column name |
||||||
|
* @param {UITypes} uiDataType - UI data type |
||||||
|
* @returns {String} - query to format a column |
||||||
|
*/ |
||||||
|
export function formatColumn(columnName: string, uiDataType: UITypes) { |
||||||
|
switch (uiDataType) { |
||||||
|
case UITypes.LongText: |
||||||
|
case UITypes.SingleLineText: |
||||||
|
case UITypes.MultiSelect: |
||||||
|
case UITypes.Email: |
||||||
|
case UITypes.URL: |
||||||
|
case UITypes.SingleSelect: |
||||||
|
case UITypes.PhoneNumber: |
||||||
|
return columnName; |
||||||
|
case UITypes.Number: |
||||||
|
case UITypes.Decimal: |
||||||
|
case UITypes.Currency: |
||||||
|
case UITypes.Percent: |
||||||
|
case UITypes.Rating: |
||||||
|
case UITypes.Duration: |
||||||
|
case UITypes.Year: |
||||||
|
return `CAST(${columnName} AS VARCHAR(255))`; |
||||||
|
case UITypes.Checkbox: |
||||||
|
return `CAST(CASE WHEN ${columnName} THEN '1' ELSE '0' END AS TEXT)`; |
||||||
|
default: |
||||||
|
return `CAST(${columnName} AS TEXT)`; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,133 @@ |
|||||||
|
import 'mocha'; |
||||||
|
import { UITypes } from 'nocodb-sdk'; |
||||||
|
import { expect } from 'chai'; |
||||||
|
import init from '../../init'; |
||||||
|
import { createProject } from '../../factory/base'; |
||||||
|
import { createTable } from '../../factory/table'; |
||||||
|
import { createBulkRows, rowMixedValue } from '../../factory/row'; |
||||||
|
import { updateColumn } from '../../factory/column'; |
||||||
|
import type Model from '../../../../src/models/Model'; |
||||||
|
import type Base from '../../../../src/models/Base'; |
||||||
|
import type Column from '../../../../src/models/Column'; |
||||||
|
|
||||||
|
const TEST_TYPES = [ |
||||||
|
UITypes.LongText, |
||||||
|
UITypes.Attachment, |
||||||
|
UITypes.Checkbox, |
||||||
|
UITypes.MultiSelect, |
||||||
|
UITypes.SingleSelect, |
||||||
|
UITypes.Date, |
||||||
|
UITypes.Year, |
||||||
|
UITypes.Time, |
||||||
|
UITypes.PhoneNumber, |
||||||
|
UITypes.GeoData, |
||||||
|
UITypes.Email, |
||||||
|
UITypes.URL, |
||||||
|
UITypes.Number, |
||||||
|
UITypes.Decimal, |
||||||
|
UITypes.Currency, |
||||||
|
UITypes.Percent, |
||||||
|
UITypes.Duration, |
||||||
|
UITypes.Rating, |
||||||
|
UITypes.Count, |
||||||
|
UITypes.DateTime, |
||||||
|
UITypes.Geometry, |
||||||
|
UITypes.JSON, |
||||||
|
UITypes.User, |
||||||
|
]; |
||||||
|
|
||||||
|
async function setup(context, base: Base, type: UITypes) { |
||||||
|
const table = await createTable(context, base, { |
||||||
|
table_name: 'sampleTable', |
||||||
|
title: 'sampleTable', |
||||||
|
columns: [ |
||||||
|
{ |
||||||
|
column_name: 'Test', |
||||||
|
title: 'Test', |
||||||
|
uidt: type, |
||||||
|
}, |
||||||
|
], |
||||||
|
}); |
||||||
|
|
||||||
|
const column = (await table.getColumns({ |
||||||
|
workspace_id: base.fk_workspace_id, |
||||||
|
base_id: base.id, |
||||||
|
}))[0]; |
||||||
|
|
||||||
|
const rowAttributes = []; |
||||||
|
for (let i = 0; i < 100; i++) { |
||||||
|
const row = { |
||||||
|
Title: rowMixedValue(column, i), |
||||||
|
}; |
||||||
|
rowAttributes.push(row); |
||||||
|
} |
||||||
|
|
||||||
|
await createBulkRows(context, { |
||||||
|
base, |
||||||
|
table, |
||||||
|
values: rowAttributes, |
||||||
|
}); |
||||||
|
|
||||||
|
return { table, column }; |
||||||
|
} |
||||||
|
|
||||||
|
function typeCastTests() { |
||||||
|
let context; |
||||||
|
let base: Base; |
||||||
|
|
||||||
|
beforeEach(async function () { |
||||||
|
context = await init(); |
||||||
|
base = await createProject(context); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('Single Line Text to ', () => { |
||||||
|
let table: Model; |
||||||
|
let column: Column; |
||||||
|
|
||||||
|
beforeEach(async function () { |
||||||
|
const data = await setup(context, base, UITypes.SingleLineText); |
||||||
|
table = data.table; |
||||||
|
column = data.column; |
||||||
|
}); |
||||||
|
|
||||||
|
for (const type of TEST_TYPES) |
||||||
|
it(type, async () => { |
||||||
|
if (context.dbConfig.client !== 'pg') return; |
||||||
|
|
||||||
|
const updatedColumn = await updateColumn(context, { |
||||||
|
table, |
||||||
|
column: column, |
||||||
|
attr: { |
||||||
|
...column, |
||||||
|
uidt: type, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
expect(updatedColumn.uidt).to.equal(type); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('Convert to Single Line Text from ', () => { |
||||||
|
for (const type of TEST_TYPES) |
||||||
|
it(type, async () => { |
||||||
|
if (context.dbConfig.client !== 'pg') return; |
||||||
|
|
||||||
|
const data = await setup(context, base, type); |
||||||
|
|
||||||
|
const updatedColumn = await updateColumn(context, { |
||||||
|
table: data.table, |
||||||
|
column: data.column, |
||||||
|
attr: { |
||||||
|
...data.column, |
||||||
|
uidt: UITypes.SingleLineText, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
expect(updatedColumn.uidt).to.equal(UITypes.SingleLineText); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
export default function () { |
||||||
|
describe('Field type conversion ', typeCastTests); |
||||||
|
} |
Loading…
Reference in new issue