Skip to content

Commit

Permalink
Add color, icon and defaultVISTheme for workspace (opensearch-project#36
Browse files Browse the repository at this point in the history
)

* feat: add color, icon and defaultVISTheme field for workspace saved object

Signed-off-by: Lin Wang <wonglam@amazon.com>

* add new fields to workspace form

Signed-off-by: Lin Wang <wonglam@amazon.com>

* feat: remove feature or group name hack

Signed-off-by: Lin Wang <wonglam@amazon.com>

---------

Signed-off-by: Lin Wang <wonglam@amazon.com>
  • Loading branch information
wanglam committed Jul 5, 2023
1 parent 40ac0a2 commit 49f0bdf
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 77 deletions.
21 changes: 11 additions & 10 deletions src/core/server/workspaces/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
}),
},
},
Expand All @@ -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,
}),
},
},
Expand Down
9 changes: 9 additions & 0 deletions src/core/server/workspaces/saved_objects/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ export const workspace: SavedObjectsType = {
features: {
type: 'text',
},
color: {
type: 'text',
},
icon: {
type: 'text',
},
defaultVISTheme: {
type: 'text',
},
},
},
};
3 changes: 3 additions & 0 deletions src/core/server/workspaces/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export interface WorkspaceAttribute {
name: string;
description?: string;
features?: string[];
color?: string;
icon?: string;
defaultVISTheme?: string;
}

export interface WorkspaceFindOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 };
Expand All @@ -59,6 +66,8 @@ const isWorkspaceFeatureGroup = (

const workspaceHtmlIdGenerator = htmlIdGenerator();

const defaultVISThemeOptions = [{ label: 'Categorical', value: 'categorical' }];

interface WorkspaceFormProps {
application: ApplicationStart;
onSubmit?: (formData: WorkspaceFormData) => void;
Expand All @@ -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<string>();
const [selectedFeatureIds, setSelectedFeatureIds] = useState(defaultValues?.features || []);
const selectedTemplate = workspaceTemplates.find(
Expand All @@ -87,6 +100,9 @@ export const WorkspaceForm = ({
name,
description,
features: selectedFeatureIds,
color,
icon,
defaultVISTheme,
});
const getFormDataRef = useRef(getFormData);
getFormDataRef.current = getFormData;
Expand Down Expand Up @@ -120,6 +136,11 @@ export const WorkspaceForm = ({
}, []);
}, [applications]);

const selectedDefaultVISThemeOptions = useMemo(
() => defaultVISThemeOptions.filter((item) => item.value === defaultVISTheme),
[defaultVISTheme]
);

if (!formIdRef.current) {
formIdRef.current = workspaceHtmlIdGenerator();
}
Expand Down Expand Up @@ -198,6 +219,20 @@ export const WorkspaceForm = ({
setDescription(e.target.value);
}, []);

const handleColorChange = useCallback<Required<EuiColorPickerProps>['onChange']>((text) => {
setColor(text);
}, []);

const handleIconChange = useCallback((newIcon: string) => {
setIcon(newIcon);
}, []);

const handleDefaultVISThemeInputChange = useCallback<
Required<EuiComboBoxProps<string>>['onChange']
>((options) => {
setDefaultVISTheme(options[0]?.value);
}, []);

return (
<EuiForm id={formIdRef.current} onSubmit={handleFormSubmit} component="form">
<EuiPanel>
Expand All @@ -217,6 +252,25 @@ export const WorkspaceForm = ({
>
<EuiFieldText value={description} onChange={handleDescriptionInputChange} />
</EuiFormRow>
<EuiFormRow label="Color" isInvalid={!!formErrors.color} error={formErrors.color}>
<EuiColorPicker color={color} onChange={handleColorChange} />
</EuiFormRow>
<EuiFormRow label="Icon" isInvalid={!!formErrors.icon} error={formErrors.icon}>
<WorkspaceIconSelector value={icon} onChange={handleIconChange} color={color} />
</EuiFormRow>
<EuiFormRow
label="Default VIS Theme"
isInvalid={!!formErrors.defaultVISTheme}
error={formErrors.defaultVISTheme}
>
<EuiComboBox
options={defaultVISThemeOptions}
singleSelection
onChange={handleDefaultVISThemeInputChange}
selectedOptions={selectedDefaultVISThemeOptions}
isClearable={false}
/>
</EuiFormRow>
</EuiPanel>
<EuiSpacer />
<EuiPanel>
Expand Down Expand Up @@ -267,74 +321,65 @@ export const WorkspaceForm = ({
<EuiSpacer />
</>
)}
<EuiAccordion
id={workspaceHtmlIdGenerator()}
buttonContent={
<>
<EuiTitle size="xs">
<h3>Advanced Options</h3>
</EuiTitle>
</>
}
>
<EuiFlexGrid style={{ paddingLeft: 20, paddingTop: 20 }} columns={2}>
{featureOrGroups.map((featureOrGroup) => {
const features = isWorkspaceFeatureGroup(featureOrGroup)
</EuiPanel>
<EuiSpacer />
<EuiPanel>
<EuiTitle size="s">
<h2>Workspace features</h2>
</EuiTitle>
<EuiFlexGrid style={{ paddingLeft: 20, paddingTop: 20 }} columns={2}>
{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 (
<EuiFlexItem key={featureOrGroup.name}>
<EuiCheckbox
id={
isWorkspaceFeatureGroup(featureOrGroup)
? featureOrGroup.name
: featureOrGroup.id
}
onChange={
isWorkspaceFeatureGroup(featureOrGroup)
? handleFeatureGroupChange
: handleFeatureCheckboxChange
}
label={`${
featureOrGroup.name === 'OpenSearch Plugins'
? 'OpenSearch Features'
: featureOrGroup.name
}${features.length > 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 (
<EuiFlexItem key={featureOrGroup.name}>
<EuiCheckbox
id={
isWorkspaceFeatureGroup(featureOrGroup)
? featureOrGroup.name
: featureOrGroup.id
}
onChange={
isWorkspaceFeatureGroup(featureOrGroup)
? handleFeatureGroupChange
: handleFeatureCheckboxChange
}
label={`${featureOrGroup.name}${
features.length > 0 ? `(${selectedIds.length}/${features.length})` : ''
}`}
checked={selectedIds.length > 0}
indeterminate={
isWorkspaceFeatureGroup(featureOrGroup) &&
selectedIds.length > 0 &&
selectedIds.length < features.length
}
/>
{isWorkspaceFeatureGroup(featureOrGroup) && (
<EuiCheckboxGroup
options={featureOrGroup.features.map((item) => ({
id: item.id,
label: item.name,
}))}
idToSelectedMap={selectedIds.reduce(
(previousValue, currentValue) => ({
...previousValue,
[currentValue]: true,
}),
{}
)}
onChange={handleFeatureChange}
style={{ marginLeft: 40 }}
/>
{isWorkspaceFeatureGroup(featureOrGroup) && (
<EuiCheckboxGroup
options={featureOrGroup.features.map((item) => ({
id: item.id,
label: item.name,
}))}
idToSelectedMap={selectedIds.reduce(
(previousValue, currentValue) => ({
...previousValue,
[currentValue]: true,
}),
{}
)}
onChange={handleFeatureChange}
style={{ marginLeft: 40 }}
/>
)}
</EuiFlexItem>
);
})}
</EuiFlexGrid>
</EuiAccordion>
)}
</EuiFlexItem>
);
})}
</EuiFlexGrid>
</EuiPanel>
<EuiSpacer />
<EuiText textAlign="right">
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<EuiFlexGroup>
{icons.map((item) => (
<EuiFlexItem
key={item}
onClick={() => {
onChange(item);
}}
grow={false}
>
<EuiIcon size="l" type={item} color={value === item ? color : undefined} />
</EuiFlexItem>
))}
</EuiFlexGroup>
);
};

0 comments on commit 49f0bdf

Please sign in to comment.