Skip to content

Commit fd00996

Browse files
craig[bot]dhartunian
andcommitted
Merge #129420
129420: ui: add license change notification to db console r=xinhaoz a=dhartunian This change adds a dismissable alert to the Overview page of DB Console that informs users about upcoming license changes. This popup is only shown if the cluster does not have an active "Enterprise" license The popup links to this page: "https://www.cockroachlabs.com/enterprise-license-update/" When the popup is dismissed, the dismissal is stored in the DB for this user and they don't see this notification again. Resolves: CRDB-40939 Release note (ui change): DB Console will show a notification alerting customers without an Enterprise license, to upcoming license changes with a link to more information. Co-authored-by: David Hartunian <[email protected]>
2 parents 1ebf545 + a597c17 commit fd00996

File tree

7 files changed

+158
-15
lines changed

7 files changed

+158
-15
lines changed

pkg/ui/workspaces/db-console/src/redux/alerts.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import * as protos from "src/js/protos";
1717
import { cockroach } from "src/js/protos";
1818
import { versionsSelector } from "src/redux/nodes";
1919
import { API_PREFIX } from "src/util/api";
20+
import { setDataFromServer } from "src/util/dataFromServer";
2021
import fetchMock from "src/util/fetch-mock";
2122

2223
import {
@@ -32,6 +33,7 @@ import {
3233
emailSubscriptionAlertSelector,
3334
clusterPreserveDowngradeOptionDismissedSetting,
3435
clusterPreserveDowngradeOptionOvertimeSelector,
36+
licenseUpdateNotificationSelector,
3537
} from "./alerts";
3638
import {
3739
livenessReducerObj,
@@ -46,6 +48,7 @@ import { AdminUIState, AppDispatch, createAdminUIStore } from "./state";
4648
import {
4749
VERSION_DISMISSED_KEY,
4850
INSTRUCTIONS_BOX_COLLAPSED_KEY,
51+
LICENSE_UPDATE_DISMISSED_KEY,
4952
setUIDataKey,
5053
isInFlight,
5154
} from "./uiData";
@@ -258,6 +261,29 @@ describe("alerts", function () {
258261
});
259262
});
260263

264+
describe("licence update notification", function () {
265+
it("displays the alert when nothing is done", function () {
266+
dispatch(setUIDataKey(LICENSE_UPDATE_DISMISSED_KEY, null));
267+
const alert = licenseUpdateNotificationSelector(state());
268+
expect(typeof alert).toBe("object");
269+
expect(alert.level).toEqual(AlertLevel.INFORMATION);
270+
expect(alert.text).toEqual("Important changes to CockroachDB’s licensing model.");
271+
})
272+
273+
it("hides the alert when dismissed timestamp is present", function () {
274+
dispatch(setUIDataKey(LICENSE_UPDATE_DISMISSED_KEY, moment()));
275+
expect(licenseUpdateNotificationSelector(state())).toBeUndefined();
276+
})
277+
278+
it("hides the alert when license is enterprise", function () {
279+
dispatch(setUIDataKey(LICENSE_UPDATE_DISMISSED_KEY, null));
280+
setDataFromServer({
281+
LicenseType: "Enterprise",
282+
} as any)
283+
expect(licenseUpdateNotificationSelector(state())).toBeUndefined();
284+
})
285+
})
286+
261287
describe("new version available notification", function () {
262288
it("displays nothing when versions have not yet been loaded", function () {
263289
dispatch(setUIDataKey(VERSION_DISMISSED_KEY, null));
@@ -631,6 +657,7 @@ describe("alerts", function () {
631657
);
632658
dispatch(setUIDataKey(VERSION_DISMISSED_KEY, "blank"));
633659
dispatch(setUIDataKey(INSTRUCTIONS_BOX_COLLAPSED_KEY, false));
660+
dispatch(setUIDataKey(LICENSE_UPDATE_DISMISSED_KEY, moment()));
634661
dispatch(
635662
versionReducerObj.receiveData({
636663
details: [],

pkg/ui/workspaces/db-console/src/redux/alerts.ts

Lines changed: 101 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
import { LocalSetting } from "./localsettings";
4848
import { AdminUIState, AppDispatch } from "./state";
4949
import {
50+
LICENSE_UPDATE_DISMISSED_KEY,
5051
VERSION_DISMISSED_KEY,
5152
INSTRUCTIONS_BOX_COLLAPSED_KEY,
5253
saveUIData,
@@ -61,6 +62,7 @@ export enum AlertLevel {
6162
WARNING,
6263
CRITICAL,
6364
SUCCESS,
65+
INFORMATION,
6466
}
6567

6668
export interface AlertInfo {
@@ -635,20 +637,6 @@ export const upgradeNotFinalizedWarningSelector = createSelector(
635637
},
636638
);
637639

638-
/**
639-
* Selector which returns an array of all active alerts which should be
640-
* displayed in the overview list page, these should be non-critical alerts.
641-
*/
642-
643-
export const overviewListAlertsSelector = createSelector(
644-
staggeredVersionWarningSelector,
645-
clusterPreserveDowngradeOptionOvertimeSelector,
646-
upgradeNotFinalizedWarningSelector,
647-
(...alerts: Alert[]): Alert[] => {
648-
return without(alerts, null, undefined);
649-
},
650-
);
651-
652640
/**
653641
* Selector which returns an array of all active alerts which should be
654642
* displayed in the alerts panel, which is embedded within the cluster overview
@@ -707,6 +695,104 @@ export const licenseTypeSelector = createSelector(
707695
data => licenseTypeNames.get(data.LicenseType) || "None",
708696
);
709697

698+
export const licenseUpdateDismissedLocalSetting = new LocalSetting(
699+
"license_update_dismissed",
700+
localSettingsSelector,
701+
moment(0),
702+
);
703+
704+
const licenseUpdateDismissedPersistentLoadedSelector = createSelector(
705+
(state: AdminUIState) => state.uiData,
706+
uiData => uiData && uiData.hasOwnProperty(LICENSE_UPDATE_DISMISSED_KEY),
707+
);
708+
709+
const licenseUpdateDismissedPersistentSelector = createSelector(
710+
(state: AdminUIState) => state.uiData,
711+
uiData => moment(uiData?.[LICENSE_UPDATE_DISMISSED_KEY]?.data ?? 0),
712+
);
713+
714+
export const licenseUpdateNotificationSelector = createSelector(
715+
licenseTypeSelector,
716+
licenseUpdateDismissedLocalSetting.selector,
717+
licenseUpdateDismissedPersistentSelector,
718+
licenseUpdateDismissedPersistentLoadedSelector,
719+
(
720+
licenseType,
721+
licenseUpdateDismissed,
722+
licenseUpdateDismissedPersistent,
723+
licenseUpdateDismissedPersistentLoaded,
724+
): Alert => {
725+
// If customer has Enterprise license they don't need to worry about this.
726+
if (licenseType === "Enterprise") {
727+
return undefined;
728+
}
729+
730+
// If the notification has been dismissed based on the session storage
731+
// timestamp, don't show it.'
732+
//
733+
// Note: `licenseUpdateDismissed` is wrapped in `moment()` because
734+
// the local storage selector won't convert it back from a string.
735+
// We omit fixing that here since this change is being backported
736+
// to many versions.
737+
if (moment(licenseUpdateDismissed).isAfter(moment(0))) {
738+
return undefined;
739+
}
740+
741+
// If the notification has been dismissed based on the uiData
742+
// storage in the cluster, don't show it. Note that this is
743+
// different from how version upgrade notifications work, this one
744+
// is dismissed forever and won't return even if you upgrade
745+
// further or time passes.
746+
if (
747+
licenseUpdateDismissedPersistentLoaded &&
748+
licenseUpdateDismissedPersistent &&
749+
licenseUpdateDismissedPersistent.isAfter(moment(0))
750+
) {
751+
return undefined;
752+
}
753+
754+
return {
755+
level: AlertLevel.INFORMATION,
756+
title: "Coming November 18, 2024",
757+
text: "Important changes to CockroachDB’s licensing model.",
758+
link: docsURL.enterpriseLicenseUpdate,
759+
dismiss: (dispatch: any) => {
760+
const dismissedAt = moment();
761+
// Note(davidh): I haven't been able to find historical context
762+
// for why some alerts have both a "local" and a "persistent"
763+
// dismissal. My thinking is that just the persistent dismissal
764+
// should be adequate, but I'm preserving that behavior here to
765+
// match the version upgrade notification.
766+
767+
// Dismiss locally.
768+
dispatch(licenseUpdateDismissedLocalSetting.set(dismissedAt));
769+
// Dismiss persistently.
770+
return dispatch(
771+
saveUIData({
772+
key: LICENSE_UPDATE_DISMISSED_KEY,
773+
value: dismissedAt.valueOf(),
774+
})
775+
);
776+
},
777+
};
778+
},
779+
);
780+
781+
/**
782+
* Selector which returns an array of all active alerts which should be
783+
* displayed in the overview list page, these should be non-critical alerts.
784+
*/
785+
786+
export const overviewListAlertsSelector = createSelector(
787+
staggeredVersionWarningSelector,
788+
clusterPreserveDowngradeOptionOvertimeSelector,
789+
upgradeNotFinalizedWarningSelector,
790+
licenseUpdateNotificationSelector,
791+
(...alerts: Alert[]): Alert[] => {
792+
return without(alerts, null, undefined);
793+
},
794+
);
795+
710796
// daysUntilLicenseExpiresSelector returns number of days remaining before license expires.
711797
export const daysUntilLicenseExpiresSelector = createSelector(
712798
getDataFromServer,
@@ -780,6 +866,7 @@ export function alertDataSync(store: Store<AdminUIState>) {
780866
const keysToMaybeLoad = [
781867
VERSION_DISMISSED_KEY,
782868
INSTRUCTIONS_BOX_COLLAPSED_KEY,
869+
LICENSE_UPDATE_DISMISSED_KEY,
783870
];
784871
const keysToLoad = filter(keysToMaybeLoad, key => {
785872
return !(has(uiData, key) || isInFlight(state, key));

pkg/ui/workspaces/db-console/src/redux/uiData.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ export class OptInAttributes {
6262
// was last dismissed.
6363
export const VERSION_DISMISSED_KEY = "version_dismissed";
6464

65+
// LICENSE_UPDATE_DISMISSED_KEY is the uiData key on the server that tracks when the licence
66+
// update banner was last dismissed. This banner notifies the user that we've changed our
67+
// licensing if they're deployed without an active license.
68+
export const LICENSE_UPDATE_DISMISSED_KEY = "license_update_dismissed";
69+
6570
// INSTRUCTIONS_BOX_COLLAPSED_KEY is the uiData key on the server that tracks whether the
6671
// instructions box on the cluster viz has been collapsed or not.
6772
export const INSTRUCTIONS_BOX_COLLAPSED_KEY =

pkg/ui/workspaces/db-console/src/util/docs.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export let upgradeTroubleshooting: string;
6363
export let licensingFaqs: string;
6464
// Note that these explicitly don't use the current version, since we want to
6565
// link to the most up-to-date documentation available.
66+
export const enterpriseLicenseUpdate = "https://www.cockroachlabs.com/enterprise-license-update/"
6667
export const upgradeCockroachVersion =
6768
"https://www.cockroachlabs.com/docs/stable/upgrade-cockroach-version.html";
6869
export const enterpriseLicensing =

pkg/ui/workspaces/db-console/src/views/shared/components/alertBox/alertbox.styl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@
4646
border-color $notification-info-border-color
4747
background-color $notification-info-fill-color
4848

49+
&--information
50+
color $body-color
51+
border-color $notification-info-border-color
52+
background-color $notification-info-fill-color
53+
path
54+
fill $colors--primary-blue-3
55+
4956
&--warning
5057
color $body-color
5158
border-color $notification-warning-border-color

pkg/ui/workspaces/db-console/src/views/shared/components/alertBox/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
warningIcon,
1818
notificationIcon,
1919
criticalIcon,
20+
informationIcon,
2021
} from "src/views/shared/components/icons";
2122

2223
import "./alertbox.styl";
@@ -27,6 +28,8 @@ function alertIcon(level: AlertLevel) {
2728
return trustIcon(criticalIcon);
2829
case AlertLevel.WARNING:
2930
return trustIcon(warningIcon);
31+
case AlertLevel.INFORMATION:
32+
return trustIcon(informationIcon);
3033
default:
3134
return trustIcon(notificationIcon);
3235
}
@@ -49,7 +52,7 @@ export class AlertBox extends React.Component<AlertBoxProps, {}> {
4952

5053
const learnMore = this.props.link && (
5154
<a className="" href={this.props.link}>
52-
Learn More.
55+
Learn More
5356
</a>
5457
);
5558
content = (

pkg/ui/workspaces/db-console/src/views/shared/components/icons/index.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,19 @@ export const warningIcon = `<svg width="21px" height="21px" viewBox="0 0 21 21"
9898
</g>
9999
</svg>`;
100100

101+
export const informationIcon = `
102+
<svg width="24" height="24" viewBox="0 0 24 24" fill="" xmlns="http://www.w3.org/2000/svg">
103+
<g clip-path="url(#clip0_11029_22094)">
104+
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 12C0 5.37261 5.39028 0 12 0C18.6274 0 24 5.37261 24 12C24 18.6274 18.6274 24 12 24C5.37261 24 0 18.6274 0 12ZM11.1163 18.7865H12.8836V9.52586H11.1163V18.7865ZM11.1163 7.68786H12.8836V5.21364H11.1163V7.68786Z" fill="#394455"/>
105+
</g>
106+
<defs>
107+
<clipPath id="clip0_11029_22094">
108+
<rect width="24" height="24" fill="white" transform="matrix(1 0 0 -1 0 24)"/>
109+
</clipPath>
110+
</defs>
111+
</svg>
112+
`
113+
101114
export const notificationIcon = `
102115
<svg width="22px" height="22px" viewBox="0 0 38 38" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
103116
<!-- Generator: Sketch 3.3.3 (12081) - http://www.bohemiancoding.com/sketch -->

0 commit comments

Comments
 (0)