Skip to content

Commit

Permalink
Implement search filter for matching case (mozilla#3318)
Browse files Browse the repository at this point in the history
  • Loading branch information
harmitgoswami authored Sep 9, 2024
1 parent 6df98ce commit 7c56875
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 48 deletions.
1 change: 1 addition & 0 deletions pontoon/base/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ class GetEntitiesForm(forms.Form):
search_identifiers = forms.BooleanField(required=False)
search_translations_only = forms.BooleanField(required=False)
search_rejected_translations = forms.BooleanField(required=False)
search_match_case = forms.BooleanField(required=False)
tag = forms.CharField(required=False)
time = forms.CharField(required=False)
author = forms.CharField(required=False)
Expand Down
69 changes: 46 additions & 23 deletions pontoon/base/models/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,7 @@ def for_project_locale(
search_identifiers=None,
search_translations_only=None,
search_rejected_translations=None,
search_match_case=None,
time=None,
author=None,
review_time=None,
Expand Down Expand Up @@ -842,21 +843,29 @@ def for_project_locale(
# 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)

# Include rejected translations
q_rejected = (
Q()
) # if search_rejected_translations else Q(translation__rejected=False)
Q() if search_rejected_translations else Q(translation__rejected=False)
)

# Modify query based on case sensitivity filter
translation_case_lookup = (
"contains" if search_match_case else "icontains_collate"
)

translation_filters = (
Q(translation__string__icontains_collate=(search, locale.db_collation))
Q(
**{
f"translation__string__{translation_case_lookup}": (
search,
locale.db_collation,
)
}
)
& Q(translation__locale=locale)
& q_rejected
for search in search_list
Expand All @@ -866,22 +875,36 @@ def for_project_locale(
"id", flat=True
)

# if not search_translations_only:
q_key = Q(key__icontains=search) # if search_identifiers else Q()
entity_filters = (
Q(string__icontains=search) | Q(string_plural__icontains=search) | q_key
for search in search_list
)
# Search in source strings
if not search_translations_only:
# Search in string (context) identifiers

case_lookup = "contains" if search_match_case else "icontains"
q_key = Q()
# TODO: Uncomment the 5 lines below to reactivate the
# context identifiers filter once .ftl bug is fixed (issue #3284):
# q_key = (
# Q(**{f"key__{case_lookup}": (search)})
# if search_identifiers
# else Q()
# )

entity_filters = (
Q(**{f"string__{case_lookup}": (search)})
| Q(**{f"string_plural__{case_lookup}": (search)})
| q_key
for search in search_list
)

entity_matches = entities.filter(*entity_filters).values_list(
"id", flat=True
)
entity_matches = entities.filter(*entity_filters).values_list(
"id", flat=True
)

entities = Entity.objects.filter(
pk__in=set(list(translation_matches) + list(entity_matches))
)
# else:
# entities = Entity.objects.filter(pk__in=set(list(translation_matches)))
entities = Entity.objects.filter(
pk__in=set(list(translation_matches) + list(entity_matches))
)
else:
entities = Entity.objects.filter(pk__in=set(list(translation_matches)))

order_fields = ("resource__order", "order")
if project.slug == "all-projects":
Expand Down
1 change: 1 addition & 0 deletions pontoon/base/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ def entities(request):
"search_identifiers",
"search_translations_only",
"search_rejected_translations",
"search_match_case",
"time",
"author",
"review_time",
Expand Down
1 change: 1 addition & 0 deletions translate/src/api/entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ function buildFetchPayload(
'search_identifiers',
'search_translations_only',
'search_rejected_translations',
'search_match_case',
'extra',
'tag',
'author',
Expand Down
4 changes: 4 additions & 0 deletions translate/src/context/Location.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type Location = {
search_identifiers: boolean;
search_translations_only: boolean;
search_rejected_translations: boolean;
search_match_case: boolean;
tag: string | null;
author: string | null;
time: string | null;
Expand All @@ -39,6 +40,7 @@ const emptyParams = {
search_identifiers: false,
search_translations_only: false,
search_rejected_translations: false,
search_match_case: false,
tag: null,
author: null,
time: null,
Expand Down Expand Up @@ -103,6 +105,7 @@ function parse(
search_rejected_translations: params.has(
'search_rejected_translations',
),
search_match_case: params.has('search_match_case'),
tag: params.get('tag'),
author: params.get('author'),
time: params.get('time'),
Expand Down Expand Up @@ -136,6 +139,7 @@ function stringify(prev: Location, next: string | Partial<Location>) {
'search_identifiers',
'search_translations_only',
'search_rejected_translations',
'search_match_case',
'tag',
'author',
'time',
Expand Down
11 changes: 8 additions & 3 deletions translate/src/modules/placeable/components/Highlight.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Localized } from '@fluent/react';
import escapeRegExp from 'lodash.escaperegexp';
import React from 'react';
import React, { useContext } from 'react';
import { TermState } from '~/modules/terms';
import { Location } from '~/context/Location';

import './Highlight.css';
import { ReactElement } from 'react';
Expand Down Expand Up @@ -63,6 +64,7 @@ export function Highlight({
length: number;
mark: ReactElement;
}> = [];
const location = useContext(Location);

for (const match of source.matchAll(placeholder)) {
let l10nId: string;
Expand Down Expand Up @@ -171,10 +173,13 @@ export function Highlight({
if (term.startsWith('"') && term.length >= 3 && term.endsWith('"')) {
term = term.slice(1, -1);
}
let lcTerm = term.toLowerCase();
const highlightTerm = location.search_match_case
? term
: term.toLowerCase();
const highlightSource = location.search_match_case ? source : lcSource;
let pos = 0;
let next: number;
while ((next = lcSource.indexOf(lcTerm, pos)) !== -1) {
while ((next = highlightSource.indexOf(highlightTerm, pos)) !== -1) {
let i = marks.findIndex((m) => m.index + m.length > next);
if (i === -1) {
i = marks.length;
Expand Down
12 changes: 11 additions & 1 deletion translate/src/modules/search/components/SearchBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ export type FilterType = 'authors' | 'extras' | 'statuses' | 'tags';
export type SearchType =
| 'search_identifiers'
| 'search_translations_only'
| 'search_rejected_translations';
| 'search_rejected_translations'
| 'search_match_case';

function getTimeRangeFromURL(timeParameter: string): TimeRangeType {
const [from, to] = timeParameter.split('-');
Expand All @@ -67,6 +68,7 @@ export type SearchState = {
search_identifiers: boolean;
search_translations_only: boolean;
search_rejected_translations: boolean;
search_match_case: boolean;
};

export type SearchAction = {
Expand Down Expand Up @@ -129,6 +131,7 @@ export function SearchBoxBase({
search_identifiers: false,
search_translations_only: false,
search_rejected_translations: false,
search_match_case: false,
},
);

Expand Down Expand Up @@ -160,6 +163,7 @@ export function SearchBoxBase({
search_identifiers,
search_translations_only,
search_rejected_translations,
search_match_case,
time,
} = parameters;
updateSearchOptions([
Expand All @@ -172,6 +176,10 @@ export function SearchBoxBase({
searchOption: 'search_rejected_translations',
value: search_rejected_translations,
},
{
searchOption: 'search_match_case',
value: search_match_case,
},
]);
setTimeRange(time);
}, [parameters]);
Expand Down Expand Up @@ -253,13 +261,15 @@ export function SearchBoxBase({
search_identifiers,
search_translations_only,
search_rejected_translations,
search_match_case,
} = 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,
search_match_case: search_match_case,
entity: 0, // With the new results, the current entity might not be available anymore.
});
}),
Expand Down
15 changes: 2 additions & 13 deletions translate/src/modules/search/components/SearchPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ import { useOnDiscard } from '~/utils';

import './SearchPanel.css';

// TODO: Remove the variable below to reactivate the feature once
// all search options are implemented
// Disable SearchPanel component until fully complete
const disable: Boolean = true;

type Props = {
searchOptions: SearchState;
applyOptions: () => void;
Expand Down Expand Up @@ -123,16 +118,10 @@ export function SearchPanel({

return (
<div className='search-panel'>
{/* {TODO: Remove the style attribute for the div below} */}
<div
className='visibility-switch'
style={{ cursor: disable ? 'default' : 'pointer' }}
onClick={toggleVisible}
>
<div className='visibility-switch' onClick={toggleVisible}>
<span className='fa fa-search'></span>
</div>
{/* {TODO: Remove the second condition below} */}
{visible && !disable ? (
{visible ? (
<SearchPanelDialog
searchOptions={searchOptions}
onApplyOptions={handleApplyOptions}
Expand Down
16 changes: 8 additions & 8 deletions translate/src/modules/search/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ export const FILTERS_EXTRA = [
] as const;

export const SEARCH_OPTIONS = [
{
name: 'Search in string identifiers',
slug: 'search_identifiers',
},
// {
// name: 'Search in string identifiers',
// slug: 'search_identifiers',
// },
{
name: 'Search in translations only',
slug: 'search_translations_only',
Expand All @@ -76,8 +76,8 @@ export const SEARCH_OPTIONS = [
// name: 'Match whole words',
// slug: 'matchWords',
// },
// {
// name: 'Match case',
// slug: 'matchCase',
// },
{
name: 'Match case',
slug: 'search_match_case',
},
] as const;

0 comments on commit 7c56875

Please sign in to comment.