Browse Source

Merge pull request #4593 from nocodb/feat/datetime-options

feat(nc-gui): datetime options
pull/4692/head
աɨռɢӄաօռɢ 2 years ago committed by GitHub
parent
commit
bda3132a74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      packages/nc-gui/components.d.ts
  2. 13
      packages/nc-gui/components/cell/DateTimePicker.vue
  3. 38
      packages/nc-gui/components/smartsheet/column/DateTimeOptions.vue
  4. 1
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  5. 8
      packages/nc-gui/utils/dateTimeUtils.ts
  6. 26
      tests/playwright/pages/Dashboard/Grid/Column/index.ts
  7. 67
      tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts
  8. 19
      tests/playwright/pages/Dashboard/common/Cell/index.ts
  9. 113
      tests/playwright/tests/columnDateTime.spec.ts

3
packages/nc-gui/components.d.ts vendored

@ -170,8 +170,6 @@ declare module '@vue/runtime-core' {
MdiExport: typeof import('~icons/mdi/export')['default'] MdiExport: typeof import('~icons/mdi/export')['default']
MdiEyeCircleOutline: typeof import('~icons/mdi/eye-circle-outline')['default'] MdiEyeCircleOutline: typeof import('~icons/mdi/eye-circle-outline')['default']
MdiEyeOffOutline: typeof import('~icons/mdi/eye-off-outline')['default'] MdiEyeOffOutline: typeof import('~icons/mdi/eye-off-outline')['default']
MdiEyeSettings: typeof import('~icons/mdi/eye-settings')['default']
MdiEyeSettingsOutline: typeof import('~icons/mdi/eye-settings-outline')['default']
MdiFileDocumentOutline: typeof import('~icons/mdi/file-document-outline')['default'] MdiFileDocumentOutline: typeof import('~icons/mdi/file-document-outline')['default']
MdiFileExcel: typeof import('~icons/mdi/file-excel')['default'] MdiFileExcel: typeof import('~icons/mdi/file-excel')['default']
MdiFileEyeOutline: typeof import('~icons/mdi/file-eye-outline')['default'] MdiFileEyeOutline: typeof import('~icons/mdi/file-eye-outline')['default']
@ -203,7 +201,6 @@ declare module '@vue/runtime-core' {
MdiMagnify: typeof import('~icons/mdi/magnify')['default'] MdiMagnify: typeof import('~icons/mdi/magnify')['default']
MdiMenu: typeof import('~icons/mdi/menu')['default'] MdiMenu: typeof import('~icons/mdi/menu')['default']
MdiMenuDown: typeof import('~icons/mdi/menu-down')['default'] MdiMenuDown: typeof import('~icons/mdi/menu-down')['default']
MdiMenuIcon: typeof import('~icons/mdi/menu-icon')['default']
MdiMicrosoftTeams: typeof import('~icons/mdi/microsoft-teams')['default'] MdiMicrosoftTeams: typeof import('~icons/mdi/microsoft-teams')['default']
MdiMinusCircleOutline: typeof import('~icons/mdi/minus-circle-outline')['default'] MdiMinusCircleOutline: typeof import('~icons/mdi/minus-circle-outline')['default']
MdiMoonFull: typeof import('~icons/mdi/moon-full')['default'] MdiMoonFull: typeof import('~icons/mdi/moon-full')['default']

13
packages/nc-gui/components/cell/DateTimePicker.vue

@ -2,10 +2,13 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { import {
ActiveCellInj, ActiveCellInj,
ColumnInj,
ReadonlyInj, ReadonlyInj,
dateFormats,
inject, inject,
isDrawerOrModalExist, isDrawerOrModalExist,
ref, ref,
timeFormats,
useProject, useProject,
useSelectedCellKeyupListener, useSelectedCellKeyupListener,
watch, watch,
@ -32,7 +35,11 @@ const column = inject(ColumnInj)!
let isDateInvalid = $ref(false) let isDateInvalid = $ref(false)
const dateFormat = isMysql(column.value.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ' const dateTimeFormat = $computed(() => {
const dateFormat = column?.value?.meta?.date_format ?? dateFormats[0]
const timeFormat = column?.value?.meta?.time_format ?? timeFormats[0]
return `${dateFormat} ${timeFormat}`
})
let localState = $computed({ let localState = $computed({
get() { get() {
@ -54,7 +61,7 @@ let localState = $computed({
} }
if (val.isValid()) { if (val.isValid()) {
emit('update:modelValue', val?.format(dateFormat)) emit('update:modelValue', val?.format(isMysql(column.value.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ'))
} }
}, },
}) })
@ -165,7 +172,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
:show-time="true" :show-time="true"
:bordered="false" :bordered="false"
class="!w-full !px-0 !border-none" class="!w-full !px-0 !border-none"
format="YYYY-MM-DD HH:mm" :format="dateTimeFormat"
:placeholder="isDateInvalid ? 'Invalid date' : ''" :placeholder="isDateInvalid ? 'Invalid date' : ''"
:allow-clear="!readOnly && !localState && !isPk" :allow-clear="!readOnly && !localState && !isPk"
:input-read-only="true" :input-read-only="true"

38
packages/nc-gui/components/smartsheet/column/DateTimeOptions.vue

@ -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>

1
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue

@ -177,6 +177,7 @@ useEventListener('keydown', (e: KeyboardEvent) => {
<LazySmartsheetColumnCheckboxOptions v-if="formState.uidt === UITypes.Checkbox" v-model:value="formState" /> <LazySmartsheetColumnCheckboxOptions v-if="formState.uidt === UITypes.Checkbox" v-model:value="formState" />
<LazySmartsheetColumnLookupOptions v-if="!isEdit && formState.uidt === UITypes.Lookup" v-model:value="formState" /> <LazySmartsheetColumnLookupOptions v-if="!isEdit && formState.uidt === UITypes.Lookup" v-model:value="formState" />
<LazySmartsheetColumnDateOptions v-if="formState.uidt === UITypes.Date" v-model:value="formState" /> <LazySmartsheetColumnDateOptions v-if="formState.uidt === UITypes.Date" v-model:value="formState" />
<LazySmartsheetColumnDateTimeOptions v-if="formState.uidt === UITypes.DateTime" v-model:value="formState" />
<LazySmartsheetColumnRollupOptions v-if="!isEdit && formState.uidt === UITypes.Rollup" v-model:value="formState" /> <LazySmartsheetColumnRollupOptions v-if="!isEdit && formState.uidt === UITypes.Rollup" v-model:value="formState" />
<LazySmartsheetColumnLinkedToAnotherRecordOptions <LazySmartsheetColumnLinkedToAnotherRecordOptions
v-if="!isEdit && formState.uidt === UITypes.LinkToAnotherRecord" v-if="!isEdit && formState.uidt === UITypes.LinkToAnotherRecord"

8
packages/nc-gui/utils/dateTimeUtils.ts

@ -5,17 +5,19 @@ export const timeAgo = (date: any) => {
} }
export const dateFormats = [ export const dateFormats = [
'YYYY-MM-DD',
'YYYY/MM/DD',
'DD-MM-YYYY', 'DD-MM-YYYY',
'MM-DD-YYYY', 'MM-DD-YYYY',
'YYYY-MM-DD',
'DD/MM/YYYY', 'DD/MM/YYYY',
'MM/DD/YYYY', 'MM/DD/YYYY',
'YYYY/MM/DD',
'DD MM YYYY', 'DD MM YYYY',
'MM DD YYYY', 'MM DD YYYY',
'YYYY MM DD', 'YYYY MM DD',
] ]
export const timeFormats = ['HH:mm', 'HH:mm:ss']
export const handleTZ = (val: any) => { export const handleTZ = (val: any) => {
if (!val) { if (!val) {
return return
@ -60,7 +62,7 @@ export function getDateFormat(v: string) {
export function getDateTimeFormat(v: string) { export function getDateTimeFormat(v: string) {
for (const format of dateFormats) { for (const format of dateFormats) {
for (const timeFormat of ['HH:mm', 'HH:mm:ss', 'HH:mm:ss.SSS']) { for (const timeFormat of timeFormats) {
const dateTimeFormat = `${format} ${timeFormat}` const dateTimeFormat = `${format} ${timeFormat}`
if (dayjs(v, dateTimeFormat, true).isValid() as any) { if (dayjs(v, dateTimeFormat, true).isValid() as any) {
return dateTimeFormat return dateTimeFormat

26
tests/playwright/pages/Dashboard/Grid/Column/index.ts

@ -34,7 +34,9 @@ export class ColumnPageObject extends BasePage {
childColumn = '', childColumn = '',
relationType = '', relationType = '',
rollupType = '', rollupType = '',
format, format = '',
dateFormat = '',
timeFormat = '',
insertAfterColumnTitle, insertAfterColumnTitle,
insertBeforeColumnTitle, insertBeforeColumnTitle,
}: { }: {
@ -47,6 +49,8 @@ export class ColumnPageObject extends BasePage {
relationType?: string; relationType?: string;
rollupType?: string; rollupType?: string;
format?: string; format?: string;
dateFormat?: string;
timeFormat?: string;
insertBeforeColumnTitle?: string; insertBeforeColumnTitle?: string;
insertAfterColumnTitle?: string; insertAfterColumnTitle?: string;
}) { }) {
@ -90,6 +94,14 @@ export class ColumnPageObject extends BasePage {
.click(); .click();
} }
break; break;
case 'DateTime':
// Date Format
await this.get().locator('.nc-date-select').click();
await this.rootPage.locator('.ant-select-item').locator(`text="${dateFormat}"`).click();
// Time Format
await this.get().locator('.nc-time-select').click();
await this.rootPage.locator('.ant-select-item').locator(`text="${timeFormat}"`).click();
break;
case 'Formula': case 'Formula':
await this.get().locator('.nc-formula-input').fill(formula); await this.get().locator('.nc-formula-input').fill(formula);
break; break;
@ -222,11 +234,15 @@ export class ColumnPageObject extends BasePage {
type = 'SingleLineText', type = 'SingleLineText',
formula = '', formula = '',
format, format,
dateFormat = '',
timeFormat = '',
}: { }: {
title: string; title: string;
type?: string; type?: string;
formula?: string; formula?: string;
format?: string; format?: string;
dateFormat?: string;
timeFormat?: string;
}) { }) {
await this.getColumnHeader(title).locator('.nc-ui-dt-dropdown').click(); await this.getColumnHeader(title).locator('.nc-ui-dt-dropdown').click();
await this.rootPage.locator('li[role="menuitem"]:has-text("Edit")').click(); await this.rootPage.locator('li[role="menuitem"]:has-text("Edit")').click();
@ -245,6 +261,14 @@ export class ColumnPageObject extends BasePage {
}) })
.click(); .click();
break; break;
case 'DateTime':
// Date Format
await this.get().locator('.nc-date-select').click();
await this.rootPage.locator('.ant-select-item').locator(`text="${dateFormat}"`).click();
// Time Format
await this.get().locator('.nc-time-select').click();
await this.rootPage.locator('.ant-select-item').locator(`text="${timeFormat}"`).click();
break;
default: default:
break; break;
} }

67
tests/playwright/pages/Dashboard/common/Cell/DateTimeCell.ts

@ -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');
}
}

19
tests/playwright/pages/Dashboard/common/Cell/index.ts

@ -7,6 +7,7 @@ import { SharedFormPage } from '../../../SharedForm';
import { CheckboxCellPageObject } from './CheckboxCell'; import { CheckboxCellPageObject } from './CheckboxCell';
import { RatingCellPageObject } from './RatingCell'; import { RatingCellPageObject } from './RatingCell';
import { DateCellPageObject } from './DateCell'; import { DateCellPageObject } from './DateCell';
import { DateTimeCellPageObject } from './DateTimeCell';
export interface CellProps { export interface CellProps {
index?: number; index?: number;
@ -20,6 +21,7 @@ export class CellPageObject extends BasePage {
readonly checkbox: CheckboxCellPageObject; readonly checkbox: CheckboxCellPageObject;
readonly rating: RatingCellPageObject; readonly rating: RatingCellPageObject;
readonly date: DateCellPageObject; readonly date: DateCellPageObject;
readonly dateTime: DateTimeCellPageObject;
constructor(parent: GridPage | SharedFormPage) { constructor(parent: GridPage | SharedFormPage) {
super(parent.rootPage); super(parent.rootPage);
@ -29,6 +31,7 @@ export class CellPageObject extends BasePage {
this.checkbox = new CheckboxCellPageObject(this); this.checkbox = new CheckboxCellPageObject(this);
this.rating = new RatingCellPageObject(this); this.rating = new RatingCellPageObject(this);
this.date = new DateCellPageObject(this); this.date = new DateCellPageObject(this);
this.dateTime = new DateTimeCellPageObject(this);
} }
get({ index, columnHeader }: CellProps): Locator { get({ index, columnHeader }: CellProps): Locator {
@ -113,6 +116,22 @@ export class CellPageObject extends BasePage {
} }
} }
async verifyDateCell({ index, columnHeader, value }: { index: number; columnHeader: string; value: string }) {
const _verify = async expectedValue => {
await expect
.poll(async () => {
const cell = await this.get({
index,
columnHeader,
}).locator('input');
return await cell.getAttribute('title');
})
.toEqual(expectedValue);
};
await _verify(value);
}
async verifyQrCodeCell({ async verifyQrCodeCell({
index, index,
columnHeader, columnHeader,

113
tests/playwright/tests/columnDateTime.spec.ts

@ -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…
Cancel
Save