From 6df98ce4796630bbf8b1f3fc0a4fc6371033ce0b Mon Sep 17 00:00:00 2001 From: Harmit Goswami <90732381+harmitgoswami@users.noreply.github.com> Date: Fri, 6 Sep 2024 10:40:50 -0400 Subject: [PATCH] Implement filter for searching in rejected translations (#3317) --- pontoon/base/forms.py | 1 + pontoon/base/models/entity.py | 14 ++++++++--- pontoon/base/views.py | 1 + translate/src/api/entity.ts | 1 + translate/src/context/Location.tsx | 6 +++++ .../modules/search/components/SearchBox.tsx | 25 ++++++++++++++++--- translate/src/modules/search/constants.ts | 8 +++--- 7 files changed, 46 insertions(+), 10 deletions(-) diff --git a/pontoon/base/forms.py b/pontoon/base/forms.py index e722ee78f..1b4e9fd7e 100644 --- a/pontoon/base/forms.py +++ b/pontoon/base/forms.py @@ -299,6 +299,7 @@ class GetEntitiesForm(forms.Form): extra = forms.CharField(required=False) search_identifiers = forms.BooleanField(required=False) search_translations_only = forms.BooleanField(required=False) + search_rejected_translations = forms.BooleanField(required=False) tag = forms.CharField(required=False) time = forms.CharField(required=False) author = forms.CharField(required=False) diff --git a/pontoon/base/models/entity.py b/pontoon/base/models/entity.py index 5c82d1cae..ac0ef9af4 100644 --- a/pontoon/base/models/entity.py +++ b/pontoon/base/models/entity.py @@ -733,6 +733,7 @@ class Entity(DirtyFieldsMixin, models.Model): extra=None, search_identifiers=None, search_translations_only=None, + search_rejected_translations=None, time=None, author=None, review_time=None, @@ -841,13 +842,23 @@ class Entity(DirtyFieldsMixin, models.Model): # only tag needs `distinct` as it traverses m2m fields entities = entities.distinct() + # TODO: Uncomment the following lines to reactivate the + # feature once all search options are implemented: + # - 857 (rejected translations) + # - 869-870 (context identifiers) + # - 883-884 (translations only) + # Filter by search parameters if search: search_list = utils.get_search_phrases(search) + q_rejected = ( + Q() + ) # if search_rejected_translations else Q(translation__rejected=False) translation_filters = ( Q(translation__string__icontains_collate=(search, locale.db_collation)) & Q(translation__locale=locale) + & q_rejected for search in search_list ) @@ -856,9 +867,6 @@ class Entity(DirtyFieldsMixin, models.Model): ) # if not search_translations_only: - - # TODO: remove the comments above and below to reactivate the feature once - # all search options are implemented q_key = Q(key__icontains=search) # if search_identifiers else Q() entity_filters = ( Q(string__icontains=search) | Q(string_plural__icontains=search) | q_key diff --git a/pontoon/base/views.py b/pontoon/base/views.py index 16db2f20a..328ad6661 100755 --- a/pontoon/base/views.py +++ b/pontoon/base/views.py @@ -247,6 +247,7 @@ def entities(request): "extra", "search_identifiers", "search_translations_only", + "search_rejected_translations", "time", "author", "review_time", diff --git a/translate/src/api/entity.ts b/translate/src/api/entity.ts index 41622bc70..d2b61830d 100644 --- a/translate/src/api/entity.ts +++ b/translate/src/api/entity.ts @@ -126,6 +126,7 @@ function buildFetchPayload( 'status', 'search_identifiers', 'search_translations_only', + 'search_rejected_translations', 'extra', 'tag', 'author', diff --git a/translate/src/context/Location.tsx b/translate/src/context/Location.tsx index 1f7c8b4d4..8ea4232c3 100644 --- a/translate/src/context/Location.tsx +++ b/translate/src/context/Location.tsx @@ -22,6 +22,7 @@ export type Location = { extra: string | null; search_identifiers: boolean; search_translations_only: boolean; + search_rejected_translations: boolean; tag: string | null; author: string | null; time: string | null; @@ -37,6 +38,7 @@ const emptyParams = { extra: null, search_identifiers: false, search_translations_only: false, + search_rejected_translations: false, tag: null, author: null, time: null, @@ -98,6 +100,9 @@ function parse( extra: params.get('extra'), search_identifiers: params.has('search_identifiers'), search_translations_only: params.has('search_translations_only'), + search_rejected_translations: params.has( + 'search_rejected_translations', + ), tag: params.get('tag'), author: params.get('author'), time: params.get('time'), @@ -130,6 +135,7 @@ function stringify(prev: Location, next: string | Partial) { 'extra', 'search_identifiers', 'search_translations_only', + 'search_rejected_translations', 'tag', 'author', 'time', diff --git a/translate/src/modules/search/components/SearchBox.tsx b/translate/src/modules/search/components/SearchBox.tsx index f5a71ffaa..d619b82e2 100644 --- a/translate/src/modules/search/components/SearchBox.tsx +++ b/translate/src/modules/search/components/SearchBox.tsx @@ -41,7 +41,10 @@ type InternalProps = Props & { export type FilterType = 'authors' | 'extras' | 'statuses' | 'tags'; -export type SearchType = 'search_identifiers' | 'search_translations_only'; +export type SearchType = + | 'search_identifiers' + | 'search_translations_only' + | 'search_rejected_translations'; function getTimeRangeFromURL(timeParameter: string): TimeRangeType { const [from, to] = timeParameter.split('-'); @@ -63,6 +66,7 @@ export type FilterAction = { export type SearchState = { search_identifiers: boolean; search_translations_only: boolean; + search_rejected_translations: boolean; }; export type SearchAction = { @@ -124,6 +128,7 @@ export function SearchBoxBase({ { search_identifiers: false, search_translations_only: false, + search_rejected_translations: false, }, ); @@ -151,13 +156,22 @@ export function SearchBoxBase({ }, [parameters]); const updateOptionsFromURL = useCallback(() => { - const { search_identifiers, search_translations_only, time } = parameters; + const { + search_identifiers, + search_translations_only, + search_rejected_translations, + time, + } = parameters; updateSearchOptions([ { searchOption: 'search_identifiers', value: search_identifiers }, { searchOption: 'search_translations_only', value: search_translations_only, }, + { + searchOption: 'search_rejected_translations', + value: search_rejected_translations, + }, ]); setTimeRange(time); }, [parameters]); @@ -235,12 +249,17 @@ export function SearchBoxBase({ const applyOptions = useCallback( () => checkUnsavedChanges(() => { - const { search_identifiers, search_translations_only } = searchOptions; + const { + search_identifiers, + search_translations_only, + search_rejected_translations, + } = searchOptions; dispatch(resetEntities()); parameters.push({ ...parameters, // Persist all other variables to next state search_identifiers: search_identifiers, search_translations_only: search_translations_only, + search_rejected_translations: search_rejected_translations, entity: 0, // With the new results, the current entity might not be available anymore. }); }), diff --git a/translate/src/modules/search/constants.ts b/translate/src/modules/search/constants.ts index af5d1c6d8..64ea7ab4a 100644 --- a/translate/src/modules/search/constants.ts +++ b/translate/src/modules/search/constants.ts @@ -68,10 +68,10 @@ export const SEARCH_OPTIONS = [ name: 'Search in translations only', slug: 'search_translations_only', }, - // { - // name: 'Search in rejected translations', - // slug: 'rejected', - // }, + { + name: 'Search in rejected translations', + slug: 'search_rejected_translations', + }, // { // name: 'Match whole words', // slug: 'matchWords',