Skip to content

Commit

Permalink
Merge #44856
Browse files Browse the repository at this point in the history
44856: ui: Release notes signup - Show Alert notification after signup r=dhartunian a=koorosh

Resolves: #43912

Existing AlertBox component doesn't fit to new designs from Design system,
so new one is added as a wrapper on top of Ant design component.
The major problem which occur is how to integrate new AlertMessage component
to live with old one and do not affect each other behaviour.
To achieve this:
- AlertMessage component (new one) has the same set of properties. As result it will allow easily consume old alert messages by new component.
- To preserve old messages without any changes, one additional property is
added to specify which Alert component to show, old or new one.
- Current implementation reuses existing logic where we store state of alert in
local settings instead of redux store.

Co-authored-by: Andrii Vorobiov <[email protected]>
  • Loading branch information
craig[bot] and koorosh committed Feb 25, 2020
2 parents ca557e0 + fd7139c commit 7b4af8c
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 6 deletions.
31 changes: 28 additions & 3 deletions pkg/ui/src/redux/alerts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,14 @@ import { AdminUIState, createAdminUIStore } from "./state";
import {
AlertLevel,
alertDataSync,
staggeredVersionWarningSelector, staggeredVersionDismissedSetting,
newVersionNotificationSelector, newVersionDismissedLocalSetting,
disconnectedAlertSelector, disconnectedDismissedLocalSetting,
staggeredVersionWarningSelector,
staggeredVersionDismissedSetting,
newVersionNotificationSelector,
newVersionDismissedLocalSetting,
disconnectedAlertSelector,
disconnectedDismissedLocalSetting,
emailSubscriptionAlertLocalSetting,
emailSubscriptionAlertSelector,
} from "./alerts";
import { versionsSelector } from "src/redux/nodes";
import {
Expand Down Expand Up @@ -350,6 +355,26 @@ describe("alerts", function() {
});
});
});

describe("email signup for release notes alert", () => {
it("initialized with default 'false' (hidden) state", () => {
const settingState = emailSubscriptionAlertLocalSetting.selector(state());
assert.isFalse(settingState);
});

it("dismissed by alert#dismiss", async () => {
// set alert to open state
dispatch(emailSubscriptionAlertLocalSetting.set(true));
let openState = emailSubscriptionAlertLocalSetting.selector(state());
assert.isTrue(openState);

// dismiss alert
const alert = emailSubscriptionAlertSelector(state());
await alert.dismiss(dispatch, state);
openState = emailSubscriptionAlertLocalSetting.selector(state());
assert.isFalse(openState);
});
});
});

describe("data sync listener", function() {
Expand Down
28 changes: 28 additions & 0 deletions pkg/ui/src/redux/alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export enum AlertLevel {
NOTIFICATION,
WARNING,
CRITICAL,
SUCCESS,
}

export interface AlertInfo {
Expand All @@ -50,6 +51,9 @@ export interface Alert extends AlertInfo {
// ThunkAction which will result in this alert being dismissed. This
// function will be dispatched to the redux store when the alert is dismissed.
dismiss: ThunkAction<Promise<void>, AdminUIState, void>;
// Makes alert to be positioned in the top right corner of the screen instead of
// stretching to full width.
showAsAlert?: boolean;
}

const localSettingsSelector = (state: AdminUIState) => state.localSettings;
Expand Down Expand Up @@ -240,6 +244,29 @@ export const disconnectedAlertSelector = createSelector(
},
);

export const emailSubscriptionAlertLocalSetting = new LocalSetting(
"email_subscription_alert", localSettingsSelector, false,
);

export const emailSubscriptionAlertSelector = createSelector(
emailSubscriptionAlertLocalSetting.selector,
( emailSubscriptionAlert): Alert => {
if (!emailSubscriptionAlert) {
return undefined;
}
return {
level: AlertLevel.SUCCESS,
title: "You successfully signed up for CockroachDB release notes",
text: "You will receive emails about CockroachDB releases and best practices. You can unsubscribe from these emails anytime.",
showAsAlert: true,
dismiss: (dispatch: Dispatch<Action, AdminUIState>) => {
dispatch(emailSubscriptionAlertLocalSetting.set(false));
return Promise.resolve();
},
};
},
);

/**
* Selector which returns an array of all active alerts which should be
* displayed in the alerts panel, which is embedded within the cluster overview
Expand All @@ -261,6 +288,7 @@ export const panelAlertsSelector = createSelector(
*/
export const bannerAlertsSelector = createSelector(
disconnectedAlertSelector,
emailSubscriptionAlertSelector,
(...alerts: Alert[]): Alert[] => {
return _.without(alerts, null, undefined);
},
Expand Down
12 changes: 9 additions & 3 deletions pkg/ui/src/views/app/containers/alertBanner/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import "./alertbanner.styl";
import { AlertBox } from "src/views/shared/components/alertBox";
import { Alert, bannerAlertsSelector } from "src/redux/alerts";
import { AdminUIState } from "src/redux/state";
import { AlertMessage } from "src/views/shared/components/alertMessage";

interface AlertBannerProps {
/**
Expand Down Expand Up @@ -45,9 +46,14 @@ class AlertBanner extends React.Component<AlertBannerProps, {}> {
// Display only the first visible component.
const { dismiss, ...alertProps } = alerts[0];
const boundDismiss = bindActionCreators(() => dismiss, dispatch);
return <div className="alert-banner">
<AlertBox dismiss={boundDismiss} {...alertProps} />
</div>;
// tslint:disable-next-line:variable-name
const AlertComponent = alertProps.showAsAlert ? AlertMessage : AlertBox;

return (
<div className="alert-banner">
<AlertComponent dismiss={boundDismiss} {...alertProps} />
</div>
);
}
}

Expand Down
46 changes: 46 additions & 0 deletions pkg/ui/src/views/shared/components/alertMessage/alertMessage.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2020 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

@require '~src/components/core/index.styl'

.alert-massage
max-width 470px
font-size 12px
// it is !important because Ant sets margin to 0
// when closing animation happens and Alert panel
// jumps to the left side.
margin 0 0 0 auto!important
padding-left 48px
border-radius 3px
box-shadow 0 0 10px 0 rgba(71, 88, 114, 0.47)
background-color #ffffff
border-color #ffffff
.alert-massage__icon
font-size 20px
left 16px
.ant-alert-close-icon
margin unset
right 0
top 0
padding-right 16px
.ant-alert-message
font-size 14px
color #242a35
font-weight 600

&__close-text
color $colors--neutral-7
margin $spacing-smaller 0
font-size $font-size--large
line-height $spacing-smaller
font-weight $font-weight--extra-bold

.ant-alert-success .alert-massage__icon
color #37a806
89 changes: 89 additions & 0 deletions pkg/ui/src/views/shared/components/alertMessage/alertMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2020 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

import React from "react";
import { Alert, Icon } from "antd";
import { Link } from "react-router-dom";

import { AlertInfo, AlertLevel } from "src/redux/alerts";
import "./alertMessage.styl";

interface AlertMessageProps extends AlertInfo {
dismiss(): void;
}

type AlertType = "success" | "info" | "warning" | "error";

const mapAlertLevelToType = (alertLevel: AlertLevel): AlertType => {
switch (alertLevel) {
case AlertLevel.SUCCESS:
return "success";
case AlertLevel.NOTIFICATION:
return "info";
case AlertLevel.WARNING:
return "warning";
case AlertLevel.CRITICAL:
return "error";
default:
return "info";
}
};

const getIconType = (alertLevel: AlertLevel): string => {
switch (alertLevel) {
case AlertLevel.SUCCESS:
return "check-circle";
case AlertLevel.NOTIFICATION:
return "info-circle";
case AlertLevel.WARNING:
return "warning";
case AlertLevel.CRITICAL:
return "close-circle";
default:
return "info-circle";
}
};

export class AlertMessage extends React.Component<AlertMessageProps> {
render() {
const {
level,
dismiss,
link,
title,
text,
} = this.props;

let description: React.ReactNode = text;

if (link) {
description = <Link to={link} onClick={dismiss}>{text}</Link>;
}

const type = mapAlertLevelToType(level);
const iconType = getIconType(level);
return (
<Alert
className="alert-massage"
message={title}
description={description}
showIcon
icon={<Icon type={iconType} theme="filled" className="alert-massage__icon" />}
closable
onClose={dismiss}
closeText={
<div className="alert-massage__close-text">
&times;
</div>
}
type={type} />
);
}
}
11 changes: 11 additions & 0 deletions pkg/ui/src/views/shared/components/alertMessage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2020 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

export * from "./alertMessage";

0 comments on commit 7b4af8c

Please sign in to comment.