From cf63dac135ba79bfaf8210ebfc6f39d65b443b27 Mon Sep 17 00:00:00 2001
From: Julian Gernun <17549662+jcger@users.noreply.github.com>
Date: Thu, 9 Nov 2023 17:40:28 +0100
Subject: [PATCH 01/32] first commit
---
.../components/all_cases/table_filters.tsx | 255 +++++++++++++++---
.../toggle/configure_toggle_field.ts | 1 +
.../public/components/custom_fields/types.ts | 1 +
3 files changed, 215 insertions(+), 42 deletions(-)
diff --git a/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx b/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx
index bb96b0dfa4346..0d032cbe6244e 100644
--- a/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx
@@ -6,9 +6,11 @@
*/
import React, { useCallback, useState } from 'react';
-import { isEqual } from 'lodash/fp';
+import { isEqual, difference } from 'lodash/fp';
import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch, EuiFilterGroup, EuiButton } from '@elastic/eui';
+import { builderMap as customFieldsBuilder } from '../custom_fields/builder';
+import { useGetCaseConfiguration } from '../../containers/configure/use_get_case_configuration';
import type { CaseStatuses } from '../../../common/types/domain';
import { MAX_TAGS_FILTER_LENGTH, MAX_CATEGORY_FILTER_LENGTH } from '../../../common/constants';
import type { FilterOptions } from '../../containers/types';
@@ -38,6 +40,184 @@ interface CasesTableFiltersProps {
filterOptions: FilterOptions;
}
+const getSystemFilterConfig = ({
+ availableSolutions,
+ caseAssignmentAuthorized,
+ isSelectorView,
+}: {
+ availableSolutions: string[];
+ caseAssignmentAuthorized: boolean;
+ isSelectorView: boolean;
+}) => {
+ return [
+ {
+ key: 'severity',
+ isActive: true,
+ isAvailable: true,
+ render: ({ filterOptions, onChange }) => (
+
+ ),
+ },
+ {
+ key: 'status',
+ isActive: true,
+ isAvailable: true,
+ render: ({
+ filterOptions,
+ onChange,
+ hiddenStatuses,
+ countClosedCases,
+ countInProgressCases,
+ countOpenCases,
+ }) => (
+
+ ),
+ },
+ // {
+ // key: 'assignee',
+ // isActive: true,
+ // isAvailable: caseAssignmentAuthorized && !isSelectorView,
+ // render: ({ filterOptions, onChange }) => (
+ //
+ // ),
+ // },
+ {
+ key: 'tags',
+ isActive: true,
+ isAvailable: true,
+ render: ({ filterOptions, onChange, tags }) => (
+
+ ),
+ },
+ {
+ key: 'category',
+ isActive: true,
+ isAvailable: true,
+ render: ({ filterOptions, onChange, categories }) => (
+
+ ),
+ },
+ {
+ key: 'owner',
+ isActive: true,
+ isAvailable: availableSolutions.length > 1,
+ render: ({ filterOptions, onChange }) => (
+
+ ),
+ },
+ ].filter((filter) => filter.isAvailable);
+};
+
+const useCustomFieldsFilterConfig = () => {
+ const {
+ data: { customFields },
+ } = useGetCaseConfiguration();
+
+ const customFieldsFilterConfig = [];
+ for (const { type, label } of customFields ?? []) {
+ if (customFieldsBuilder[type]) {
+ customFieldsFilterConfig.push({
+ key: label,
+ isActive: false,
+ render: () =>
TODO
,
+ });
+ }
+ }
+
+ return { customFieldsFilterConfig };
+};
+
+const useFilterConfig = ({
+ availableSolutions,
+ caseAssignmentAuthorized,
+ isSelectorView,
+}: {
+ availableSolutions: string[];
+ caseAssignmentAuthorized: boolean;
+ isSelectorView: boolean;
+}) => {
+ const { customFieldsFilterConfig } = useCustomFieldsFilterConfig();
+ const [config, setConfig] = useState(() => [
+ ...getSystemFilterConfig({
+ availableSolutions,
+ caseAssignmentAuthorized,
+ isSelectorView,
+ }),
+ ...customFieldsFilterConfig,
+ ]);
+
+ const filterConfigOptions = config.map((filter) => filter.key);
+ const selectedFilterConfigOptions = config
+ .filter((filter) => filter.isActive)
+ .filter(Boolean)
+ .map((filter) => filter.key);
+
+ const onFilterConfigChange = ({ filterId, options }: { filterId: string; options: string[] }) => {
+ const addedOption = difference(options, selectedFilterConfigOptions)[0];
+ const removedOption = difference(selectedFilterConfigOptions, options)[0];
+
+ let newConfig;
+ if (addedOption) {
+ const addedFilter = config.find((filter) => filter.key === addedOption);
+ newConfig = config.filter((filter) => filter.key !== addedOption);
+ newConfig.push({
+ ...addedFilter,
+ isActive: true,
+ });
+ } else if (removedOption) {
+ newConfig = config.map((filter) => {
+ if (filter.key === removedOption) {
+ return {
+ ...filter,
+ isActive: false,
+ };
+ }
+ return filter;
+ });
+ }
+ setConfig(newConfig);
+ };
+
+ return {
+ config,
+ filterConfigOptions: filterConfigOptions.sort(),
+ onFilterConfigChange,
+ selectedFilterConfigOptions,
+ };
+};
+
const CasesTableFiltersComponent = ({
countClosedCases,
countOpenCases,
@@ -57,7 +237,18 @@ const CasesTableFiltersComponent = ({
const { data: categories = [] } = useGetCategories();
const { caseAssignmentAuthorized } = useCasesFeatures();
- const onChange = ({
+ const {
+ config: filterConfig,
+ filterConfigOptions,
+ onFilterConfigChange,
+ selectedFilterConfigOptions,
+ } = useFilterConfig({
+ availableSolutions,
+ caseAssignmentAuthorized,
+ isSelectorView,
+ });
+
+ const onFilterOptionChange = ({
filterId,
options,
}: {
@@ -129,48 +320,28 @@ const CasesTableFiltersComponent = ({
-
-
- {caseAssignmentAuthorized && !isSelectorView ? (
-
- ) : null}
-
+ {filterConfig
+ .filter((filter) => filter.isActive)
+ .map((filter) =>
+ filter.render({
+ onChange: onFilterOptionChange,
+ filterOptions,
+ hiddenStatuses,
+ countClosedCases,
+ countInProgressCases,
+ countOpenCases,
+ tags,
+ categories,
+ })
+ )}
- {availableSolutions.length > 1 && (
-
- )}
diff --git a/x-pack/plugins/cases/public/components/custom_fields/toggle/configure_toggle_field.ts b/x-pack/plugins/cases/public/components/custom_fields/toggle/configure_toggle_field.ts
index 00f103fcfdd6a..e5fba24f5b5f2 100644
--- a/x-pack/plugins/cases/public/components/custom_fields/toggle/configure_toggle_field.ts
+++ b/x-pack/plugins/cases/public/components/custom_fields/toggle/configure_toggle_field.ts
@@ -23,4 +23,5 @@ export const configureToggleCustomFieldFactory: CustomFieldFactory = () => {
id: string;
label: string;
build: () => CustomFieldType;
+ filterOptions?: string[];
};
export type CustomFieldBuilderMap = {
From 4b5371f713e1d2f01c4d91a933cf82776f48014f Mon Sep 17 00:00:00 2001
From: Julian Gernun <17549662+jcger@users.noreply.github.com>
Date: Fri, 10 Nov 2023 16:23:52 +0100
Subject: [PATCH 02/32] working filter status
---
.../all_cases/multi_select_filter.tsx | 18 +-
.../components/all_cases/severity_filter.tsx | 3 +-
.../components/all_cases/status_filter.tsx | 3 +-
.../components/all_cases/table_filters.tsx | 272 +++---------------
.../all_cases/use_filter_config.tsx | 185 ++++++++++++
.../all_cases/use_system_filter_config.tsx | 227 +++++++++++++++
6 files changed, 464 insertions(+), 244 deletions(-)
create mode 100644 x-pack/plugins/cases/public/components/all_cases/use_filter_config.tsx
create mode 100644 x-pack/plugins/cases/public/components/all_cases/use_system_filter_config.tsx
diff --git a/x-pack/plugins/cases/public/components/all_cases/multi_select_filter.tsx b/x-pack/plugins/cases/public/components/all_cases/multi_select_filter.tsx
index 552753105f1ab..a56c6cc447d5f 100644
--- a/x-pack/plugins/cases/public/components/all_cases/multi_select_filter.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/multi_select_filter.tsx
@@ -20,7 +20,6 @@ import {
useEuiTheme,
} from '@elastic/eui';
import { isEqual } from 'lodash/fp';
-import type { FilterOptions } from '../../../common/ui/types';
import * as i18n from './translations';
const fromRawOptionsToEuiSelectableOptions = (options: string[], selectedOptions: string[]) => {
@@ -44,29 +43,32 @@ const getEuiSelectableCheckedOptions = (options: EuiSelectableOption[]) =>
interface UseFilterParams {
buttonLabel?: string;
- id: keyof FilterOptions;
+ hideActiveOptionsNumber?: boolean;
+ id: string;
limit?: number;
limitReachedMessage?: string;
- onChange: ({ filterId, options }: { filterId: keyof FilterOptions; options: string[] }) => void;
+ onChange: ({ filterId, options }: { filterId: string; options: string[] }) => void;
options: string[];
- selectedOptions?: string[];
renderOption?: (option: T) => React.ReactNode;
+ selectedOptions?: string[];
}
export const MultiSelectFilter = ({
buttonLabel,
+ hideActiveOptionsNumber,
id,
limit,
limitReachedMessage,
onChange,
options: rawOptions,
- selectedOptions = [],
renderOption,
+ selectedOptions = [],
}: UseFilterParams) => {
const { euiTheme } = useEuiTheme();
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const toggleIsPopoverOpen = () => setIsPopoverOpen((prevValue) => !prevValue);
const isInvalid = Boolean(limit && limitReachedMessage && selectedOptions.length >= limit);
const options = fromRawOptionsToEuiSelectableOptions(rawOptions, selectedOptions);
+ const showActiveOptionsNumber = !hideActiveOptionsNumber;
useEffect(() => {
const trimmedSelectedOptions = selectedOptions.filter((option) => rawOptions.includes(option));
@@ -99,9 +101,9 @@ export const MultiSelectFilter = ({
iconType="arrowDown"
onClick={toggleIsPopoverOpen}
isSelected={isPopoverOpen}
- numFilters={options.length}
- hasActiveFilters={selectedOptions.length > 0}
- numActiveFilters={selectedOptions.length}
+ numFilters={showActiveOptionsNumber ? options.length : undefined} // FIXME: add tests to check that the number is not shown
+ hasActiveFilters={showActiveOptionsNumber ? selectedOptions.length > 0 : undefined}
+ numActiveFilters={showActiveOptionsNumber ? selectedOptions.length : undefined}
aria-label={buttonLabel}
>
{buttonLabel}
diff --git a/x-pack/plugins/cases/public/components/all_cases/severity_filter.tsx b/x-pack/plugins/cases/public/components/all_cases/severity_filter.tsx
index f17af3de75686..4141ea7f68ede 100644
--- a/x-pack/plugins/cases/public/components/all_cases/severity_filter.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/severity_filter.tsx
@@ -8,7 +8,6 @@
import { EuiFlexGroup, EuiFlexItem, EuiHealth } from '@elastic/eui';
import React from 'react';
import type { CaseSeverity } from '../../../common/types/domain';
-import type { FilterOptions } from '../../containers/types';
import { severities } from '../severity/config';
import { MultiSelectFilter } from './multi_select_filter';
import * as i18n from './translations';
@@ -19,7 +18,7 @@ interface SeverityOption {
interface Props {
selectedOptions: CaseSeverity[];
- onChange: ({ filterId, options }: { filterId: keyof FilterOptions; options: string[] }) => void;
+ onChange: ({ filterId, options }: { filterId: string; options: string[] }) => void;
}
const options = Object.keys(severities) as CaseSeverity[];
diff --git a/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx b/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx
index 0c40d601b56b6..dd16f26c0900c 100644
--- a/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/status_filter.tsx
@@ -10,7 +10,6 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { Status } from '@kbn/cases-components/src/status/status';
import { CaseStatuses } from '../../../common/types/domain';
import { statuses } from '../status';
-import type { FilterOptions } from '../../../common/ui/types';
import { MultiSelectFilter } from './multi_select_filter';
import * as i18n from './translations';
@@ -23,7 +22,7 @@ interface Props {
countInProgressCases: number | null;
countOpenCases: number | null;
hiddenStatuses?: CaseStatuses[];
- onChange: ({ filterId, options }: { filterId: keyof FilterOptions; options: string[] }) => void;
+ onChange: ({ filterId, options }: { filterId: string; options: string[] }) => void;
selectedOptions: string[];
}
diff --git a/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx b/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx
index 0d032cbe6244e..3d0cde2540770 100644
--- a/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx
@@ -6,25 +6,18 @@
*/
import React, { useCallback, useState } from 'react';
-import { isEqual, difference } from 'lodash/fp';
+import { isEqual } from 'lodash/fp';
import { EuiFlexGroup, EuiFlexItem, EuiFieldSearch, EuiFilterGroup, EuiButton } from '@elastic/eui';
-
-import { builderMap as customFieldsBuilder } from '../custom_fields/builder';
-import { useGetCaseConfiguration } from '../../containers/configure/use_get_case_configuration';
import type { CaseStatuses } from '../../../common/types/domain';
-import { MAX_TAGS_FILTER_LENGTH, MAX_CATEGORY_FILTER_LENGTH } from '../../../common/constants';
import type { FilterOptions } from '../../containers/types';
-import { MultiSelectFilter } from './multi_select_filter';
-import { SolutionFilter } from './solution_filter';
-import { StatusFilter } from './status_filter';
import * as i18n from './translations';
-import { SeverityFilter } from './severity_filter';
import { useGetTags } from '../../containers/use_get_tags';
import { useGetCategories } from '../../containers/use_get_categories';
-import { AssigneesFilterPopover } from './assignees_filter';
import type { CurrentUserProfile } from '../types';
import { useCasesFeatures } from '../../common/use_cases_features';
import type { AssigneesFilteringSelection } from '../user_profiles/types';
+import { useSystemFilterConfig } from './use_system_filter_config';
+import { useFilterConfig } from './use_filter_config';
interface CasesTableFiltersProps {
countClosedCases: number | null;
@@ -40,184 +33,6 @@ interface CasesTableFiltersProps {
filterOptions: FilterOptions;
}
-const getSystemFilterConfig = ({
- availableSolutions,
- caseAssignmentAuthorized,
- isSelectorView,
-}: {
- availableSolutions: string[];
- caseAssignmentAuthorized: boolean;
- isSelectorView: boolean;
-}) => {
- return [
- {
- key: 'severity',
- isActive: true,
- isAvailable: true,
- render: ({ filterOptions, onChange }) => (
-
- ),
- },
- {
- key: 'status',
- isActive: true,
- isAvailable: true,
- render: ({
- filterOptions,
- onChange,
- hiddenStatuses,
- countClosedCases,
- countInProgressCases,
- countOpenCases,
- }) => (
-
- ),
- },
- // {
- // key: 'assignee',
- // isActive: true,
- // isAvailable: caseAssignmentAuthorized && !isSelectorView,
- // render: ({ filterOptions, onChange }) => (
- //
- // ),
- // },
- {
- key: 'tags',
- isActive: true,
- isAvailable: true,
- render: ({ filterOptions, onChange, tags }) => (
-
- ),
- },
- {
- key: 'category',
- isActive: true,
- isAvailable: true,
- render: ({ filterOptions, onChange, categories }) => (
-
- ),
- },
- {
- key: 'owner',
- isActive: true,
- isAvailable: availableSolutions.length > 1,
- render: ({ filterOptions, onChange }) => (
-
- ),
- },
- ].filter((filter) => filter.isAvailable);
-};
-
-const useCustomFieldsFilterConfig = () => {
- const {
- data: { customFields },
- } = useGetCaseConfiguration();
-
- const customFieldsFilterConfig = [];
- for (const { type, label } of customFields ?? []) {
- if (customFieldsBuilder[type]) {
- customFieldsFilterConfig.push({
- key: label,
- isActive: false,
- render: () => TODO
,
- });
- }
- }
-
- return { customFieldsFilterConfig };
-};
-
-const useFilterConfig = ({
- availableSolutions,
- caseAssignmentAuthorized,
- isSelectorView,
-}: {
- availableSolutions: string[];
- caseAssignmentAuthorized: boolean;
- isSelectorView: boolean;
-}) => {
- const { customFieldsFilterConfig } = useCustomFieldsFilterConfig();
- const [config, setConfig] = useState(() => [
- ...getSystemFilterConfig({
- availableSolutions,
- caseAssignmentAuthorized,
- isSelectorView,
- }),
- ...customFieldsFilterConfig,
- ]);
-
- const filterConfigOptions = config.map((filter) => filter.key);
- const selectedFilterConfigOptions = config
- .filter((filter) => filter.isActive)
- .filter(Boolean)
- .map((filter) => filter.key);
-
- const onFilterConfigChange = ({ filterId, options }: { filterId: string; options: string[] }) => {
- const addedOption = difference(options, selectedFilterConfigOptions)[0];
- const removedOption = difference(selectedFilterConfigOptions, options)[0];
-
- let newConfig;
- if (addedOption) {
- const addedFilter = config.find((filter) => filter.key === addedOption);
- newConfig = config.filter((filter) => filter.key !== addedOption);
- newConfig.push({
- ...addedFilter,
- isActive: true,
- });
- } else if (removedOption) {
- newConfig = config.map((filter) => {
- if (filter.key === removedOption) {
- return {
- ...filter,
- isActive: false,
- };
- }
- return filter;
- });
- }
- setConfig(newConfig);
- };
-
- return {
- config,
- filterConfigOptions: filterConfigOptions.sort(),
- onFilterConfigChange,
- selectedFilterConfigOptions,
- };
-};
-
const CasesTableFiltersComponent = ({
countClosedCases,
countOpenCases,
@@ -237,24 +52,43 @@ const CasesTableFiltersComponent = ({
const { data: categories = [] } = useGetCategories();
const { caseAssignmentAuthorized } = useCasesFeatures();
- const {
- config: filterConfig,
- filterConfigOptions,
- onFilterConfigChange,
- selectedFilterConfigOptions,
- } = useFilterConfig({
+ const handleSelectedAssignees = useCallback(
+ (newAssignees: AssigneesFilteringSelection[]) => {
+ if (!isEqual(newAssignees, selectedAssignees)) {
+ setSelectedAssignees(newAssignees);
+ onFilterChanged({
+ assignees: newAssignees.map((assignee) => assignee?.uid ?? null),
+ });
+ }
+ },
+ [selectedAssignees, onFilterChanged]
+ );
+
+ const { systemFilterConfig } = useSystemFilterConfig({
availableSolutions,
caseAssignmentAuthorized,
+ categories,
+ countClosedCases,
+ countInProgressCases,
+ countOpenCases,
+ currentUserProfile,
+ handleSelectedAssignees,
+ hiddenStatuses,
+ isLoading,
isSelectorView,
+ selectedAssignees,
+ tags,
});
- const onFilterOptionChange = ({
- filterId,
- options,
- }: {
- filterId: keyof FilterOptions;
- options: string[];
- }) => {
+ const {
+ config: filterConfig,
+ moreFiltersSelectableComponent: MoreFiltersSelectable,
+ filterConfigOptions,
+ selectedFilterConfigOptions,
+ onFilterConfigChange,
+ } = useFilterConfig({ systemFilterConfig });
+
+ const onFilterOptionChange = ({ filterId, options }: { filterId: string; options: string[] }) => {
const newFilters = {
...filterOptions,
[filterId]: options,
@@ -265,18 +99,6 @@ const CasesTableFiltersComponent = ({
}
};
- const handleSelectedAssignees = useCallback(
- (newAssignees: AssigneesFilteringSelection[]) => {
- if (!isEqual(newAssignees, selectedAssignees)) {
- setSelectedAssignees(newAssignees);
- onFilterChanged({
- assignees: newAssignees.map((assignee) => assignee?.uid ?? null),
- });
- }
- },
- [selectedAssignees, onFilterChanged]
- );
-
const handleOnSearch = useCallback(
(newSearch) => {
const trimSearch = newSearch.trim();
@@ -322,25 +144,11 @@ const CasesTableFiltersComponent = ({
{filterConfig
.filter((filter) => filter.isActive)
- .map((filter) =>
- filter.render({
- onChange: onFilterOptionChange,
- filterOptions,
- hiddenStatuses,
- countClosedCases,
- countInProgressCases,
- countOpenCases,
- tags,
- categories,
- })
- )}
- filter.render({ filterOptions, onChange: onFilterOptionChange }))}
+
diff --git a/x-pack/plugins/cases/public/components/all_cases/use_filter_config.tsx b/x-pack/plugins/cases/public/components/all_cases/use_filter_config.tsx
new file mode 100644
index 0000000000000..c0c70b4381193
--- /dev/null
+++ b/x-pack/plugins/cases/public/components/all_cases/use_filter_config.tsx
@@ -0,0 +1,185 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useState, useEffect } from 'react';
+import { difference, differenceWith, intersectionWith, isEqual, unionWith } from 'lodash';
+import { CustomFieldTypes } from '../../../common/types/domain';
+import { builderMap as customFieldsBuilder } from '../custom_fields/builder';
+import { useGetCaseConfiguration } from '../../containers/configure/use_get_case_configuration';
+import { MultiSelectFilter } from './multi_select_filter';
+import type { SystemFilterConfig } from './use_system_filter_config';
+
+type CustomFieldFilterConfig = SystemFilterConfig;
+
+const MoreFiltersSelectable = ({
+ filterConfigOptions,
+ selectedFilterConfigOptions,
+ onFilterConfigChange,
+}: {
+ filterConfigOptions: string[];
+ selectedFilterConfigOptions: string[];
+ onFilterConfigChange: ({ filterId, options }: { filterId: string; options: string[] }) => void;
+}) => {
+ return (
+
+ );
+};
+
+MoreFiltersSelectable.displayName = 'MoreFiltersSelectable';
+
+const useCustomFieldsFilterConfig = () => {
+ const [filterConfig, setFilterConfig] = useState([]); // FIXME: type
+
+ const {
+ data: { customFields },
+ } = useGetCaseConfiguration();
+
+ useEffect(() => {
+ const customFieldsFilterConfig: CustomFieldFilterConfig[] = [];
+ for (const { key, type, label } of customFields ?? []) {
+ if (customFieldsBuilder[type]) {
+ const customField = customFieldsBuilder[type]();
+ customFieldsFilterConfig.push({
+ key,
+ isActive: false,
+ isAvailable: type === CustomFieldTypes.TOGGLE,
+ label,
+ render: ({ filterOptions, onChange }) => {
+ return (
+
+ );
+ },
+ });
+ }
+ }
+
+ setFilterConfig(customFieldsFilterConfig);
+ }, [customFields]);
+
+ return { customFieldsFilterConfig: filterConfig };
+};
+let loop = 0;
+export const useFilterConfig = ({
+ systemFilterConfig,
+}: {
+ systemFilterConfig: SystemFilterConfig[];
+}) => {
+ const { customFieldsFilterConfig } = useCustomFieldsFilterConfig();
+ const [config, setConfig] = useState(() => [...systemFilterConfig, ...customFieldsFilterConfig]);
+
+ useEffect(() => {
+ if (loop++ > 100) throw new Error('loop');
+ setConfig((prevConfig) => {
+ const newConfig: SystemFilterConfig[] = [];
+ for (const prevFilter of prevConfig) {
+ const systemFilter = systemFilterConfig.find((filter) => filter.key === prevFilter.key);
+ if (systemFilter) {
+ newConfig.push({
+ ...systemFilter,
+ isActive: prevFilter.isActive,
+ });
+ } else {
+ newConfig.push(prevFilter);
+ }
+ }
+ return newConfig;
+ });
+ }, [systemFilterConfig]);
+
+ useEffect(() => {
+ if (loop++ > 100) throw new Error('loop 2');
+ setConfig((prevConfig) => {
+ const newConfig = [];
+ for (const prevFilter of prevConfig) {
+ const customFieldsFilter = customFieldsFilterConfig.find(
+ (filter) => filter.key === prevFilter.key
+ );
+ if (customFieldsFilter) {
+ newConfig.push({
+ ...customFieldsFilter,
+ isActive: prevFilter.isActive,
+ });
+ } else {
+ newConfig.push(prevFilter);
+ }
+ }
+
+ for (const customFieldsFilter of customFieldsFilterConfig) {
+ const prevFilter = prevConfig.find((filter) => filter.key === customFieldsFilter.key);
+ if (!prevFilter) {
+ newConfig.push(customFieldsFilter);
+ }
+ }
+ console.log({ newConfig });
+ return newConfig;
+ });
+ }, [customFieldsFilterConfig]);
+
+ const filterConfigOptions = config
+ .filter((filter) => filter.isAvailable)
+ .map((filter) => filter.label);
+
+ const selectedFilterConfigOptions = config
+ .filter((filter) => filter.isAvailable && filter.isActive)
+ .filter(Boolean)
+ .map((filter) => filter.label);
+
+ const onFilterConfigChange = ({ filterId, options }: { filterId: string; options: string[] }) => {
+ const addedOption = difference(options, selectedFilterConfigOptions)[0];
+ const removedOption = difference(selectedFilterConfigOptions, options)[0];
+
+ let newConfig: SystemFilterConfig[] = [];
+ if (addedOption) {
+ const addedFilter = config.find((filter) => filter.label === addedOption);
+ newConfig = config.filter((filter) => filter.label !== addedOption);
+ console.log({
+ addedOption: {
+ ...addedFilter,
+ isActive: true,
+ },
+ });
+ newConfig.push({
+ ...addedFilter,
+ isActive: true,
+ } as CustomFieldFilterConfig);
+ } else if (removedOption) {
+ newConfig = config.map((filter) => {
+ if (filter.label === removedOption) {
+ return {
+ ...filter,
+ isActive: false,
+ };
+ }
+ return filter;
+ });
+ }
+ console.log({ newConfig });
+ setConfig(newConfig);
+ };
+
+ return {
+ config,
+ filterConfigOptions: filterConfigOptions.sort(),
+ onFilterConfigChange,
+ selectedFilterConfigOptions,
+ moreFiltersSelectableComponent: MoreFiltersSelectable,
+ };
+};
diff --git a/x-pack/plugins/cases/public/components/all_cases/use_system_filter_config.tsx b/x-pack/plugins/cases/public/components/all_cases/use_system_filter_config.tsx
new file mode 100644
index 0000000000000..dc6759defc468
--- /dev/null
+++ b/x-pack/plugins/cases/public/components/all_cases/use_system_filter_config.tsx
@@ -0,0 +1,227 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { useState, useEffect } from 'react';
+
+import type { CaseStatuses } from '../../../common/types/domain';
+import { MAX_TAGS_FILTER_LENGTH, MAX_CATEGORY_FILTER_LENGTH } from '../../../common/constants';
+import { MultiSelectFilter } from './multi_select_filter';
+import { SolutionFilter } from './solution_filter';
+import { StatusFilter } from './status_filter';
+import * as i18n from './translations';
+import { SeverityFilter } from './severity_filter';
+import { AssigneesFilterPopover } from './assignees_filter';
+import type { CurrentUserProfile } from '../types';
+import type { AssigneesFilteringSelection } from '../user_profiles/types';
+import type { FilterOptions } from '../../containers/types';
+
+interface UseSystemFilterConfigProps {
+ availableSolutions: string[];
+ caseAssignmentAuthorized: boolean;
+ categories: string[];
+ countClosedCases: number | null;
+ countInProgressCases: number | null;
+ countOpenCases: number | null;
+ currentUserProfile: CurrentUserProfile;
+ handleSelectedAssignees: (newAssignees: AssigneesFilteringSelection[]) => void;
+ hiddenStatuses?: CaseStatuses[];
+ isLoading: boolean;
+ isSelectorView?: boolean;
+ selectedAssignees: AssigneesFilteringSelection[];
+ tags: string[];
+}
+
+export interface SystemFilterConfig {
+ key: string;
+ label: string;
+ isActive: boolean;
+ isAvailable: boolean;
+ render: ({
+ filterOptions,
+ onChange,
+ }: {
+ filterOptions: FilterOptions;
+ onChange: ({ filterId, options }: { filterId: string; options: string[] }) => void;
+ }) => React.ReactNode;
+}
+
+export const getSystemFilterConfig = ({
+ availableSolutions,
+ caseAssignmentAuthorized,
+ categories,
+ countClosedCases,
+ countInProgressCases,
+ countOpenCases,
+ currentUserProfile,
+ handleSelectedAssignees,
+ hiddenStatuses,
+ isLoading,
+ isSelectorView,
+ selectedAssignees,
+ tags,
+}: UseSystemFilterConfigProps): SystemFilterConfig[] => {
+ return [
+ {
+ key: 'severity',
+ label: i18n.SEVERITY,
+ isActive: true,
+ isAvailable: true,
+ render: ({ filterOptions, onChange }) => (
+
+ ),
+ },
+ {
+ key: 'status',
+ label: i18n.STATUS,
+ isActive: true,
+ isAvailable: true,
+ render: ({ filterOptions, onChange }) => (
+
+ ),
+ },
+ {
+ key: 'assignee',
+ label: i18n.ASSIGNEES,
+ isActive: true,
+ isAvailable: caseAssignmentAuthorized && !isSelectorView,
+ render: ({ filterOptions, onChange }) => (
+
+ ),
+ },
+ {
+ key: 'tags',
+ label: i18n.TAGS,
+ isActive: true,
+ isAvailable: true,
+ render: ({ filterOptions, onChange }) => (
+
+ ),
+ },
+ {
+ key: 'category',
+ label: i18n.CATEGORIES,
+ isActive: true,
+ isAvailable: true,
+ render: ({ filterOptions, onChange }) => (
+
+ ),
+ },
+ {
+ key: 'owner',
+ label: i18n.SOLUTION,
+ isActive: true,
+ isAvailable: availableSolutions.length > 1,
+ render: ({ filterOptions, onChange }) => (
+
+ ),
+ },
+ ].filter((filter) => filter.isAvailable);
+};
+
+export const useSystemFilterConfig = ({
+ availableSolutions,
+ caseAssignmentAuthorized,
+ categories,
+ countClosedCases,
+ countInProgressCases,
+ countOpenCases,
+ currentUserProfile,
+ handleSelectedAssignees,
+ hiddenStatuses,
+ isLoading,
+ isSelectorView,
+ selectedAssignees,
+ tags,
+}: UseSystemFilterConfigProps) => {
+ const [filterConfig, setFilterConfig] = useState(() =>
+ getSystemFilterConfig({
+ availableSolutions,
+ caseAssignmentAuthorized,
+ categories,
+ countClosedCases,
+ countInProgressCases,
+ countOpenCases,
+ currentUserProfile,
+ handleSelectedAssignees,
+ hiddenStatuses,
+ isLoading,
+ isSelectorView,
+ selectedAssignees,
+ tags,
+ })
+ );
+
+ useEffect(() => {
+ setFilterConfig(
+ getSystemFilterConfig({
+ availableSolutions,
+ caseAssignmentAuthorized,
+ categories,
+ countClosedCases,
+ countInProgressCases,
+ countOpenCases,
+ currentUserProfile,
+ handleSelectedAssignees,
+ hiddenStatuses,
+ isLoading,
+ isSelectorView,
+ selectedAssignees,
+ tags,
+ })
+ );
+ }, [
+ availableSolutions,
+ caseAssignmentAuthorized,
+ categories,
+ countClosedCases,
+ countInProgressCases,
+ countOpenCases,
+ currentUserProfile,
+ handleSelectedAssignees,
+ hiddenStatuses,
+ isLoading,
+ isSelectorView,
+ selectedAssignees,
+ tags,
+ ]);
+
+ return {
+ systemFilterConfig: filterConfig,
+ };
+};
From 7b86adcd772b07a4e2f846aa3af74922c20d8a66 Mon Sep 17 00:00:00 2001
From: Julian Gernun <17549662+jcger@users.noreply.github.com>
Date: Mon, 13 Nov 2023 07:22:37 +0100
Subject: [PATCH 03/32] use maps
---
.../components/all_cases/table_filters.tsx | 14 +-
.../all_cases/use_filter_config.tsx | 167 +++++++-----------
.../all_cases/use_system_filter_config.tsx | 12 +-
3 files changed, 76 insertions(+), 117 deletions(-)
diff --git a/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx b/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx
index 3d0cde2540770..c0af080fd3179 100644
--- a/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/table_filters.tsx
@@ -84,7 +84,7 @@ const CasesTableFiltersComponent = ({
config: filterConfig,
moreFiltersSelectableComponent: MoreFiltersSelectable,
filterConfigOptions,
- selectedFilterConfigOptions,
+ activeFilters,
onFilterConfigChange,
} = useFilterConfig({ systemFilterConfig });
@@ -142,13 +142,13 @@ const CasesTableFiltersComponent = ({
- {filterConfig
- .filter((filter) => filter.isActive)
- .map((filter) => filter.render({ filterOptions, onChange: onFilterOptionChange }))}
+ {filterConfig.map((filter) =>
+ filter.render({ filterOptions, onChange: onFilterOptionChange })
+ )}
diff --git a/x-pack/plugins/cases/public/components/all_cases/use_filter_config.tsx b/x-pack/plugins/cases/public/components/all_cases/use_filter_config.tsx
index c0c70b4381193..493fb61aefaff 100644
--- a/x-pack/plugins/cases/public/components/all_cases/use_filter_config.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/use_filter_config.tsx
@@ -6,48 +6,44 @@
*/
import React, { useState, useEffect } from 'react';
-import { difference, differenceWith, intersectionWith, isEqual, unionWith } from 'lodash';
import { CustomFieldTypes } from '../../../common/types/domain';
import { builderMap as customFieldsBuilder } from '../custom_fields/builder';
import { useGetCaseConfiguration } from '../../containers/configure/use_get_case_configuration';
import { MultiSelectFilter } from './multi_select_filter';
-import type { SystemFilterConfig } from './use_system_filter_config';
-
-type CustomFieldFilterConfig = SystemFilterConfig;
+import type { FilterConfig } from './use_system_filter_config';
const MoreFiltersSelectable = ({
- filterConfigOptions,
- selectedFilterConfigOptions,
- onFilterConfigChange,
+ options,
+ activeFilters,
+ onChange,
}: {
- filterConfigOptions: string[];
- selectedFilterConfigOptions: string[];
- onFilterConfigChange: ({ filterId, options }: { filterId: string; options: string[] }) => void;
+ options: string[];
+ activeFilters: string[];
+ onChange: ({ filterId, options }: { filterId: string; options: string[] }) => void;
}) => {
return (
);
};
-
MoreFiltersSelectable.displayName = 'MoreFiltersSelectable';
const useCustomFieldsFilterConfig = () => {
- const [filterConfig, setFilterConfig] = useState([]); // FIXME: type
+ const [filterConfig, setFilterConfig] = useState([]);
const {
data: { customFields },
} = useGetCaseConfiguration();
useEffect(() => {
- const customFieldsFilterConfig: CustomFieldFilterConfig[] = [];
+ const customFieldsFilterConfig: FilterConfig[] = [];
for (const { key, type, label } of customFields ?? []) {
if (customFieldsBuilder[type]) {
const customField = customFieldsBuilder[type]();
@@ -76,110 +72,73 @@ const useCustomFieldsFilterConfig = () => {
return { customFieldsFilterConfig: filterConfig };
};
-let loop = 0;
-export const useFilterConfig = ({
- systemFilterConfig,
-}: {
- systemFilterConfig: SystemFilterConfig[];
-}) => {
+
+export const useFilterConfig = ({ systemFilterConfig }: { systemFilterConfig: FilterConfig[] }) => {
const { customFieldsFilterConfig } = useCustomFieldsFilterConfig();
- const [config, setConfig] = useState(() => [...systemFilterConfig, ...customFieldsFilterConfig]);
+ const [config, setConfig] = useState