mirror of https://github.com/nocodb/nocodb
Browse Source
* chore(nocodb): add fk_parent_column_id in filter schema * feat(nocodb): form view field level filter support * fix(nc-gui): add migration for `fk_parent_column_id` filter property * fix: add support to fetch all view filters * fix(nc-gui): filter castType issue * fix(nc-gui): form field title autofocus issue * fix(nc-gui): small changes * fix(nc-gui): update local form view filter on updating filter * fix(nc-gui): add validate field visibility function * fix(nc-gui): toggle eye icon based on field conditional visibility * fix(nc-gui): show tooltip on hover form field visibility icon * fix(nc-gui): show unique errors * fix(nc-gui): sort form view field issue * fix(nc-gui): add error handleling in form conditional field * fix(nc-gui): validate field on reorder * fix(nc-gui): disable add new filter if form field is first * fix(nc-gui): disable undo redo filters in form view * fix(nc-gui): move form filter class to ee * fix(nc-gui): prevent unwanted api call on form field select * fix(nc-gui): remove unwanted console * feat(nc-gui): shared form view conditional fields * fix(nc-gui): form filter cache issue * fix(nc-gui): delete form filters by fk_parent_col_id * fix(nc-gui): form view duplicate filters * fix(nc-gui): column meta copy issue while duplicating form view * fix(nc-gui): review changes * docs: show on conditions * fix(nc-gui): remove merge conflict code part * fix(nc-gui): show first validation error in visible form columns on hover over config error * fix(nc-gui): form view filter validate link field issue * fix(nc-gui): duplicate form column filters on duplicating table * fix(nc-gui): rename form field filters label to conditions * fix(nc-gui): minor changes * chore(nc-gui): lint * fix(nocodb): migration conflict issue * fix(nc-gui): currency field ui issue in filter input * fix(nc-gui): rating field overflow issue in filter menu * fix(nc-gui): form conditional field oss visibility issue * test(nc-gui): form conditional field test * fix(nc-gui): typo error * chore(nc-gui): lint * fix(nc-gui): filter input width issue * fix: pw test fail issue * fix(nc-gui): update pw test * fix(nc-gui): show field field config error in form field list * fix(nc-gui): grayed out form field list icon color * fix(nc-gui): give precedence to hidden pre-filled fields over conditional fields * fix(nocodb): use string type instead of any * fix(nocodb): typo mistake * fix(nocodb): use stringifyMetaProp instead of JSON.stringify * fix(nc-gui): remove lazy loading from child components of form field settings * fix(nc-gui): increase gap between plus & delete btn from group filter menu * fix(nc-gui): max callstack issue after adding group filter from form view * fix(nc-gui): increase min width of filter dropdown in form view * chore(nc-gui): lint * fix(nc-gui): required virtual field validation issue in shared form * fix(nc-gui): delete conditionally hidden field data while submiting form * fix(nc-gui): handle bt or oo cell conditional field validation issue * chore(nc-gui): lint * fix(nc-gui): new is utils file function name conflicts * fix(nc-gui): remove console --------- Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>pull/9502/head
Ramesh Mane
2 months ago
committed by
GitHub
38 changed files with 1428 additions and 295 deletions
@ -1,3 +1,11 @@
|
||||
<script setup lang="ts"></script> |
||||
<script setup lang="ts"> |
||||
import { type ColumnType } from 'nocodb-sdk' |
||||
|
||||
interface Props { |
||||
column: ColumnType |
||||
mode: 'preview' | 'list' |
||||
} |
||||
defineProps<Props>() |
||||
</script> |
||||
|
||||
<template><div></div></template> |
||||
|
@ -0,0 +1,48 @@
|
||||
<script lang="ts" setup> |
||||
const { activeField } = useFormViewStoreOrThrow() |
||||
|
||||
const isOpen = ref<boolean>(false) |
||||
</script> |
||||
|
||||
<template> |
||||
<div v-if="activeField" class="flex flex-col"> |
||||
<div class="flex flex-col gap-3"> |
||||
<div class="flex items-center justify-between"> |
||||
<div class="text-gray-800 font-medium">{{ $t('labels.showOnConditions') }}</div> |
||||
|
||||
<div class="flex flex-col"> |
||||
<NcTooltip placement="left"> |
||||
<template #title> |
||||
<div class="text-center"> |
||||
{{ $t('msg.info.thisFeatureIsOnlyAvailableInEnterpriseEdition') }} |
||||
</div> |
||||
</template> |
||||
<div |
||||
class="nc-form-field-visibility-btn border-1 rounded-lg py-1 px-3 flex items-center justify-between gap-2 !min-w-[170px] transition-all select-none text-sm opacity-50 cursor-not-allowed" |
||||
:class="{ |
||||
'!border-brand-500': isOpen, |
||||
'border-gray-200': !isOpen, |
||||
}" |
||||
> |
||||
<div class="nc-form-field-visibility-conditions-count flex-1">No conditions</div> |
||||
|
||||
<GeneralIcon icon="settings" class="flex-none w-4 h-4" /> |
||||
</div> |
||||
</NcTooltip> |
||||
</div> |
||||
</div> |
||||
<div> |
||||
<div class="text-sm text-gray-500">{{ $t('labels.showFieldOnConditionsMet') }}</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped></style> |
||||
|
||||
<style lang="scss"> |
||||
.nc-form-field-visibility-dropdown { |
||||
@apply rounded-2xl border-gray-200; |
||||
box-shadow: 0px 20px 24px -4px rgba(0, 0, 0, 0.1), 0px 8px 8px -4px rgba(0, 0, 0, 0.04); |
||||
} |
||||
</style> |
@ -0,0 +1,47 @@
|
||||
import type { ColumnType } from 'ant-design-vue/lib/table' |
||||
import { type FilterType } from 'nocodb-sdk' |
||||
|
||||
type FormViewColumn = ColumnType & Record<string, any> |
||||
|
||||
export class FormFilters { |
||||
allViewFilters: FilterType[] |
||||
protected groupedFilters: Record<string, FilterType[]> |
||||
nestedGroupedFilters: Record<string, FilterType[]> |
||||
formViewColumns: FormViewColumn[] |
||||
formViewColumnsMapByFkColumnId: Record<string, FormViewColumn> |
||||
formState: Record<string, any> |
||||
value: any |
||||
isMysql?: (sourceId?: string) => boolean |
||||
|
||||
constructor({ |
||||
data = [], |
||||
nestedGroupedFilters = {}, |
||||
formViewColumns = [], |
||||
formViewColumnsMapByFkColumnId = {}, |
||||
formState = {}, |
||||
isMysql = undefined, |
||||
}: { |
||||
data?: FilterType[] |
||||
nestedGroupedFilters?: Record<string, FilterType[]> |
||||
formViewColumns?: FormViewColumn[] |
||||
formViewColumnsMapByFkColumnId?: Record<string, FormViewColumn> |
||||
formState?: Record<string, any> |
||||
isMysql?: (sourceId?: string) => boolean |
||||
} = {}) { |
||||
this.allViewFilters = data |
||||
this.groupedFilters = {} |
||||
this.nestedGroupedFilters = nestedGroupedFilters |
||||
this.formViewColumns = formViewColumns |
||||
this.formViewColumnsMapByFkColumnId = formViewColumnsMapByFkColumnId |
||||
this.formState = formState |
||||
this.isMysql = isMysql |
||||
} |
||||
|
||||
setFilters(filters: FilterType[]) { |
||||
this.allViewFilters = filters |
||||
} |
||||
|
||||
getNestedGroupedFilters() {} |
||||
|
||||
validateVisibility() {} |
||||
} |
@ -0,0 +1,178 @@
|
||||
/** |
||||
* Checks if a value is an object (excluding null). |
||||
* |
||||
* @param value - The value to check. |
||||
* @returns {boolean} - True if the value is an object, false otherwise. |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const value = { key: 'value' }; |
||||
* console.log(ncIsObject(value)); // true
|
||||
* ``` |
||||
*/ |
||||
export function ncIsObject(value: any): boolean { |
||||
return value !== null && typeof value === 'object' && !ncIsArray(value) |
||||
} |
||||
|
||||
/** |
||||
* Checks if a value is an empty object. |
||||
* |
||||
* @param value - The value to check. |
||||
* @returns {boolean} - True if the value is an empty object, false otherwise. |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const value = {}; |
||||
* console.log(ncIsEmptyObject(value)); // true
|
||||
* ``` |
||||
*/ |
||||
export function ncIsEmptyObject(value: any): boolean { |
||||
return ncIsObject(value) && Object.keys(value).length === 0 |
||||
} |
||||
|
||||
/** |
||||
* Checks if a value is an array. |
||||
* |
||||
* @param value - The value to check. |
||||
* @returns {boolean} - True if the value is an array, false otherwise. |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const value = [1, 2, 3]; |
||||
* console.log(ncIsArray(value)); // true
|
||||
* ``` |
||||
*/ |
||||
export function ncIsArray(value: any): boolean { |
||||
return Array.isArray(value) |
||||
} |
||||
|
||||
/** |
||||
* Checks if a value is an empty array. |
||||
* |
||||
* @param value - The value to check. |
||||
* @returns {boolean} - True if the value is an empty array, false otherwise. |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const value = []; |
||||
* console.log(ncIsEmptyArray(value)); // true
|
||||
* |
||||
* const nonEmptyArray = [1, 2, 3]; |
||||
* console.log(ncIsEmptyArray(nonEmptyArray)); // false
|
||||
* ``` |
||||
*/ |
||||
export function ncIsEmptyArray(value: any): boolean { |
||||
return ncIsArray(value) && value.length === 0 |
||||
} |
||||
|
||||
/** |
||||
* Checks if a value is a string. |
||||
* |
||||
* @param value - The value to check. |
||||
* @returns {boolean} - True if the value is a string, false otherwise. |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const value = 'Hello, world!'; |
||||
* console.log(ncIsString(value)); // true
|
||||
* ``` |
||||
*/ |
||||
export function ncIsString(value: any): boolean { |
||||
return typeof value === 'string' |
||||
} |
||||
|
||||
/** |
||||
* Checks if a value is a number. |
||||
* |
||||
* @param value - The value to check. |
||||
* @returns {boolean} - True if the value is a number, false otherwise. |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const value = 42; |
||||
* console.log(ncIsNumber(value)); // true
|
||||
* ``` |
||||
*/ |
||||
export function ncIsNumber(value: any): boolean { |
||||
return typeof value === 'number' && !isNaN(value) |
||||
} |
||||
|
||||
/** |
||||
* Checks if a value is a boolean. |
||||
* |
||||
* @param value - The value to check. |
||||
* @returns {boolean} - True if the value is a boolean, false otherwise. |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const value = true; |
||||
* console.log(ncIsBoolean(value)); // true
|
||||
* ``` |
||||
*/ |
||||
export function ncIsBoolean(value: any): boolean { |
||||
return typeof value === 'boolean' |
||||
} |
||||
|
||||
/** |
||||
* Checks if a value is undefined. |
||||
* |
||||
* @param value - The value to check. |
||||
* @returns {boolean} - True if the value is undefined, false otherwise. |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const value = undefined; |
||||
* console.log(ncIsUndefined(value)); // true
|
||||
* ``` |
||||
*/ |
||||
export function ncIsUndefined(value: any): boolean { |
||||
return typeof value === 'undefined' |
||||
} |
||||
|
||||
/** |
||||
* Checks if a value is null. |
||||
* |
||||
* @param value - The value to check. |
||||
* @returns {boolean} - True if the value is null, false otherwise. |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const value = null; |
||||
* console.log(ncIsNull(value)); // true
|
||||
* ``` |
||||
*/ |
||||
export function ncIsNull(value: any): boolean { |
||||
return value === null |
||||
} |
||||
|
||||
/** |
||||
* Checks if a value is a function. |
||||
* |
||||
* @param value - The value to check. |
||||
* @returns {boolean} - True if the value is a function, false otherwise. |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const value = () => {}; |
||||
* console.log(ncIsFunction(value)); // true
|
||||
* ``` |
||||
*/ |
||||
export function ncIsFunction(value: any): boolean { |
||||
return typeof value === 'function' |
||||
} |
||||
|
||||
/** |
||||
* Checks if a value is a promise. |
||||
* |
||||
* @param value - The value to check. |
||||
* @returns {boolean} - True if the value is a Promise, false otherwise. |
||||
* |
||||
* @example |
||||
* ```typescript
|
||||
* const value = new Promise((resolve) => resolve(true)); |
||||
* console.log(ncIsPromise(value)); // true
|
||||
* ``` |
||||
*/ |
||||
export function ncIsPromise(value: any): boolean { |
||||
return value instanceof Promise |
||||
} |
After Width: | Height: | Size: 269 KiB |
@ -0,0 +1,16 @@
|
||||
import type { Knex } from 'knex'; |
||||
import { MetaTable } from '~/utils/globals'; |
||||
|
||||
const up = async (knex: Knex) => { |
||||
await knex.schema.alterTable(MetaTable.FILTER_EXP, (table) => { |
||||
table.string('fk_parent_column_id', 20).index(); |
||||
}); |
||||
}; |
||||
|
||||
const down = async (knex: Knex) => { |
||||
await knex.schema.alterTable(MetaTable.FILTER_EXP, (table) => { |
||||
table.dropColumn('fk_parent_column_id'); |
||||
}); |
||||
}; |
||||
|
||||
export { up, down }; |
@ -0,0 +1,60 @@
|
||||
import { getTextExcludeIconText } from '../../../tests/utils/general'; |
||||
import BasePage from '../../Base'; |
||||
import { FormPage } from './index'; |
||||
import { expect } from '@playwright/test'; |
||||
|
||||
export class FormConditionalFieldsPage extends BasePage { |
||||
readonly parent: FormPage; |
||||
|
||||
constructor(parent: FormPage) { |
||||
super(parent.rootPage); |
||||
this.parent = parent; |
||||
} |
||||
|
||||
get() { |
||||
return this.rootPage.getByTestId('nc-form-field-visibility-btn'); |
||||
} |
||||
|
||||
async click() { |
||||
await this.get().waitFor({ state: 'visible' }); |
||||
await this.get().click(); |
||||
await this.rootPage.getByTestId('nc-filter-menu').waitFor({ state: 'visible' }); |
||||
} |
||||
|
||||
async verify({ isDisabled, count, isVisible }: { isDisabled: boolean; count?: string; isVisible?: boolean }) { |
||||
const conditionalFieldBtn = this.get(); |
||||
|
||||
await conditionalFieldBtn.waitFor({ state: 'visible' }); |
||||
|
||||
if (isDisabled) { |
||||
await expect(conditionalFieldBtn).toHaveClass(/nc-disabled/); |
||||
} else { |
||||
await expect(conditionalFieldBtn).not.toHaveClass(/nc-disabled/); |
||||
} |
||||
|
||||
if (count !== undefined) { |
||||
const conditionCount = await getTextExcludeIconText(conditionalFieldBtn); |
||||
|
||||
await expect(conditionCount).toContain(count); |
||||
} |
||||
|
||||
if (isVisible !== undefined) { |
||||
if (isVisible) { |
||||
} |
||||
} |
||||
} |
||||
|
||||
async verifyVisibility({ title, isVisible }: { title: string; isVisible: boolean }) { |
||||
const field = this.parent.get().locator(`[data-testid="nc-form-fields"][data-title="${title}"]`); |
||||
await field.scrollIntoViewIfNeeded(); |
||||
|
||||
// Wait for icon change transition complete
|
||||
await this.rootPage.waitForTimeout(300); |
||||
|
||||
if (isVisible) { |
||||
await expect(field.locator('.nc-field-visibility-icon')).toHaveClass(/nc-field-visible/); |
||||
} else { |
||||
await expect(field.locator('.nc-field-visibility-icon')).not.toHaveClass(/nc-field-visible/); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue