Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new advanced filters experience #8570

Merged
merged 43 commits into from
Oct 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
a6d3ed0
Remove advanced filter sidebar detail
rijkvanzanten Sep 30, 2021
9f1d355
Remove filter conversion logic
rijkvanzanten Sep 30, 2021
4cfaa1f
Start replacing/removing old skool filters
rijkvanzanten Oct 1, 2021
24ef0ec
Add inline mode for usages in search bar
rijkvanzanten Oct 1, 2021
ebf0d88
Make filter work in header bar
rijkvanzanten Oct 1, 2021
6cbd238
Merge branch 'main' into advanceder-filters
rijkvanzanten Oct 1, 2021
14211ce
Emit empty string as null in filter
rijkvanzanten Oct 1, 2021
8195545
Move shared filter types to shared
rijkvanzanten Oct 1, 2021
8bdf96f
Upgrade use-items
rijkvanzanten Oct 2, 2021
b3c34eb
Fix manual sort on tabular
rijkvanzanten Oct 2, 2021
5739ddd
Cleanup styling in search bar usage
rijkvanzanten Oct 2, 2021
94e7e45
Merge branch 'main' into advanceder-filters
rijkvanzanten Oct 4, 2021
abff985
Tweak styling
rijkvanzanten Oct 4, 2021
f4c453e
Fix filtering issues
rijkvanzanten Oct 4, 2021
b31769f
Update cards
rijkvanzanten Oct 4, 2021
99dd9f0
Remove activeFilterCount from tabular
rijkvanzanten Oct 4, 2021
46bc211
Update maps to work with new filters
rijkvanzanten Oct 4, 2021
097a6a9
Merge branch 'main' into advanceder-filters
rijkvanzanten Oct 5, 2021
a2f8adc
Merge branch 'main' into advanceder-filters
rijkvanzanten Oct 5, 2021
1d18b77
Update calendar to new filter/sort structure
rijkvanzanten Oct 5, 2021
48033fb
Fix activity module nav/search
rijkvanzanten Oct 5, 2021
e289dff
Fix no-results message
rijkvanzanten Oct 5, 2021
c978e03
Update file library filtering
rijkvanzanten Oct 5, 2021
0b610a5
Finalize user search
rijkvanzanten Oct 5, 2021
0d05b32
Allow filtering in drawer-collection
rijkvanzanten Oct 5, 2021
f022695
Handle cancelled responses semi-gracefully
rijkvanzanten Oct 5, 2021
fdbd445
Add loading start state timeout
rijkvanzanten Oct 5, 2021
feca8a5
Replace sort type in api
rijkvanzanten Oct 6, 2021
d97ff3e
Last commit before redoing a bunch
rijkvanzanten Oct 6, 2021
9f56c4b
Finish new visual style
rijkvanzanten Oct 6, 2021
f054e4e
Remove unused rounded prop from v-menu
rijkvanzanten Oct 6, 2021
5991467
Tweak sizing
rijkvanzanten Oct 6, 2021
ada0c15
Enough size tweaking for now
rijkvanzanten Oct 6, 2021
00b5740
Count all filter operators instead of top
rijkvanzanten Oct 6, 2021
c5dd20e
Fix archive casting
rijkvanzanten Oct 6, 2021
b7f416a
Fix api build
rijkvanzanten Oct 6, 2021
9a22fc4
Add merge filters util
rijkvanzanten Oct 6, 2021
249aa70
Split filter in user vs system
rijkvanzanten Oct 6, 2021
44bf86d
Fix export sidebar detail
rijkvanzanten Oct 6, 2021
b55c73a
Show field label on permissions configuration
rijkvanzanten Oct 6, 2021
8b69396
Merge branch 'main' into advanceder-filters
rijkvanzanten Oct 7, 2021
13aa2d2
Add migration for filter/sort
rijkvanzanten Oct 7, 2021
153e593
Use filters in insights
rijkvanzanten Oct 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/src/database/helpers/geometry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class KnexSpatial {
}
_intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
const geometry = this.fromGeoJSON(geojson);
return this.knex.raw('intersects(??, ?)', [key, geometry]);
return this.knex.raw('st_intersects(??, ?)', [key, geometry]);
}
intersects_bbox(key: string, geojson: GeoJSONGeometry): Knex.Raw {
return this.isTrue(this._intersects_bbox(key, geojson));
Expand Down
141 changes: 141 additions & 0 deletions api/src/database/migrations/20211007A-update-presets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { Filter, LogicalFilterAND } from '@directus/shared/types';
import { Knex } from 'knex';
import { nanoid } from 'nanoid';

type OldFilter = {
key: string;
field: string;
value: any;
operator: string;
};

export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_presets', (table) => {
table.json('filter');
});

const presets = await knex
.select<{ id: number; filters: string | OldFilter[]; layout_query: string | Record<string, any> }[]>(
'id',
'filters',
'layout_query'
)
.from('directus_presets');

for (const preset of presets) {
if (preset.filters) {
const oldFilters: OldFilter[] =
(typeof preset.filters === 'string' ? JSON.parse(preset.filters) : preset.filters) ?? [];

if (oldFilters.length === 0) continue;

const newFilter: Filter = {
_and: [],
};

for (const oldFilter of oldFilters) {
if (oldFilter.key === 'hide-archived') continue;

newFilter._and.push({
[oldFilter.field]: {
['_' + oldFilter.operator]: oldFilter.value,
},
});
}

if (newFilter._and.length > 0) {
await knex('directus_presets')
.update({ filter: JSON.stringify(newFilter) })
.where('id', '=', preset.id);
}
}

if (preset.layout_query) {
const layoutQuery: Record<string, any> =
typeof preset.layout_query === 'string' ? JSON.parse(preset.layout_query) : preset.layout_query;

for (const [layout, query] of Object.entries(layoutQuery)) {
if (query.sort) {
query.sort = [query.sort];
}

layoutQuery[layout] = query;
}

await knex('directus_presets')
.update({ layout_query: JSON.stringify(layoutQuery) })
.where('id', '=', preset.id);
}
}

await knex.schema.alterTable('directus_presets', (table) => {
table.dropColumn('filters');
});
}

export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable('directus_presets', (table) => {
table.json('filters');
});

const presets = await knex
.select<{ id: number; filter: string | OldFilter[]; layout_query: string | Record<string, any> }[]>(
'id',
'filter',
'layout_query'
)
.from('directus_presets');

for (const preset of presets) {
if (preset.filter) {
const newFilter: LogicalFilterAND =
(typeof preset.filter === 'string' ? JSON.parse(preset.filter) : preset.filter) ?? {};

if (Object.keys(newFilter).length === 0) continue;

const oldFilters: OldFilter[] = [];

for (const filter of newFilter._and ?? []) {
const field = Object.keys(filter)?.[0];
const operator = Object.keys(Object.values(filter)?.[0] ?? {})?.[0];
const value = Object.values(Object.values(filter)?.[0] ?? {})?.[0];

if (!field || !operator || !value) continue;

oldFilters.push({
key: nanoid(),
field,
operator: operator.substring(1),
value,
});
}

if (oldFilters.length > 0) {
await knex('directus_presets')
.update({ filters: JSON.stringify(oldFilters) })
.where('id', '=', preset.id);
}
}

if (preset.layout_query) {
const layoutQuery: Record<string, any> =
typeof preset.layout_query === 'string' ? JSON.parse(preset.layout_query) : preset.layout_query;

for (const [layout, query] of Object.entries(layoutQuery)) {
if (query.sort && Array.isArray(query.sort)) {
query.sort = query.sort?.[0] ?? null;
}

layoutQuery[layout] = query;
}

await knex('directus_presets')
.update({ layout_query: JSON.stringify(layoutQuery) })
.where('id', '=', preset.id);
}
}

await knex.schema.alterTable('directus_presets', (table) => {
table.dropColumn('filter');
});
}
14 changes: 11 additions & 3 deletions api/src/database/run-ast.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Knex } from 'knex';
import { clone, cloneDeep, pick, uniq } from 'lodash';
import { PayloadService } from '../services/payload';
import { Item, Query, SchemaOverview } from '../types';
import { Item, SchemaOverview } from '../types';
import { AST, FieldNode, NestedCollectionNode, M2ONode } from '../types/ast';
import { applyFunctionToColumnName } from '../utils/apply-function-to-column-name';
import applyQuery from '../utils/apply-query';
import { getColumn } from '../utils/get-column';
import { stripFunction } from '../utils/strip-function';
import { toArray } from '@directus/shared/utils';
import { Query } from '@directus/shared/types';
import getDatabase from './index';
import { isNativeGeometry } from '../utils/geometry';
import { getGeometryHelper } from '../database/helpers/geometry';
Expand Down Expand Up @@ -289,7 +290,7 @@ function applyParentFilters(
...nestedNode.query[relatedCollection],
filter: {
_and: [
nestedNode.query[relatedCollection].filter,
nestedNode.query[relatedCollection].filter ?? {},
{
[nestedNode.relatedKey[relatedCollection]]: {
_in: uniq(keysPerCollection[relatedCollection]),
Expand Down Expand Up @@ -343,7 +344,14 @@ function mergeWithParentItems(
})
.sort((a, b) => {
// This is pre-filled in get-ast-from-query
const { column, order } = nestedNode.query.sort![0]!;
const sortField = nestedNode.query.sort![0]!;
let column = sortField;
let order: 'asc' | 'desc' = 'asc';

if (sortField.startsWith('-')) {
column = sortField.substring(1);
order = 'desc';
}

if (a[column] === b[column]) return 0;
if (a[column] === null) return 1;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { merge } from 'lodash';
import { Permission } from '../../../types';
import { Permission } from '@directus/shared/types';
import { requireYAML } from '../../../utils/require-yaml';

const defaults: Partial<Permission> = {
Expand Down
2 changes: 1 addition & 1 deletion api/src/database/system-data/fields/presets.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
table: directus_presets

fields:
- field: filters
- field: filter
hidden: true
special: json

Expand Down
15 changes: 8 additions & 7 deletions api/src/services/authorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,10 @@ import {
FieldNode,
Item,
NestedCollectionNode,
Permission,
PermissionsAction,
PrimaryKey,
Query,
SchemaOverview,
Aggregate,
} from '../types';
import { Query, Aggregate, Permission, PermissionsAction } from '@directus/shared/types';
import { ItemsService } from './items';
import { PayloadService } from './payload';

Expand Down Expand Up @@ -105,7 +102,11 @@ export class AuthorizationService {
}
}

function checkFields(collection: string, children: (NestedCollectionNode | FieldNode)[], aggregate?: Aggregate) {
function checkFields(
collection: string,
children: (NestedCollectionNode | FieldNode)[],
aggregate?: Aggregate | null
) {
// We check the availability of the permissions in the step before this is run
const permissions = permissionsForCollections.find((permission) => permission.collection === collection)!;
const allowedFields = permissions.fields || [];
Expand Down Expand Up @@ -185,7 +186,7 @@ export class AuthorizationService {
query.filter._and.push(parsedPermissions);
}

if (query.filter._and.length === 0) delete query.filter._and;
if (query.filter._and.length === 0) delete query.filter;
}
}
}
Expand Down Expand Up @@ -258,7 +259,7 @@ export class AuthorizationService {
}

if (requiredColumns.length > 0) {
permission.validation = hasValidationRules ? { _and: [permission.validation] } : { _and: [] };
permission.validation = hasValidationRules ? { _and: [permission.validation!] } : { _and: [] };

for (const field of requiredColumns) {
if (action === 'create' && field.defaultValue === null) {
Expand Down
6 changes: 3 additions & 3 deletions api/src/services/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ import env from '../env';
import { BaseException } from '@directus/shared/exceptions';
import { ForbiddenException, GraphQLValidationException, InvalidPayloadException } from '../exceptions';
import { getExtensionManager } from '../extensions';
import { Accountability } from '@directus/shared/types';
import { AbstractServiceOptions, Action, Aggregate, GraphQLParams, Item, Query, SchemaOverview } from '../types';
import { Accountability, Query, Aggregate } from '@directus/shared/types';
import { AbstractServiceOptions, Action, GraphQLParams, Item, SchemaOverview } from '../types';
import { getGraphQLType } from '../utils/get-graphql-type';
import { reduceSchema } from '../utils/reduce-schema';
import { sanitizeQuery } from '../utils/sanitize-query';
Expand Down Expand Up @@ -1413,7 +1413,7 @@ export class GraphQLService {
return uniq(fields);
};

const replaceFuncs = (filter?: Filter): undefined | Filter => {
const replaceFuncs = (filter?: Filter | null): null | undefined | Filter => {
if (!filter) return filter;

return replaceFuncDeep(filter);
Expand Down
13 changes: 2 additions & 11 deletions api/src/services/items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,8 @@ import env from '../env';
import { ForbiddenException, InvalidPayloadException } from '../exceptions';
import { translateDatabaseError } from '../exceptions/database/translate';
import logger from '../logger';
import { Accountability } from '@directus/shared/types';
import {
AbstractService,
AbstractServiceOptions,
Action,
Item as AnyItem,
PermissionsAction,
PrimaryKey,
Query,
SchemaOverview,
} from '../types';
import { Accountability, Query, PermissionsAction } from '@directus/shared/types';
import { AbstractService, AbstractServiceOptions, Action, Item as AnyItem, PrimaryKey, SchemaOverview } from '../types';
import getASTFromQuery from '../utils/get-ast-from-query';
import { toArray } from '@directus/shared/utils';
import { AuthorizationService } from './authorization';
Expand Down
7 changes: 3 additions & 4 deletions api/src/services/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { Knex } from 'knex';
import getDatabase from '../database';
import { ForbiddenException } from '../exceptions';
import { AbstractServiceOptions, SchemaOverview } from '../types';
import { Accountability } from '@directus/shared/types';
import { Query } from '../types/query';
import { Accountability, Query } from '@directus/shared/types';
import { applyFilter, applySearch } from '../utils/apply-query';
import { parseFilter } from '@directus/shared/utils';

Expand All @@ -18,11 +17,11 @@ export class MetaService {
this.schema = options.schema;
}

async getMetaForQuery(collection: string, query: Query): Promise<Record<string, any> | undefined> {
async getMetaForQuery(collection: string, query: any): Promise<Record<string, any> | undefined> {
if (!query || !query.meta) return;

const results = await Promise.all(
query.meta.map((metaVal) => {
query.meta.map((metaVal: string) => {
if (metaVal === 'total_count') return this.totalCount(collection);
if (metaVal === 'filter_count') return this.filterCount(collection, query);
})
Expand Down
4 changes: 2 additions & 2 deletions api/src/services/payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { clone, cloneDeep, isObject, isPlainObject, omit, isNil } from 'lodash';
import { v4 as uuidv4 } from 'uuid';
import getDatabase from '../database';
import { ForbiddenException, InvalidPayloadException } from '../exceptions';
import { AbstractServiceOptions, Item, PrimaryKey, Query, SchemaOverview, Alterations } from '../types';
import { Accountability } from '@directus/shared/types';
import { AbstractServiceOptions, Item, PrimaryKey, SchemaOverview, Alterations } from '../types';
import { Accountability, Query } from '@directus/shared/types';
import { toArray } from '@directus/shared/utils';
import { ItemsService } from './items';
import { unflatten } from 'flat';
Expand Down
3 changes: 2 additions & 1 deletion api/src/services/permissions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { appAccessMinimalPermissions } from '../database/system-data/app-access-permissions';
import logger from '../logger';
import { ItemsService, QueryOptions } from '../services/items';
import { AbstractServiceOptions, Item, PermissionsAction, PrimaryKey, Query } from '../types';
import { AbstractServiceOptions, Item, PrimaryKey } from '../types';
import { Query, PermissionsAction } from '@directus/shared/types';
import { filterItems } from '../utils/filter-items';

export class PermissionsService extends ItemsService {
Expand Down
3 changes: 2 additions & 1 deletion api/src/services/relations.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Knex } from 'knex';
import { systemRelationRows } from '../database/system-data/relations';
import { ForbiddenException, InvalidPayloadException } from '../exceptions';
import { AbstractServiceOptions, SchemaOverview, Query, Relation, RelationMeta } from '../types';
import { AbstractServiceOptions, SchemaOverview, Relation, RelationMeta } from '../types';
import { Query } from '@directus/shared/types';
import { Accountability } from '@directus/shared/types';
import { toArray } from '@directus/shared/utils';
import { ItemsService, QueryOptions } from './items';
Expand Down
3 changes: 2 additions & 1 deletion api/src/services/roles.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ForbiddenException, UnprocessableEntityException } from '../exceptions';
import { AbstractServiceOptions, PrimaryKey, Query, Alterations, Item } from '../types';
import { AbstractServiceOptions, PrimaryKey, Alterations, Item } from '../types';
import { Query } from '@directus/shared/types';
import { ItemsService, MutationOptions } from './items';
import { PermissionsService } from './permissions';
import { PresetsService } from './presets';
Expand Down
4 changes: 2 additions & 2 deletions api/src/services/specifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import {
import { version } from '../../package.json';
import getDatabase from '../database';
import env from '../env';
import { AbstractServiceOptions, Collection, Permission, Relation, SchemaOverview } from '../types';
import { Accountability, Field, Type } from '@directus/shared/types';
import { AbstractServiceOptions, Collection, Relation, SchemaOverview } from '../types';
import { Accountability, Field, Type, Permission } from '@directus/shared/types';
import { getRelationType } from '../utils/get-relation-type';
import { CollectionsService } from './collections';
import { FieldsService } from './fields';
Expand Down
3 changes: 2 additions & 1 deletion api/src/services/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { FailedValidationException } from '@directus/shared/exceptions';
import { ForbiddenException, InvalidPayloadException, UnprocessableEntityException } from '../exceptions';
import { RecordNotUniqueException } from '../exceptions/database/record-not-unique';
import logger from '../logger';
import { AbstractServiceOptions, Item, PrimaryKey, Query, SchemaOverview } from '../types';
import { AbstractServiceOptions, Item, PrimaryKey, SchemaOverview } from '../types';
import { Query } from '@directus/shared/types';
import { Accountability } from '@directus/shared/types';
import isUrlAllowed from '../utils/is-url-allowed';
import { toArray } from '@directus/shared/utils';
Expand Down
2 changes: 1 addition & 1 deletion api/src/types/ast.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Query } from './query';
import { Query } from '@directus/shared/types';
import { Relation } from './relation';

export type M2ONode = {
Expand Down
2 changes: 1 addition & 1 deletion api/src/types/express.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

import { Accountability } from '@directus/shared/types';
import { Query } from './query';
import { Query } from '@directus/shared/types';
import { SchemaOverview } from './schema';

export {};
Expand Down