mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Add functions support to the app + add count function (#12488)
* Rename date functions to fn, add json_array_length for pg * Add json count to mssql * Add json array count support to other vendors * Add UI for selecting API functions * Make it not break * Render functions in filter preview better * Include functions in field altering * Add schema access to database helper * Allow filtering against o2m/m2m/m2a count * Add data function execution helpers in utils * Fix type issue * Inject function results in validate step * Render field keys with function names translated * Allow selecting nested/functions in field validation step * Make sure number comparisons are treated as numbers * Add check if instanceof date when casting to a Number * Prevent selecting foreign keys for junction sort (#12463) * [SDK] Add further request options to `items` functions (#12503) * add possibility to set further options to the request * fix options type * add typings to interface * add test if headers are passed thourght * create reusable options param * set higher priority to options param * Small stylistic cleanup Co-authored-by: Azri Kahar <42867097+azrikahar@users.noreply.github.com> Co-authored-by: ian <licitdev@gmail.com> Co-authored-by: Jürg Hunziker <juerg.hunziker@gmail.com>
This commit is contained in:
@@ -16,19 +16,19 @@
|
||||
<div v-if="filterInfo[index].isField" block class="node field">
|
||||
<div class="header" :class="{ inline }">
|
||||
<v-icon name="drag_indicator" class="drag-handle" small></v-icon>
|
||||
<v-menu placement="bottom-start" show-arrow :disabled="!!field === false">
|
||||
<v-menu placement="bottom-start" show-arrow>
|
||||
<template #activator="{ toggle }">
|
||||
<button
|
||||
class="name"
|
||||
:disabled="!!field === false"
|
||||
:class="{ disabled: !!field === false }"
|
||||
@click="toggle"
|
||||
>
|
||||
<button class="name" @click="toggle">
|
||||
<span>{{ getFieldPreview(element) }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<v-field-list :collection="collection" @select-field="updateField(index, $event)" />
|
||||
<v-field-list
|
||||
:collection="collection"
|
||||
:field="field"
|
||||
include-functions
|
||||
@select-field="updateField(index, $event)"
|
||||
/>
|
||||
</v-menu>
|
||||
<v-select
|
||||
inline
|
||||
@@ -96,18 +96,25 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useFieldTree } from '@/composables/use-field-tree';
|
||||
import { computed, toRefs } from 'vue';
|
||||
import InputGroup from './input-group.vue';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { useFieldsStore } from '@/stores';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { getFilterOperatorsForType } from '@directus/shared/utils';
|
||||
import { get } from 'lodash';
|
||||
import { FieldFilter, Filter, FieldFilterOperator, LogicalFilterAND, LogicalFilterOR } from '@directus/shared/types';
|
||||
import { extractFieldFromFunction } from '@/utils/extract-field-from-function';
|
||||
import { useSync } from '@directus/shared/composables';
|
||||
import { fieldToFilter, getField, getNodeName, getComparator } from './utils';
|
||||
import { toArray } from '@directus/shared/utils';
|
||||
import {
|
||||
FieldFilter,
|
||||
FieldFilterOperator,
|
||||
Filter,
|
||||
LogicalFilterAND,
|
||||
LogicalFilterOR,
|
||||
Type,
|
||||
} from '@directus/shared/types';
|
||||
import { getFilterOperatorsForType, getOutputTypeForFunction, toArray } from '@directus/shared/utils';
|
||||
import { get } from 'lodash';
|
||||
import { computed, toRefs } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import Draggable from 'vuedraggable';
|
||||
import InputGroup from './input-group.vue';
|
||||
import { fieldToFilter, getComparator, getField, getNodeName } from './utils';
|
||||
import { getFunctionsForType } from '@directus/shared/utils';
|
||||
|
||||
type FilterInfo =
|
||||
| {
|
||||
@@ -145,7 +152,6 @@ const emit = defineEmits(['remove-node', 'update:filter', 'change']);
|
||||
|
||||
const { collection } = toRefs(props);
|
||||
const filterSync = useSync(props, 'filter', emit);
|
||||
const { treeList: fieldOptions, loadFieldRelations } = useFieldTree(collection);
|
||||
const fieldsStore = useFieldsStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -181,9 +187,27 @@ function getFieldPreview(node: Record<string, any>) {
|
||||
const fieldParts = fieldKey.split('.');
|
||||
|
||||
const fieldNames = fieldParts.map((fieldKey, index) => {
|
||||
const hasFunction = fieldKey.includes('(') && fieldKey.includes(')');
|
||||
|
||||
let key = fieldKey;
|
||||
let functionName;
|
||||
|
||||
if (hasFunction) {
|
||||
const { field, fn } = extractFieldFromFunction(fieldKey);
|
||||
functionName = fn;
|
||||
key = field;
|
||||
}
|
||||
|
||||
const pathPrefix = fieldParts.slice(0, index);
|
||||
const field = fieldsStore.getField(props.collection, [...pathPrefix, fieldKey].join('.'));
|
||||
return field?.name ?? fieldKey;
|
||||
const field = fieldsStore.getField(props.collection, [...pathPrefix, key].join('.'));
|
||||
|
||||
const name = field?.name ?? key;
|
||||
|
||||
if (hasFunction) {
|
||||
return t(`functions.${functionName}`) + ` (${name})`;
|
||||
}
|
||||
|
||||
return name;
|
||||
});
|
||||
|
||||
return fieldNames.join(' -> ');
|
||||
@@ -294,10 +318,17 @@ function replaceNode(index: number, newFilter: Filter) {
|
||||
}
|
||||
|
||||
function getCompareOptions(name: string) {
|
||||
const fieldInfo = fieldsStore.getField(props.collection, name);
|
||||
if (fieldInfo === null) return [];
|
||||
let type: Type;
|
||||
|
||||
return getFilterOperatorsForType(fieldInfo.type, { includeValidation: true }).map((type) => ({
|
||||
if (name.includes('(') && name.includes(')')) {
|
||||
const functionName = name.split('(')[0];
|
||||
type = getOutputTypeForFunction(functionName);
|
||||
} else {
|
||||
const fieldInfo = fieldsStore.getField(props.collection, name);
|
||||
type = fieldInfo?.type || 'unknown';
|
||||
}
|
||||
|
||||
return getFilterOperatorsForType(type, { includeValidation: true }).map((type) => ({
|
||||
text: t(`operators.${type}`),
|
||||
value: `_${type}`,
|
||||
}));
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<v-field-list :collection="collection" @select-field="addNode($event)">
|
||||
<v-field-list :collection="collection" include-functions @select-field="addNode($event)">
|
||||
<template #prepend>
|
||||
<v-list-item clickable @click="addNode('$group')">
|
||||
<v-list-item-content>
|
||||
@@ -54,14 +54,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { get, set, isEmpty, cloneDeep } from 'lodash';
|
||||
import { useFieldsStore } from '@/stores';
|
||||
import { Filter, Type } from '@directus/shared/types';
|
||||
import { getFilterOperatorsForType, getOutputTypeForFunction } from '@directus/shared/utils';
|
||||
import { cloneDeep, get, isEmpty, set } from 'lodash';
|
||||
import { computed, inject, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Filter } from '@directus/shared/types';
|
||||
import Nodes from './nodes.vue';
|
||||
import { getNodeName } from './utils';
|
||||
import { getFilterOperatorsForType } from '@directus/shared/utils';
|
||||
import { useFieldsStore } from '@/stores';
|
||||
|
||||
interface Props {
|
||||
value?: Record<string, any>;
|
||||
@@ -129,8 +129,16 @@ function addNode(key: string) {
|
||||
if (key === '$group') {
|
||||
innerValue.value = innerValue.value.concat({ _and: [] });
|
||||
} else {
|
||||
const field = fieldsStore.getField(collection.value, key)!;
|
||||
const operator = getFilterOperatorsForType(field.type)[0];
|
||||
let type: Type;
|
||||
|
||||
if (key.includes('(') && key.includes(')')) {
|
||||
const functionName = key.split('(')[0];
|
||||
type = getOutputTypeForFunction(functionName);
|
||||
} else {
|
||||
const field = fieldsStore.getField(collection.value, key)!;
|
||||
type = field?.type || 'unknown';
|
||||
}
|
||||
const operator = getFilterOperatorsForType(type)[0];
|
||||
const node = set({}, key, { ['_' + operator]: null });
|
||||
innerValue.value = innerValue.value.concat(node);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user