From b846e8061ff27d50f72e514b2060a0a1b51550d6 Mon Sep 17 00:00:00 2001 From: Josh Romero Date: Thu, 31 Aug 2023 00:41:53 -0700 Subject: [PATCH] [Next theme] Add modal to notify users of theme updates (#4715) * Feat (home): Add modal to introduce `next` theme changes Copy and images provisional, for demo purposes only. Signed-off-by: Josh Romero * revert unintended config change Signed-off-by: Josh Romero * add changelog Signed-off-by: Josh Romero * Disable new theme modal for functional tests Signed-off-by: Josh Romero * add deep link to settings and hide modal if v7 theme is in use Signed-off-by: Josh Romero * add unit tests and fix imports Signed-off-by: Josh Romero --------- Signed-off-by: Josh Romero --- CHANGELOG.md | 1 + config/opensearch_dashboards.yml | 7 +- src/plugins/home/config.ts | 1 + .../__snapshots__/home.test.js.snap | 63 +++++++++++ .../public/application/components/home.js | 23 ++++ .../application/components/home.test.js | 71 +++++++++++- .../components/new_theme_modal.tsx | 103 ++++++++++++++++++ src/plugins/home/server/index.ts | 1 + test/common/config.js | 1 + 9 files changed, 266 insertions(+), 5 deletions(-) create mode 100644 src/plugins/home/public/application/components/new_theme_modal.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index c9786371a782..ddab663718c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Saved Object Service] Customize saved objects service status ([#4696](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4696)) - Remove minimum constraint on opensearch hosts to allow empty host ([#4701](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4701)) - [Discover] Update styles to compatible with OUI `next` theme ([#4644](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4644)) +- [Home] Add modal to introduce the `next` theme ([#4715](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4715)) - Remove visualization editor sidebar background ([#4719](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4719)) - [Vis Colors] Remove customized colors from sample visualizations and dashboards ([#4741](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4741)) - [Decouple] Allow plugin manifest config to define semver compatible OpenSearch plugin and verify if it is installed on the cluster([#4612](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4612)) diff --git a/config/opensearch_dashboards.yml b/config/opensearch_dashboards.yml index 73f31233a783..9c6c040433b4 100644 --- a/config/opensearch_dashboards.yml +++ b/config/opensearch_dashboards.yml @@ -32,6 +32,12 @@ # The default application to load. #opensearchDashboards.defaultAppId: "home" +# Set the value to true to disable the welcome screen +#home.disableWelcomeScreen: false + +# Set the value to true to disable the new theme introduction modal +#home.disableNewThemeModal: false + # Setting for an optimized healthcheck that only uses the local OpenSearch node to do Dashboards healthcheck. # This settings should be used for large clusters or for clusters with ingest heavy nodes. # It allows Dashboards to only healthcheck using the local OpenSearch node rather than fan out requests across all nodes. @@ -267,4 +273,3 @@ # Set the value of this setting to true to enable plugin augmentation on Dashboard # vis_augmenter.pluginAugmentationEnabled: true - diff --git a/src/plugins/home/config.ts b/src/plugins/home/config.ts index e3372ce77767..399c92174c6a 100644 --- a/src/plugins/home/config.ts +++ b/src/plugins/home/config.ts @@ -32,6 +32,7 @@ import { schema, TypeOf } from '@osd/config-schema'; export const configSchema = schema.object({ disableWelcomeScreen: schema.boolean({ defaultValue: false }), + disableNewThemeModal: schema.boolean({ defaultValue: false }), }); export type ConfigSchema = TypeOf; diff --git a/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap b/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap index 61855f17954e..31df3b66efb5 100644 --- a/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap +++ b/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap @@ -650,6 +650,61 @@ exports[`home isNewOpenSearchDashboardsInstance should set isNewOpenSearchDashbo `; +exports[`home new theme modal should show the new theme modal if not previously dismissed 1`] = ` +
+ + } + /> +
+ + + + + + + + +
+
+`; + exports[`home should render home component 1`] = `
+
diff --git a/src/plugins/home/public/application/components/home.test.js b/src/plugins/home/public/application/components/home.test.js index 444c5f388bb2..0c0953dbd63f 100644 --- a/src/plugins/home/public/application/components/home.test.js +++ b/src/plugins/home/public/application/components/home.test.js @@ -32,20 +32,27 @@ import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; import { Home } from './home'; +import { NewThemeModal } from './new_theme_modal'; import { FeatureCatalogueCategory } from '../../services'; +const mockHomeConfig = jest.fn(); +const mockUiSettings = jest.fn(); + jest.mock('../opensearch_dashboards_services', () => ({ getServices: () => ({ getBasePath: () => 'path', tutorialVariables: () => ({}), - homeConfig: { disableWelcomeScreen: false }, + homeConfig: mockHomeConfig(), chrome: { setBreadcrumbs: () => {}, }, injectedMetadata: { getBranding: () => ({}), }, + uiSettings: { + get: () => mockUiSettings(), + }, }), })); @@ -80,7 +87,7 @@ describe('home', () => { }, localStorage: { getItem: sinon.spy((path) => { - expect(path).toEqual('home:welcome:show'); + expect(path).toMatch(/home:(welcome|newThemeModal):show/); return 'false'; }), setItem: sinon.mock(), @@ -93,7 +100,17 @@ describe('home', () => { }; }); - async function renderHome(props = {}) { + async function renderHome(props = {}, homeConfig, uiSettings) { + if (homeConfig) { + mockHomeConfig.mockReturnValue(homeConfig); + } else { + mockHomeConfig.mockReturnValue({ disableWelcomeScreen: false, disableNewThemeModal: false }); + } + if (uiSettings) { + mockUiSettings.mockReturnValue(uiSettings); + } else { + mockUiSettings.mockReturnValue('v8'); + } const component = shallow(); // Ensure all promises resolve @@ -284,7 +301,7 @@ describe('home', () => { find: () => Promise.resolve({ total: 0 }), }); - sinon.assert.calledOnce(defaultProps.localStorage.getItem); + sinon.assert.calledWith(defaultProps.localStorage.getItem, 'home:welcome:show'); expect(component).toMatchSnapshot(); }); @@ -350,4 +367,50 @@ describe('home', () => { expect(component).toMatchSnapshot(); }); }); + + describe('new theme modal', () => { + test('should show the new theme modal if not previously dismissed', async () => { + defaultProps.localStorage.getItem = sinon.spy(() => undefined); + + const component = await renderHome(); + + sinon.assert.calledWith(defaultProps.localStorage.getItem, 'home:newThemeModal:show'); + + expect(component.find(NewThemeModal).exists()).toBeTruthy(); + expect(component).toMatchSnapshot(); + }); + test('should not show the new theme modal if v7 theme in use', async () => { + defaultProps.localStorage.getItem = sinon.spy(() => undefined); + + const component = await renderHome({}, undefined, 'v7'); + + sinon.assert.neverCalledWith(defaultProps.localStorage.getItem, 'home:newThemeModal:show'); + + expect(component.find(NewThemeModal).exists()).toBeFalsy(); + }); + test('should not show the new theme modal if disabled in config', async () => { + defaultProps.localStorage.getItem = sinon.spy(() => undefined); + + const component = await renderHome( + {}, + { + disableWelcomeScreen: true, + disableNewThemeModal: true, + } + ); + + sinon.assert.neverCalledWith(defaultProps.localStorage.getItem, 'home:newThemeModal:show'); + + expect(component.find(NewThemeModal).exists()).toBeFalsy(); + }); + test('should not show the new theme modal if previously dismissed', async () => { + defaultProps.localStorage.getItem = sinon.spy(() => 'false'); + + const component = await renderHome(); + + sinon.assert.calledWith(defaultProps.localStorage.getItem, 'home:newThemeModal:show'); + + expect(component.find(NewThemeModal).exists()).toBeFalsy(); + }); + }); }); diff --git a/src/plugins/home/public/application/components/new_theme_modal.tsx b/src/plugins/home/public/application/components/new_theme_modal.tsx new file mode 100644 index 000000000000..ca9f9c0ce523 --- /dev/null +++ b/src/plugins/home/public/application/components/new_theme_modal.tsx @@ -0,0 +1,103 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { FC } from 'react'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiImage, + EuiLink, + EuiModal, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiModalFooter, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { FormattedMessage } from '@osd/i18n/react'; +import { CoreStart } from 'opensearch-dashboards/public'; +import { + RedirectAppLinks, + useOpenSearchDashboards, +} from '../../../../../../src/plugins/opensearch_dashboards_react/public'; + +interface Props { + addBasePath: (path: string) => string; + onClose: () => void; +} + +export const NewThemeModal: FC = ({ addBasePath, onClose }) => { + const { + services: { application }, + } = useOpenSearchDashboards(); + + // TODO: Finalize copy + return ( + + + + + + + + + + + + + + ), + }} + /> + + + + + + {/* TODO: Replace with actual next theme preview images. Using welcome graphics as placeholders */} + + + + {/* TODO: Replace with actual next theme preview images. Using welcome graphics as placeholders */} + + + + + + + + + + + + ); +}; diff --git a/src/plugins/home/server/index.ts b/src/plugins/home/server/index.ts index 47c321243b5d..8a9052419009 100644 --- a/src/plugins/home/server/index.ts +++ b/src/plugins/home/server/index.ts @@ -38,6 +38,7 @@ import { configSchema, ConfigSchema } from '../config'; export const config: PluginConfigDescriptor = { exposeToBrowser: { disableWelcomeScreen: true, + disableNewThemeModal: true, }, schema: configSchema, deprecations: ({ renameFromRoot }) => [ diff --git a/test/common/config.js b/test/common/config.js index cff91302899d..d95b46ae3b11 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -58,6 +58,7 @@ export default function () { `--opensearch.username=${opensearchDashboardsServerTestUser.username}`, `--opensearch.password=${opensearchDashboardsServerTestUser.password}`, `--home.disableWelcomeScreen=false`, + `--home.disableNewThemeModal=true`, // Needed for async search functional tests to introduce a delay `--data.search.aggs.shardDelay.enabled=true`, //`--security.showInsecureClusterWarning=false`,