diff --git a/src/plugins/home/public/application/components/homepage/_homepage.scss b/src/plugins/home/public/application/components/homepage/_homepage.scss
index dc971440c126..25a8530c7394 100644
--- a/src/plugins/home/public/application/components/homepage/_homepage.scss
+++ b/src/plugins/home/public/application/components/homepage/_homepage.scss
@@ -1,3 +1,5 @@
+@import "sections/hero";
+
.home-homepage-pageBody {
// This is needed to make sure the page body is not wider than the page.
// This is otherwise not possible with the props on EuiPageTemplate.
diff --git a/src/plugins/home/public/application/components/homepage/sections/hero.scss b/src/plugins/home/public/application/components/homepage/sections/hero.scss
new file mode 100644
index 000000000000..3e226faeffc3
--- /dev/null
+++ b/src/plugins/home/public/application/components/homepage/sections/hero.scss
@@ -0,0 +1,35 @@
+.home-hero-title {
+ color: $euiColorPrimary;
+}
+
+.home-hero-descriptionSection {
+ align-items: flex-start;
+}
+
+.home-hero-group > * {
+ flex-shrink: 0;
+}
+
+.home-getStarted-chatIcon {
+ padding-left: $euiSizeXS;
+}
+
+.home-hero-illustrationContainer {
+ position: relative;
+ display: inline-block;
+}
+
+.home-hero-illustrationButton {
+ z-index: 1040;
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+
+ &:hover:not([class*="isDisabled"]),
+ &:active:not([class*="isDisabled"]),
+ &:focus:not([class*="isDisabled"]) {
+ transform: translate(-50%, -50%);
+ animation: none !important;
+ }
+}
diff --git a/src/plugins/home/public/application/components/homepage/sections/hero.tsx b/src/plugins/home/public/application/components/homepage/sections/hero.tsx
new file mode 100644
index 000000000000..5a5c6b6b9e3a
--- /dev/null
+++ b/src/plugins/home/public/application/components/homepage/sections/hero.tsx
@@ -0,0 +1,170 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+import { i18n } from '@osd/i18n';
+import { FormattedMessage } from '@osd/i18n/react';
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButton,
+ EuiTitle,
+ EuiImage,
+ EuiLink,
+ EuiButtonIcon,
+ EuiText,
+ EuiSpacer,
+} from '@elastic/eui';
+import illustration from '../../../../assets/illustration.svg';
+import { getServices } from '../../../opensearch_dashboards_services';
+import { renderFn } from './utils';
+
+// TODO: This is hardcoded for the playground. Do not use long term
+const DEFAULT_HERO_DATA = {
+ title: 'Try the Query Assistant',
+ body:
+ 'Automatically generate complex queries using simple conversational prompts. AI assisted summary helps you navigate and understand errors from your logs.{br}{br}You will be redirected to the AI playground where you will need to login. All the {terms} of the playground still apply.',
+ externalActionButton: {
+ text: 'Login to try',
+ link: 'https://ai.playground.opensearch.org/',
+ },
+ img: {
+ link: 'https://www.youtube.com/watch?v=VTiJtGI2Sr4',
+ },
+ secondaryButton: {
+ text: 'Learn more',
+ link: 'https://opensearch.org/blog/opensearch-adds-new-generative-ai-assistant-toolkit/',
+ },
+};
+
+export const GetStartedSection: React.FC = () => {
+ const services = getServices();
+ const addBasePath = services.http.basePath.prepend;
+ const heroConfig = DEFAULT_HERO_DATA;
+
+ const description = (
+ ,
+ terms: (
+
+
+
+ ),
+ }}
+ />
+ );
+
+ const actionButton = (
+
+
+
+ );
+
+ const illustrationPanel = heroConfig.img ? (
+
+
+
+
+ ) : (
+
+ );
+
+ function getIllustrationImage() {
+ return addBasePath('/plugins/home/assets/screenshot.png');
+ }
+
+ const links = [
+ {
+ text: i18n.translate('home.getStarted.learnMore', {
+ defaultMessage: heroConfig.secondaryButton.text,
+ }),
+ url: heroConfig.secondaryButton.link,
+ },
+ ];
+
+ return (
+ }
+ illustrationEle={illustrationPanel}
+ />
+ );
+};
+
+interface HeroSectionProps {
+ title: string;
+ description: React.ReactNode;
+ links: Array<{
+ text: string;
+ url: string;
+ }>;
+ actionButton: React.ReactNode;
+ content: React.ReactNode;
+ illustrationEle: React.ReactNode;
+}
+
+export const HeroSection: React.FC = ({
+ title,
+ description,
+ links,
+ actionButton,
+ content,
+ illustrationEle,
+}) => {
+ return (
+
+ {illustrationEle}
+
+
+ {title}
+
+ {description}
+
+ {actionButton}
+
+
+ {links.map((link) => (
+
+
+ {link.text}
+
+
+ ))}
+
+
+ {content}
+
+ );
+};
+
+export const render = renderFn(() => {
+ return ;
+});
+
+export const heroSection = {
+ id: 'home:query-assist',
+ render,
+};
diff --git a/src/plugins/home/public/assets/illustration.svg b/src/plugins/home/public/assets/illustration.svg
new file mode 100644
index 000000000000..879f8deb8efa
--- /dev/null
+++ b/src/plugins/home/public/assets/illustration.svg
@@ -0,0 +1,341 @@
+
+
diff --git a/src/plugins/home/public/assets/screenshot.png b/src/plugins/home/public/assets/screenshot.png
new file mode 100644
index 000000000000..60963cdb9429
Binary files /dev/null and b/src/plugins/home/public/assets/screenshot.png differ
diff --git a/src/plugins/home/public/plugin.ts b/src/plugins/home/public/plugin.ts
index 75b39dadd8c0..1fafbc0e8a17 100644
--- a/src/plugins/home/public/plugin.ts
+++ b/src/plugins/home/public/plugin.ts
@@ -61,6 +61,7 @@ import { PLUGIN_ID, HOME_APP_BASE_PATH } from '../common/constants';
import { DataSourcePluginStart } from '../../data_source/public';
import { workWithDataSection } from './application/components/homepage/sections/work_with_data';
import { learnBasicsSection } from './application/components/homepage/sections/learn_basics';
+import { heroSection } from './application/components/homepage/sections/hero';
export interface HomePluginStartDependencies {
data: DataPublicPluginStart;
@@ -159,6 +160,7 @@ export class HomePublicPlugin
sectionTypes.registerSection(workWithDataSection);
sectionTypes.registerSection(learnBasicsSection);
+ sectionTypes.registerHeroSection(heroSection);
return {
featureCatalogue,
diff --git a/src/plugins/home/public/services/section_type/section_type.ts b/src/plugins/home/public/services/section_type/section_type.ts
index 5b57b6e0f272..a80181575769 100644
--- a/src/plugins/home/public/services/section_type/section_type.ts
+++ b/src/plugins/home/public/services/section_type/section_type.ts
@@ -11,6 +11,23 @@ import { DataPublicPluginStart } from '../../../../data/public';
import { SavedObjectLoader } from '../../../../saved_objects/public';
import { createSavedHomepageLoader, SavedHomepage } from '../../saved_homepage';
+// TODO: This is temporarily hardcoded for the playground
+const PLAYGROUND_HOMEPAGE = {
+ heroes: [
+ {
+ id: 'home:query-assist',
+ },
+ ],
+ sections: [
+ {
+ id: 'home:workWithData',
+ },
+ {
+ id: 'home:learnBasics',
+ },
+ ],
+};
+
// TODO: this should support error handling explicitly
// TODO: this should support async rendering through a promise
export type RenderFn = (element: HTMLElement) => () => void;
@@ -102,42 +119,43 @@ export class SectionTypeService {
const subscriptions = new Subscription();
- this.fetchHomepageData()
- .then((homepage) => {
- const initialHeroes = Array.isArray(homepage.heroes) ? homepage.heroes : [homepage.heroes];
- const initialSections = Array.isArray(homepage.sections)
- ? homepage.sections
- : [homepage.sections];
-
- heroes$.next(initialHeroes.map((hero) => this.heroSections[hero.id]).filter(Boolean));
- sections$.next(initialSections.map((section) => this.sections[section.id]).filter(Boolean));
- error$.next(undefined);
-
- // TODO: make this debounce time configurable
- const combinedSave$ = combineLatest([heroes$, sections$]).pipe(debounceTime(1000));
-
- subscriptions.add(
- combinedSave$.subscribe(([heroes, sections]) => {
- if (heroes) {
- homepage.heroes = heroes.map((hero) => ({ id: hero.id }));
- }
-
- if (sections) {
- homepage.sections = sections.map((section) => ({ id: section.id }));
- }
-
- if (heroes || sections) {
- homepage
- .save({})
- .then(() => error$.next(undefined))
- .catch((e) => error$.next(e));
- }
- })
- );
+ // this.fetchHomepageData()
+ // .then((homepage) => {
+ const homepage = PLAYGROUND_HOMEPAGE;
+ const initialHeroes = Array.isArray(homepage.heroes) ? homepage.heroes : [homepage.heroes];
+ const initialSections = Array.isArray(homepage.sections)
+ ? homepage.sections
+ : [homepage.sections];
+
+ heroes$.next(initialHeroes.map((hero) => this.heroSections[hero.id]).filter(Boolean));
+ sections$.next(initialSections.map((section) => this.sections[section.id]).filter(Boolean));
+ error$.next(undefined);
+
+ // TODO: make this debounce time configurable
+ const combinedSave$ = combineLatest([heroes$, sections$]).pipe(debounceTime(1000));
+
+ subscriptions.add(
+ combinedSave$.subscribe(([heroes, sections]) => {
+ if (heroes) {
+ homepage.heroes = heroes.map((hero) => ({ id: hero.id }));
+ }
+
+ if (sections) {
+ homepage.sections = sections.map((section) => ({ id: section.id }));
+ }
+
+ // if (heroes || sections) {
+ // homepage
+ // .save({})
+ // .then(() => error$.next(undefined))
+ // .catch((e) => error$.next(e));
+ // }
})
- .catch((e) => {
- error$.next(e);
- });
+ );
+ // })
+ // .catch((e) => {
+ // error$.next(e);
+ // });
return {
heroes$: heroes$.asObservable(),