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
Show file tree
Hide file tree
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
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 { 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 @@
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 @@
isLoading: isWelcomeEnabled,
isNewOpenSearchDashboardsInstance: false,
isWelcomeEnabled,
isNewThemeModalEnabled,
};
}

Expand Down Expand Up @@ -122,6 +131,15 @@
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 @@

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 @@

<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
Loading