Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport 2.x] [Next theme] Add modal to notify users of theme updates #4873

Merged
merged 1 commit into from
Sep 1, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
[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 <rmerqg@amazon.com>

* revert unintended config change

Signed-off-by: Josh Romero <rmerqg@amazon.com>

* add changelog

Signed-off-by: Josh Romero <rmerqg@amazon.com>

* Disable new theme modal for functional tests

Signed-off-by: Josh Romero <rmerqg@amazon.com>

* add deep link to settings

and hide modal if v7 theme is in use

Signed-off-by: Josh Romero <rmerqg@amazon.com>

* add unit tests and fix imports

Signed-off-by: Josh Romero <rmerqg@amazon.com>

---------

Signed-off-by: Josh Romero <rmerqg@amazon.com>
(cherry picked from commit b846e80)
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

# Conflicts:
#	CHANGELOG.md
github-actions[bot] committed Aug 31, 2023
commit 12e285dc9d44110c2e17c04d42265686abcd1215
7 changes: 6 additions & 1 deletion config/opensearch_dashboards.yml
Original file line number Diff line number Diff line change
@@ -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
# vis_augmenter.pluginAugmentationEnabled: true

1 change: 1 addition & 0 deletions src/plugins/home/config.ts
Original file line number Diff line number Diff line change
@@ -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<typeof configSchema>;

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions src/plugins/home/public/application/components/home.js
Original file line number Diff line number Diff line change
@@ -42,10 +42,12 @@ import { FeatureCatalogueCategory } from '../../services';
import { getServices } from '../opensearch_dashboards_services';
import { AddData } from './add_data';
import { ManageData } from './manage_data';
import { NewThemeModal } from './new_theme_modal';
import { SolutionsSection } from './solutions_section';
import { Welcome } from './welcome';

const KEY_ENABLE_WELCOME = 'home:welcome:show';
const KEY_ENABLE_NEW_THEME_MODAL = 'home:newThemeModal:show';

export class Home extends Component {
constructor(props) {
@@ -56,6 +58,12 @@ export class Home extends Component {
props.localStorage.getItem(KEY_ENABLE_WELCOME) === 'false'
);

const isNewThemeModalEnabled = !(
getServices().uiSettings.get('theme:version') === 'v7' ||
getServices().homeConfig.disableNewThemeModal ||
props.localStorage.getItem(KEY_ENABLE_NEW_THEME_MODAL) === 'false'
);

const body = document.querySelector('body');
body.classList.add('isHomPage');

@@ -67,6 +75,7 @@ export class Home extends Component {
isLoading: isWelcomeEnabled,
isNewOpenSearchDashboardsInstance: false,
isWelcomeEnabled,
isNewThemeModalEnabled,
};
}

@@ -122,6 +131,15 @@ export class Home extends Component {
this._isMounted && this.setState({ isWelcomeEnabled: false });
};

dismissNewThemeModal = () => {
this.props.localStorage.setItem(KEY_ENABLE_NEW_THEME_MODAL, 'false');
this._isMounted && this.setState({ isNewThemeModalEnabled: false });
};

onCloseNewThemeModal = () => {
this.dismissNewThemeModal();
};

findDirectoryById = (id) => this.props.directories.find((directory) => directory.id === id);

getFeaturesByCategory = (category) =>
@@ -131,6 +149,7 @@ export class Home extends Component {

renderNormal() {
const { addBasePath, solutions, directories } = this.props;
const { isNewThemeModalEnabled } = this.state;

const devTools = this.findDirectoryById('console');
const addDataFeatures = this.getFeaturesByCategory(FeatureCatalogueCategory.DATA);
@@ -185,6 +204,10 @@ export class Home extends Component {

<EuiHorizontalRule margin="xl" aria-hidden="true" />

{isNewThemeModalEnabled && (
<NewThemeModal addBasePath={addBasePath} onClose={this.onCloseNewThemeModal} />
)}

<OverviewPageFooter addBasePath={addBasePath} path={HOME_APP_BASE_PATH} />
</div>
</main>
71 changes: 67 additions & 4 deletions src/plugins/home/public/application/components/home.test.js
Original file line number Diff line number Diff line change
@@ -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(<Home {...defaultProps} {...props} />);

// 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();
});
});
});
103 changes: 103 additions & 0 deletions src/plugins/home/public/application/components/new_theme_modal.tsx
Original file line number Diff line number Diff line change
@@ -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<Props> = ({ addBasePath, onClose }) => {
const {
services: { application },
} = useOpenSearchDashboards<CoreStart>();

// TODO: Finalize copy
return (
<EuiModal onClose={onClose}>
<EuiModalHeader>
<EuiModalHeaderTitle>
<FormattedMessage
id="home.newThemeModal.title"
defaultMessage="Introducing new OpenSearch Dashboards look & feel"
/>
</EuiModalHeaderTitle>
</EuiModalHeader>

<EuiModalBody>
<RedirectAppLinks application={application}>
<EuiText>
<FormattedMessage
id="home.newThemeModal.previewDescription.previewDetail"
defaultMessage="You are now previewing the newest OpenSearch Dashboards theme with improved light and dark
modes. You or your administrator can change to the previous theme by visiting {advancedSettingsLink}."
values={{
advancedSettingsLink: (
<EuiLink
href={addBasePath('/app/management/opensearch-dashboards/settings#appearance')}
>
<FormattedMessage
id="home.newThemeModal.previewDescription.advancedSettingsLinkText"
defaultMessage="Advanced Settings"
/>
</EuiLink>
),
}}
/>
</EuiText>
</RedirectAppLinks>
<EuiSpacer />
<EuiFlexGroup gutterSize="none">
<EuiFlexItem>
{/* TODO: Replace with actual next theme preview images. Using welcome graphics as placeholders */}
<EuiImage
url={addBasePath('/plugins/home/assets/welcome_graphic_light_2x.png')}
alt={i18n.translate('home.newThemeModal.lightModeImageAltDescription', {
defaultMessage: 'screenshot of new theme in light mode',
})}
/>
</EuiFlexItem>
<EuiFlexItem>
{/* TODO: Replace with actual next theme preview images. Using welcome graphics as placeholders */}
<EuiImage
url={addBasePath('/plugins/home/assets/welcome_graphic_dark_2x.png')}
alt={i18n.translate('home.newThemeModal.darkModeImageAltDescription', {
defaultMessage: 'screenshot of new theme in dark mode',
})}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiModalBody>

<EuiModalFooter>
<EuiButton onClick={onClose} fill>
<FormattedMessage id="home.newThemeModal.dismissButtonLabel" defaultMessage="Dismiss" />
</EuiButton>
</EuiModalFooter>
</EuiModal>
);
};
Loading