diff --git a/pkg/col/coldata/bytes.go b/pkg/col/coldata/bytes.go index 800ca78518fa..702eb452695e 100644 --- a/pkg/col/coldata/bytes.go +++ b/pkg/col/coldata/bytes.go @@ -87,7 +87,7 @@ func (b *Bytes) UpdateOffsetsToBeNonDecreasing(n uint64) { // maybeBackfillOffsets is an optimized version of // UpdateOffsetsToBeNonDecreasing that assumes that all offsets up to -// b.maxSetIndex+1 are non-decreasing. Note that this method can be a noop when +// b.maxSetIndex+1 are non-decreasing. Note that this method is a noop when // i <= b.maxSetIndex+1. func (b *Bytes) maybeBackfillOffsets(i int) { // Note that we're not checking whether this Bytes is a window because @@ -96,6 +96,9 @@ func (b *Bytes) maybeBackfillOffsets(i int) { for j := b.maxSetIndex + 2; j <= i; j++ { b.offsets[j] = b.offsets[b.maxSetIndex+1] } + if i > b.maxSetIndex { + b.maxSetIndex = i - 1 + } } // Get returns the ith []byte in Bytes. Note that the returned byte slice is @@ -123,17 +126,12 @@ func (b *Bytes) Set(i int, v []byte) { ), ) } - if i == b.maxSetIndex { - // We are overwriting an element at the end of b.data, truncate so we can - // append in every path. - b.data = b.data[:b.offsets[i]] - } else { - // We're maybe setting an element not right after the last already present - // element (i.e. there might be gaps in b.offsets). This is probably due to - // NULL values that are stored separately. In order to maintain the - // assumption of non-decreasing offsets, we need to backfill them. - b.maybeBackfillOffsets(i) - } + // We're maybe setting an element not right after the last already present + // element (i.e. there might be gaps in b.offsets). This is probably due to + // NULL values that are stored separately. In order to maintain the + // assumption of non-decreasing offsets, we need to backfill them. + b.maybeBackfillOffsets(i) + b.data = b.data[:b.offsets[i]] b.offsets[i] = int32(len(b.data)) b.data = append(b.data, v...) b.offsets[i+1] = int32(len(b.data)) diff --git a/pkg/ui/assets/dashboard/email_signup_background.svg b/pkg/ui/assets/dashboard/email_signup_background.svg new file mode 100644 index 000000000000..dea0ca3da0c1 --- /dev/null +++ b/pkg/ui/assets/dashboard/email_signup_background.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/pkg/ui/src/components/core/typography.styl b/pkg/ui/src/components/core/typography.styl index 3517a7f8dc2f..2077846a2610 100644 --- a/pkg/ui/src/components/core/typography.styl +++ b/pkg/ui/src/components/core/typography.styl @@ -17,6 +17,7 @@ $font-family--bold = SourceSansPro-Bold $font-family--semi-bold = SourceSansPro-SemiBold $font-family--monospace = SFMono +$font-weight--extra-bold = 900 $font-weight--bold = 600 $font-weight--medium = 500 $font-weight--light = 400 diff --git a/pkg/ui/src/redux/customAnalytics/customAnalyticsSagas.spec.ts b/pkg/ui/src/redux/customAnalytics/customAnalyticsSagas.spec.ts index 9e70d0cde700..c1303130b069 100644 --- a/pkg/ui/src/redux/customAnalytics/customAnalyticsSagas.spec.ts +++ b/pkg/ui/src/redux/customAnalytics/customAnalyticsSagas.spec.ts @@ -14,7 +14,6 @@ import Analytics from "analytics-node"; import { signUpEmailSubscription } from "./customAnalyticsSagas"; import { - completeEmailSubscriptionSignUp, signUpForEmailSubscription, } from "./customAnanlyticsActions"; @@ -33,7 +32,6 @@ describe("customAnalyticsSagas", () => { const action = signUpForEmailSubscription(clusterId, email); return expectSaga(signUpEmailSubscription, action) - .put(completeEmailSubscriptionSignUp()) .dispatch(action) .run() .then(() => { diff --git a/pkg/ui/src/redux/customAnalytics/customAnalyticsSagas.ts b/pkg/ui/src/redux/customAnalytics/customAnalyticsSagas.ts index 1ced91ad9dad..57eb9c94714f 100644 --- a/pkg/ui/src/redux/customAnalytics/customAnalyticsSagas.ts +++ b/pkg/ui/src/redux/customAnalytics/customAnalyticsSagas.ts @@ -13,8 +13,9 @@ import Analytics from "analytics-node"; import { PayloadAction } from "src/interfaces/action"; import { COCKROACHLABS_ADDR } from "src/util/cockroachlabsAPI"; +import { emailSubscriptionAlertLocalSetting } from "src/redux/alerts"; + import { - completeEmailSubscriptionSignUp, EMAIL_SUBSCRIPTION_SIGN_UP, EmailSubscriptionSignUpPayload, } from "./customAnanlyticsActions"; @@ -44,9 +45,11 @@ export function* signUpEmailSubscription(action: PayloadAction { return (
+
diff --git a/pkg/ui/src/views/dashboard/emailSubscription.styl b/pkg/ui/src/views/dashboard/emailSubscription.styl new file mode 100644 index 000000000000..50c6edf8222d --- /dev/null +++ b/pkg/ui/src/views/dashboard/emailSubscription.styl @@ -0,0 +1,79 @@ +// 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' + +$text-width = 352px + +.crl-email-subscription + border-radius 5px + box-shadow: 0 0 1px 0 rgba(67, 90, 111, 0.41) + display flex + flex-direction row + align-items center + justify-content flex-end + height 90px + background-color $colors--white + background-image url("../../../assets/dashboard/email_signup_background.svg") + background-repeat no-repeat + background-position-x $text-width + + &__text + font-size $font-size--large + font-family $font-family--base + line-height 1.6 + letter-spacing -0.2px + color $colors--white + flex-grow 1 + height inherit + border-radius inherit + & > div + max-width $text-width + background $colors--primary-green-3 + padding-left $spacing-large + height inherit + border-bottom-left-radius: inherit; + border-top-left-radius: inherit; + display flex + align-items center + + + &__controls + flex-grow 0 + margin-left $spacing-medium + + &__close-button + flex-grow 0 + color $colors--neutral-7 + align-self flex-start + margin $spacing-smaller $spacing-small $spacing-small $spacing-medium + cursor pointer + font-size $font-size--large + line-height $spacing-smaller + font-weight $font-weight--extra-bold + + + @media (max-width: 1055px) { + background-position center + + .crl-email-subscription__text { + font-size $font-size--tall + font-family $font-family--base + line-height 1.6 + } + } + + @media (max-width: 940px) { + background-position left + + .crl-email-subscription__text { + display none + } + } diff --git a/pkg/ui/src/views/dashboard/emailSubscription.tsx b/pkg/ui/src/views/dashboard/emailSubscription.tsx new file mode 100644 index 000000000000..66db364e6bc5 --- /dev/null +++ b/pkg/ui/src/views/dashboard/emailSubscription.tsx @@ -0,0 +1,87 @@ +// 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 { connect } from "react-redux"; + +import { EmailSubscriptionForm } from "src/views/shared/components/emailSubscriptionForm"; +import { signUpForEmailSubscription } from "src/redux/customAnalytics"; +import { AdminUIState } from "src/redux/state"; +import { clusterIdSelector } from "src/redux/nodes"; +import { LocalSetting } from "src/redux/localsettings"; + +import "./emailSubscription.styl"; + +type EmailSubscriptionProps = MapDispatchToProps & MapStateToProps; + +class EmailSubscription extends React.Component { + handleEmailSubscriptionSubmit = (email: string) => { + this.props.signUpForEmailSubscription(this.props.clusterId, email); + } + + handlePanelHide = () => { + this.props.hidePanel(); + } + + render() { + const { isHiddenPanel } = this.props; + + if (isHiddenPanel) { + return null; + } + + return ( +
+
+
+
+ Keep up-to-date with CockroachDB + software releases and best practices. +
+
+
+ +
+
+ × +
+
+
+ ); + } +} + +const hidePanelLocalSetting = new LocalSetting( + "dashboard/release_notes_signup/hide", (s) => s.localSettings, false, +); + +interface MapDispatchToProps { + signUpForEmailSubscription: (clusterId: string, email: string) => void; + hidePanel: () => void; +} + +const mapDispatchToProps = { + signUpForEmailSubscription, + hidePanel: () => hidePanelLocalSetting.set(true), +}; + +interface MapStateToProps { + isHiddenPanel: boolean; + clusterId: string; +} +const mapStateToProps = (state: AdminUIState) => ({ + isHiddenPanel: hidePanelLocalSetting.selector(state), + clusterId: clusterIdSelector(state), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(EmailSubscription);