HCP Link Status Parsing and Modal Update (#17279)
* updates hcp link status parsing for new format and updates to modal view * fixes missing wormhole in tests * fixes transit backend tests * reverts adding wormhole to LinkStatus for testing and instead adds it to impacted tests
This commit is contained in:
parent
0c76168d3d
commit
efe5193a59
|
@ -14,7 +14,6 @@ import { inject as service } from '@ember/service';
|
|||
*/
|
||||
|
||||
export default class LinkStatus extends Component {
|
||||
@service store;
|
||||
@service version;
|
||||
|
||||
get state() {
|
||||
|
@ -28,47 +27,14 @@ export default class LinkStatus extends Component {
|
|||
|
||||
get timestamp() {
|
||||
try {
|
||||
return this.state !== 'connected' ? this.args.status.split('since')[1].split('m=')[0].trim() : null;
|
||||
return this.state !== 'connected' ? this.args.status.split('since')[1].split(';')[0].trim() : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
get message() {
|
||||
if (this.args.status) {
|
||||
const error = this.args.status.split('error:')[1] || '';
|
||||
const timestamp = this.timestamp ? ` [${this.timestamp}]` : '';
|
||||
const sinceTimestamp = timestamp ? ` since${timestamp}` : '';
|
||||
if (this.state === 'disconnected') {
|
||||
// if generally disconnected hide the banner
|
||||
return !error || error.includes('UNKNOWN')
|
||||
? null
|
||||
: `Vault has been disconnected from HCP${sinceTimestamp}. Error: ${error}`;
|
||||
} else if (this.state === 'connecting') {
|
||||
if (error.includes('connection refused')) {
|
||||
return `Vault has been trying to connect to HCP${sinceTimestamp}, but HCP is not reachable. Vault will try again soon.`;
|
||||
} else if (error.includes('principal does not have permission to register as provider')) {
|
||||
return `Vault tried connecting to HCP, but the Resource ID is invalid. Check your resource ID.${timestamp}`;
|
||||
} else if (error.includes('cannot fetch token: 401 Unauthorized')) {
|
||||
return `Vault tried connecting to HCP, but the authorization information is wrong. Update it and try again.${timestamp}`;
|
||||
} else {
|
||||
// catch all for any unknown errors or missing error
|
||||
const errorMessage = error ? ` Error: ${error}` : '';
|
||||
return `Vault has been trying to connect to HCP${sinceTimestamp}. Vault will try again soon.${errorMessage}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get showStatus() {
|
||||
// enterprise only feature at this time but will expand to OSS in future release
|
||||
if (!this.version.isEnterprise || !this.args.status) {
|
||||
return false;
|
||||
}
|
||||
if (this.state !== 'connected' && !this.message) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
get error() {
|
||||
const status = this.args.status;
|
||||
return status && status !== 'connected' ? status.split('error:')[1] : null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -280,5 +280,4 @@ a.button.disabled {
|
|||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
cursor: pointer;
|
||||
color: $link;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{{#if this.showStatus}}
|
||||
{{#if (and this.state this.version.isEnterprise)}}
|
||||
<div class="navbar-status {{if (eq this.state 'connected') 'connected' 'warning'}}">
|
||||
<Icon @name="info" />
|
||||
<p data-test-link-status>
|
||||
|
@ -8,8 +8,55 @@
|
|||
HCP.
|
||||
</a>
|
||||
{{else}}
|
||||
{{this.message}}
|
||||
There was an error connecting to HCP. Click
|
||||
<button type="button" class="text-button is-underline" {{on "click" (fn (mut this.showModal))}}>
|
||||
here
|
||||
</button>
|
||||
for more information.
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
<Modal
|
||||
@title="HCP Link error"
|
||||
@onClose={{fn (mut this.showModal) false}}
|
||||
@isActive={{this.showModal}}
|
||||
@type="info"
|
||||
@showCloseButton={{true}}
|
||||
>
|
||||
<section class="modal-card-body">
|
||||
<div>
|
||||
<p class="has-text-weight-bold">Timestamp</p>
|
||||
<p data-test-link-status-timestamp>
|
||||
{{or this.timestamp "Not available"}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="has-top-bottom-margin">
|
||||
<p class="has-text-weight-bold">Error</p>
|
||||
{{#if this.error}}
|
||||
<code class="tag has-text-danger" data-test-link-status-error>
|
||||
{{this.error}}
|
||||
</code>
|
||||
{{else}}
|
||||
<p data-test-link-status-error>
|
||||
Not available
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div>
|
||||
<p class="has-text-weight-bold">Additional information</p>
|
||||
<p>Check the logs for more information</p>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot modal-card-foot-outlined">
|
||||
<button
|
||||
type="button"
|
||||
class="button is-primary"
|
||||
{{on "click" (fn (mut this.showModal) false)}}
|
||||
data-test-link-status-close
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</footer>
|
||||
</Modal>
|
|
@ -39,7 +39,7 @@
|
|||
You can use Alt+Tab (Option+Tab on MacOS) in the code editor to skip to the next field. See
|
||||
<button
|
||||
type="button"
|
||||
class="text-button"
|
||||
class="text-button has-text-info"
|
||||
{{on "click" (fn (mut this.showTemplateModal))}}
|
||||
data-test-oidc-scope-example
|
||||
>
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
export const statuses = [
|
||||
'connected',
|
||||
'disconnected since 2022-09-13 14:45:40.666697 -0700 PDT m=+21.065498483; error: UNKNOWN',
|
||||
'disconnected since 2022-09-13 14:45:40.666697 -0700 PDT m=+21.065498483; error: some other error other than unknown',
|
||||
'connecting since 2022-09-13 14:45:40.666697 -0700 PDT m=+21.065498483; error: dial tcp [::1]:28083: connect: connection refused',
|
||||
'connecting since 2022-09-13 14:45:40.666697 -0700 PDT m=+21.065498483; error: principal does not have permission to register as provider: rpc error: code = PermissionDenied desc =',
|
||||
'connecting since 2022-09-13 14:45:40.666697 -0700 PDT m=+21.065498483; error: failed to get access token: oauth2: cannot fetch token: 401 Unauthorized. Response: {"error":"access_denied","error_description":"Unauthorized"}',
|
||||
'connecting since 2022-09-13 14:45:40.666697 -0700 PDT m=+21.065498483; error: connection error we are unaware of',
|
||||
// the following were identified as dev only errors -- leaving in case they need to be handled
|
||||
// 'connecting since 2022-09-13 14:45:40.666697 -0700 PDT m=+21.065498483; error: failed to get access token: Post "https://aauth.idp.hcp.dev/oauth2/token": x509: “*.hcp.dev” certificate name does not match input',
|
||||
// 'connecting since 2022-09-13 14:45:40.666697 -0700 PDT m=+21.065498483; error: UNKNOWN',
|
||||
'disconnected since 2022-09-21T11:25:02.196835-07:00; error: unable to establish a connection with HCP',
|
||||
'connecting since 2022-09-21T11:25:02.196835-07:00; error: unable to establish a connection with HCP',
|
||||
'connecting since 2022-09-21T11:25:02.196835-07:00; error: principal does not have the permission to register as a provider',
|
||||
'connecting since 2022-09-21T11:25:02.196835-07:00; error: could not obtain a token with the supplied credentials',
|
||||
];
|
||||
let index = null;
|
||||
|
||||
|
|
|
@ -258,7 +258,7 @@ const testConvergentEncryption = async function (assert, keyName) {
|
|||
}
|
||||
// store ciphertext for decryption step
|
||||
const copiedCiphertext = find('[data-test-encrypted-value="ciphertext"]').innerText;
|
||||
await click('[data-test-modal-background]');
|
||||
await click('.modal.is-active [data-test-modal-background]');
|
||||
|
||||
assert.dom('.modal.is-active').doesNotExist(`${name}: Modal closes after background clicked`);
|
||||
await click('[data-test-transit-action-link="decrypt"]');
|
||||
|
@ -275,7 +275,7 @@ const testConvergentEncryption = async function (assert, keyName) {
|
|||
testCase.assertAfterDecrypt(keyName);
|
||||
}
|
||||
|
||||
await click('[data-test-modal-background]');
|
||||
await click('.modal.is-active [data-test-modal-background]');
|
||||
|
||||
assert.dom('.modal.is-active').doesNotExist(`${name}: Modal closes after background clicked`);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { click, render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import { statuses } from '../../../mirage/handlers/hcp-link';
|
||||
|
||||
const timestamp = '[2022-09-13 14:45:40.666697 -0700 PDT]';
|
||||
|
||||
module('Integration | Component | link-status', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
@ -17,111 +15,79 @@ module('Integration | Component | link-status', function (hooks) {
|
|||
this.statuses = statuses;
|
||||
});
|
||||
|
||||
test('it does not render banner or error when status is not present', async function (assert) {
|
||||
await render(hbs`<LinkStatus @status={{undefined}} />`);
|
||||
test('it does not render banner when status is not present', async function (assert) {
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<LinkStatus @status={{undefined}} />
|
||||
`);
|
||||
|
||||
assert.dom('.navbar-status').doesNotExist('Banner is hidden for disconnected state');
|
||||
assert.dom('.navbar-status').doesNotExist('Banner is hidden for missing status message');
|
||||
});
|
||||
|
||||
test('it does not render banner if not enterprise version', async function (assert) {
|
||||
test('it does not render banner in oss version', async function (assert) {
|
||||
this.owner.lookup('service:version').set('isEnterprise', false);
|
||||
|
||||
await render(hbs`<LinkStatus @status={{get this.statuses 0}} />`);
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<LinkStatus @status={{get this.statuses 0}} />
|
||||
`);
|
||||
|
||||
assert.dom('.navbar-status').doesNotExist('Banner is hidden for disconnected state');
|
||||
assert.dom('.navbar-status').doesNotExist('Banner is hidden in oss');
|
||||
});
|
||||
|
||||
test('it renders connected status', async function (assert) {
|
||||
await render(hbs`<LinkStatus @status={{get this.statuses 0}} />`);
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<LinkStatus @status={{get this.statuses 0}} />
|
||||
`);
|
||||
|
||||
assert.dom('.navbar-status').hasClass('connected', 'Correct class renders for connected state');
|
||||
assert.dom('.navbar-status').hasClass('connected', 'Correct banner class renders for connected state');
|
||||
assert
|
||||
.dom('[data-test-link-status]')
|
||||
.hasText('This self-managed Vault is linked to HCP.', 'Copy renders for connected state');
|
||||
.hasText('This self-managed Vault is linked to HCP.', 'Banner copy renders for connected state');
|
||||
assert
|
||||
.dom('[data-test-link-status] a')
|
||||
.hasAttribute('href', 'https://portal.cloud.hashicorp.com/sign-in', 'HCP sign in link renders');
|
||||
});
|
||||
|
||||
test('it does not render banner for disconnected state with unknown error', async function (assert) {
|
||||
await render(hbs`<LinkStatus @status={{get this.statuses 1}} />`);
|
||||
test('it should render error states', async function (assert) {
|
||||
// disconnected error
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<LinkStatus @status={{get this.statuses 1}} />
|
||||
`);
|
||||
|
||||
assert.dom('.navbar-status').doesNotExist('Banner is hidden for disconnected state');
|
||||
});
|
||||
|
||||
test('it should render for disconnected error state', async function (assert) {
|
||||
await render(hbs`<LinkStatus @status={{get this.statuses 2}} />`);
|
||||
|
||||
assert.dom('.navbar-status').hasClass('warning', 'Correct class renders for disconnected error state');
|
||||
assert.dom('.navbar-status').hasClass('warning', 'Correct banner class renders for error state');
|
||||
assert
|
||||
.dom('[data-test-link-status]')
|
||||
.hasText(
|
||||
`Vault has been disconnected from HCP since ${timestamp}. Error: some other error other than unknown`,
|
||||
'Copy renders for disconnected error state'
|
||||
'There was an error connecting to HCP. Click here for more information.',
|
||||
'Banner copy renders for error state'
|
||||
);
|
||||
});
|
||||
|
||||
test('it should render for connection refused error state', async function (assert) {
|
||||
await render(hbs`<LinkStatus @status={{get this.statuses 3}} />`);
|
||||
|
||||
await click('[data-test-link-status] button');
|
||||
assert
|
||||
.dom('.navbar-status')
|
||||
.hasClass('warning', 'Correct class renders for connection refused error state');
|
||||
.dom('[data-test-link-status-timestamp]')
|
||||
.hasText('2022-09-21T11:25:02.196835-07:00', 'Timestamp renders');
|
||||
assert
|
||||
.dom('[data-test-link-status]')
|
||||
.hasText(
|
||||
`Vault has been trying to connect to HCP since ${timestamp}, but HCP is not reachable. Vault will try again soon.`,
|
||||
'Copy renders for connection refused error state'
|
||||
);
|
||||
});
|
||||
.dom('[data-test-link-status-error]')
|
||||
.hasText('unable to establish a connection with HCP', 'Error renders');
|
||||
|
||||
test('it should render for resource id error state', async function (assert) {
|
||||
await render(hbs`<LinkStatus @status={{get this.statuses 4}} />`);
|
||||
|
||||
assert.dom('.navbar-status').hasClass('warning', 'Correct class renders for resource id error state');
|
||||
// connecting error
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<LinkStatus @status={{get this.statuses 3}} />
|
||||
`);
|
||||
assert
|
||||
.dom('[data-test-link-status]')
|
||||
.hasText(
|
||||
`Vault tried connecting to HCP, but the Resource ID is invalid. Check your resource ID. ${timestamp}`,
|
||||
'Copy renders for resource id error state'
|
||||
);
|
||||
});
|
||||
.dom('[data-test-link-status-error]')
|
||||
.hasText('principal does not have the permission to register as a provider', 'Error renders');
|
||||
|
||||
test('it should render for unauthorized error state', async function (assert) {
|
||||
await render(hbs`<LinkStatus @status={{get this.statuses 5}} />`);
|
||||
|
||||
assert.dom('.navbar-status').hasClass('warning', 'Correct class renders for unauthorized error state');
|
||||
assert
|
||||
.dom('[data-test-link-status]')
|
||||
.hasText(
|
||||
`Vault tried connecting to HCP, but the authorization information is wrong. Update it and try again. ${timestamp}`,
|
||||
'Copy renders for unauthorized error state'
|
||||
);
|
||||
});
|
||||
|
||||
test('it should render generic message for unknown error state', async function (assert) {
|
||||
await render(hbs`<LinkStatus @status={{get this.statuses 6}} />`);
|
||||
|
||||
assert.dom('.navbar-status').hasClass('warning', 'Correct class renders for unknown error state');
|
||||
assert
|
||||
.dom('[data-test-link-status]')
|
||||
.hasText(
|
||||
`Vault has been trying to connect to HCP since ${timestamp}. Vault will try again soon. Error: connection error we are unaware of`,
|
||||
'Copy renders for unknown error state'
|
||||
);
|
||||
});
|
||||
|
||||
// connecting state should always be returned with timestamp and error
|
||||
// this case came up in manual testing and should be fixed on the backend but additional checks were added just in case
|
||||
test('it renders generic message for connecting state with no timestamp or error', async function (assert) {
|
||||
await render(hbs`<LinkStatus @status="connecting" />`);
|
||||
|
||||
assert.dom('.navbar-status').hasClass('warning', 'Correct class renders for unknown error state');
|
||||
assert
|
||||
.dom('[data-test-link-status]')
|
||||
.hasText(
|
||||
`Vault has been trying to connect to HCP. Vault will try again soon.`,
|
||||
'Copy renders for unknown error state'
|
||||
);
|
||||
// this shouldn't happen but placeholders should render if disconnected/connecting status is returned without timestamp and/or error
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<LinkStatus @status="connecting" />
|
||||
`);
|
||||
assert.dom('[data-test-link-status-timestamp]').hasText('Not available', 'Timestamp placeholder renders');
|
||||
assert.dom('[data-test-link-status-error]').hasText('Not available', 'Error placeholder renders');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,6 +12,7 @@ module('Integration | Component | nav header', function (hooks) {
|
|||
|
||||
test('it renders', async function (assert) {
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
{{#nav-header as |h|}}
|
||||
{{#h.home}}
|
||||
Home!
|
||||
|
|
|
@ -28,15 +28,19 @@ module('Integration | Component | replication-header', function (hooks) {
|
|||
});
|
||||
|
||||
test('it renders', async function (assert) {
|
||||
await render(hbs`<ReplicationHeader @data={{data}} @isSecondary={{isSecondary}} @title={{title}}/>`);
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<ReplicationHeader @data={{data}} @isSecondary={{isSecondary}} @title={{title}}/>
|
||||
`);
|
||||
|
||||
assert.dom('[data-test-replication-header]').exists();
|
||||
});
|
||||
|
||||
test('it renders with mode and secondaryId when set', async function (assert) {
|
||||
await render(
|
||||
hbs`<ReplicationHeader @data={{data}} @isSecondary={{isSecondary}} @title={{title}} @secondaryId={{secondaryId}}/>`
|
||||
);
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<ReplicationHeader @data={{data}} @isSecondary={{isSecondary}} @title={{title}} @secondaryId={{secondaryId}}/>
|
||||
`);
|
||||
|
||||
assert.dom('[data-test-secondaryId]').includesText(SECONDARY_ID, `shows the correct secondaryId value`);
|
||||
assert.dom('[data-test-mode]').includesText('secondary', `shows the correct mode value`);
|
||||
|
@ -48,16 +52,20 @@ module('Integration | Component | replication-header', function (hooks) {
|
|||
this.set('data', notEnabled);
|
||||
this.set('secondaryId', noId);
|
||||
|
||||
await render(
|
||||
hbs`<ReplicationHeader @data={{data}} @isSecondary={{isSecondary}} @title={{title}} @secondaryId={{secondaryId}}/>`
|
||||
);
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<ReplicationHeader @data={{data}} @isSecondary={{isSecondary}} @title={{title}} @secondaryId={{secondaryId}}/>
|
||||
`);
|
||||
|
||||
assert.dom('[data-test-secondaryId]').doesNotExist();
|
||||
assert.dom('[data-test-mode]').doesNotExist();
|
||||
});
|
||||
|
||||
test('it does not show tabs when showTabs is not set', async function (assert) {
|
||||
await render(hbs`<ReplicationHeader @data={{data}} @isSecondary={{isSecondary}} @title={{title}}/>`);
|
||||
await render(hbs`
|
||||
<div id="modal-wormhole"></div>
|
||||
<ReplicationHeader @data={{data}} @isSecondary={{isSecondary}} @title={{title}}/>
|
||||
`);
|
||||
|
||||
assert.dom('[data-test-tabs]').doesNotExist();
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue