mirror of https://github.com/nocodb/nocodb
Wing-Kam Wong
2 years ago
20 changed files with 759 additions and 11 deletions
@ -0,0 +1,38 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import { dateFormats, timeFormats, useVModel } from '#imports' |
||||||
|
|
||||||
|
const props = defineProps<{ |
||||||
|
value: any |
||||||
|
}>() |
||||||
|
|
||||||
|
const emit = defineEmits(['update:value']) |
||||||
|
|
||||||
|
const vModel = useVModel(props, 'value', emit) |
||||||
|
|
||||||
|
if (!vModel.value.meta?.date_format) { |
||||||
|
if (!vModel.value.meta) vModel.value.meta = {} |
||||||
|
vModel.value.meta.date_format = dateFormats[0] |
||||||
|
} |
||||||
|
|
||||||
|
if (!vModel.value.meta?.time_format) { |
||||||
|
if (!vModel.value.meta) vModel.value.meta = {} |
||||||
|
vModel.value.meta.time_format = timeFormats[0] |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<a-form-item label="Date Format"> |
||||||
|
<a-select v-model:value="vModel.meta.date_format" class="nc-date-select" dropdown-class-name="nc-dropdown-date-format"> |
||||||
|
<a-select-option v-for="(format, i) of dateFormats" :key="i" :value="format"> |
||||||
|
{{ format }} |
||||||
|
</a-select-option> |
||||||
|
</a-select> |
||||||
|
</a-form-item> |
||||||
|
<a-form-item label="Time Format"> |
||||||
|
<a-select v-model:value="vModel.meta.time_format" class="nc-time-select" dropdown-class-name="nc-dropdown-time-format"> |
||||||
|
<a-select-option v-for="(format, i) of timeFormats" :key="i" :value="format"> |
||||||
|
{{ format }} |
||||||
|
</a-select-option> |
||||||
|
</a-select> |
||||||
|
</a-form-item> |
||||||
|
</template> |
@ -0,0 +1,137 @@ |
|||||||
|
export function convertUnits( |
||||||
|
unit: string, |
||||||
|
type: 'mysql' | 'mssql' | 'pg' | 'sqlite' |
||||||
|
) { |
||||||
|
switch (unit) { |
||||||
|
case 'milliseconds': |
||||||
|
case 'ms': { |
||||||
|
switch (type) { |
||||||
|
case 'mssql': |
||||||
|
return 'millisecond'; |
||||||
|
case 'mysql': |
||||||
|
// MySQL doesn't support millisecond
|
||||||
|
// hence change from MICROSECOND to millisecond manually
|
||||||
|
return 'MICROSECOND'; |
||||||
|
case 'pg': |
||||||
|
case 'sqlite': |
||||||
|
return 'milliseconds'; |
||||||
|
default: |
||||||
|
return unit; |
||||||
|
} |
||||||
|
} |
||||||
|
case 'seconds': |
||||||
|
case 's': { |
||||||
|
switch (type) { |
||||||
|
case 'mssql': |
||||||
|
case 'pg': |
||||||
|
return 'second'; |
||||||
|
case 'mysql': |
||||||
|
return 'SECOND'; |
||||||
|
case 'sqlite': |
||||||
|
return 'seconds'; |
||||||
|
default: |
||||||
|
return unit; |
||||||
|
} |
||||||
|
} |
||||||
|
case 'minutes': |
||||||
|
case 'm': { |
||||||
|
switch (type) { |
||||||
|
case 'mssql': |
||||||
|
case 'pg': |
||||||
|
return 'minute'; |
||||||
|
case 'mysql': |
||||||
|
return 'MINUTE'; |
||||||
|
case 'sqlite': |
||||||
|
return 'minutes'; |
||||||
|
default: |
||||||
|
return unit; |
||||||
|
} |
||||||
|
} |
||||||
|
case 'hours': |
||||||
|
case 'h': { |
||||||
|
switch (type) { |
||||||
|
case 'mssql': |
||||||
|
case 'pg': |
||||||
|
return 'hour'; |
||||||
|
case 'mysql': |
||||||
|
return 'HOUR'; |
||||||
|
case 'sqlite': |
||||||
|
return 'hours'; |
||||||
|
default: |
||||||
|
return unit; |
||||||
|
} |
||||||
|
} |
||||||
|
case 'days': |
||||||
|
case 'd': { |
||||||
|
switch (type) { |
||||||
|
case 'mssql': |
||||||
|
case 'pg': |
||||||
|
return 'day'; |
||||||
|
case 'mysql': |
||||||
|
return 'DAY'; |
||||||
|
case 'sqlite': |
||||||
|
return 'days'; |
||||||
|
default: |
||||||
|
return unit; |
||||||
|
} |
||||||
|
} |
||||||
|
case 'weeks': |
||||||
|
case 'w': { |
||||||
|
switch (type) { |
||||||
|
case 'mssql': |
||||||
|
case 'pg': |
||||||
|
return 'week'; |
||||||
|
case 'mysql': |
||||||
|
return 'WEEK'; |
||||||
|
case 'sqlite': |
||||||
|
return 'weeks'; |
||||||
|
default: |
||||||
|
return unit; |
||||||
|
} |
||||||
|
} |
||||||
|
case 'months': |
||||||
|
case 'M': { |
||||||
|
switch (type) { |
||||||
|
case 'mssql': |
||||||
|
case 'pg': |
||||||
|
return 'month'; |
||||||
|
case 'mysql': |
||||||
|
return 'MONTH'; |
||||||
|
case 'sqlite': |
||||||
|
return 'months'; |
||||||
|
default: |
||||||
|
return unit; |
||||||
|
} |
||||||
|
} |
||||||
|
case 'quarters': |
||||||
|
case 'Q': { |
||||||
|
switch (type) { |
||||||
|
case 'mssql': |
||||||
|
case 'pg': |
||||||
|
return 'quarter'; |
||||||
|
case 'mysql': |
||||||
|
return 'QUARTER'; |
||||||
|
case 'sqlite': |
||||||
|
return 'quarters'; |
||||||
|
default: |
||||||
|
return unit; |
||||||
|
} |
||||||
|
} |
||||||
|
case 'years': |
||||||
|
case 'y': { |
||||||
|
switch (type) { |
||||||
|
case 'mssql': |
||||||
|
case 'pg': |
||||||
|
return 'year'; |
||||||
|
case 'mysql': |
||||||
|
return 'YEAR'; |
||||||
|
case 'sqlite': |
||||||
|
return 'years'; |
||||||
|
default: |
||||||
|
return unit; |
||||||
|
} |
||||||
|
} |
||||||
|
default: |
||||||
|
return unit; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,75 @@ |
|||||||
|
import dayjs from 'dayjs'; |
||||||
|
|
||||||
|
export const dateFormats = [ |
||||||
|
'DD-MM-YYYY', |
||||||
|
'MM-DD-YYYY', |
||||||
|
'YYYY-MM-DD', |
||||||
|
'DD/MM/YYYY', |
||||||
|
'MM/DD/YYYY', |
||||||
|
'YYYY/MM/DD', |
||||||
|
'DD MM YYYY', |
||||||
|
'MM DD YYYY', |
||||||
|
'YYYY MM DD', |
||||||
|
]; |
||||||
|
|
||||||
|
export function validateDateFormat(v: string) { |
||||||
|
return dateFormats.includes(v); |
||||||
|
} |
||||||
|
|
||||||
|
export function validateDateWithUnknownFormat(v: string) { |
||||||
|
for (const format of dateFormats) { |
||||||
|
if (dayjs(v, format, true).isValid() as any) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
for (const timeFormat of ['HH:mm', 'HH:mm:ss', 'HH:mm:ss.SSS']) { |
||||||
|
if (dayjs(v, `${format} ${timeFormat}`, true).isValid() as any) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
export function getDateFormat(v: string) { |
||||||
|
for (const format of dateFormats) { |
||||||
|
if (dayjs(v, format, true).isValid()) { |
||||||
|
return format; |
||||||
|
} |
||||||
|
} |
||||||
|
return 'YYYY/MM/DD'; |
||||||
|
} |
||||||
|
|
||||||
|
export function getDateTimeFormat(v: string) { |
||||||
|
for (const format of dateFormats) { |
||||||
|
for (const timeFormat of ['HH:mm', 'HH:mm:ss', 'HH:mm:ss.SSS']) { |
||||||
|
const dateTimeFormat = `${format} ${timeFormat}`; |
||||||
|
if (dayjs(v, dateTimeFormat, true).isValid() as any) { |
||||||
|
return dateTimeFormat; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return 'YYYY/MM/DD'; |
||||||
|
} |
||||||
|
|
||||||
|
export function parseStringDate(v: string, dateFormat: string) { |
||||||
|
const dayjsObj = dayjs(v); |
||||||
|
if (dayjsObj.isValid()) { |
||||||
|
v = dayjsObj.format('YYYY-MM-DD'); |
||||||
|
} else { |
||||||
|
v = dayjs(v, dateFormat).format('YYYY-MM-DD'); |
||||||
|
} |
||||||
|
return v; |
||||||
|
} |
||||||
|
|
||||||
|
export function convertToTargetFormat( |
||||||
|
v: string, |
||||||
|
oldDataFormat, |
||||||
|
newDateFormat: string |
||||||
|
) { |
||||||
|
if ( |
||||||
|
!dateFormats.includes(oldDataFormat) || |
||||||
|
!dateFormats.includes(newDateFormat) |
||||||
|
) |
||||||
|
return v; |
||||||
|
return dayjs(v, oldDataFormat).format(newDateFormat); |
||||||
|
} |
@ -0,0 +1,67 @@ |
|||||||
|
import { CellPageObject } from '.'; |
||||||
|
import BasePage from '../../../Base'; |
||||||
|
|
||||||
|
export class DateTimeCellPageObject extends BasePage { |
||||||
|
readonly cell: CellPageObject; |
||||||
|
|
||||||
|
constructor(cell: CellPageObject) { |
||||||
|
super(cell.rootPage); |
||||||
|
this.cell = cell; |
||||||
|
} |
||||||
|
|
||||||
|
get({ index, columnHeader }: { index?: number; columnHeader: string }) { |
||||||
|
return this.cell.get({ index, columnHeader }); |
||||||
|
} |
||||||
|
|
||||||
|
async open({ index, columnHeader }: { index: number; columnHeader: string }) { |
||||||
|
await this.rootPage.locator('.nc-grid-add-new-cell').click(); |
||||||
|
|
||||||
|
await this.cell.dblclick({ |
||||||
|
index, |
||||||
|
columnHeader, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
async save() { |
||||||
|
await this.rootPage.locator('button:has-text("Ok")').click(); |
||||||
|
} |
||||||
|
|
||||||
|
async selectDate({ |
||||||
|
// date formats in `YYYY-MM-DD`
|
||||||
|
date, |
||||||
|
}: { |
||||||
|
date: string; |
||||||
|
}) { |
||||||
|
// title date format needs to be YYYY-MM-DD
|
||||||
|
await this.rootPage.locator(`td[title="${date}"]`).click(); |
||||||
|
} |
||||||
|
|
||||||
|
async selectTime({ |
||||||
|
// hour: 0 - 23
|
||||||
|
// minute: 0 - 59
|
||||||
|
// second: 0 - 59
|
||||||
|
hour, |
||||||
|
minute, |
||||||
|
second, |
||||||
|
}: { |
||||||
|
hour: number; |
||||||
|
minute: number; |
||||||
|
second?: number | null; |
||||||
|
}) { |
||||||
|
await this.rootPage |
||||||
|
.locator(`.ant-picker-time-panel-column:nth-child(1) > .ant-picker-time-panel-cell:nth-child(${hour + 1})`) |
||||||
|
.click(); |
||||||
|
await this.rootPage |
||||||
|
.locator(`.ant-picker-time-panel-column:nth-child(2) > .ant-picker-time-panel-cell:nth-child(${minute + 1})`) |
||||||
|
.click(); |
||||||
|
if (second != null) { |
||||||
|
await this.rootPage |
||||||
|
.locator(`.ant-picker-time-panel-column:nth-child(3) > .ant-picker-time-panel-cell:nth-child(${second + 1})`) |
||||||
|
.click(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async close() { |
||||||
|
await this.rootPage.keyboard.press('Escape'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,113 @@ |
|||||||
|
import { test } from '@playwright/test'; |
||||||
|
import { DashboardPage } from '../pages/Dashboard'; |
||||||
|
import setup from '../setup'; |
||||||
|
|
||||||
|
const dateTimeData = [ |
||||||
|
{ |
||||||
|
dateFormat: 'YYYY-MM-DD', |
||||||
|
timeFormat: 'HH:mm', |
||||||
|
date: '2022-12-12', |
||||||
|
hour: 10, |
||||||
|
minute: 20, |
||||||
|
output: '2022-12-12 10:20', |
||||||
|
}, |
||||||
|
{ |
||||||
|
dateFormat: 'YYYY-MM-DD', |
||||||
|
timeFormat: 'HH:mm:ss', |
||||||
|
date: '2022-12-11', |
||||||
|
hour: 20, |
||||||
|
minute: 30, |
||||||
|
second: 40, |
||||||
|
output: '2022-12-11 20:30:40', |
||||||
|
}, |
||||||
|
{ |
||||||
|
dateFormat: 'YYYY/MM/DD', |
||||||
|
timeFormat: 'HH:mm', |
||||||
|
date: '2022-12-13', |
||||||
|
hour: 10, |
||||||
|
minute: 20, |
||||||
|
output: '2022/12/13 10:20', |
||||||
|
}, |
||||||
|
{ |
||||||
|
dateFormat: 'YYYY/MM/DD', |
||||||
|
timeFormat: 'HH:mm:ss', |
||||||
|
date: '2022-12-14', |
||||||
|
hour: 5, |
||||||
|
minute: 30, |
||||||
|
second: 40, |
||||||
|
output: '2022/12/14 05:30:40', |
||||||
|
}, |
||||||
|
{ |
||||||
|
dateFormat: 'DD-MM-YYYY', |
||||||
|
timeFormat: 'HH:mm', |
||||||
|
date: '2022-12-10', |
||||||
|
hour: 4, |
||||||
|
minute: 30, |
||||||
|
output: '10-12-2022 04:30', |
||||||
|
}, |
||||||
|
{ |
||||||
|
dateFormat: 'DD-MM-YYYY', |
||||||
|
timeFormat: 'HH:mm:ss', |
||||||
|
date: '2022-12-26', |
||||||
|
hour: 2, |
||||||
|
minute: 30, |
||||||
|
second: 40, |
||||||
|
output: '26-12-2022 02:30:40', |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
test.describe('DateTime Column', () => { |
||||||
|
let dashboard: DashboardPage; |
||||||
|
let context: any; |
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => { |
||||||
|
context = await setup({ page }); |
||||||
|
dashboard = new DashboardPage(page, context.project); |
||||||
|
}); |
||||||
|
|
||||||
|
test('Create DateTime Column', async () => { |
||||||
|
await dashboard.treeView.createTable({ title: 'test_datetime' }); |
||||||
|
// Create DateTime column
|
||||||
|
await dashboard.grid.column.create({ |
||||||
|
title: 'NC_DATETIME_0', |
||||||
|
type: 'DateTime', |
||||||
|
dateFormat: dateTimeData[0].dateFormat, |
||||||
|
timeFormat: dateTimeData[0].timeFormat, |
||||||
|
}); |
||||||
|
|
||||||
|
for (let i = 0; i < dateTimeData.length; i++) { |
||||||
|
// Edit DateTime column
|
||||||
|
await dashboard.grid.column.openEdit({ |
||||||
|
title: 'NC_DATETIME_0', |
||||||
|
type: 'DateTime', |
||||||
|
dateFormat: dateTimeData[i].dateFormat, |
||||||
|
timeFormat: dateTimeData[i].timeFormat, |
||||||
|
}); |
||||||
|
|
||||||
|
await dashboard.grid.column.save({ isUpdated: true }); |
||||||
|
|
||||||
|
await dashboard.grid.cell.dateTime.open({ |
||||||
|
index: 0, |
||||||
|
columnHeader: 'NC_DATETIME_0', |
||||||
|
}); |
||||||
|
|
||||||
|
await dashboard.grid.cell.dateTime.selectDate({ |
||||||
|
date: dateTimeData[i].date, |
||||||
|
}); |
||||||
|
|
||||||
|
await dashboard.grid.cell.dateTime.selectTime({ |
||||||
|
hour: dateTimeData[i].hour, |
||||||
|
minute: dateTimeData[i].minute, |
||||||
|
second: dateTimeData[i].second, |
||||||
|
}); |
||||||
|
|
||||||
|
await dashboard.grid.cell.dateTime.save(); |
||||||
|
|
||||||
|
await dashboard.grid.cell.verifyDateCell({ |
||||||
|
index: 0, |
||||||
|
columnHeader: 'NC_DATETIME_0', |
||||||
|
value: dateTimeData[i].output, |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
Loading…
Reference in new issue