Skip to content

Commit f561be4

Browse files
author
zhuoyu.chen
committed
Merge remote-tracking branch 'origin/master'
2 parents 628da6e + 382177d commit f561be4

File tree

20 files changed

+831
-497
lines changed

20 files changed

+831
-497
lines changed

desktop/core/src/desktop/js/apps/admin/Configuration/Configuration.scss

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,14 @@
2121
background-color: $fluidx-gray-100;
2222
padding: 24px;
2323

24-
.config__section-header {
24+
.config__section-dropdown-label {
2525
color: $fluidx-gray-700;
2626
}
2727

28+
.config__section-header {
29+
margin-top: 16px;
30+
}
31+
2832
.config__main-item {
2933
padding: 16px 0 8px 16px;
3034
border-bottom: solid 1px $fluidx-gray-300;

desktop/core/src/desktop/js/apps/admin/Configuration/ConfigurationTab.test.tsx

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import React from 'react';
1818
import { render, waitFor, screen, fireEvent } from '@testing-library/react';
19+
import userEvent from '@testing-library/user-event';
1920
import '@testing-library/jest-dom';
2021
import Configuration from './ConfigurationTab';
2122
import { ConfigurationKey } from './ConfigurationKey';
@@ -31,7 +32,10 @@ beforeEach(() => {
3132
jest.clearAllMocks();
3233
ApiHelper.fetchHueConfigAsync = jest.fn(() =>
3334
Promise.resolve({
34-
apps: [{ name: 'desktop', has_ui: true, display_name: 'Desktop' }],
35+
apps: [
36+
{ name: 'desktop', has_ui: true, display_name: 'Desktop' },
37+
{ name: 'test', has_ui: true, display_name: 'test' }
38+
],
3539
config: [
3640
{
3741
help: 'Main configuration section',
@@ -51,6 +55,19 @@ beforeEach(() => {
5155
value: 'Another value'
5256
}
5357
]
58+
},
59+
{
60+
help: '',
61+
key: 'test',
62+
is_anonymous: false,
63+
values: [
64+
{
65+
help: 'Example config help text2',
66+
key: 'test.config2',
67+
is_anonymous: false,
68+
value: 'Hello World'
69+
}
70+
]
5471
}
5572
],
5673
conf_dir: '/conf/directory'
@@ -63,7 +80,7 @@ describe('Configuration Component', () => {
6380
jest.clearAllMocks();
6481
});
6582

66-
test('Renders Configuration component with fetched data', async () => {
83+
test('Renders Configuration component with fetched data for default desktop section', async () => {
6784
render(<Configuration />);
6885

6986
await waitFor(() => {
@@ -75,6 +92,70 @@ describe('Configuration Component', () => {
7592
});
7693
});
7794

95+
test('Renders Configuration component with fetched data for all sections', async () => {
96+
render(<Configuration />);
97+
98+
await waitFor(() => {
99+
expect(screen.getByText(/Sections/i)).toBeInTheDocument();
100+
expect(screen.getByText(/Desktop/i)).toBeInTheDocument();
101+
expect(screen.getByText(/example\.config/i)).toBeInTheDocument();
102+
expect(screen.getByText(/Example value/i)).toBeInTheDocument();
103+
expect(screen.queryAllByText(/test/i)).toHaveLength(0);
104+
});
105+
106+
const user = userEvent.setup();
107+
108+
// Open dropdown
109+
const select = screen.getByRole('combobox');
110+
await user.click(select);
111+
112+
// Wait for and select "ALL" option
113+
const allOption = await screen.findByTitle('ALL');
114+
await user.click(allOption);
115+
116+
// Verify the updated content
117+
await waitFor(() => {
118+
expect(screen.getAllByText(/test/i)).toHaveLength(3);
119+
expect(screen.getByText(/test\.config2/i)).toBeInTheDocument();
120+
expect(screen.getByText(/Hello World/i)).toBeInTheDocument();
121+
});
122+
});
123+
124+
test('Renders Configuration component mathcing search', async () => {
125+
render(<Configuration />);
126+
127+
await waitFor(() => {
128+
expect(screen.getByText(/Sections/i)).toBeInTheDocument();
129+
expect(screen.getByText(/Desktop/i)).toBeInTheDocument();
130+
expect(screen.getByText(/example\.config/i)).toBeInTheDocument();
131+
expect(screen.getByText(/Example value/i)).toBeInTheDocument();
132+
expect(screen.queryAllByText(/test/i)).toHaveLength(0);
133+
});
134+
135+
const user = userEvent.setup();
136+
137+
// Open dropdown
138+
const select = screen.getByRole('combobox');
139+
await user.click(select);
140+
141+
// Wait for and select "ALL" option
142+
const allOption = await screen.findByTitle('ALL');
143+
await user.click(allOption);
144+
145+
const filterinput = screen.getByPlaceholderText('Filter...');
146+
await user.click(filterinput);
147+
await user.type(filterinput, 'config2');
148+
149+
// Verify the updated content
150+
await waitFor(() => {
151+
expect(screen.getAllByText(/test/i)).toHaveLength(3);
152+
expect(screen.getByText(/test\.config2/i)).toBeInTheDocument();
153+
expect(screen.getByText(/Hello World/i)).toBeInTheDocument();
154+
155+
expect(screen.queryByText(/example\.config/i)).not.toBeInTheDocument();
156+
});
157+
});
158+
78159
test('Filters configuration based on input text', async () => {
79160
render(<Configuration />);
80161

desktop/core/src/desktop/js/apps/admin/Configuration/ConfigurationTab.tsx

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,27 @@ interface HueConfig {
5151
conf_dir: string;
5252
}
5353

54+
interface VisualSection {
55+
header: string;
56+
content: Array<AdminConfigValue>;
57+
}
58+
5459
const Configuration: React.FC = (): JSX.Element => {
5560
const { t } = i18nReact.useTranslation();
5661
const [hueConfig, setHueConfig] = useState<HueConfig>();
5762
const [loading, setLoading] = useState(true);
5863
const [error, setError] = useState<string>();
59-
const [selectedApp, setSelectedApp] = useState<string>('desktop');
64+
const [selectedSection, setSelectedSection] = useState<string>('desktop');
6065
const [filter, setFilter] = useState<string>('');
6166

67+
const ALL_SECTIONS_OPTION = t('ALL');
68+
6269
useEffect(() => {
6370
ApiHelper.fetchHueConfigAsync()
6471
.then(data => {
6572
setHueConfig(data);
6673
if (data.apps.find(app => app.name === 'desktop')) {
67-
setSelectedApp('desktop');
74+
setSelectedSection('desktop');
6875
}
6976
})
7077
.catch(error => {
@@ -98,50 +105,80 @@ const Configuration: React.FC = (): JSX.Element => {
98105
return undefined;
99106
};
100107

101-
const selectedConfig = useMemo(() => {
102-
const filterSelectedApp = hueConfig?.config?.find(config => config.key === selectedApp);
108+
const visualSections = useMemo(() => {
109+
const showAllSections = selectedSection === ALL_SECTIONS_OPTION;
110+
const selectedFullConfigs = !hueConfig?.config
111+
? []
112+
: showAllSections
113+
? hueConfig.config
114+
: hueConfig.config.filter(config => config.key === selectedSection);
115+
116+
return selectedFullConfigs
117+
.map(selectedSection => ({
118+
header: selectedSection.key,
119+
content: selectedSection.values
120+
.map(config => filterConfig(config, filter.toLowerCase()))
121+
.filter(Boolean) as Config[]
122+
}))
123+
.filter(headerContentObj => !!headerContentObj.content) as Array<VisualSection>;
124+
}, [hueConfig, filter, selectedSection]);
125+
126+
const renderVisualSection = (visualSection: VisualSection) => {
127+
const showingMultipleSections = selectedSection === ALL_SECTIONS_OPTION;
128+
const content = visualSection.content;
129+
return content.length > 0 ? (
130+
<>
131+
{showingMultipleSections && (
132+
<h4 className="config__section-header">{visualSection.header}</h4>
133+
)}
134+
{content.map((record, index) => (
135+
<div key={index} className="config__main-item">
136+
<ConfigurationKey record={record} />
137+
<ConfigurationValue record={record} />
138+
</div>
139+
))}
140+
</>
141+
) : (
142+
!showingMultipleSections && <i>{t('Empty configuration section')}</i>
143+
);
144+
};
103145

104-
return filterSelectedApp?.values
105-
.map(config => filterConfig(config, filter.toLowerCase()))
106-
.filter(Boolean) as Config[];
107-
}, [hueConfig, filter, selectedApp]);
146+
const optionsIncludingAll = [
147+
ALL_SECTIONS_OPTION,
148+
...(hueConfig?.apps.map(app => app.name) || [])
149+
];
108150

109151
return (
110152
<div className="config-component">
111153
<Spin spinning={loading}>
112154
{error && (
113155
<Alert
114156
message={`Error: ${error}`}
115-
description="Error in displaying the Configuration!"
157+
description={t('Error in displaying the Configuration!')}
116158
type="error"
117159
/>
118160
)}
119161

120162
{!error && (
121163
<>
122-
<div className="config__section-header">Sections</div>
164+
<div className="config__section-dropdown-label">{t('Sections')}</div>
123165
<AdminHeader
124-
options={hueConfig?.apps.map(app => app.name) || []}
125-
selectedValue={selectedApp}
126-
onSelectChange={setSelectedApp}
166+
options={optionsIncludingAll}
167+
selectedValue={selectedSection}
168+
onSelectChange={setSelectedSection}
127169
filterValue={filter}
128170
onFilterChange={setFilter}
129-
placeholder={`Filter in ${selectedApp}...`}
171+
placeholder={
172+
selectedSection === ALL_SECTIONS_OPTION
173+
? t('Filter...')
174+
: `${t('Filter in')} ${selectedSection}...`
175+
}
130176
configAddress={hueConfig?.conf_dir}
131177
/>
132-
{selectedApp &&
133-
selectedConfig &&
134-
(selectedConfig.length > 0 ? (
135-
<>
136-
{selectedConfig.map((record, index) => (
137-
<div key={index} className="config__main-item">
138-
<ConfigurationKey record={record} />
139-
<ConfigurationValue record={record} />
140-
</div>
141-
))}
142-
</>
143-
) : (
144-
<i>{t('Empty configuration section')}</i>
178+
{selectedSection &&
179+
visualSections?.length &&
180+
visualSections.map(visualSection => (
181+
<div key={visualSection.header}>{renderVisualSection(visualSection)}</div>
145182
))}
146183
</>
147184
)}

desktop/core/src/desktop/js/apps/storageBrowser/StorageDirectoryPage/StorageDirectoryPage.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,10 @@ import formatBytes from '../../../utils/formatBytes';
3636
import './StorageDirectoryPage.scss';
3737
import { formatTimestamp } from '../../../utils/dateTimeUtils';
3838
import useLoadData from '../../../utils/hooks/useLoadData/useLoadData';
39-
import {
40-
DEFAULT_PAGE_SIZE,
41-
DEFAULT_POLLING_TIME,
42-
FileUploadStatus
43-
} from '../../../utils/constants/storageBrowser';
39+
import { DEFAULT_PAGE_SIZE, DEFAULT_POLLING_TIME } from '../../../utils/constants/storageBrowser';
4440
import DragAndDrop from '../../../reactComponents/DragAndDrop/DragAndDrop';
4541
import UUID from '../../../utils/string/UUID';
46-
import { UploadItem } from '../../../utils/hooks/useFileUpload/util';
42+
import { RegularFile, FileStatus } from '../../../utils/hooks/useFileUpload/types';
4743
import FileUploadQueue from '../../../reactComponents/FileUploadQueue/FileUploadQueue';
4844
import { useWindowSize } from '../../../utils/hooks/useWindowSize/useWindowSize';
4945
import LoadingErrorWrapper from '../../../reactComponents/LoadingErrorWrapper/LoadingErrorWrapper';
@@ -74,7 +70,7 @@ const StorageDirectoryPage = ({
7470
}: StorageDirectoryPageProps): JSX.Element => {
7571
const [loadingFiles, setLoadingFiles] = useState<boolean>(false);
7672
const [selectedFiles, setSelectedFiles] = useState<StorageDirectoryTableData[]>([]);
77-
const [filesToUpload, setFilesToUpload] = useState<UploadItem[]>([]);
73+
const [filesToUpload, setFilesToUpload] = useState<RegularFile[]>([]);
7874
const [polling, setPolling] = useState<boolean>(false);
7975

8076
const [pageSize, setPageSize] = useState<number>(DEFAULT_PAGE_SIZE);
@@ -152,7 +148,7 @@ const StorageDirectoryPage = ({
152148
file,
153149
filePath: fileStats.path,
154150
uuid: UUID(),
155-
status: FileUploadStatus.Pending
151+
status: FileStatus.Pending
156152
};
157153
});
158154
setPolling(true);

desktop/core/src/desktop/js/reactComponents/FileUploadQueue/FileUploadQueue.scss

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -44,37 +44,6 @@
4444
overflow: auto;
4545
padding: 16px;
4646
gap: 16px;
47-
48-
&__row {
49-
display: flex;
50-
align-items: center;
51-
padding: 8px;
52-
font-size: 14px;
53-
gap: 8px;
54-
border: solid 1px vars.$fluidx-gray-300;
55-
56-
&__status {
57-
display: flex;
58-
width: 20px;
59-
}
60-
61-
&__name {
62-
display: flex;
63-
flex: 1;
64-
text-overflow: ellipsis;
65-
}
66-
67-
&__size {
68-
display: flex;
69-
text-align: right;
70-
color: vars.$fluidx-gray-700;
71-
}
72-
73-
&__close {
74-
display: flex;
75-
}
76-
}
7747
}
78-
7948
}
80-
}
49+
}

0 commit comments

Comments
 (0)