diff --git a/.changelog/20275.txt b/.changelog/20275.txt
new file mode 100644
index 0000000000..e82fc542bc
--- /dev/null
+++ b/.changelog/20275.txt
@@ -0,0 +1,3 @@
+```release-note:feature
+ui: Added a banner to let users link their clusters to HCP
+```
\ No newline at end of file
diff --git a/ui/packages/consul-ui/app/components/hashicorp-consul/index.js b/ui/packages/consul-ui/app/components/hashicorp-consul/index.js
index e47ca22c62..f8f4a0663d 100644
--- a/ui/packages/consul-ui/app/components/hashicorp-consul/index.js
+++ b/ui/packages/consul-ui/app/components/hashicorp-consul/index.js
@@ -11,7 +11,7 @@ export default class HashiCorpConsul extends Component {
@service('env') env;
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}`;
}
}
diff --git a/ui/packages/consul-ui/app/components/link-to-hcp-banner/index.hbs b/ui/packages/consul-ui/app/components/link-to-hcp-banner/index.hbs
new file mode 100644
index 0000000000..950c30c945
--- /dev/null
+++ b/ui/packages/consul-ui/app/components/link-to-hcp-banner/index.hbs
@@ -0,0 +1,24 @@
+{{!
+ Copyright (c) HashiCorp, Inc.
+ SPDX-License-Identifier: BUSL-1.1
+}}
+{{#if this.hcpLinkStatus.shouldDisplayBanner}}
+
+ {{t "components.link-to-hcp-banner.title"}}
+ {{t "components.link-to-hcp-banner.description"
+ isEnterprise=this.env.isEnterprise}}
+
+
+
+
+
+{{/if}}
\ No newline at end of file
diff --git a/ui/packages/consul-ui/app/components/link-to-hcp-banner/index.js b/ui/packages/consul-ui/app/components/link-to-hcp-banner/index.js
new file mode 100644
index 0000000000..3d0c96f2fa
--- /dev/null
+++ b/ui/packages/consul-ui/app/components/link-to-hcp-banner/index.js
@@ -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
+ }
+}
diff --git a/ui/packages/consul-ui/app/services/env.js b/ui/packages/consul-ui/app/services/env.js
index 450206b15f..39f2a740e9 100644
--- a/ui/packages/consul-ui/app/services/env.js
+++ b/ui/packages/consul-ui/app/services/env.js
@@ -7,6 +7,9 @@ import Service from '@ember/service';
import { env } from 'consul-ui/env';
export default class EnvService extends Service {
+ get isEnterprise() {
+ return !['', 'oss'].includes(this.var('CONSUL_BINARY_TYPE'));
+ }
// deprecated
// TODO: Remove this elsewhere in the app and use var instead
env(key) {
diff --git a/ui/packages/consul-ui/app/services/hcp-link-status.js b/ui/packages/consul-ui/app/services/hcp-link-status.js
new file mode 100644
index 0000000000..4fec7f5bab
--- /dev/null
+++ b/ui/packages/consul-ui/app/services/hcp-link-status.js
@@ -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;
+ }
+}
diff --git a/ui/packages/consul-ui/app/styles/routes/dc/services/index.scss b/ui/packages/consul-ui/app/styles/routes/dc/services/index.scss
index 3065579e11..c7c6732734 100644
--- a/ui/packages/consul-ui/app/styles/routes/dc/services/index.scss
+++ b/ui/packages/consul-ui/app/styles/routes/dc/services/index.scss
@@ -18,6 +18,9 @@ html[data-route^='dc.services.instance'] .app-view > header dl {
margin-bottom: 23px;
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 {
font-weight: var(--token-typography-font-weight-bold);
}
diff --git a/ui/packages/consul-ui/app/templates/dc/services/index.hbs b/ui/packages/consul-ui/app/templates/dc/services/index.hbs
index 100e7c2d97..7f33e16c6a 100644
--- a/ui/packages/consul-ui/app/templates/dc/services/index.hbs
+++ b/ui/packages/consul-ui/app/templates/dc/services/index.hbs
@@ -62,7 +62,7 @@ as |route|>
(or route.params.nspace route.model.user.token.Namespace 'default')
as |sort filters items partition nspace|}}
-
+
diff --git a/ui/packages/consul-ui/tests/acceptance/link-to-hcp-banner-test.js b/ui/packages/consul-ui/tests/acceptance/link-to-hcp-banner-test.js
new file mode 100644
index 0000000000..aa501a78e3
--- /dev/null
+++ b/ui/packages/consul-ui/tests/acceptance/link-to-hcp-banner-test.js
@@ -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');
+ });
+});
diff --git a/ui/packages/consul-ui/tests/integration/components/link-to-hcp-banner-test.js b/ui/packages/consul-ui/tests/integration/components/link-to-hcp-banner-test.js
new file mode 100644
index 0000000000..d20646bf34
--- /dev/null
+++ b/ui/packages/consul-ui/tests/integration/components/link-to-hcp-banner-test.js
@@ -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``);
+
+ 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, you’ll 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``);
+ 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``);
+ assert
+ .dom('[data-test-link-to-hcp-banner-description]')
+ .hasText(
+ 'By linking your clusters to HCP Consul Central, you’ll get global, cross-cluster metrics, visual service maps, and a global API. HCP Consul Central’s full feature set is included with an Enterprise license.'
+ );
+ });
+});
diff --git a/ui/packages/consul-ui/translations/components/link-to-hcp-banner/en-us.yaml b/ui/packages/consul-ui/translations/components/link-to-hcp-banner/en-us.yaml
new file mode 100644
index 0000000000..e2b8149b3a
--- /dev/null
+++ b/ui/packages/consul-ui/translations/components/link-to-hcp-banner/en-us.yaml
@@ -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, you’ll get global, cross-cluster metrics, visual service maps, and a global API. {isEnterprise, select,
+ true {HCP Consul Central’s 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