-
Notifications
You must be signed in to change notification settings - Fork 786
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
3418 available plugins #3620
3418 available plugins #3620
Changes from 4 commits
3352617
d95e70d
56d484b
3039256
c450fcc
6914256
1e417f9
3e1cf61
200f0f3
98fb68a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import React, {useEffect, useState} from 'react'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a PR for that component. |
||
import {Box, IconButton, InputAdornment, TextField} from '@mui/material'; | ||
import SearchIcon from '@mui/icons-material/Search'; | ||
import ClearIcon from '@mui/icons-material/Clear'; | ||
import {nanoid} from 'nanoid'; | ||
|
||
const EMPTY_STRING = ''; | ||
const SearchBar = (props) => { | ||
const {variant = 'standard', label, placeholder = 'Search', setQuery} = {...props}; | ||
const [currentValue, setCurrentValue] = useState(EMPTY_STRING); | ||
|
||
useEffect(() => { | ||
setQuery && setQuery(currentValue); | ||
}, [currentValue]); | ||
|
||
const handleValueChange = (e) => { | ||
const currentValue = e?.target?.value?.trim() || EMPTY_STRING; | ||
setCurrentValue(currentValue); | ||
} | ||
|
||
const clearValue = () => { | ||
setCurrentValue(EMPTY_STRING); | ||
} | ||
|
||
return ( | ||
<Box> | ||
<TextField id={`search-bar-${nanoid()}`} | ||
variant={variant} | ||
label={label} | ||
value={currentValue} | ||
placeholder={placeholder} | ||
onChange={handleValueChange} | ||
InputProps={{ | ||
startAdornment: ( | ||
<InputAdornment position="start"> | ||
<SearchIcon/> | ||
</InputAdornment> | ||
), | ||
endAdornment: ( | ||
currentValue !== '' && ( | ||
<InputAdornment position="end"> | ||
<IconButton onClick={clearValue}> | ||
<ClearIcon/> | ||
</IconButton> | ||
</InputAdornment> | ||
) | ||
) | ||
}}/> | ||
</Box> | ||
) | ||
} | ||
|
||
export default SearchBar; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import React, {useContext, useState} from 'react'; | ||
import {installPlugin} from './mocksHelper'; | ||
import {shallowAdditionOfUniqueValueToArray, shallowRemovalOfUniqueValueFromArray} from '../../../utils/objectUtils'; | ||
import {PluginsContext} from './PluginsContext'; | ||
import {GridActionsCellItem} from '@mui/x-data-grid'; | ||
import {nanoid} from 'nanoid'; | ||
import FileDownloadIcon from '@mui/icons-material/FileDownload'; | ||
import DownloadDoneIcon from '@mui/icons-material/DownloadDone'; | ||
import DownloadingIcon from '@mui/icons-material/Downloading'; | ||
import BasePlugins from './BasePlugins'; | ||
import SearchBar from '../SearchBar'; | ||
import {Box} from '@mui/material'; | ||
import AuthComponent from '../../AuthComponent'; | ||
|
||
// Provides the plugins, filtering out the installed plugins | ||
class AvailablePluginsView { | ||
availablePlugins = []; // all plugins, grouped by type then name | ||
installedPlugins = []; // all installed plugins, grouped by type then name | ||
|
||
constructor(availablePlugins, installedPlugins) { | ||
this.availablePlugins = availablePlugins; | ||
this.installedPlugins = installedPlugins; | ||
} | ||
|
||
pluginInstalled(plugin_type, plugin_name) { | ||
return plugin_type in this.installedPlugins && plugin_name in this.installedPlugins[plugin_type]; | ||
} | ||
|
||
* makeAvailablePluginsIterator() { | ||
for (const plugin_type in this.availablePlugins) { | ||
for (const plugin_name in this.availablePlugins[plugin_type]) { | ||
if (!this.pluginInstalled(plugin_type, plugin_name)) { | ||
// There may be multiple versions of the same plugin, so we only want the latest one | ||
yield this.availablePlugins[plugin_type][plugin_name].slice(-1)[0]; | ||
} | ||
} | ||
} | ||
} | ||
|
||
[Symbol.iterator]() { | ||
return this.makeAvailablePluginsIterator(); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is object-oriented approach in an otherwise functional code. We can transform the data if we need a list, but creating our own collection that's sometimes object sometimes list complicates usage |
||
}; | ||
|
||
const AvailablePlugins = () => { | ||
const {availablePlugins} = useContext(PluginsContext); | ||
const {installedPlugins} = useContext(PluginsContext); | ||
const {refreshInstalledPlugins} = useContext(PluginsContext); | ||
const availablePluginsView = new AvailablePluginsView(availablePlugins, installedPlugins); | ||
|
||
const [successfullyInstalledPluginsIds, setSuccessfullyInstalledPluginsIds] = useState([]); | ||
const [pluginsInInstallationProcess, setPluginsInInstallationProcess] = useState([]); | ||
const authComponent = new AuthComponent({}); | ||
|
||
const onRefreshCallback = () => { | ||
setSuccessfullyInstalledPluginsIds([]); | ||
} | ||
|
||
const onInstallClick = (pluginId, pluginName, pluginType, pluginVersion) => { | ||
console.log('installing plugin: ', pluginName) | ||
|
||
setPluginsInInstallationProcess((prevState) => { | ||
return shallowAdditionOfUniqueValueToArray(prevState, pluginId); | ||
}); | ||
|
||
authComponent.authFetch('/api/install-agent-plugin', {method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({plugin_type: pluginType, name: pluginName, version: pluginVersion})}, true).then(() => { | ||
setSuccessfullyInstalledPluginsIds((prevState) => { | ||
return shallowAdditionOfUniqueValueToArray(prevState, pluginId); | ||
}); | ||
refreshInstalledPlugins(); | ||
}).catch(() => { | ||
console.log('error installing plugin'); | ||
}).finally(() => { | ||
setPluginsInInstallationProcess((prevState => { | ||
return shallowRemovalOfUniqueValueFromArray(prevState, pluginId); | ||
})); | ||
}); | ||
}; | ||
|
||
const getRowActions = (row) => { | ||
const pluginId = row.id; | ||
if (pluginsInInstallationProcess.includes(pluginId)) { | ||
return [ | ||
<GridActionsCellItem | ||
key={nanoid()} | ||
icon={<DownloadingIcon/>} | ||
label="Downloading" | ||
className="textPrimary" | ||
color="inherit" | ||
/> | ||
] | ||
} | ||
|
||
if (successfullyInstalledPluginsIds.includes(pluginId)) { | ||
return [ | ||
<GridActionsCellItem | ||
key={nanoid()} | ||
icon={<DownloadDoneIcon/>} | ||
label="Download Done" | ||
className="textPrimary" | ||
color="inherit" | ||
/> | ||
] | ||
} | ||
|
||
const pluginName = row.name; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move these declarations to the top of the function. |
||
const pluginType = row.type; | ||
const pluginVersion = row.version; | ||
return [ | ||
<GridActionsCellItem | ||
key={nanoid()} | ||
icon={<FileDownloadIcon/>} | ||
label="Download" | ||
className="textPrimary" | ||
onClick={() => onInstallClick(pluginId, pluginName, pluginType, pluginVersion)} | ||
color="inherit" | ||
/> | ||
]; | ||
} | ||
|
||
return ( | ||
<Box> | ||
<SearchBar /> | ||
<BasePlugins plugins={availablePluginsView} | ||
loadingMessage="Loading all available plugins..." | ||
onRefreshCallback={onRefreshCallback} | ||
getRowActions={getRowActions} | ||
/> | ||
</Box> | ||
) | ||
}; | ||
|
||
export default AvailablePlugins; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import React, {useMemo, useState} from 'react'; | ||
import {Box} from '@mui/material'; | ||
import XDataGrid from '../XDataGrid'; | ||
import {getPluginsGridHeaders, getPluginsGridRows} from './mocksHelper'; | ||
import styles from '../../../styles/components/plugins-marketplace/BasePlugins.module.scss'; | ||
|
||
const DEFAULT_LOADING_MESSAGE = 'Loading plugins...'; | ||
const initialState = { | ||
sorting: { | ||
sortModel: [{field: 'name', sort: 'asc'}] | ||
} | ||
}; | ||
|
||
const BasePlugins = (props) => { | ||
const {plugins, getRowActions, onRefreshCallback, loadingMessage = DEFAULT_LOADING_MESSAGE} = {...props}; | ||
|
||
const [isLoadingPlugins, setIsLoadingPlugins] = useState(false); | ||
|
||
const rows = useMemo(() => { | ||
return getPluginsGridRows(plugins); | ||
}, [plugins]); | ||
|
||
// // eslint-disable-next-line no-unused-vars | ||
// const onRefreshClick = () => { | ||
// setPluginsList(); | ||
// if(onRefreshCallback) { | ||
// onRefreshCallback(); | ||
// } | ||
// } | ||
|
||
return ( | ||
<Box className={styles['plugins-wrapper']}> | ||
{/*<PluginsActions showUpgradableToggle={showUpgradableToggle}/>*/} | ||
|
||
{isLoadingPlugins | ||
? loadingMessage | ||
: <XDataGrid columns={getPluginsGridHeaders(getRowActions)} | ||
rows={[...rows]} | ||
rowHeight={'25px'} | ||
showToolbar={false} | ||
maxHeight={'500px'} | ||
className="marketplace-plugins-list" | ||
initialState={initialState} | ||
needCustomWorkaround={false} | ||
setFlex={false}/> | ||
} | ||
</Box> | ||
) | ||
} | ||
|
||
export default BasePlugins; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not do it in the first useEffect?