diff --git a/frontend/src/Components/Filter/Builder/FilterBuilderRow.js b/frontend/src/Components/Filter/Builder/FilterBuilderRow.js
index 9fd450d2ef..c8218cc1b9 100644
--- a/frontend/src/Components/Filter/Builder/FilterBuilderRow.js
+++ b/frontend/src/Components/Filter/Builder/FilterBuilderRow.js
@@ -3,6 +3,7 @@ import React, { Component } from 'react';
import SelectInput from 'Components/Form/SelectInput';
import IconButton from 'Components/Link/IconButton';
import { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Props';
+import * as filterTypes from 'Helpers/Props/filterTypes';
import sortByProp from 'Utilities/Array/sortByProp';
import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
import DateFilterBuilderRowValue from './DateFilterBuilderRowValue';
@@ -226,6 +227,13 @@ class FilterBuilderRow extends Component {
const selectedFilterBuilderProp = this.selectedFilterBuilderProp;
+ const operatorsWithoutValue = [
+ filterTypes.IS_EMPTY,
+ filterTypes.IS_NOT_EMPTY
+ ];
+
+ const requiresValue = !operatorsWithoutValue.includes(filterType);
+
const keyOptions = filterBuilderProps.map((availablePropFilter) => {
const { name, label } = availablePropFilter;
@@ -262,10 +270,11 @@ class FilterBuilderRow extends Component {
/>
}
-
-
- {
- filterValue != null && !!selectedFilterBuilderProp &&
+ {
+ requiresValue &&
+ filterValue != null &&
+ !!selectedFilterBuilderProp &&
+
- }
-
+
+ }
translate('FilterDoesNotContain')
+ },
+ {
+ key: filterTypes.IS_EMPTY,
+ value: () => translate('FilterIsEmpty')
+ },
+ {
+ key: filterTypes.IS_NOT_EMPTY,
+ value: () => translate('FilterIsNotEmpty')
}
],
diff --git a/frontend/src/Helpers/Props/filterTypePredicates.js b/frontend/src/Helpers/Props/filterTypePredicates.js
index d07059c02a..007533ece2 100644
--- a/frontend/src/Helpers/Props/filterTypePredicates.js
+++ b/frontend/src/Helpers/Props/filterTypePredicates.js
@@ -55,6 +55,30 @@ const filterTypePredicates = {
[filterTypes.NOT_ENDS_WITH]: function(itemValue, filterValue) {
return !itemValue.toLowerCase().endsWith(filterValue.toLowerCase());
+ },
+
+ [filterTypes.IS_EMPTY]: function(itemValue, filterValue) {
+ if (itemValue === null || itemValue === undefined) {
+ return true;
+ }
+
+ if (Array.isArray(itemValue) || typeof itemValue === 'string') {
+ return itemValue.length === 0;
+ }
+
+ return false;
+ },
+
+ [filterTypes.IS_NOT_EMPTY]: function(itemValue, filterValue) {
+ if (itemValue === null || itemValue === undefined) {
+ return false;
+ }
+
+ if (Array.isArray(itemValue) || typeof itemValue === 'string') {
+ return itemValue.length > 0;
+ }
+
+ return true;
}
};
diff --git a/frontend/src/Helpers/Props/filterTypes.js b/frontend/src/Helpers/Props/filterTypes.js
index 239a4e7e94..c47f8b29f9 100644
--- a/frontend/src/Helpers/Props/filterTypes.js
+++ b/frontend/src/Helpers/Props/filterTypes.js
@@ -14,6 +14,8 @@ export const STARTS_WITH = 'startsWith';
export const NOT_STARTS_WITH = 'notStartsWith';
export const ENDS_WITH = 'endsWith';
export const NOT_ENDS_WITH = 'notEndsWith';
+export const IS_EMPTY = 'isEmpty';
+export const IS_NOT_EMPTY = 'isNotEmpty';
export const all = [
CONTAINS,
@@ -31,5 +33,7 @@ export const all = [
STARTS_WITH,
NOT_STARTS_WITH,
ENDS_WITH,
- NOT_ENDS_WITH
+ NOT_ENDS_WITH,
+ IS_EMPTY,
+ IS_NOT_EMPTY
];
diff --git a/frontend/src/Store/Selectors/createClientSideCollectionSelector.js b/frontend/src/Store/Selectors/createClientSideCollectionSelector.js
index 1bac14f087..a87e354a11 100644
--- a/frontend/src/Store/Selectors/createClientSideCollectionSelector.js
+++ b/frontend/src/Store/Selectors/createClientSideCollectionSelector.js
@@ -43,7 +43,7 @@ function filter(items, state) {
if (filterPredicates && filterPredicates.hasOwnProperty(key)) {
const predicate = filterPredicates[key];
- if (Array.isArray(value)) {
+ if (Array.isArray(value) && value.length > 0) {
if (
type === filterTypes.NOT_CONTAINS ||
type === filterTypes.NOT_EQUAL
diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json
index 23a3b71ab8..10c1377c81 100644
--- a/src/NzbDrone.Core/Localization/Core/en.json
+++ b/src/NzbDrone.Core/Localization/Core/en.json
@@ -697,6 +697,8 @@
"FilterNotInLast": "not in the last",
"FilterNotInNext": "not in the next",
"FilterStartsWith": "starts with",
+ "FilterIsEmpty": "is empty",
+ "FilterIsNotEmpty": "is NOT empty",
"Filters": "Filters",
"FilterMoviePropertiesOnlyNotFileWarning": "Filters are available only for the properties of a movie, they are not available for properties of the file(s) you may have for that movie.",
"FirstDayOfWeek": "First Day of Week",