Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 53 additions & 0 deletions docs/docs/reference/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ The available operators are:
| not_eq | different | |
| nil | empty / no value | |
| not_nil | non-empty / any value | |
| only_eq_to | is only equal to | |
| not_only_eq_to | is not only equal to | |
| gt | greater than | numbers and dates |
| gte | greater than or equal | numbers and dates |
| lt | lower than | numbers and dates |
Expand Down Expand Up @@ -122,6 +124,57 @@ filters = {
};
```

- The operator `only_eq_to` differs from `eq` operator for attributes that can have several `values`. It indicates a field values are all equal to the provided filter values, and no other values. With the `or` local operator, the field should have only one value, equal to one of the filter values. With the `and` local mode, the field should have exactly all the filter values, and no other values.

The operator `not_only_eq_to` is the opposite of `only_eq_to` and returns the values that don't match the same filter with `only_eq_to` instead of `not_only_eq_to`.


<details>

<summary><strong>'Only equal to' and 'Not only equal to' detailed examples</strong></summary>

- `only_eq_to` with a single value: fields having exactly one value, corresponding to the filter value

![Only Equal To filter operator with one value](./assets/filters-OnlyEqTo.png)

- `only_eq_to` with several values and `or` local mode: fields having one value only among the filter values

![Only Equal To filter operator with 'or' mode](./assets/filters-OnlyEqTo-or.png)

- `only_eq_to` with several values and `and` local mode: fields having exactly all the filter values, and no other values

![Only Equal To filter operator with 'or' mode](./assets/filters-OnlyEqTo-and.png)

- `not_only_eq_to` with a single value

![Not Only Equal To filter operator with one value](./assets/filters-NotOnlyEqTo.png)

- `not_only_eq_to` with several values and `or` local mode

![Not Only Equal To filter operator with 'or' mode](./assets/filters-NotOnlyEqTo-or.png)

- `not_only_eq_to` with several values and `and` local mode

![Not Only Equal To filter operator with 'or' mode](./assets/filters-NotOnlyEqTo-and.png)


</details>

```ts
// Example: labels are only equal to 'label1' or 'label2', no other labels are accepted
filters = {
mode: 'and',
filters: [{
key: 'objectLabel',
values: ['label1', 'label2'],
operator: 'only_eq_to',
mode: 'or',
}],
filterGroups: [],
};
```


- There is a small difference between `search` and `contains`. `search` finds any occurrence of specified words regardless of their order, while `contains` specifically looks for the exact sequence of words you provide.

```ts
Expand Down
2 changes: 2 additions & 0 deletions opencti-platform/opencti-front/lang/front/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -2868,6 +2868,7 @@
"Not subscribed": "Nicht abonniert",
"not_contains": "enthält nicht",
"not_ends_with": "endet nicht mit",
"not_only_eq_to": "Nicht nur gleichwertig mit",
"not_starts_with": "beginnt nicht mit",
"Note type": "Art der Notiz",
"Note types": "Notiztypen",
Expand Down Expand Up @@ -2981,6 +2982,7 @@
"Only lowercase letters, numbers and hyphens are allowed": "Nur Kleinbuchstaben, Zahlen und Bindestriche sind erlaubt",
"Only successful tests allow the ingestion creation.": "Nur erfolgreiche Tests ermöglichen die Erstellung der Aufnahme.",
"Only successful tests allow the ingestion edition.": "Nur erfolgreiche Tests erlauben die Ingestion-Edition.",
"only_eq_to": "Entspricht nur",
"Open chatbot": "Chatbot öffnen",
"Open correlation details": "Details der offenen Korrelation",
"Open export panel": "Export-Panel öffnen",
Expand Down
2 changes: 2 additions & 0 deletions opencti-platform/opencti-front/lang/front/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2868,6 +2868,7 @@
"Not subscribed": "Not subscribed",
"not_contains": "not contains",
"not_ends_with": "not ends with",
"not_only_eq_to": "Not only equal to",
"not_starts_with": "not starts with",
"Note type": "Note type",
"Note types": "Note types",
Expand Down Expand Up @@ -2981,6 +2982,7 @@
"Only lowercase letters, numbers and hyphens are allowed": "Only lowercase letters, numbers and hyphens are allowed",
"Only successful tests allow the ingestion creation.": "Only successful tests allow the ingestion creation.",
"Only successful tests allow the ingestion edition.": "Only successful tests allow the ingestion edition.",
"only_eq_to": "Only equal to",
"Open chatbot": "Open chatbot",
"Open correlation details": "Open correlation details",
"Open export panel": "Open export panel",
Expand Down
2 changes: 2 additions & 0 deletions opencti-platform/opencti-front/lang/front/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -2868,6 +2868,7 @@
"Not subscribed": "No suscrito",
"not_contains": "no contiene",
"not_ends_with": "no termina con",
"not_only_eq_to": "No sólo igual a",
"not_starts_with": "no comienza con",
"Note type": "Tipo de nota",
"Note types": "Tipos de notas",
Expand Down Expand Up @@ -2981,6 +2982,7 @@
"Only lowercase letters, numbers and hyphens are allowed": "Sólo se permiten letras minúsculas, números y guiones",
"Only successful tests allow the ingestion creation.": "Sólo las pruebas exitosas permiten la creación de la ingesta.",
"Only successful tests allow the ingestion edition.": "Sólo las pruebas exitosas permiten la edición de ingestión.",
"only_eq_to": "Sólo equivale a",
"Open chatbot": "Abrir chatbot",
"Open correlation details": "Detalles de la correlación abierta",
"Open export panel": "Abrir panel de exportación",
Expand Down
2 changes: 2 additions & 0 deletions opencti-platform/opencti-front/lang/front/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -2868,6 +2868,7 @@
"Not subscribed": "Pas d'abonnement",
"not_contains": "ne contient pas",
"not_ends_with": "ne se termine pas par",
"not_only_eq_to": "Non seulement égale à",
"not_starts_with": "ne commence pas par",
"Note type": "Type de note",
"Note types": "Types de note",
Expand Down Expand Up @@ -2981,6 +2982,7 @@
"Only lowercase letters, numbers and hyphens are allowed": "Seuls les lettres minuscules, les chiffres et les traits d'union sont autorisés",
"Only successful tests allow the ingestion creation.": "Seuls les tests réussis permettent la création de l'ingestion.",
"Only successful tests allow the ingestion edition.": "Seuls les tests réussis permettent l'édition de l'ingestion.",
"only_eq_to": "Seulement égal à",
"Open chatbot": "Chatbot ouvert",
"Open correlation details": "Détails de la corrélation ouverte",
"Open export panel": "Ouvrir le volet d'export",
Expand Down
2 changes: 2 additions & 0 deletions opencti-platform/opencti-front/lang/front/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -2868,6 +2868,7 @@
"Not subscribed": "Non iscritto",
"not_contains": "non contiene",
"not_ends_with": "non termina con",
"not_only_eq_to": "Non solo uguale a",
"not_starts_with": "non inizia con",
"Note type": "Tipo di nota",
"Note types": "Tipi di note",
Expand Down Expand Up @@ -2981,6 +2982,7 @@
"Only lowercase letters, numbers and hyphens are allowed": "Sono consentiti solo lettere minuscole, numeri e trattini",
"Only successful tests allow the ingestion creation.": "Solo i test riusciti consentono la creazione dell'ingestione.",
"Only successful tests allow the ingestion edition.": "Solo i test riusciti consentono la modifica dell'ingestione.",
"only_eq_to": "Solo uguale a",
"Open chatbot": "Chatbot aperto",
"Open correlation details": "Apri i dettagli della correlazione",
"Open export panel": "Apri il pannello di esportazione",
Expand Down
2 changes: 2 additions & 0 deletions opencti-platform/opencti-front/lang/front/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -2868,6 +2868,7 @@
"Not subscribed": "未登録",
"not_contains": "を含まない",
"not_ends_with": "で終わらない",
"not_only_eq_to": "に匹敵するだけではない。",
"not_starts_with": "で始まらない",
"Note type": "ノートタイプ",
"Note types": "ノートの種類",
Expand Down Expand Up @@ -2981,6 +2982,7 @@
"Only lowercase letters, numbers and hyphens are allowed": "小文字、数字、ハイフンのみ使用可",
"Only successful tests allow the ingestion creation.": "成功したテストのみが取り込みの作成を許可します。",
"Only successful tests allow the ingestion edition.": "成功したテストのみがインジェスト版を許可します。",
"only_eq_to": "に等しい。",
"Open chatbot": "オープンチャットボット",
"Open correlation details": "相関関係の詳細を開く",
"Open export panel": "出力画面を開く",
Expand Down
2 changes: 2 additions & 0 deletions opencti-platform/opencti-front/lang/front/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -2868,6 +2868,7 @@
"Not subscribed": "구독하지 않음",
"not_contains": "포함하지 않음",
"not_ends_with": "끝나지 않음",
"not_only_eq_to": "다음과 같을 뿐만 아니라",
"not_starts_with": "시작하지 않음",
"Note type": "노트 유형",
"Note types": "노트 유형",
Expand Down Expand Up @@ -2981,6 +2982,7 @@
"Only lowercase letters, numbers and hyphens are allowed": "소문자, 숫자 및 하이픈만 허용됩니다",
"Only successful tests allow the ingestion creation.": "성공적인 테스트만 인게스트 생성을 허용합니다.",
"Only successful tests allow the ingestion edition.": "성공적인 테스트만 인게스트 에디션을 허용합니다.",
"only_eq_to": "다음과 같습니다",
"Open chatbot": "오픈 챗봇",
"Open correlation details": "상관관계 세부 정보 열기",
"Open export panel": "내보내기 패널 열기",
Expand Down
2 changes: 2 additions & 0 deletions opencti-platform/opencti-front/lang/front/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -2868,6 +2868,7 @@
"Not subscribed": "Не подписаны",
"not_contains": "не_содержит",
"not_ends_with": "не_заканчивается_с",
"not_only_eq_to": "Не только равный",
"not_starts_with": "не_начинается_с",
"Note type": "Тип ноты",
"Note types": "Типы заметок",
Expand Down Expand Up @@ -2981,6 +2982,7 @@
"Only lowercase letters, numbers and hyphens are allowed": "Допускаются только строчные буквы, цифры и дефисы",
"Only successful tests allow the ingestion creation.": "Только успешные тесты позволяют создать впуск.",
"Only successful tests allow the ingestion edition.": "Только успешно пройденные тесты дают право на включение издания.",
"only_eq_to": "Равняется только",
"Open chatbot": "Открытый чатбот",
"Open correlation details": "Детали открытой корреляции",
"Open export panel": "Откройте панель экспорта",
Expand Down
2 changes: 2 additions & 0 deletions opencti-platform/opencti-front/lang/front/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -2868,6 +2868,7 @@
"Not subscribed": "未订阅",
"not_contains": "不包含",
"not_ends_with": "不以...结尾",
"not_only_eq_to": "不仅等于",
"not_starts_with": "不以...开始",
"Note type": "注释类型",
"Note types": "笔记类型",
Expand Down Expand Up @@ -2981,6 +2982,7 @@
"Only lowercase letters, numbers and hyphens are allowed": "只允许使用小写字母、数字和连字符",
"Only successful tests allow the ingestion creation.": "只有成功的测试才允许创建摄取。",
"Only successful tests allow the ingestion edition.": "只有成功的测试才允许摄取版本。",
"only_eq_to": "只相当于",
"Open chatbot": "开放式聊天机器人",
"Open correlation details": "开放式关联详细信息",
"Open export panel": "打开导出面板",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ const OperatorKeyValues: {
not_ends_with: 'Not ends with',
search: 'Search',
within: 'Within',
only_eq_to: 'Only equal to',
not_only_eq_to: 'Not only equal to',
};

export const FilterChipPopover: FunctionComponent<FilterChipMenuProps> = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ enum FilterOperator {
not_nil
search
within
only_eq_to
not_only_eq_to
}

enum XTMHubRegistrationStatus {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,11 @@ export const getAvailableOperatorForFilterKey = (
}
}

return ['eq', 'not_eq', 'nil', 'not_nil']; // vocabulary or id
if (filterDefinition.multiple) {
return ['eq', 'not_eq', 'only_eq_to', 'not_only_eq_to', 'nil', 'not_nil'];
}

return ['eq', 'not_eq', 'nil', 'not_nil'];
};

export const getAvailableOperatorForFilter = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ enum FilterOperator {
not_nil
search
within
only_eq_to
not_only_eq_to
}

enum XTMHubRegistrationStatus {
Expand Down
28 changes: 27 additions & 1 deletion opencti-platform/opencti-graphql/src/database/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2285,6 +2285,9 @@ const buildFieldForQuery = (field: string) => {
? field
: `${field}.keyword`;
};
const buildFieldForScriptQuery = (field: string) => {
return buildFieldForQuery(field).replaceAll('*', 'internal_id');
};
export const buildLocalMustFilter = (validFilter: any) => {
const valuesFiltering = [];
const noValuesFiltering = [];
Expand All @@ -2294,7 +2297,7 @@ export const buildLocalMustFilter = (validFilter: any) => {
}
const arrayKeys = Array.isArray(key) ? key : [key];
const headKey = R.head(arrayKeys);
const dontHandleMultipleKeys = nested || operator === 'nil' || operator === 'not_nil';
const dontHandleMultipleKeys = nested || operator === 'nil' || operator === 'not_nil' || operator === 'only_eq_to' || operator === 'not_only_eq_to';
if (dontHandleMultipleKeys && arrayKeys.length > 1) {
throw UnsupportedError('Filter must have only one field', { keys: arrayKeys, operator });
}
Expand Down Expand Up @@ -2560,6 +2563,29 @@ export const buildLocalMustFilter = (validFilter: any) => {
query: values[i].toString(),
},
});
} else if (operator === 'only_eq_to' || operator === 'not_only_eq_to') {
const targets = operator === 'only_eq_to' ? valuesFiltering : noValuesFiltering;
targets.push({
script: {
script: {
source: `
def fieldValues = doc['${buildFieldForScriptQuery(headKey)}'];
if (fieldValues == null || fieldValues.length == 0) return false;
def filterValues = params.values;
if (params.mode == 'and') {
return fieldValues.length == filterValues.length && fieldValues.every(v -> filterValues.contains(v));
} else if (params.mode == 'or') {
return fieldValues.length == 1 && filterValues.contains(fieldValues[0]);
}
return false;
`,
params: {
values,
mode: localFilterMode,
},
},
},
});
} else if (operator === 'match') {
valuesFiltering.push({
multi_match: {
Expand Down
2 changes: 2 additions & 0 deletions opencti-platform/opencti-graphql/src/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9538,7 +9538,9 @@ export enum FilterOperator {
NotEndsWith = 'not_ends_with',
NotEq = 'not_eq',
NotNil = 'not_nil',
NotOnlyEqTo = 'not_only_eq_to',
NotStartsWith = 'not_starts_with',
OnlyEqTo = 'only_eq_to',
Script = 'script',
Search = 'search',
StartsWith = 'starts_with',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ export const operatorDescription = {
description:
'Filters for values falling within the specified boundaries or range (e.g., between two numbers or dates).',
},
[FilterOperator.OnlyEqTo]: {
description:
'Filters for all values exactly matching the given criterion.',
},
[FilterOperator.NotOnlyEqTo]: {
description:
'Filters for all values that do not exactly match the given criterion.',
},
};

export const operatorKeys = Object.values(FilterOperator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const testGenericFilter = <T extends string | number | boolean>(
stixCandidates: T[],
) => {
const op = operator ?? 'eq';
const operationMode = mode ?? 'and';
const operationMode = mode ?? 'or';
// "(not) nil" or "(not) equal to nothing" is resolved the same way
if (op === 'nil' || (op === 'eq' && adaptedFilterValues.length === 0)) {
return stixCandidates.length === 0;
Expand All @@ -60,8 +60,10 @@ export const testGenericFilter = <T extends string | number | boolean>(
|| (op === 'not_starts_with' && adaptedFilterValues.every((v) => !stixCandidates.some((c) => typeof c === 'string' && typeof v === 'string' && c.startsWith(v))))
|| (op === 'ends_with' && adaptedFilterValues.every((v) => stixCandidates.some((c) => typeof c === 'string' && typeof v === 'string' && c.endsWith(v))))
|| (op === 'not_ends_with' && adaptedFilterValues.every((v) => !stixCandidates.some((c) => typeof c === 'string' && typeof v === 'string' && c.endsWith(v))))
|| (op === 'only_eq_to' && adaptedFilterValues.every((v) => stixCandidates.includes(v)) && stixCandidates.every((c) => adaptedFilterValues.includes(c)))
|| (op === 'not_only_eq_to' && !(adaptedFilterValues.every((v) => stixCandidates.includes(v)) && stixCandidates.every((c) => adaptedFilterValues.includes(c))))
|| (op === 'search' && adaptedFilterValues.every((v) => stixCandidates.some((c) => typeof c === 'string' && typeof v === 'string'
&& (v.split(' ').some((word) => c.includes(word)))))) // a stix candidate should contains at least one of the filter values words
&& (v.split(' ').some((word) => c.includes(word)))))) // a stix candidate should contain at least one of the filter values words

// In real cases, there is only 1 filter value with the next operators (not much sense otherwise)
|| (op === 'lt' && adaptedFilterValues.every((v) => stixCandidates.some((c) => c < v)))
Expand All @@ -80,8 +82,10 @@ export const testGenericFilter = <T extends string | number | boolean>(
|| (op === 'not_starts_with' && adaptedFilterValues.some((v) => !stixCandidates.some((c) => typeof c === 'string' && typeof v === 'string' && c.startsWith(v))))
|| (op === 'ends_with' && adaptedFilterValues.some((v) => stixCandidates.some((c) => typeof c === 'string' && typeof v === 'string' && c.endsWith(v))))
|| (op === 'not_ends_with' && adaptedFilterValues.some((v) => !stixCandidates.some((c) => typeof c === 'string' && typeof v === 'string' && c.endsWith(v))))
|| (op === 'only_eq_to' && stixCandidates.length === 1 && adaptedFilterValues.some((v) => stixCandidates[0] === v))
|| (op === 'not_only_eq_to' && !(stixCandidates.length === 1 && adaptedFilterValues.some((v) => stixCandidates[0] === v)))
|| (op === 'search' && adaptedFilterValues.some((v) => stixCandidates.some((c) => typeof c === 'string' && typeof v === 'string'
&& (v.split(' ').some((word) => c.includes(word)))))) // a stix candidate should contains at least one of the filter values words
&& (v.split(' ').some((word) => c.includes(word)))))) // a stix candidate should contain at least one of the filter values words

// In real cases, there is only 1 filter value with the next operators (not much sense otherwise)
|| (op === 'lt' && adaptedFilterValues.some((v) => stixCandidates.some((c) => c < v)))
Expand Down
Loading