Skip to content

Commit

Permalink
feat: add filter for owned entities
Browse files Browse the repository at this point in the history
closes #202
  • Loading branch information
drodil committed Nov 1, 2024
1 parent 75dd8bb commit 3e7f4b0
Show file tree
Hide file tree
Showing 12 changed files with 362 additions and 112 deletions.
4 changes: 2 additions & 2 deletions plugins/qeta-backend/src/database/DatabaseQetaStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,14 +235,14 @@ describe.each(databases.eachSupportedId())(
});

const ret1 = await storage.getPosts('user1', {
entity: 'component:default/comp1',
entities: ['component:default/comp1'],
type: 'question',
});
expect(ret1.posts.length).toEqual(1);
expect(ret1.posts.at(0)?.id).toEqual(q1.id);

const ret2 = await storage.getPosts('user1', {
entity: 'component:default/comp2',
entities: ['component:default/comp2'],
type: 'question',
});

Expand Down
127 changes: 90 additions & 37 deletions plugins/qeta-backend/src/database/DatabaseQetaStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,19 +277,36 @@ export class DatabaseQetaStore implements QetaStore {

if (options.tags) {
const tags = filterTags(options.tags);
tags.forEach((t, i) => {
query.innerJoin(`post_tags AS qt${i}`, 'posts.id', `qt${i}.postId`);
query.innerJoin(`tags AS t${i}`, `qt${i}.tagId`, `t${i}.id`);
query.where(`t${i}.tag`, '=', t);
});
if (options.tagsRelation === 'or') {
query.innerJoin('post_tags', 'posts.id', 'post_tags.postId');
query.innerJoin('tags', 'post_tags.tagId', 'tags.id');
query.whereIn('tags.tag', tags);
} else {
tags.forEach((t, i) => {
query.innerJoin(`post_tags AS qt${i}`, 'posts.id', `qt${i}.postId`);
query.innerJoin(`tags AS t${i}`, `qt${i}.tagId`, `t${i}.id`);
query.where(`t${i}.tag`, '=', t);
});
}
}

if (options.entities) {
options.entities.forEach((t, i) => {
query.innerJoin(`post_entities AS pe${i}`, 'posts.id', `pe${i}.postId`);
query.innerJoin(`entities AS e${i}`, `pe${i}.entityId`, `e${i}.id`);
query.where(`e${i}.entity_ref`, '=', t);
});
if (options.entitiesRelation === 'or') {
query
.innerJoin('post_entities', 'posts.id', 'post_entities.postId')
.innerJoin('entities', 'post_entities.entityId', 'entities.id')
.whereIn('entities.entity_ref', options.entities);
} else {
options.entities.forEach((t, i) => {
query.innerJoin(
`post_entities AS pe${i}`,
'posts.id',
`pe${i}.postId`,
);
query.innerJoin(`entities AS e${i}`, `pe${i}.entityId`, `e${i}.id`);
query.where(`e${i}.entity_ref`, '=', t);
});
}
}

if (options.collectionId) {
Expand Down Expand Up @@ -687,27 +704,43 @@ export class DatabaseQetaStore implements QetaStore {

if (options.tags) {
const tags = filterTags(options.tags);
tags.forEach((t, i) => {
query.innerJoin(
`post_tags AS at${i}`,
'answers.postId',
`at${i}.postId`,
);
query.innerJoin(`tags AS t${i}`, `at${i}.tagId`, `t${i}.id`);
query.where(`t${i}.tag`, '=', t);
});
if (options.tagsRelation === 'or') {
query.innerJoin('post_tags', 'answers.postId', 'post_tags.postId');
query.innerJoin('tags', 'post_tags.tagId', 'tags.id');
query.whereIn('tags.tag', tags);
} else {
tags.forEach((t, i) => {
query.innerJoin(
`post_tags AS at${i}`,
'answers.postId',
`at${i}.postId`,
);
query.innerJoin(`tags AS t${i}`, `at${i}.tagId`, `t${i}.id`);
query.where(`t${i}.tag`, '=', t);
});
}
}

if (options.entities) {
options.entities.forEach((t, i) => {
if (options.entitiesRelation === 'or') {
query.innerJoin(
`post_entities AS pe${i}`,
'post_entities',
'answers.postId',
`pe${i}.postId`,
'post_entities.postId',
);
query.innerJoin(`entities AS e${i}`, `pe${i}.entityId`, `e${i}.id`);
query.where(`e${i}.entity_ref`, '=', t);
});
query.innerJoin('entities', 'post_entities.entityId', 'entities.id');
query.whereIn('entities.entity_ref', options.entities);
} else {
options.entities.forEach((t, i) => {
query.innerJoin(
`post_entities AS pe${i}`,
'answers.postId',
`pe${i}.postId`,
);
query.innerJoin(`entities AS e${i}`, `pe${i}.entityId`, `e${i}.id`);
query.where(`e${i}.entity_ref`, '=', t);
});
}
}

if (options.noCorrectAnswer) {
Expand Down Expand Up @@ -1653,27 +1686,47 @@ export class DatabaseQetaStore implements QetaStore {

if (options.tags) {
const tags = filterTags(options.tags);
tags.forEach((t, i) => {
if (options.tagsRelation === 'or') {
query.innerJoin(
`post_tags AS qt${i}`,
'post_tags',
'collection_posts.postId',
`qt${i}.postId`,
'post_tags.postId',
);
query.innerJoin(`tags AS t${i}`, `qt${i}.tagId`, `t${i}.id`);
query.where(`t${i}.tag`, '=', t);
});
query.innerJoin('tags', 'post_tags.tagId', 'tags.id');
query.whereIn('tags.tag', tags);
} else {
tags.forEach((t, i) => {
query.innerJoin(
`post_tags AS qt${i}`,
'collection_posts.postId',
`qt${i}.postId`,
);
query.innerJoin(`tags AS t${i}`, `qt${i}.tagId`, `t${i}.id`);
query.where(`t${i}.tag`, '=', t);
});
}
}

if (options.entities) {
options.entities.forEach((t, i) => {
if (options.entitiesRelation === 'or') {
query.innerJoin(
`post_entities AS pe${i}`,
'post_entities',
'collection_posts.postId',
`pe${i}.postId`,
'post_entities.postId',
);
query.innerJoin(`entities AS e${i}`, `pe${i}.entityId`, `e${i}.id`);
query.where(`e${i}.entity_ref`, '=', t);
});
query.innerJoin('entities', 'post_entities.entityId', 'entities.id');
query.whereIn('entities.entity_ref', options.entities);
} else {
options.entities.forEach((t, i) => {
query.innerJoin(
`post_entities AS pe${i}`,
'collection_posts.postId',
`pe${i}.postId`,
);
query.innerJoin(`entities AS e${i}`, `pe${i}.entityId`, `e${i}.id`);
query.where(`e${i}.entity_ref`, '=', t);
});
}
}

query.where('owner', user_ref).orWhere('readAccess', 'public');
Expand Down
4 changes: 4 additions & 0 deletions plugins/qeta-backend/src/database/QetaStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ export interface PostOptions {
noVotes?: boolean;
favorite?: boolean;
tags?: string[];
tagsRelation?: 'and' | 'or';
entities?: string[];
entitiesRelation?: 'and' | 'or';
includeAnswers?: boolean;
includeVotes?: boolean;
includeEntities?: boolean;
Expand All @@ -104,7 +106,9 @@ export interface AnswersOptions {
| 'trend';
order?: 'desc' | 'asc';
tags?: string[];
tagsRelation?: 'and' | 'or';
entities?: string[];
entitiesRelation?: 'and' | 'or';
searchQuery?: string;
fromDate?: string;
toDate?: string;
Expand Down
6 changes: 6 additions & 0 deletions plugins/qeta-backend/src/service/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ export const CollectionsQuerySchema: JSONSchemaType<CollectionsQuery> = {
order: { type: 'string', enum: ['desc', 'asc'], nullable: true },
tags: { type: 'array', items: { type: 'string' }, nullable: true },
entities: { type: 'array', items: { type: 'string' }, nullable: true },
entitiesRelation: { type: 'string', enum: ['and', 'or'], nullable: true },
tagsRelation: { type: 'string', enum: ['and', 'or'], nullable: true },
fromDate: { type: 'string', nullable: true, format: 'date' },
toDate: { type: 'string', nullable: true, format: 'date' },
},
Expand Down Expand Up @@ -140,6 +142,8 @@ export const PostsQuerySchema: JSONSchemaType<PostsQuery> = {
random: { type: 'boolean', nullable: true },
tags: { type: 'array', items: { type: 'string' }, nullable: true },
entities: { type: 'array', items: { type: 'string' }, nullable: true },
entitiesRelation: { type: 'string', enum: ['and', 'or'], nullable: true },
tagsRelation: { type: 'string', enum: ['and', 'or'], nullable: true },
includeAnswers: { type: 'boolean', nullable: true },
includeVotes: { type: 'boolean', nullable: true },
includeEntities: { type: 'boolean', nullable: true },
Expand Down Expand Up @@ -221,6 +225,8 @@ export const AnswersQuerySchema: JSONSchemaType<AnswersQuery> = {
noVotes: { type: 'boolean', nullable: true },
tags: { type: 'array', items: { type: 'string' }, nullable: true },
entities: { type: 'array', items: { type: 'string' }, nullable: true },
entitiesRelation: { type: 'string', enum: ['and', 'or'], nullable: true },
tagsRelation: { type: 'string', enum: ['and', 'or'], nullable: true },
includeVotes: { type: 'boolean', nullable: true },
includeEntities: { type: 'boolean', nullable: true },
includeComments: { type: 'boolean', nullable: true },
Expand Down
6 changes: 6 additions & 0 deletions plugins/qeta-common/src/api/QetaApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ export interface PaginatedQuery {

export interface PostsQuery extends PaginatedQuery {
tags?: string[];
tagsRelation?: 'and' | 'or';
entities?: string[];
entitiesRelation?: 'and' | 'or';
author?: string;
noCorrectAnswer?: boolean;
noAnswers?: boolean;
Expand All @@ -64,14 +66,18 @@ export interface PostsQuery extends PaginatedQuery {
export interface CollectionsQuery extends PaginatedQuery {
owner?: string;
entities?: string[];
entitiesRelation?: 'and' | 'or';
tags?: string[];
tagsRelation?: 'and' | 'or';
fromDate?: string;
toDate?: string;
}

export interface AnswersQuery extends PaginatedQuery {
tags?: string[];
tagsRelation?: 'and' | 'or';
entities?: string[];
entitiesRelation?: 'and' | 'or';
author?: string;
orderBy?: 'score' | 'created' | 'updated';
noCorrectAnswer?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ export interface AnswersContainerProps {
title?: string;
}

export type AnswerFilterChange = {
key: keyof AnswerFilters;
value?: AnswerFilters[keyof AnswerFilters];
};

export const AnswersContainer = (props: AnswersContainerProps) => {
const { tags, author, entity, showFilters, showTitle, title } = props;
const analytics = useAnalytics();
Expand Down Expand Up @@ -61,29 +66,33 @@ export const AnswersContainer = (props: AnswersContainerProps) => {
};

const onFilterChange = (
key: keyof AnswerFilters,
value: string | string[],
changes: AnswerFilterChange | AnswerFilterChange[],
) => {
if (filters[key] === value) {
return;
}

const changesArray = Array.isArray(changes) ? changes : [changes];
setPage(1);
setFilters({ ...filters, ...{ [key]: value } });
setFilters(prev => {
const newValue = { ...prev };
for (const { key, value } of changesArray) {
(newValue as any)[key] = value;
}
return newValue;
});
setSearchParams(prev => {
const newValue = prev;
if (!value || value === 'false') {
newValue.delete(key);
} else if (Array.isArray(value)) {
if (value.length === 0) {
for (const { key, value } of changesArray) {
if (!value || value === 'false') {
newValue.delete(key);
} else if (Array.isArray(value)) {
if (value.length === 0) {
newValue.delete(key);
} else {
newValue.set(key, value.join(','));
}
} else if (value.length > 0) {
newValue.set(key, value);
} else {
newValue.set(key, value.join(','));
newValue.delete(key);
}
} else if (value.length > 0) {
newValue.set(key, value);
} else {
newValue.delete(key);
}
return newValue;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export type CollectionsGridProps = {
showFilters?: boolean;
};

export type CollectionFilterChange = {
key: keyof CollectionFilters;
value?: CollectionFilters[keyof CollectionFilters];
};

export const CollectionsGrid = (props: CollectionsGridProps) => {
const { showFilters } = props;
const { t } = useTranslation();
Expand Down Expand Up @@ -67,14 +72,17 @@ export const CollectionsGrid = (props: CollectionsGridProps) => {
}, [response, collectionsPerPage]);

const onFilterChange = (
key: keyof CollectionFilters,
value: string | string[],
changes: CollectionFilterChange | CollectionFilterChange[],
) => {
if (filters[key] === value) {
return;
}
const changesArray = Array.isArray(changes) ? changes : [changes];
setPage(1);
setFilters({ ...filters, ...{ [key]: value } });
setFilters(prev => {
const newValue = { ...prev };
for (const { key, value } of changesArray) {
(newValue as any)[key] = value;
}
return newValue;
});
};

return (
Expand Down
Loading

0 comments on commit 3e7f4b0

Please sign in to comment.