diff --git a/src/core/server/workspaces/routes/index.ts b/src/core/server/workspaces/routes/index.ts index 980364103ba..b00da3c3f38 100644 --- a/src/core/server/workspaces/routes/index.ts +++ b/src/core/server/workspaces/routes/index.ts @@ -9,6 +9,15 @@ import { IWorkspaceDBImpl } from '../types'; const WORKSPACES_API_BASE_URL = '/api/workspaces'; +const workspaceAttributesSchema = schema.object({ + description: schema.maybe(schema.string()), + name: schema.string(), + features: schema.maybe(schema.arrayOf(schema.string())), + color: schema.maybe(schema.string()), + icon: schema.maybe(schema.string()), + defaultVISTheme: schema.maybe(schema.string()), +}); + export function registerRoutes({ client, logger, @@ -72,11 +81,7 @@ export function registerRoutes({ path: '', validate: { body: schema.object({ - attributes: schema.object({ - description: schema.maybe(schema.string()), - name: schema.string(), - features: schema.maybe(schema.arrayOf(schema.string())), - }), + attributes: workspaceAttributesSchema, }), }, }, @@ -102,11 +107,7 @@ export function registerRoutes({ id: schema.string(), }), body: schema.object({ - attributes: schema.object({ - description: schema.maybe(schema.string()), - name: schema.string(), - features: schema.maybe(schema.arrayOf(schema.string())), - }), + attributes: workspaceAttributesSchema, }), }, }, diff --git a/src/core/server/workspaces/saved_objects/workspace.ts b/src/core/server/workspaces/saved_objects/workspace.ts index e3fbaa0dad6..d73a9e3b760 100644 --- a/src/core/server/workspaces/saved_objects/workspace.ts +++ b/src/core/server/workspaces/saved_objects/workspace.ts @@ -41,6 +41,15 @@ export const workspace: SavedObjectsType = { features: { type: 'text', }, + color: { + type: 'text', + }, + icon: { + type: 'text', + }, + defaultVISTheme: { + type: 'text', + }, }, }, }; diff --git a/src/core/server/workspaces/types.ts b/src/core/server/workspaces/types.ts index e098b4905a1..532a69ab9ce 100644 --- a/src/core/server/workspaces/types.ts +++ b/src/core/server/workspaces/types.ts @@ -15,6 +15,9 @@ export interface WorkspaceAttribute { name: string; description?: string; features?: string[]; + color?: string; + icon?: string; + defaultVISTheme?: string; } export interface WorkspaceFindOptions { diff --git a/src/plugins/workspace/public/components/workspace_creator/workspace_form.tsx b/src/plugins/workspace/public/components/workspace_creator/workspace_form.tsx index 98273625d17..8e83b705724 100644 --- a/src/plugins/workspace/public/components/workspace_creator/workspace_form.tsx +++ b/src/plugins/workspace/public/components/workspace_creator/workspace_form.tsx @@ -20,19 +20,23 @@ import { EuiFlexGrid, EuiFlexGroup, EuiImage, - EuiAccordion, EuiCheckbox, EuiCheckboxGroup, EuiCheckableCardProps, EuiCheckboxGroupProps, EuiCheckboxProps, EuiFieldTextProps, + EuiColorPicker, + EuiColorPickerProps, + EuiComboBox, + EuiComboBoxProps, } from '@elastic/eui'; import { WorkspaceTemplate } from '../../../../../core/types'; import { AppNavLinkStatus, ApplicationStart } from '../../../../../core/public'; import { useApplications, useWorkspaceTemplate } from '../../hooks'; import { WORKSPACE_OP_TYPE_CREATE, WORKSPACE_OP_TYPE_UPDATE } from '../../../common/constants'; +import { WorkspaceIconSelector } from './workspace_icon_selector'; interface WorkspaceFeature { id: string; @@ -49,6 +53,9 @@ export interface WorkspaceFormData { name: string; description?: string; features: string[]; + color?: string; + icon?: string; + defaultVISTheme?: string; } type WorkspaceFormErrors = { [key in keyof WorkspaceFormData]?: string }; @@ -59,6 +66,8 @@ const isWorkspaceFeatureGroup = ( const workspaceHtmlIdGenerator = htmlIdGenerator(); +const defaultVISThemeOptions = [{ label: 'Categorical', value: 'categorical' }]; + interface WorkspaceFormProps { application: ApplicationStart; onSubmit?: (formData: WorkspaceFormData) => void; @@ -76,6 +85,10 @@ export const WorkspaceForm = ({ const [name, setName] = useState(defaultValues?.name); const [description, setDescription] = useState(defaultValues?.description); + const [color, setColor] = useState(defaultValues?.color); + const [icon, setIcon] = useState(defaultValues?.icon); + const [defaultVISTheme, setDefaultVISTheme] = useState(defaultValues?.defaultVISTheme); + const [selectedTemplateId, setSelectedTemplateId] = useState(); const [selectedFeatureIds, setSelectedFeatureIds] = useState(defaultValues?.features || []); const selectedTemplate = workspaceTemplates.find( @@ -87,6 +100,9 @@ export const WorkspaceForm = ({ name, description, features: selectedFeatureIds, + color, + icon, + defaultVISTheme, }); const getFormDataRef = useRef(getFormData); getFormDataRef.current = getFormData; @@ -120,6 +136,11 @@ export const WorkspaceForm = ({ }, []); }, [applications]); + const selectedDefaultVISThemeOptions = useMemo( + () => defaultVISThemeOptions.filter((item) => item.value === defaultVISTheme), + [defaultVISTheme] + ); + if (!formIdRef.current) { formIdRef.current = workspaceHtmlIdGenerator(); } @@ -198,6 +219,20 @@ export const WorkspaceForm = ({ setDescription(e.target.value); }, []); + const handleColorChange = useCallback['onChange']>((text) => { + setColor(text); + }, []); + + const handleIconChange = useCallback((newIcon: string) => { + setIcon(newIcon); + }, []); + + const handleDefaultVISThemeInputChange = useCallback< + Required>['onChange'] + >((options) => { + setDefaultVISTheme(options[0]?.value); + }, []); + return ( @@ -217,6 +252,25 @@ export const WorkspaceForm = ({ > + + + + + + + + + @@ -267,74 +321,65 @@ export const WorkspaceForm = ({ )} - - -

Advanced Options

-
- - } - > - - {featureOrGroups.map((featureOrGroup) => { - const features = isWorkspaceFeatureGroup(featureOrGroup) +
+ + + +

Workspace features

+
+ + {featureOrGroups.map((featureOrGroup) => { + const features = isWorkspaceFeatureGroup(featureOrGroup) ? featureOrGroup.features : []; + const selectedIds = selectedFeatureIds.filter((id) => + (isWorkspaceFeatureGroup(featureOrGroup) ? featureOrGroup.features - : []; - const selectedIds = selectedFeatureIds.filter((id) => - (isWorkspaceFeatureGroup(featureOrGroup) - ? featureOrGroup.features - : [featureOrGroup] - ).find((item) => item.id === id) - ); - return ( - - 0 ? `(${selectedIds.length}/${features.length})` : ''}`} - checked={selectedIds.length > 0} - indeterminate={ - isWorkspaceFeatureGroup(featureOrGroup) && - selectedIds.length > 0 && - selectedIds.length < features.length - } + : [featureOrGroup] + ).find((item) => item.id === id) + ); + return ( + + 0 ? `(${selectedIds.length}/${features.length})` : '' + }`} + checked={selectedIds.length > 0} + indeterminate={ + isWorkspaceFeatureGroup(featureOrGroup) && + selectedIds.length > 0 && + selectedIds.length < features.length + } + /> + {isWorkspaceFeatureGroup(featureOrGroup) && ( + ({ + id: item.id, + label: item.name, + }))} + idToSelectedMap={selectedIds.reduce( + (previousValue, currentValue) => ({ + ...previousValue, + [currentValue]: true, + }), + {} + )} + onChange={handleFeatureChange} + style={{ marginLeft: 40 }} /> - {isWorkspaceFeatureGroup(featureOrGroup) && ( - ({ - id: item.id, - label: item.name, - }))} - idToSelectedMap={selectedIds.reduce( - (previousValue, currentValue) => ({ - ...previousValue, - [currentValue]: true, - }), - {} - )} - onChange={handleFeatureChange} - style={{ marginLeft: 40 }} - /> - )} - - ); - })} - - + )} + + ); + })} +
diff --git a/src/plugins/workspace/public/components/workspace_creator/workspace_icon_selector.tsx b/src/plugins/workspace/public/components/workspace_creator/workspace_icon_selector.tsx new file mode 100644 index 00000000000..80e08d8e2e9 --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_creator/workspace_icon_selector.tsx @@ -0,0 +1,36 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; + +import { EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; + +const icons = ['glasses', 'search', 'bell']; + +export const WorkspaceIconSelector = ({ + color, + value, + onChange, +}: { + color?: string; + value?: string; + onChange: (value: string) => void; +}) => { + return ( + + {icons.map((item) => ( + { + onChange(item); + }} + grow={false} + > + + + ))} + + ); +};