Skip to content

Commit

Permalink
[Next theme] Add modal to notify users of theme updates (#4715)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
github-actions[bot] committed Aug 31, 2023
1 parent 6542257 commit 12e285d
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 5 deletions.
7 changes: 6 additions & 1 deletion config/opensearch_dashboards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Up @@ -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
Expand Up @@ -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) {
Expand All @@ -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');

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

Expand Down Expand Up @@ -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 });

Check warning on line 136 in src/plugins/home/public/application/components/home.js

View check run for this annotation

Codecov / codecov/patch

src/plugins/home/public/application/components/home.js#L135-L136

Added lines #L135 - L136 were not covered by tests
};

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

Check warning on line 140 in src/plugins/home/public/application/components/home.js

View check run for this annotation

Codecov / codecov/patch

src/plugins/home/public/application/components/home.js#L140

Added line #L140 was not covered by tests
};

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

getFeaturesByCategory = (category) =>
Expand All @@ -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);
Expand Down Expand Up @@ -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>
Expand Down
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
Expand Up @@ -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(),
},
}),
}));

Expand Down Expand Up @@ -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(),
Expand All @@ -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
Expand Down Expand Up @@ -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();
});
Expand Down Expand Up @@ -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>();

Check warning on line 37 in src/plugins/home/public/application/components/new_theme_modal.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/home/public/application/components/new_theme_modal.tsx#L37

Added line #L37 was not covered by tests

// TODO: Finalize copy
return (

Check warning on line 40 in src/plugins/home/public/application/components/new_theme_modal.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/home/public/application/components/new_theme_modal.tsx#L40

Added line #L40 was not covered by tests
<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

0 comments on commit 12e285d

Please sign in to comment.