Ver Fonte

[ui-config] add search across multiple sections]

Björn Alm há 8 meses atrás
pai
commit
ba4f1511d1

+ 5 - 1
desktop/core/src/desktop/js/apps/admin/Configuration/Configuration.scss

@@ -21,10 +21,14 @@
     background-color: $fluidx-gray-100;
     padding: 24px;
 
-    .config__section-header {
+    .config__section-dropdown-label {
       color: $fluidx-gray-700;
     }
 
+    .config__section-header {
+      margin-top: 16px;      
+    }
+
     .config__main-item {
       padding: 16px 0 8px 16px;
       border-bottom: solid 1px $fluidx-gray-300;

+ 85 - 2
desktop/core/src/desktop/js/apps/admin/Configuration/ConfigurationTab.test.tsx

@@ -16,6 +16,7 @@
 
 import React from 'react';
 import { render, waitFor, screen, fireEvent } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
 import '@testing-library/jest-dom';
 import Configuration from './ConfigurationTab';
 import { ConfigurationKey } from './ConfigurationKey';
@@ -31,7 +32,10 @@ beforeEach(() => {
   jest.clearAllMocks();
   ApiHelper.fetchHueConfigAsync = jest.fn(() =>
     Promise.resolve({
-      apps: [{ name: 'desktop', has_ui: true, display_name: 'Desktop' }],
+      apps: [
+        { name: 'desktop', has_ui: true, display_name: 'Desktop' },
+        { name: 'test', has_ui: true, display_name: 'test' }
+      ],
       config: [
         {
           help: 'Main configuration section',
@@ -51,6 +55,19 @@ beforeEach(() => {
               value: 'Another value'
             }
           ]
+        },
+        {
+          help: '',
+          key: 'test',
+          is_anonymous: false,
+          values: [
+            {
+              help: 'Example config help text2',
+              key: 'test.config2',
+              is_anonymous: false,
+              value: 'Hello World'
+            }
+          ]
         }
       ],
       conf_dir: '/conf/directory'
@@ -63,7 +80,7 @@ describe('Configuration Component', () => {
     jest.clearAllMocks();
   });
 
-  test('Renders Configuration component with fetched data', async () => {
+  test('Renders Configuration component with fetched data for default desktop section', async () => {
     render(<Configuration />);
 
     await waitFor(() => {
@@ -75,6 +92,72 @@ describe('Configuration Component', () => {
     });
   });
 
+  test('Renders Configuration component with fetched data for all sections', async () => {
+    render(<Configuration />);
+
+    await waitFor(() => {
+      expect(screen.getByText(/Sections/i)).toBeInTheDocument();
+      expect(screen.getByText(/Desktop/i)).toBeInTheDocument();
+      expect(screen.getByText(/example\.config/i)).toBeInTheDocument();
+      expect(screen.getByText(/Example value/i)).toBeInTheDocument();
+      expect(screen.queryAllByText(/test/i)).toHaveLength(0);
+    });
+
+    const user = userEvent.setup();
+
+    // Open dropdown
+    const select = screen.getByRole('combobox');
+    await user.click(select);
+
+    // Wait for and select "ALL" option
+    const allOption = await screen.findByTitle('ALL');
+    await user.click(allOption);
+
+    // Verify the updated content
+    await waitFor(() => {
+      expect(screen.getAllByText(/test/i)).toHaveLength(3);
+      expect(screen.getByText(/test\.config2/i)).toBeInTheDocument();
+      expect(screen.getByText(/Hello World/i)).toBeInTheDocument();
+    });
+  });
+
+
+  test('Renders Configuration component mathcing search', async () => {
+    render(<Configuration />);
+
+    await waitFor(() => {
+      expect(screen.getByText(/Sections/i)).toBeInTheDocument();
+      expect(screen.getByText(/Desktop/i)).toBeInTheDocument();
+      expect(screen.getByText(/example\.config/i)).toBeInTheDocument();
+      expect(screen.getByText(/Example value/i)).toBeInTheDocument();
+      expect(screen.queryAllByText(/test/i)).toHaveLength(0);
+    });
+
+    const user = userEvent.setup();
+
+    // Open dropdown
+    const select = screen.getByRole('combobox');
+    await user.click(select);
+
+    // Wait for and select "ALL" option
+    const allOption = await screen.findByTitle('ALL');
+    await user.click(allOption);
+
+    const filterinput = screen.getByPlaceholderText('Filter...');
+    await user.click(filterinput);
+    await user.type(filterinput, 'config2');
+
+    // Verify the updated content
+    await waitFor(() => {
+      expect(screen.getAllByText(/test/i)).toHaveLength(3);
+      expect(screen.getByText(/test\.config2/i)).toBeInTheDocument();
+      expect(screen.getByText(/Hello World/i)).toBeInTheDocument();
+      
+      expect(screen.queryByText(/example\.config/i)).not.toBeInTheDocument();
+    });
+  });
+
+
   test('Filters configuration based on input text', async () => {
     render(<Configuration />);
 

+ 64 - 27
desktop/core/src/desktop/js/apps/admin/Configuration/ConfigurationTab.tsx

@@ -51,20 +51,27 @@ interface HueConfig {
   conf_dir: string;
 }
 
+interface VisualSection {
+  header: string;
+  content: Array<AdminConfigValue>;
+}
+
 const Configuration: React.FC = (): JSX.Element => {
   const { t } = i18nReact.useTranslation();
   const [hueConfig, setHueConfig] = useState<HueConfig>();
   const [loading, setLoading] = useState(true);
   const [error, setError] = useState<string>();
-  const [selectedApp, setSelectedApp] = useState<string>('desktop');
+  const [selectedSection, setSelectedSection] = useState<string>('desktop');
   const [filter, setFilter] = useState<string>('');
 
+  const ALL_SECTIONS_OPTION = t('ALL');
+
   useEffect(() => {
     ApiHelper.fetchHueConfigAsync()
       .then(data => {
         setHueConfig(data);
         if (data.apps.find(app => app.name === 'desktop')) {
-          setSelectedApp('desktop');
+          setSelectedSection('desktop');
         }
       })
       .catch(error => {
@@ -98,13 +105,48 @@ const Configuration: React.FC = (): JSX.Element => {
     return undefined;
   };
 
-  const selectedConfig = useMemo(() => {
-    const filterSelectedApp = hueConfig?.config?.find(config => config.key === selectedApp);
+  const visualSections = useMemo(() => {
+    const showAllSections = selectedSection === ALL_SECTIONS_OPTION;
+    const selectedFullConfigs = !hueConfig?.config
+      ? []
+      : showAllSections
+        ? hueConfig.config
+        : hueConfig.config.filter(config => config.key === selectedSection);
+
+    return selectedFullConfigs
+      .map(selectedSection => ({
+        header: selectedSection.key,
+        content: selectedSection.values
+          .map(config => filterConfig(config, filter.toLowerCase()))
+          .filter(Boolean) as Config[]
+      }))
+      .filter(headerContentObj => !!headerContentObj.content) as Array<VisualSection>;
+  }, [hueConfig, filter, selectedSection]);
+
+  const renderVisualSection = (visualSection: VisualSection) => {
+    const showingMultipleSections = selectedSection === ALL_SECTIONS_OPTION;
+    const content = visualSection.content;
+    return content.length > 0 ? (
+      <>
+        {showingMultipleSections && (
+          <h4 className="config__section-header">{visualSection.header}</h4>
+        )}
+        {content.map((record, index) => (
+          <div key={index} className="config__main-item">
+            <ConfigurationKey record={record} />
+            <ConfigurationValue record={record} />
+          </div>
+        ))}
+      </>
+    ) : (
+      !showingMultipleSections && <i>{t('Empty configuration section')}</i>
+    );
+  };
 
-    return filterSelectedApp?.values
-      .map(config => filterConfig(config, filter.toLowerCase()))
-      .filter(Boolean) as Config[];
-  }, [hueConfig, filter, selectedApp]);
+  const optionsIncludingAll = [
+    ALL_SECTIONS_OPTION,
+    ...(hueConfig?.apps.map(app => app.name) || [])
+  ];
 
   return (
     <div className="config-component">
@@ -112,36 +154,31 @@ const Configuration: React.FC = (): JSX.Element => {
         {error && (
           <Alert
             message={`Error: ${error}`}
-            description="Error in displaying the Configuration!"
+            description={t('Error in displaying the Configuration!')}
             type="error"
           />
         )}
 
         {!error && (
           <>
-            <div className="config__section-header">Sections</div>
+            <div className="config__section-dropdown-label">{t('Sections')}</div>
             <AdminHeader
-              options={hueConfig?.apps.map(app => app.name) || []}
-              selectedValue={selectedApp}
-              onSelectChange={setSelectedApp}
+              options={optionsIncludingAll}
+              selectedValue={selectedSection}
+              onSelectChange={setSelectedSection}
               filterValue={filter}
               onFilterChange={setFilter}
-              placeholder={`Filter in ${selectedApp}...`}
+              placeholder={
+                selectedSection === ALL_SECTIONS_OPTION
+                  ? t('Filter...')
+                  : `${t('Filter in')} ${selectedSection}...`
+              }
               configAddress={hueConfig?.conf_dir}
             />
-            {selectedApp &&
-              selectedConfig &&
-              (selectedConfig.length > 0 ? (
-                <>
-                  {selectedConfig.map((record, index) => (
-                    <div key={index} className="config__main-item">
-                      <ConfigurationKey record={record} />
-                      <ConfigurationValue record={record} />
-                    </div>
-                  ))}
-                </>
-              ) : (
-                <i>{t('Empty configuration section')}</i>
+            {selectedSection &&
+              visualSections?.length &&
+              visualSections.map(visualSection => (
+                <div key={visualSection.header}>{renderVisualSection(visualSection)}</div>
               ))}
           </>
         )}