diff --git a/common/constants.ts b/common/constants.ts new file mode 100644 index 00000000..3cc13ee1 --- /dev/null +++ b/common/constants.ts @@ -0,0 +1,7 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +export const PLUGIN_ID = 'aiFlowDashboards'; + +export const BASE_NODE_API_PATH = '/api/ai_flow'; diff --git a/common/index.ts b/common/index.ts new file mode 100644 index 00000000..2e209c79 --- /dev/null +++ b/common/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './constants'; diff --git a/opensearch_dashboards.json b/opensearch_dashboards.json new file mode 100644 index 00000000..1f62086b --- /dev/null +++ b/opensearch_dashboards.json @@ -0,0 +1,9 @@ +{ + "id": "aiFlowDashboards", + "version": "3.0.0.0", + "opensearchDashboardsVersion": "3.0.0", + "server": true, + "ui": true, + "requiredPlugins": ["navigation"], + "optionalPlugins": [] +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..ae6192bd --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "ai-flow-dashboards", + "version": "3.0.0.0", + "description": "OpenSearch AI Flow Dashboards Plugin", + "main": "index.js", + "config": { + "plugin_version": "3.0.0.0", + "plugin_name": "aiFlowDashboards", + "plugin_zip_name": "ai-flow-dashboards" + }, + "private": true, + "scripts": { + "plugin-helpers": "../../scripts/use_node ../../scripts/plugin_helpers", + "osd": "../../scripts/use_node ../../scripts/osd", + "opensearch": "node ../../scripts/opensearch", + "lint": "node ../../scripts/eslint .", + "build": "yarn plugin-helpers build && echo Renaming artifact to $npm_package_config_plugin_zip_name-$npm_package_config_plugin_version.zip && mv ./build/$npm_package_config_plugin_name*.zip ./build/$npm_package_config_plugin_zip_name-$npm_package_config_plugin_version.zip" + }, + "lint-staged": { + "*.{ts,tsx,js,jsx,json,css,md}": [ + "prettier --write", + "git add" + ] + }, + "devDependencies": {}, + "dependencies": {}, + "resolutions": {} +} diff --git a/public/app.tsx b/public/app.tsx new file mode 100644 index 00000000..4ad21a9e --- /dev/null +++ b/public/app.tsx @@ -0,0 +1,80 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { I18nProvider } from '@osd/i18n/react'; +import { Route, RouteComponentProps, Switch } from 'react-router-dom'; +import { EuiPageSideBar, EuiSideNav, EuiPageTemplate } from '@elastic/eui'; +import { CoreStart } from '../../../src/core/public'; +import { Navigation, APP_PATH } from './utils'; +import { Overview, UseCases, Workflows } from './pages'; +import { CoreServicesConsumer } from './core_services'; + +interface Props extends RouteComponentProps {} + +export const AiFlowDashboardsApp = (props: Props) => { + const sidebar = ( + + ); + + // Render the application DOM. + return ( + + {(core: CoreStart | null) => + core && ( + + <> + + + } + /> + } + /> + {/* Defaulting to Overview page */} + } + /> + + + + + ) + } + + ); +}; diff --git a/public/core_services.ts b/public/core_services.ts new file mode 100644 index 00000000..e2e8ced4 --- /dev/null +++ b/public/core_services.ts @@ -0,0 +1,13 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { createContext } from 'react'; +import { CoreStart } from '../../../src/core/public'; + +const CoreServicesContext = createContext(null); + +const CoreServicesConsumer = CoreServicesContext.Consumer; + +export { CoreServicesContext, CoreServicesConsumer }; diff --git a/public/index.scss b/public/index.scss new file mode 100644 index 00000000..ff711240 --- /dev/null +++ b/public/index.scss @@ -0,0 +1 @@ +/* stylelint-disable no-empty-source */ diff --git a/public/index.ts b/public/index.ts new file mode 100644 index 00000000..2447574c --- /dev/null +++ b/public/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import './index.scss'; + +import { AiFlowDashboardsPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, OpenSearch Dashboards Platform `plugin()` initializer. +export function plugin() { + return new AiFlowDashboardsPlugin(); +} +export { + AiFlowDashboardsPluginSetup, + AiFlowDashboardsPluginStart, +} from './types'; + +export * from './core_services'; diff --git a/public/pages/index.ts b/public/pages/index.ts new file mode 100644 index 00000000..98812058 --- /dev/null +++ b/public/pages/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './use_cases'; +export * from './workflows'; +export * from './overview'; diff --git a/public/pages/overview/index.ts b/public/pages/overview/index.ts new file mode 100644 index 00000000..5c460c3d --- /dev/null +++ b/public/pages/overview/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { Overview } from './overview'; diff --git a/public/pages/overview/overview.tsx b/public/pages/overview/overview.tsx new file mode 100644 index 00000000..7d8c73d4 --- /dev/null +++ b/public/pages/overview/overview.tsx @@ -0,0 +1,23 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useEffect } from 'react'; +import { EuiPageHeader, EuiText } from '@elastic/eui'; +import { CoreServicesContext } from '../../core_services'; +import { CoreStart } from '../../../../../src/core/public'; +import { BREADCRUMBS } from '../../utils'; + +export function Overview() { + const core = React.useContext(CoreServicesContext) as CoreStart; + useEffect(() => { + core.chrome.setBreadcrumbs([BREADCRUMBS.AI_APPLICATION_BUILDER]); + }); + + return ( + + Welcome to the AI Application Builder! + + ); +} diff --git a/public/pages/use_cases/index.ts b/public/pages/use_cases/index.ts new file mode 100644 index 00000000..d569570e --- /dev/null +++ b/public/pages/use_cases/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { UseCases } from './use_cases'; diff --git a/public/pages/use_cases/use_cases.tsx b/public/pages/use_cases/use_cases.tsx new file mode 100644 index 00000000..efd3c310 --- /dev/null +++ b/public/pages/use_cases/use_cases.tsx @@ -0,0 +1,26 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useEffect } from 'react'; +import { EuiPageHeader, EuiText } from '@elastic/eui'; +import { CoreServicesContext } from '../../core_services'; +import { CoreStart } from '../../../../../src/core/public'; +import { BREADCRUMBS } from '../../utils'; + +export function UseCases() { + const core = React.useContext(CoreServicesContext) as CoreStart; + useEffect(() => { + core.chrome.setBreadcrumbs([ + BREADCRUMBS.AI_APPLICATION_BUILDER, + BREADCRUMBS.USE_CASES, + ]); + }); + + return ( + + Use cases page placeholder... + + ); +} diff --git a/public/pages/workflows/index.ts b/public/pages/workflows/index.ts new file mode 100644 index 00000000..047a1868 --- /dev/null +++ b/public/pages/workflows/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { Workflows } from './workflows'; diff --git a/public/pages/workflows/workflows.tsx b/public/pages/workflows/workflows.tsx new file mode 100644 index 00000000..405151d0 --- /dev/null +++ b/public/pages/workflows/workflows.tsx @@ -0,0 +1,26 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useEffect } from 'react'; +import { EuiPageHeader, EuiText } from '@elastic/eui'; +import { CoreServicesContext } from '../../core_services'; +import { CoreStart } from '../../../../../src/core/public'; +import { BREADCRUMBS } from '../../utils'; + +export function Workflows() { + const core = React.useContext(CoreServicesContext) as CoreStart; + useEffect(() => { + core.chrome.setBreadcrumbs([ + BREADCRUMBS.AI_APPLICATION_BUILDER, + BREADCRUMBS.WORKFLOWS, + ]); + }); + + return ( + + Workflows page placeholder... + + ); +} diff --git a/public/plugin.ts b/public/plugin.ts new file mode 100644 index 00000000..8ef92f99 --- /dev/null +++ b/public/plugin.ts @@ -0,0 +1,46 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + AppMountParameters, + CoreSetup, + CoreStart, + Plugin, +} from '../../../src/core/public'; +import { + AiFlowDashboardsPluginSetup, + AiFlowDashboardsPluginStart, +} from './types'; +import { PLUGIN_ID } from '../common'; + +export class AiFlowDashboardsPlugin + implements Plugin { + public setup(core: CoreSetup): AiFlowDashboardsPluginSetup { + // Register the plugin in the side navigation + core.application.register({ + id: PLUGIN_ID, + title: 'AI Application Builder', + category: { + id: 'opensearch', + label: 'OpenSearch plugins', + order: 2000, + }, + // TODO: can i remove this below order + order: 5000, + async mount(params: AppMountParameters) { + const { renderApp } = await import('./render_app'); + const [coreStart] = await core.getStartServices(); + return renderApp(coreStart, params); + }, + }); + return {}; + } + + public start(core: CoreStart): AiFlowDashboardsPluginStart { + return {}; + } + + public stop() {} +} diff --git a/public/render_app.tsx b/public/render_app.tsx new file mode 100644 index 00000000..bd80668d --- /dev/null +++ b/public/render_app.tsx @@ -0,0 +1,31 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { BrowserRouter as Router, Route } from 'react-router-dom'; +import { AppMountParameters, CoreStart } from '../../../src/core/public'; +import { AiFlowDashboardsApp } from './app'; +import { CoreServicesContext } from './core_services'; + +export const renderApp = ( + coreStart: CoreStart, + { appBasePath, element }: AppMountParameters +) => { + ReactDOM.render( + + ( + + + + )} + > + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/public/types.ts b/public/types.ts new file mode 100644 index 00000000..c6b021cc --- /dev/null +++ b/public/types.ts @@ -0,0 +1,15 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface AiFlowDashboardsPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface AiFlowDashboardsPluginStart {} + +export interface AppPluginStartDependencies { + navigation: NavigationPublicPluginStart; +} diff --git a/public/utils/constants.ts b/public/utils/constants.ts new file mode 100644 index 00000000..0bbcc29c --- /dev/null +++ b/public/utils/constants.ts @@ -0,0 +1,24 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export enum Navigation { + AiApplicationBuilder = 'AI Application Builder', + UseCases = 'Use Cases', + Workflows = 'Workflows', +} + +export enum APP_PATH { + HOME = '/', + USE_CASES = '/use-cases', + WORKSPACE = '/workspace', + WORKFLOWS = '/workflows', + WORKFLOW_DETAIL = '/workflows/:workflowId/', +} + +export const BREADCRUMBS = Object.freeze({ + AI_APPLICATION_BUILDER: { text: 'AI application builder', href: '#/' }, + USE_CASES: { text: 'Use cases', href: `#${APP_PATH.USE_CASES}` }, + WORKFLOWS: { text: 'Workflows', href: `#${APP_PATH.WORKFLOWS}` }, +}); diff --git a/public/utils/index.ts b/public/utils/index.ts new file mode 100644 index 00000000..2e209c79 --- /dev/null +++ b/public/utils/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './constants'; diff --git a/server/index.ts b/server/index.ts new file mode 100644 index 00000000..ea11c96f --- /dev/null +++ b/server/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { PluginInitializerContext } from '../../../src/core/server'; +import { AiFlowDashboardsPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, OpenSearch Dashboards Platform `plugin()` initializer. + +export function plugin(initializerContext: PluginInitializerContext) { + return new AiFlowDashboardsPlugin(initializerContext); +} + +export { + AiFlowDashboardsPluginSetup, + AiFlowDashboardsPluginStart, +} from './types'; diff --git a/server/plugin.ts b/server/plugin.ts new file mode 100644 index 00000000..766ea951 --- /dev/null +++ b/server/plugin.ts @@ -0,0 +1,44 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, + Logger, +} from '../../../src/core/server'; + +import { + AiFlowDashboardsPluginSetup, + AiFlowDashboardsPluginStart, +} from './types'; +import { defineRoutes } from './routes'; + +export class AiFlowDashboardsPlugin + implements Plugin { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup) { + this.logger.debug('ai-flow-dashboards: Setup'); + const router = core.http.createRouter(); + + // Register server side APIs + defineRoutes(router); + + return {}; + } + + public start(core: CoreStart) { + this.logger.debug('ai-flow-dashboards: Started'); + return {}; + } + + public stop() {} +} diff --git a/server/routes/index.ts b/server/routes/index.ts new file mode 100644 index 00000000..0ae579e8 --- /dev/null +++ b/server/routes/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { IRouter } from '../../../../src/core/server'; +import { BASE_NODE_API_PATH } from '../../common'; + +export function defineRoutes(router: IRouter) { + router.get( + { + path: BASE_NODE_API_PATH, + validate: false, + }, + async (context, request, response) => { + return response.ok({ + body: { + time: new Date().toISOString(), + }, + }); + } + ); +} diff --git a/server/types.ts b/server/types.ts new file mode 100644 index 00000000..1fd42a8a --- /dev/null +++ b/server/types.ts @@ -0,0 +1,9 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface AiFlowDashboardsPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface AiFlowDashboardsPluginStart {} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..a598d0f4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,60 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "skipLibCheck": true, + "baseUrl": ".", + "paths": { + // Allows for importing from `opensearch-dashboards` package for the exported types. + "opensearch-dashboards": ["./opensearch_dashboards"], + "ui/*": ["src/legacy/ui/public/*"], + "test_utils/*": ["src/test_utils/public/*"] + }, + // Support .tsx files and transform JSX into calls to React.createElement + "jsx": "react", + // Enables all strict type checking options. + "strict": true, + // enables "core language features" + "lib": [ + // ESNext auto includes previous versions all the way back to es5 + "esnext", + // includes support for browser APIs + "dom" + ], + // Node 8 should support everything output by esnext, we override this + // in webpack with loader-level compiler options + "target": "esnext", + // Use commonjs for node, overridden in webpack to keep import statements + // to maintain support for things like `await import()` + "module": "commonjs", + // Allows default imports from modules with no default export. This does not affect code emit, just type checking. + // We have to enable this option explicitly since `esModuleInterop` doesn't enable it automatically when ES2015 or + // ESNext module format is used. + "allowSyntheticDefaultImports": true, + // Emits __importStar and __importDefault helpers for runtime babel ecosystem compatibility. + "esModuleInterop": true, + // Resolve modules in the same way as Node.js. Aka make `require` works the + // same in TypeScript as it does in Node.js. + "moduleResolution": "node", + // Disallow inconsistently-cased references to the same file. + "forceConsistentCasingInFileNames": true, + // Disable the breaking keyof behaviour introduced in TS 2.9.2 until EUI is updated to support that too + "keyofStringsOnly": true, + // Forbid unused local variables as the rule was deprecated by ts-lint + "noUnusedLocals": true, + // Provide full support for iterables in for..of, spread and destructuring when targeting ES5 or ES3. + "downlevelIteration": true, + // import tslib helpers rather than inlining helpers for iteration or spreading, for instance + "importHelpers": true, + // adding global typings + "types": ["node", "jest", "react"] + }, + "include": [ + "index.ts", + "common/**/*.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../typings/**/*" + ], + "exclude": ["node_modules", "*/node_modules/"] +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..fb57ccd1 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + +