Skip to content
This repository has been archived by the owner on Mar 13, 2024. It is now read-only.

Automated cherry pick of #6448 #6506

Merged
Show file tree
Hide file tree
Changes from 1 commit
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
30 changes: 30 additions & 0 deletions components/admin_console/admin_definition.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2383,6 +2383,36 @@ const AdminDefinition = {
],
},
},
notices: {
url: 'site_config/notices',
title: t('admin.sidebar.notices'),
title_default: 'Notices',
schema: {
id: 'NoticesSettings',
name: t('admin.site.notices'),
name_default: 'Notices',
settings: [
{
type: Constants.SettingsTypes.TYPE_BOOL,
key: 'AnnouncementSettings.AdminNoticesEnabled',
label: t('admin.notices.enableAdminNoticesTitle'),
label_default: 'Enable Admin Notices: ',
help_text: t('admin.notices.enableAdminNoticesDescription'),
help_text_default: 'When enabled, System Admins will receive notices about available server upgrades and relevant system administration features. [Learn more about notices](!https://about.mattermost.com/default-notices) in our documentation.',
help_text_markdown: true,
},
{
type: Constants.SettingsTypes.TYPE_BOOL,
key: 'AnnouncementSettings.UserNoticesEnabled',
label: t('admin.notices.enableEndUserNoticesTitle'),
label_default: 'Enable End User Notices: ',
help_text: t('admin.notices.enableEndUserNoticesDescription'),
help_text_default: 'When enabled, all users will receive notices about available client upgrades and relevant end user features to improve user experience. [Learn more about notices](!https://about.mattermost.com/default-notices) in our documentation.',
help_text_markdown: true,
},
],
},
},
},
authentication: {
icon: 'fa-shield',
Expand Down
3 changes: 2 additions & 1 deletion components/channel_layout/channel_controller.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import * as UserAgent from 'utils/user_agent';
import CenterChannel from 'components/channel_layout/center_channel';
import LoadingScreen from 'components/loading_screen';
import FaviconTitleHandler from 'components/favicon_title_handler';
import ProductNoticesModal from 'components/product_notices_modal';

export default class ChannelController extends React.Component {
static propTypes = {
Expand Down Expand Up @@ -74,7 +75,7 @@ export default class ChannelController extends React.Component {
<AnnouncementBarController/>
<SystemNotice/>
<FaviconTitleHandler/>

<ProductNoticesModal/>
<div className='container-fluid'>
<SidebarRight/>
<SidebarRightMenu teamType={this.props.teamType}/>
Expand Down
12 changes: 10 additions & 2 deletions components/generic_modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type Props = {
confirmButtonClassName?: string;
cancelButtonText?: React.ReactNode;
isConfirmDisabled?: boolean;
autoCloseOnCancelButton?: boolean;
autoCloseOnConfirmButton?: boolean;
};

type State = {
Expand All @@ -28,6 +30,8 @@ type State = {
export default class GenericModal extends React.PureComponent<Props, State> {
static defaultProps: Partial<Props> = {
show: true,
autoCloseOnCancelButton: true,
autoCloseOnConfirmButton: true,
};

constructor(props: Props) {
Expand All @@ -44,15 +48,19 @@ export default class GenericModal extends React.PureComponent<Props, State> {

handleCancel = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
event.preventDefault();
this.onHide();
if (this.props.autoCloseOnCancelButton) {
this.onHide();
}
if (this.props.handleCancel) {
this.props.handleCancel();
}
}

handleConfirm = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
event.preventDefault();
this.onHide();
if (this.props.autoCloseOnConfirmButton) {
this.onHide();
}
if (this.props.handleConfirm) {
this.props.handleConfirm();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ProductNoticesModal Match snapshot for single notice 1`] = `
<GenericModal
autoCloseOnCancelButton={false}
autoCloseOnConfirmButton={true}
cancelButtonText={null}
className="productNotices"
confirmButtonText={
<span>
Download
</span>
}
handleConfirm={[Function]}
modalHeaderText={
<span>
title
</span>
}
onHide={[Function]}
show={true}
>
<span
className="productNotices__helpText"
>
<Connect(Markdown)
message="descr"
/>
</span>
<div
className="productNotices__imageDiv"
/>
<div
className="productNotices__info"
/>
</GenericModal>
`;

exports[`ProductNoticesModal Match snapshot for user notice 1`] = `
<GenericModal
autoCloseOnCancelButton={false}
autoCloseOnConfirmButton={true}
cancelButtonText={
<React.Fragment>
<PreviousIcon
additionalClassName={null}
/>
<FormattedMessage
defaultMessage="Previous"
id="generic.previous"
/>
</React.Fragment>
}
className="productNotices"
confirmButtonText={
<FormattedMessage
defaultMessage="Done"
id="generic.done"
/>
}
handleCancel={[Function]}
handleConfirm={[Function]}
modalHeaderText={
<span>
title
</span>
}
onHide={[Function]}
show={true}
>
<span
className="productNotices__helpText"
>
<Connect(Markdown)
message="descr"
/>
</span>
<a
className="GenericModal__button actionButton"
href="http:https://download.com/path"
id="actionButton"
rel="noopener noreferrer"
target="_blank"
>
Download
</a>
<div
className="productNotices__imageDiv"
/>
<div
className="productNotices__info"
>
<span
className="tutorial__circles"
>
<a
className="circle"
data-screen={0}
href="#"
id="tutorialIntroCircle0"
key="circle0"
/>
<a
className="circle active"
data-screen={1}
href="#"
id="tutorialIntroCircle1"
key="circle1"
/>
</span>
</div>
</GenericModal>
`;

exports[`ProductNoticesModal Should match snapshot for system admin notice 1`] = `
<GenericModal
autoCloseOnCancelButton={false}
autoCloseOnConfirmButton={false}
cancelButtonText={null}
className="productNotices"
confirmButtonText={
<React.Fragment>
<FormattedMessage
defaultMessage="Next"
id="generic.next"
/>
<NextIcon
additionalClassName={null}
/>
</React.Fragment>
}
handleConfirm={[Function]}
modalHeaderText={
<span>
for sysadmin
</span>
}
onHide={[Function]}
show={true}
>
<span
className="productNotices__helpText"
>
<Connect(Markdown)
message="your eyes only! [test](https://test.com)"
/>
</span>
<a
className="GenericModal__button actionButton"
href="http:https://download.com/path"
id="actionButton"
rel="noopener noreferrer"
target="_blank"
>
Download
</a>
<div
className="productNotices__imageDiv"
>
<img
className="productNotices__img"
src="https://raw.githubusercontent.com/reflog/notices-experiment/master/images/2020-08-11_11-42.png"
/>
</div>
<div
className="productNotices__info"
>
<span
className="tutorial__circles"
>
<a
className="circle active"
data-screen={0}
href="#"
id="tutorialIntroCircle0"
key="circle0"
/>
<a
className="circle"
data-screen={1}
href="#"
id="tutorialIntroCircle1"
key="circle1"
/>
</span>
<AdminEyeIcon />
<FormattedMessage
defaultMessage="Visible to Admins only"
id="inProduct_notices.adminOnlyMessage"
/>
</div>
</GenericModal>
`;

exports[`ProductNoticesModal Should match snapshot when there are no notices 1`] = `""`;
47 changes: 47 additions & 0 deletions components/product_notices_modal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import {connect} from 'react-redux';
import {bindActionCreators, Dispatch, ActionCreatorsMapObject} from 'redux';
import {ActionFunc} from 'mattermost-redux/types/actions';
import {ProductNotices} from 'mattermost-redux/types/product_notices';
import {WebsocketStatus} from 'mattermost-redux/types/websocket';
import {getInProductNotices, updateNoticesAsViewed} from 'mattermost-redux/actions/teams';
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {ClientConfig} from 'mattermost-redux/types/config';

import {getSocketStatus} from 'selectors/views/websocket';
import {GlobalState} from 'types/store';

import ProductNoticesModal from './product_notices_modal';

type Actions = {
getInProductNotices: (teamId: string, client: string, clientVersion: string) => Promise<{
data: ProductNotices;
}>;
updateNoticesAsViewed: (noticeIds: string[]) => Promise<Record<string, unknown>>;
}

function mapStateToProps(state: GlobalState) {
const config: Partial<ClientConfig> = getConfig(state);
const version: string = config.Version || ''; //this should always exist but TS throws error
const socketStatus: WebsocketStatus = getSocketStatus(state);

return {
currentTeamId: getCurrentTeamId(state),
version,
socketStatus,
};
}

function mapDispatchToProps(dispatch: Dispatch) {
return {
actions: bindActionCreators<ActionCreatorsMapObject<ActionFunc>, Actions>({
getInProductNotices,
updateNoticesAsViewed,
}, dispatch),
};
}

export default connect(mapStateToProps, mapDispatchToProps)(ProductNoticesModal);
Loading