diff --git a/src/plugins/workspace/public/components/workspace_creator/index.tsx b/src/plugins/workspace/public/components/workspace_creator/index.tsx
index 11a3e0feaf92..c8cdbfab65be 100644
--- a/src/plugins/workspace/public/components/workspace_creator/index.tsx
+++ b/src/plugins/workspace/public/components/workspace_creator/index.tsx
@@ -3,8 +3,4 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import React from 'react';
-
-export const WorkspaceCreator = () => {
- return
TODO
;
-};
+export { WorkspaceCreator } from './workspace_creator';
diff --git a/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx b/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx
new file mode 100644
index 000000000000..e7006464ba7d
--- /dev/null
+++ b/src/plugins/workspace/public/components/workspace_creator/workspace_creator.tsx
@@ -0,0 +1,39 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { useCallback } from 'react';
+import { EuiPage, EuiPageBody, EuiPageHeader, EuiPageContent } from '@elastic/eui';
+
+import { useOpenSearchDashboards } from '../../../../../plugins/opensearch_dashboards_react/public';
+
+import { WorkspaceForm } from './workspace_form';
+
+export const WorkspaceCreator = () => {
+ const {
+ services: { application },
+ } = useOpenSearchDashboards();
+
+ const handleWorkspaceFormSubmit = useCallback(() => {}, []);
+
+ return (
+
+
+
+
+ {application && (
+
+ )}
+
+
+
+ );
+};
diff --git a/src/plugins/workspace/public/components/workspace_creator/workspace_form.tsx b/src/plugins/workspace/public/components/workspace_creator/workspace_form.tsx
new file mode 100644
index 000000000000..8cb3a1e3c39d
--- /dev/null
+++ b/src/plugins/workspace/public/components/workspace_creator/workspace_form.tsx
@@ -0,0 +1,338 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { useCallback, useState, FormEventHandler, useRef, useMemo } from 'react';
+import { groupBy } from 'lodash';
+import {
+ EuiPanel,
+ EuiSpacer,
+ EuiTitle,
+ EuiForm,
+ EuiFormRow,
+ EuiFieldText,
+ EuiText,
+ EuiButton,
+ EuiFlexItem,
+ EuiCheckableCard,
+ htmlIdGenerator,
+ EuiFlexGrid,
+ EuiFlexGroup,
+ EuiImage,
+ EuiAccordion,
+ EuiCheckbox,
+ EuiCheckboxGroup,
+ EuiCheckableCardProps,
+ EuiCheckboxGroupProps,
+ EuiCheckboxProps,
+ EuiFieldTextProps,
+} from '@elastic/eui';
+
+import { WorkspaceTemplate } from '../../../../../core/types';
+import { AppNavLinkStatus, ApplicationStart } from '../../../../../core/public';
+import { useApplications, useWorkspaceTemplate } from '../../hooks';
+
+interface WorkspaceFeature {
+ id: string;
+ name: string;
+ templates: WorkspaceTemplate[];
+}
+
+interface WorkspaceFeatureGroup {
+ name: string;
+ features: WorkspaceFeature[];
+}
+
+interface WorkspaceFormData {
+ name: string;
+ description?: string;
+ features: string[];
+}
+
+type WorkspaceFormErrors = { [key in keyof WorkspaceFormData]?: string };
+
+const isWorkspaceFeatureGroup = (
+ featureOrGroup: WorkspaceFeature | WorkspaceFeatureGroup
+): featureOrGroup is WorkspaceFeatureGroup => 'features' in featureOrGroup;
+
+const workspaceHtmlIdGenerator = htmlIdGenerator();
+
+interface WorkspaceFormProps {
+ application: ApplicationStart;
+ onSubmit?: (formData: WorkspaceFormData) => void;
+ defaultValues?: WorkspaceFormData;
+}
+export const WorkspaceForm = ({ application, onSubmit, defaultValues }: WorkspaceFormProps) => {
+ const { workspaceTemplates, templateFeatureMap } = useWorkspaceTemplate(application);
+ const applications = useApplications(application);
+
+ const [name, setName] = useState(defaultValues?.name);
+ const [description, setDescription] = useState(defaultValues?.description);
+ const [selectedTemplateId, setSelectedTemplateId] = useState();
+ const [selectedFeatureIds, setSelectedFeatureIds] = useState(defaultValues?.features || []);
+ const selectedTemplate = workspaceTemplates.find(
+ (template) => template.id === selectedTemplateId
+ );
+ const [formErrors, setFormErrors] = useState({});
+ const formIdRef = useRef();
+ const getFormData = () => ({
+ name,
+ description,
+ features: selectedFeatureIds,
+ });
+ const getFormDataRef = useRef(getFormData);
+ getFormDataRef.current = getFormData;
+
+ const featureOrGroups = useMemo(() => {
+ const category2Applications = groupBy(applications, 'category.label');
+ return Object.keys(category2Applications).reduce<
+ Array
+ >((previousValue, currentKey) => {
+ const apps = category2Applications[currentKey];
+ const features = apps
+ .filter(
+ ({ navLinkStatus, chromeless }) =>
+ navLinkStatus !== AppNavLinkStatus.hidden && !chromeless
+ )
+ .map(({ id, title, workspaceTemplate }) => ({
+ id,
+ name: title,
+ templates: workspaceTemplate || [],
+ }));
+ if (features.length === 1 || currentKey === 'undefined') {
+ return [...previousValue, ...features];
+ }
+ return [
+ ...previousValue,
+ {
+ name: apps[0].category?.label || '',
+ features,
+ },
+ ];
+ }, []);
+ }, [applications]);
+
+ if (!formIdRef.current) {
+ formIdRef.current = workspaceHtmlIdGenerator();
+ }
+
+ const handleTemplateCardChange = useCallback(
+ (e) => {
+ const templateId = e.target.value;
+ setSelectedTemplateId(templateId);
+ setSelectedFeatureIds(
+ featureOrGroups.reduce(
+ (previousData, currentData) => [
+ ...previousData,
+ ...(isWorkspaceFeatureGroup(currentData) ? currentData.features : [currentData])
+ .filter(({ templates }) => !!templates.find((template) => template.id === templateId))
+ .map((feature) => feature.id),
+ ],
+ []
+ )
+ );
+ },
+ [featureOrGroups]
+ );
+
+ const handleFeatureChange = useCallback((featureId) => {
+ setSelectedFeatureIds((previousData) =>
+ previousData.includes(featureId)
+ ? previousData.filter((id) => id !== featureId)
+ : [...previousData, featureId]
+ );
+ }, []);
+
+ const handleFeatureCheckboxChange = useCallback(
+ (e) => {
+ handleFeatureChange(e.target.id);
+ },
+ [handleFeatureChange]
+ );
+
+ const handleFeatureGroupChange = useCallback(
+ (e) => {
+ for (const featureOrGroup of featureOrGroups) {
+ if (isWorkspaceFeatureGroup(featureOrGroup) && featureOrGroup.name === e.target.id) {
+ const groupFeatureIds = featureOrGroup.features.map((feature) => feature.id);
+ setSelectedFeatureIds((previousData) => {
+ const notExistsIds = groupFeatureIds.filter((id) => !previousData.includes(id));
+ if (notExistsIds.length > 0) {
+ return [...previousData, ...notExistsIds];
+ }
+ return previousData.filter((id) => !groupFeatureIds.includes(id));
+ });
+ }
+ }
+ },
+ [featureOrGroups]
+ );
+
+ const handleFormSubmit = useCallback(
+ (e) => {
+ e.preventDefault();
+ const formData = getFormDataRef.current();
+ if (!formData.name) {
+ setFormErrors({ name: "Name can't be empty." });
+ return;
+ }
+ setFormErrors({});
+ onSubmit?.({ ...formData, name: formData.name });
+ },
+ [onSubmit]
+ );
+
+ const handleNameInputChange = useCallback['onChange']>((e) => {
+ setName(e.target.value);
+ }, []);
+
+ const handleDescriptionInputChange = useCallback['onChange']>((e) => {
+ setDescription(e.target.value);
+ }, []);
+
+ return (
+
+
+
+ Workspace details
+
+
+
+
+
+
+ Description - optional
+ >
+ }
+ >
+
+
+
+
+
+
+ Workspace Template
+
+
+
+ {workspaceTemplates.map((template) => (
+
+
+
+ ))}
+
+
+ {selectedTemplate && (
+ <>
+
+ Features
+
+
+
+ {selectedTemplate.coverImage && (
+
+
+
+ )}
+
+ {selectedTemplate.description}
+
+ Key Features:
+
+
+
+ {templateFeatureMap.get(selectedTemplate.id)?.map((feature) => (
+ • {feature.title}
+ ))}
+
+
+
+
+ >
+ )}
+
+
+ Advanced Options
+
+ >
+ }
+ >
+
+ {featureOrGroups.map((featureOrGroup) => {
+ const features = 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
+ }
+ />
+ {isWorkspaceFeatureGroup(featureOrGroup) && (
+ ({
+ id: item.id,
+ label: item.name,
+ }))}
+ idToSelectedMap={selectedIds.reduce(
+ (previousValue, currentValue) => ({
+ ...previousValue,
+ [currentValue]: true,
+ }),
+ {}
+ )}
+ onChange={handleFeatureChange}
+ style={{ marginLeft: 40 }}
+ />
+ )}
+
+ );
+ })}
+
+
+
+
+
+
+ Create workspace
+
+
+
+ );
+};
diff --git a/src/plugins/workspace/public/hooks.ts b/src/plugins/workspace/public/hooks.ts
index 71019c5948a2..636a00742146 100644
--- a/src/plugins/workspace/public/hooks.ts
+++ b/src/plugins/workspace/public/hooks.ts
@@ -8,6 +8,17 @@ import { useObservable } from 'react-use';
import { useMemo } from 'react';
import { WorkspaceTemplate } from '../../../core/types';
+export function useApplications(application: ApplicationStart) {
+ const applications = useObservable(application.applications$);
+ return useMemo(() => {
+ const apps: PublicAppInfo[] = [];
+ applications?.forEach((app) => {
+ apps.push(app);
+ });
+ return apps;
+ }, [applications]);
+}
+
export function useWorkspaceTemplate(application: ApplicationStart) {
const applications = useObservable(application.applications$);