💜 Cc 7187/purple banner for linking existing clusters (#20275)

* Adding banner on services page

* Simplified version of setting/unsetting banner

* Translating the text based off of enterprise or not

* Add an integration test

* Adding an acceptance test

* Enable config dismissal as well

* Adding changelog

* Adding some copyrights to the other files

* Revert "Enable config dismissal as well"

This reverts commit e6784c4335bdff99d9183d28571aa6ab4b852cbd.

We'll be doing this in CC-7347
This commit is contained in:
Chris Hut 2024-01-23 14:29:53 -08:00 committed by GitHub
parent e5d18753c9
commit 5119667cd1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 225 additions and 2 deletions

3
.changelog/20275.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
ui: Added a banner to let users link their clusters to HCP
```

View File

@ -11,7 +11,7 @@ export default class HashiCorpConsul extends Component {
@service('env') env; @service('env') env;
get consulVersion() { get consulVersion() {
const suffix = !['', 'oss'].includes(this.env.var('CONSUL_BINARY_TYPE')) ? '+ent' : ''; const suffix = this.env.isEnterprise ? '+ent' : '';
return `${this.env.var('CONSUL_VERSION')}${suffix}`; return `${this.env.var('CONSUL_VERSION')}${suffix}`;
} }
} }

View File

@ -0,0 +1,24 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
}}
{{#if this.hcpLinkStatus.shouldDisplayBanner}}
<Hds::Alert @type="page" @color="highlight" @onDismiss={{this.onDismiss}} class="link-to-hcp-banner"
data-test-link-to-hcp-banner as |A|>
<A.Title data-test-link-to-hcp-banner-title>{{t "components.link-to-hcp-banner.title"}}</A.Title>
<A.Description data-test-link-to-hcp-banner-description>{{t "components.link-to-hcp-banner.description"
isEnterprise=this.env.isEnterprise}}
</A.Description>
<A.Button @text={{t "components.link-to-hcp-banner.clusterLinkButton"}} @color="secondary" {{on "click"
this.onClusterLink}}
data-test-link-to-hcp-banner-button/>
<A.Link::Standalone @color="secondary" @icon="docs-link" @iconPosition="trailing" @text={{t
"components.link-to-hcp-banner.viewDocumentation"}}
@href="https://developer.hashicorp.com/hcp/docs/consul/self-managed"
data-test-link-to-hcp-banner-view-documentation/>
<A.Link::Standalone @color="secondary" @icon="docs-link" @iconPosition="trailing"
@text={{t "components.link-to-hcp-banner.consulCentralDocumentation"}}
@href="https://developer.hashicorp.com/hcp/docs/consul/concepts/consul-central"
data-test-link-to-hcp-banner-consul-central-documentation/>
</Hds::Alert>
{{/if}}

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
export default class LinkToHcpBannerComponent extends Component {
@service('hcp-link-status') hcpLinkStatus;
@service('env') env;
@action
onDismiss() {
this.hcpLinkStatus.dismissHcpLinkBanner();
}
@action
onClusterLink() {
// TODO: CC-7147: Open simplified modal
}
}

View File

@ -7,6 +7,9 @@ import Service from '@ember/service';
import { env } from 'consul-ui/env'; import { env } from 'consul-ui/env';
export default class EnvService extends Service { export default class EnvService extends Service {
get isEnterprise() {
return !['', 'oss'].includes(this.var('CONSUL_BINARY_TYPE'));
}
// deprecated // deprecated
// TODO: Remove this elsewhere in the app and use var instead // TODO: Remove this elsewhere in the app and use var instead
env(key) { env(key) {

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
const LOCAL_STORAGE_KEY = 'consul:hideHcpLinkBanner';
export default class HcpLinkStatus extends Service {
@tracked
alreadyLinked = false;
@tracked
userDismissedBanner = false;
get shouldDisplayBanner() {
return !this.alreadyLinked && !this.userDismissedBanner;
}
constructor() {
super(...arguments);
this.userDismissedBanner = !!localStorage.getItem(LOCAL_STORAGE_KEY);
}
userHasLinked() {
// TODO: CC-7145 - once can fetch the link status from the backend, fetch it and set it here
}
dismissHcpLinkBanner() {
localStorage.setItem(LOCAL_STORAGE_KEY, true);
this.userDismissedBanner = true;
}
}

View File

@ -18,6 +18,9 @@ html[data-route^='dc.services.instance'] .app-view > header dl {
margin-bottom: 23px; margin-bottom: 23px;
margin-right: 50px; margin-right: 50px;
} }
html[data-route^='dc.services.index'] .link-to-hcp-banner {
margin: 0 -48px;
}
html[data-route^='dc.services.instance'] .app-view > header dt { html[data-route^='dc.services.instance'] .app-view > header dt {
font-weight: var(--token-typography-font-weight-bold); font-weight: var(--token-typography-font-weight-bold);
} }

View File

@ -62,7 +62,7 @@ as |route|>
(or route.params.nspace route.model.user.token.Namespace 'default') (or route.params.nspace route.model.user.token.Namespace 'default')
as |sort filters items partition nspace|}} as |sort filters items partition nspace|}}
<LinkToHcpBanner/>
<AppView> <AppView>
<BlockSlot @name="header"> <BlockSlot @name="header">
<h1> <h1>

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { click, visit } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
const bannerSelector = '[data-test-link-to-hcp-banner]';
module('Acceptance | link to hcp banner', function (hooks) {
setupApplicationTest(hooks);
hooks.beforeEach(function () {
// clear local storage so we don't have any settings
window.localStorage.clear();
// setupTestEnv(this.owner, {
// CONSUL_ACLS_ENABLED: true,
// });
});
test('the banner is initially displayed on services page', async function (assert) {
assert.expect(3);
// default route is services page so we're good here
await visit('/');
// Expect the banner to be visible by default
assert.dom(bannerSelector).exists({ count: 1 });
// Click on the dismiss button
await click(`${bannerSelector} button[aria-label="Dismiss"]`);
assert.dom(bannerSelector).doesNotExist('Banner is gone after dismissing');
// Refresh the page
await visit('/');
assert.dom(bannerSelector).doesNotExist('Banner is still gone after refresh');
});
});

View File

@ -0,0 +1,89 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { click, render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import Service from '@ember/service';
import sinon from 'sinon';
const userDismissedBannerStub = sinon.stub();
const userHasLinkedStub = sinon.stub();
const dismissHcpLinkBannerStub = sinon.stub();
const bannerSelector = '[data-test-link-to-hcp-banner]';
module('Integration | Component | link-to-hcp-banner', function (hooks) {
setupRenderingTest(hooks);
class HcpLinkStatusStub extends Service {
get shouldDisplayBanner() {
return true;
}
userDismissedBanner = userDismissedBannerStub;
userHasLinked = userHasLinkedStub;
dismissHcpLinkBanner = dismissHcpLinkBannerStub;
}
class EnvStub extends Service {
isEnterprise = false;
var(key) {
return key;
}
}
hooks.beforeEach(function () {
this.owner.register('service:hcp-link-status', HcpLinkStatusStub);
this.owner.register('service:env', EnvStub);
});
test('it renders banner when hcp-link-status says it should', async function (assert) {
await render(hbs`<LinkToHcpBanner />`);
assert.dom(bannerSelector).exists({ count: 1 });
await click(`${bannerSelector} button[aria-label="Dismiss"]`);
assert.ok(dismissHcpLinkBannerStub.calledOnce, 'userDismissedBanner was called');
// Can't test that banner is no longer visible since service isn't hooked up
assert
.dom('[data-test-link-to-hcp-banner-title]')
.hasText(
'Link this cluster to HCP Consul Central in a few steps to start managing your clusters in one place'
);
assert
.dom('[data-test-link-to-hcp-banner-description]')
.hasText(
'By linking your clusters to HCP Consul Central, youll get global, cross-cluster metrics, visual service maps, and a global API. Link to access a free 90 day trial for full feature access in your HCP organization.'
);
});
test('banner does not render when hcp-link-status says it should NOT', async function (assert) {
class HcpLinkStatusStub extends Service {
get shouldDisplayBanner() {
return false;
}
userDismissedBanner = sinon.stub();
userHasLinked = sinon.stub();
dismissHcpLinkBanner = sinon.stub();
}
this.owner.register('service:hcp-link-status', HcpLinkStatusStub);
await render(hbs`<LinkToHcpBanner />`);
assert.dom(bannerSelector).doesNotExist();
});
test('it displays different banner text when consul is enterprise', async function (assert) {
class EnvStub extends Service {
isEnterprise = true;
var(key) {
return key;
}
}
this.owner.register('service:env', EnvStub);
await render(hbs`<LinkToHcpBanner />`);
assert
.dom('[data-test-link-to-hcp-banner-description]')
.hasText(
'By linking your clusters to HCP Consul Central, youll get global, cross-cluster metrics, visual service maps, and a global API. HCP Consul Centrals full feature set is included with an Enterprise license.'
);
});
});

View File

@ -0,0 +1,10 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
title: Link this cluster to HCP Consul Central in a few steps to start managing your clusters in one place
description: By linking your clusters to HCP Consul Central, youll get global, cross-cluster metrics, visual service maps, and a global API. {isEnterprise, select,
true {HCP Consul Centrals full feature set is included with an Enterprise license.}
other {Link to access a free 90 day trial for full feature access in your HCP organization.}}
clusterLinkButton: Link this cluster
viewDocumentation: View documentation
consulCentralDocumentation: Consul Central documentation