diff --git a/common/constants.ts b/common/constants.ts index 5d6e0678..4d50974c 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -11,8 +11,8 @@ import { } from './interfaces'; import { customStringify } from './utils'; -export const PLUGIN_ID = 'search-studio'; -export const PLUGIN_NAME = 'Search Studio'; +export const PLUGIN_ID = 'opensearch-flow'; +export const PLUGIN_NAME = 'OpenSearch Flow'; /** * BACKEND FLOW FRAMEWORK APIs @@ -178,6 +178,8 @@ export const WORKFLOW_TUTORIAL_LINK = 'https://opensearch.org/docs/latest/automating-configurations/workflow-tutorial/'; export const NORMALIZATION_PROCESSOR_LINK = 'https://opensearch.org/docs/latest/search-plugins/search-pipelines/normalization-processor/'; +export const GITHUB_FEEDBACK_LINK = + 'https://github.com/opensearch-project/dashboards-flow-framework/issues/new/choose'; /** * Text chunking algorithm constants diff --git a/public/general_components/experimental_badge.tsx b/public/general_components/experimental_badge.tsx new file mode 100644 index 00000000..b1d098d0 --- /dev/null +++ b/public/general_components/experimental_badge.tsx @@ -0,0 +1,64 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState } from 'react'; +import { + EuiBetaBadge, + EuiPopover, + EuiPopoverFooter, + EuiPopoverTitle, + EuiSmallButton, + EuiText, + PopoverAnchorPosition, +} from '@elastic/eui'; +import { GITHUB_FEEDBACK_LINK } from '../../common'; + +interface ExperimentalBadgeProps { + popoverEnabled: boolean; + popoverAnchorPosition?: PopoverAnchorPosition; +} + +/** + * Experimental/beta badge with an optional popover for users to provide feedback + */ +export function ExperimentalBadge(props: ExperimentalBadgeProps) { + const [popoverOpen, setPopoverOpen] = useState(false); + + return props.popoverEnabled ? ( + setPopoverOpen(!popoverOpen)} + /> + } + isOpen={popoverOpen} + closePopover={() => setPopoverOpen(false)} + panelPaddingSize="s" + anchorPosition={props.popoverAnchorPosition || 'downCenter'} + > + EXPERIMENTAL FEATURE + + {`OpenSearch Flow is experimental and should not be used in a\n + production environment.`} + + + + Provide feedback on GitHub + + + + ) : ( + + ); +} diff --git a/public/general_components/index.ts b/public/general_components/index.ts index 24c72304..f475b50d 100644 --- a/public/general_components/index.ts +++ b/public/general_components/index.ts @@ -5,3 +5,5 @@ export { MultiSelectFilter } from './multi_select_filter'; export { ProcessorsTitle } from './processors_title'; +export { ExperimentalBadge } from './experimental_badge'; +export * from './service_card'; diff --git a/public/general_components/service_card/index.ts b/public/general_components/service_card/index.ts new file mode 100644 index 00000000..868d31bb --- /dev/null +++ b/public/general_components/service_card/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { registerPluginCard } from './plugin_card'; diff --git a/public/general_components/service_card/plugin_card.tsx b/public/general_components/service_card/plugin_card.tsx new file mode 100644 index 00000000..43dee810 --- /dev/null +++ b/public/general_components/service_card/plugin_card.tsx @@ -0,0 +1,93 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { + EuiSmallButton, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { PLUGIN_ID } from '../../../common'; +import { ContentManagementPluginStart } from '../../../../../src/plugins/content_management/public'; +import { CoreStart } from '../../../../../src/core/public'; +import pluginIcon from './icon.svg'; +import { ExperimentalBadge } from '../experimental_badge'; + +const HEADER_TEXT = 'Design and test your search solutions with ease'; +const DESCRIPTION_TEXT = + 'OpenSearch Flow is a visual editor for creating search AI flows to power advanced search and generative AI solutions.'; + +export const registerPluginCard = ( + contentManagement: ContentManagementPluginStart, + core: CoreStart +) => { + const icon = ( + + ); + + const footer = ( + + + { + core.application.navigateToApp(PLUGIN_ID); + }} + > + {i18n.translate('flowFrameworkDashboards.opensearchFlowCard.footer', { + defaultMessage: 'Try OpenSearch Flow', + })} + + + + ); + + contentManagement.registerContentProvider({ + id: 'opensearch_flow_card', + getContent: () => ({ + id: 'opensearch_flow', + kind: 'card', + order: 20, + getTitle: () => { + return ( + + + +

+ {i18n.translate( + 'flowFrameworkDashboards.opensearchFlowCard.title', + { + defaultMessage: HEADER_TEXT, + } + )} +

+
+
+ + + +
+ ); + }, + description: i18n.translate( + 'flowFrameworkDashboards.opensearchFlowCard.description', + { + defaultMessage: DESCRIPTION_TEXT, + } + ), + getIcon: () => icon, + cardProps: { + children: footer, + layout: 'horizontal', + }, + }), + getTargetArea: () => 'search_overview/config_evaluate_search', + }); +}; diff --git a/public/general_components/service_card/search_studio_card.tsx b/public/general_components/service_card/search_studio_card.tsx deleted file mode 100644 index 6d18b842..00000000 --- a/public/general_components/service_card/search_studio_card.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { - EuiSmallButton, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, -} from '@elastic/eui'; -import { i18n } from '@osd/i18n'; -import { PLUGIN_ID } from '../../../common'; -import { ContentManagementPluginStart } from '../../../../../src/plugins/content_management/public'; -import { CoreStart } from '../../../../../src/core/public'; -import searchStudioIcon from './icon.svg'; - -export const registerSearchStudioCard = ( - contentManagement: ContentManagementPluginStart, - core: CoreStart -) => { - const icon = ( - - ); - - const footer = ( - - - { - core.application.navigateToApp(PLUGIN_ID); - }} - > - {i18n.translate('flowFrameworkDashboards.searchStudioCard.footer', { - defaultMessage: 'Try OpenSearch Studio', - })} - - - - ); - - contentManagement.registerContentProvider({ - id: 'search_studio_card', - getContent: () => ({ - id: 'search_studio', - kind: 'card', - order: 20, - title: i18n.translate('flowFrameworkDashboards.searchStudioCard.title', { - defaultMessage: 'Design and test your search solutions with ease', - }), - description: i18n.translate( - 'flowFrameworkDashboards.searchStudioCard.description', - { - defaultMessage: - 'OpenSearch Studio is a visual editor for creating search AI flows to power advanced search and generative AI solutions.', - } - ), - getIcon: () => icon, - cardProps: { - children: footer, - layout: 'horizontal', - }, - }), - getTargetArea: () => 'search_overview/config_evaluate_search', - }); -}; diff --git a/public/pages/workflow_detail/components/header.tsx b/public/pages/workflow_detail/components/header.tsx index 77541620..5f571e67 100644 --- a/public/pages/workflow_detail/components/header.tsx +++ b/public/pages/workflow_detail/components/header.tsx @@ -27,7 +27,7 @@ import { } from '../../../../common'; import { APP_PATH, - SHOW_ACTIONS_IN_HEADER, + USE_NEW_HOME_PAGE, constructUrlWithParams, getDataSourceId, dataSourceFilterFn, @@ -99,13 +99,13 @@ export function WorkflowDetailHeader(props: WorkflowDetailHeaderProps) { // When NewHomePage is enabled, use 'application' HeaderVariant; otherwise, use 'page' HeaderVariant (default). useEffect(() => { - if (SHOW_ACTIONS_IN_HEADER) { + if (USE_NEW_HOME_PAGE) { setHeaderVariant?.(HeaderVariant.APPLICATION); } return () => { setHeaderVariant?.(); }; - }, [setHeaderVariant, SHOW_ACTIONS_IN_HEADER]); + }, [setHeaderVariant, USE_NEW_HOME_PAGE]); const onExportButtonClick = () => { setIsExportModalOpen(true); @@ -278,7 +278,7 @@ export function WorkflowDetailHeader(props: WorkflowDetailHeaderProps) { setIsExportModalOpen={setIsExportModalOpen} /> )} - {SHOW_ACTIONS_IN_HEADER ? ( + {USE_NEW_HOME_PAGE ? ( <> diff --git a/public/pages/workflow_detail/workflow_detail.tsx b/public/pages/workflow_detail/workflow_detail.tsx index acba2f9f..2b201633 100644 --- a/public/pages/workflow_detail/workflow_detail.tsx +++ b/public/pages/workflow_detail/workflow_detail.tsx @@ -21,7 +21,7 @@ import { import { APP_PATH, BREADCRUMBS, - SHOW_ACTIONS_IN_HEADER, + USE_NEW_HOME_PAGE, uiConfigToFormik, uiConfigToSchema, } from '../../utils'; @@ -109,7 +109,7 @@ export function WorkflowDetail(props: WorkflowDetailProps) { } = getCore(); useEffect(() => { setBreadcrumbs( - SHOW_ACTIONS_IN_HEADER + USE_NEW_HOME_PAGE ? [ BREADCRUMBS.TITLE_WITH_REF( dataSourceEnabled ? dataSourceId : undefined @@ -123,7 +123,7 @@ export function WorkflowDetail(props: WorkflowDetailProps) { { text: workflowName }, ] ); - }, [SHOW_ACTIONS_IN_HEADER, dataSourceEnabled, dataSourceId, workflowName]); + }, [USE_NEW_HOME_PAGE, dataSourceEnabled, dataSourceId, workflowName]); // form state const [formValues, setFormValues] = useState({}); diff --git a/public/pages/workflows/import_workflow/import_workflow_modal.tsx b/public/pages/workflows/import_workflow/import_workflow_modal.tsx index a51b0de1..e8f4ff36 100644 --- a/public/pages/workflows/import_workflow/import_workflow_modal.tsx +++ b/public/pages/workflows/import_workflow/import_workflow_modal.tsx @@ -102,7 +102,7 @@ export function ImportWorkflowModal(props: ImportWorkflowModalProps) { <> diff --git a/public/pages/workflows/workflows.tsx b/public/pages/workflows/workflows.tsx index e13541a8..e4ed9604 100644 --- a/public/pages/workflows/workflows.tsx +++ b/public/pages/workflows/workflows.tsx @@ -15,10 +15,11 @@ import { EuiFlexGroup, EuiSmallButton, EuiText, + EuiFlexItem, } from '@elastic/eui'; import queryString from 'query-string'; import { useSelector } from 'react-redux'; -import { BREADCRUMBS, SHOW_ACTIONS_IN_HEADER } from '../../utils/constants'; +import { BREADCRUMBS, USE_NEW_HOME_PAGE } from '../../utils/constants'; import { getApplication, getCore, getNavigationUI } from '../../services'; import { WorkflowList } from './workflow_list'; import { NewWorkflow } from './new_workflow'; @@ -28,18 +29,16 @@ import { FETCH_ALL_QUERY, PLUGIN_NAME } from '../../../common'; import { ImportWorkflowModal } from './import_workflow'; import { MountPoint } from '../../../../../src/core/public'; import { DataSourceSelectableConfig } from '../../../../../src/plugins/data_source_management/public'; - import { dataSourceFilterFn, getDataSourceFromURL } from '../../utils/utils'; - import { getDataSourceManagementPlugin, getDataSourceEnabled, getNotifications, getSavedObjectsClient, } from '../../services'; - import { prettifyErrorMessage } from '../../../common/utils'; import { DataSourceOption } from '../../../../../src/plugins/data_source_management/public/components/data_source_menu/types'; +import { ExperimentalBadge } from '../../general_components'; export interface WorkflowsRouterProps {} @@ -127,7 +126,7 @@ export function Workflows(props: WorkflowsProps) { useEffect(() => { setBreadcrumbs( - SHOW_ACTIONS_IN_HEADER + USE_NEW_HOME_PAGE ? [BREADCRUMBS.TITLE] : [ BREADCRUMBS.PLUGIN_NAME, @@ -207,7 +206,7 @@ export function Workflows(props: WorkflowsProps) { ingestion flows with a visual interface. Experiment with different configurations with prototyping tools and launch them into your environment.`; - const pageTitleAndDescription = SHOW_ACTIONS_IN_HEADER ? ( + const pageTitleAndDescription = USE_NEW_HOME_PAGE ? ( ) : ( - -

{PLUGIN_NAME}

-
+ + + +

{PLUGIN_NAME}

+
+
+ + + +
{DESCRIPTION}
); diff --git a/public/plugin.ts b/public/plugin.ts index 877207f8..dfe4d198 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -16,7 +16,7 @@ import { FlowFrameworkDashboardsPluginSetup, AppPluginStartDependencies, } from './types'; -import { registerSearchStudioCard } from './general_components/service_card/search_studio_card'; +import { registerPluginCard } from './general_components'; import { PLUGIN_ID, PLUGIN_NAME } from '../common'; import { setCore, @@ -84,14 +84,14 @@ export class FlowFrameworkDashboardsPlugin public start( core: CoreStart, - { navigation, contentManagement }: AppPluginStartDependencies, + { navigation, contentManagement }: AppPluginStartDependencies ): FlowFrameworkDashboardsPluginStart { setNotifications(core.notifications); setSavedObjectsClient(core.savedObjects.client); setNavigationUI(navigation.ui); setApplication(core.application); if (contentManagement) { - registerSearchStudioCard(contentManagement, core); + registerPluginCard(contentManagement, core); } return {}; } diff --git a/public/utils/constants.ts b/public/utils/constants.ts index 840367fb..7206aa89 100644 --- a/public/utils/constants.ts +++ b/public/utils/constants.ts @@ -34,6 +34,4 @@ export const BREADCRUMBS = Object.freeze({ }), }); -export const SHOW_ACTIONS_IN_HEADER = getUISettings().get( - 'home:useNewHomePage' -); +export const USE_NEW_HOME_PAGE = getUISettings().get('home:useNewHomePage'); diff --git a/test/jest.config.js b/test/jest.config.js index 91b0e142..9d1322a8 100644 --- a/test/jest.config.js +++ b/test/jest.config.js @@ -7,10 +7,10 @@ module.exports = { rootDir: '../', roots: [''], coverageDirectory: './coverage', - // we mock any style-related files and return an empty module. This is needed due to errors + // we mock any non-js-related files and return an empty module. This is needed due to errors // when jest tries to interpret these types of files. moduleNameMapper: { - '\\.(css|less|scss|sass)$': '/test/mocks/style_mock.ts', + '\\.(css|less|scss|sass|svg)$': '/test/mocks/empty_mock.ts', }, testEnvironment: 'jest-environment-jsdom', coverageReporters: ['lcov', 'text', 'cobertura'], diff --git a/test/mocks/style_mock.ts b/test/mocks/empty_mock.ts similarity index 100% rename from test/mocks/style_mock.ts rename to test/mocks/empty_mock.ts