Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: VAULT-21538 unauth endpoint message display #24665

Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0d34348
WIP unauth display
kiannaquach Jan 2, 2024
2c17d99
Add modal custom message
kiannaquach Jan 2, 2024
755d823
Close multiple modals
kiannaquach Jan 2, 2024
2ddcb5d
Update todo with ticket number
kiannaquach Jan 2, 2024
a95f867
On init make custom message request
kiannaquach Jan 2, 2024
833e390
Use serializer
kiannaquach Jan 2, 2024
58343f6
Update fetchMessages
kiannaquach Jan 3, 2024
cdf4c8c
Add copyright headers
kiannaquach Jan 3, 2024
9dfb0f7
Add services and serializers
kiannaquach Jan 3, 2024
c3a14f4
Send null instead of empty strings
kiannaquach Jan 3, 2024
7e14fd9
Fix tests!
kiannaquach Jan 3, 2024
a0ab2c0
Add copywrite headers
kiannaquach Jan 4, 2024
2ca1217
Add some acceptance tests
kiannaquach Jan 4, 2024
83912e4
Test cleanup
kiannaquach Jan 4, 2024
9aef956
Merge branch 'ui/VAULT-19096/customizable-banners' into ui/VAULT-2153…
kiannaquach Jan 4, 2024
291696e
Put tests back
kiannaquach Jan 4, 2024
48850ff
pass hooks to module
kiannaquach Jan 4, 2024
619624d
Move module out
kiannaquach Jan 4, 2024
52e39ed
Seperate tests
kiannaquach Jan 5, 2024
e34a685
Copywrite
kiannaquach Jan 5, 2024
48e73f7
Add aria-prohibited-attr runList options
kiannaquach Jan 5, 2024
be219ba
Code cleanup
kiannaquach Jan 5, 2024
9d01ca3
Add date-time-local transform
kiannaquach Jan 5, 2024
2f94937
Add copyright headers
kiannaquach Jan 5, 2024
d7c4df4
Remove comments
kiannaquach Jan 5, 2024
1675112
Remove date transform stuff for now!
kiannaquach Jan 5, 2024
1296821
Put getISODateFormat back into the serailize function
kiannaquach Jan 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ui/app/controllers/vault/cluster/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default Controller.extend({
version: service(),
auth: service(),
router: service(),
customMessages: service(),
queryParams: [{ authMethod: 'with', oidcProvider: 'o' }],
namespaceQueryParam: alias('clusterController.namespaceQueryParam'),
wrappedToken: alias('vaultController.wrappedToken'),
Expand Down Expand Up @@ -52,6 +53,7 @@ export default Controller.extend({
yield timeout(500);
const ns = this.fullNamespaceFromInput(value);
this.namespaceService.setNamespace(ns, true);
this.customMessages.fetchMessages(ns);
this.set('namespaceQueryParam', ns);
}).restartable(),

Expand Down
14 changes: 11 additions & 3 deletions ui/app/serializers/config-ui/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,27 +55,35 @@ export default class MessageSerializer extends ApplicationSerializer {
// if this date is not an object and isn’t a local date string, then return the snapshot date, which is set by default
// values defined on the model.
json.start_time = this.getISODateFormat(snapshot.record.startTime, json.start_time);
json.end_time = this.getISODateFormat(snapshot.record.endTime, json.end_time);
json.end_time = snapshot.record.endTime
? this.getISODateFormat(snapshot.record.endTime, json.end_time)
: null;
delete json?.link_title;
delete json?.link_href;
return json;
}

extractLazyPaginatedData(payload) {
mapPayload(payload) {
if (payload.data) {
if (payload.data?.keys && Array.isArray(payload.data.keys)) {
return payload.data.keys.map((key) => {
return {
const data = {
id: key,
linkTitle: payload.data.key_info.link?.title,
linkHref: payload.data.key_info.link?.href,
...payload.data.key_info[key],
};
if (data.message) data.message = decodeString(data.message);
return data;
});
}
Object.assign(payload, payload.data);
delete payload.data;
}
return payload;
}

extractLazyPaginatedData(payload) {
return this.mapPayload(payload);
}
}
47 changes: 47 additions & 0 deletions ui/app/services/custom-messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/

import Service, { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';

export default class CustomMessageService extends Service {
@service store;
@service namespace;
@tracked messages = [];
@tracked showMessageModal = true;

constructor() {
super(...arguments);
this.fetchMessages(this.namespace.path);
}

get bannerMessages() {
return this.messages?.filter((message) => message?.type === 'banner');
}

get modalMessages() {
return this.messages?.filter((message) => message?.type === 'modal');
}

async fetchMessages(ns) {
try {
const url = '/v1/sys/internal/ui/unauthenticated-messages';
const opts = {
method: 'GET',
headers: {},
};
if (ns) {
opts.headers['X-Vault-Namespace'] = ns;
}
const result = await fetch(url, opts);
const body = await result.json();
if (body.errors) return (this.messages = []);
const serializer = this.store.serializerFor('config-ui/message');
this.messages = serializer.mapPayload(body);
} catch (e) {
return e;
}
}
}
1 change: 0 additions & 1 deletion ui/app/templates/components/auth-form.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
~}}

<div class="auth-form" data-test-auth-form>
{{#if (and this.waitingForOktaNumberChallenge (not this.cancelAuthForOktaNumberChallenge))}}
<OktaNumberChallenge
Expand Down
30 changes: 30 additions & 0 deletions ui/app/templates/vault/cluster/auth.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,36 @@
</EmptyState>
</div>
{{else}}
{{#if this.customMessages.bannerMessages.length}}
{{#each this.customMessages.bannerMessages as |bannerMessage|}}
<Hds::Alert @type="inline" @color="warning" data-test-alert={{bannerMessage.id}} as |A|>
kiannaquach marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want spacing between multiple banners? If so you can use a wrapper div styling similar to .cluster-banners-wrapper

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think HCP has them stacked on top of each other without spacing. I'll confirm with Yongqi!

<A.Title data-test-alert-title={{bannerMessage.id}}>{{bannerMessage.title}}</A.Title>
<A.Description data-test-alert-description={{bannerMessage.id}}>
{{bannerMessage.message}}
</A.Description>
<A.Description class="has-top-margin-xs">
{{! TODO: VAULT-22908 display links when api is updated to { link: { 'learn': 'www.learn.com'} } }}
</A.Description>
</Hds::Alert>
{{/each}}
{{/if}}
{{#if this.customMessages.modalMessages.length}}
{{#each this.customMessages.modalMessages as |modalMessage|}}
<Hds::Modal id={{modalMessage.id}} @size="large" @color="warning" data-test-modal={{modalMessage.id}} as |M|>
<M.Header data-test-modal-title={{modalMessage.id}}>
{{modalMessage.title}}
</M.Header>
<M.Body data-test-modal-body={{modalMessage.id}}>
{{modalMessage.message}}
{{! TODO: VAULT-22908 display links when api is updated to { link: { 'learn': 'www.learn.com'} } }}
</M.Body>
<M.Footer as |F|>
<Hds::Button @text="Confirm" {{on "click" F.close}} data-test-modal-button={{modalMessage.id}} />
</M.Footer>
</Hds::Modal>
{{/each}}
{{/if}}

<SplashPage>
<:header>
{{#if this.oidcProvider}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
<M.Header data-test-modal-title={{@message.title}}>
{{@message.title}}
</M.Header>
<M.Body data-test-modal-body>
<M.Body data-test-modal-body={{@message.title}}>
{{@message.message}}
{{#if @message.linkHref}}
<Hds::Link::Inline @icon="external-link" @href={{@message.linkHref}}>
Expand Down
46 changes: 29 additions & 17 deletions ui/mirage/handlers/custom-messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,31 +92,43 @@ export default function (server) {

server.get('/sys/internal/ui/unauthenticated-messages', () => {
return {
request_id: '664fbad0-fcd8-9023-4c5b-81a7962e9f4b',
lease_id: '',
renewable: false,
lease_duration: 0,
data: {
key_info: {
'01234567-89ab-cdef-0123-456789abcdef': {
title: 'Unauthenticated Title One',
message:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur nulla augue, placerat quis risus blandit, molestie imperdiet massa. Sed blandit rutrum odio quis varius. Fusce purus orci, maximus ac libero.',
type: 'modal',
'02180e3f-bd5b-a851-bcc9-6f7983806df0': {
authenticated: false,
start_time: '2023-10-15T02:36:43.986212308Z',
end_time: '2024-10-15T02:36:43.986212308Z',
options: {},
},
'76543210-89ab-cdef-0123-456789abcdef': {
title: 'Unauthenticated Title Two',
message:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur nulla augue, placerat quis risus blandit, molestie imperdiet massa. Sed blandit rutrum odio quis varius. Fusce purus orci, maximus ac libero.',
end_time: null,
link: {
title: '',
},
message: 'aGVsbG8gd29ybGQgaGVsbG8gd29scmQ=',
options: null,
start_time: '2024-01-04T08:00:00Z',
title: 'Banner title',
type: 'banner',
},
'a7d7d9b1-a1ca-800c-17c5-0783be88e29c': {
authenticated: false,
start_time: '2021-10-15T02:36:43.986212308Z',
end_time: '2031-10-15T02:36:43.986212308Z',
options: {},
end_time: null,
link: {
title: '',
},
message: 'aGVyZSBpcyBhIGNvb2wgbWVzc2FnZQ==',
options: null,
start_time: '2024-01-01T08:00:00Z',
title: 'Modal title',
type: 'modal',
},
},
keys: ['01234567-89ab-cdef-0123-456789abcdef', '76543210-89ab-cdef-0123-456789abcdef'],
keys: ['02180e3f-bd5b-a851-bcc9-6f7983806df0', 'a7d7d9b1-a1ca-800c-17c5-0783be88e29c'],
},
wrap_info: null,
warnings: null,
auth: null,
mount_type: '',
};
});

Expand Down
114 changes: 114 additions & 0 deletions ui/tests/acceptance/custom-messages-auth-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/

import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { click, visit } from '@ember/test-helpers';
import { PAGE } from 'vault/tests/helpers/config-ui/message-selectors';
import { setupMirage } from 'ember-cli-mirage/test-support';

const unauthenticatedMessageResponse = {
request_id: '664fbad0-fcd8-9023-4c5b-81a7962e9f4b',
lease_id: '',
renewable: false,
lease_duration: 0,
data: {
key_info: {
'02180e3f-bd5b-a851-bcc9-6f7983806df0': {
authenticated: false,
end_time: null,
link: {
title: '',
},
message: 'aGVsbG8gd29ybGQgaGVsbG8gd29scmQ=',
options: null,
start_time: '2024-01-04T08:00:00Z',
title: 'Banner title',
type: 'banner',
},
'a7d7d9b1-a1ca-800c-17c5-0783be88e29c': {
authenticated: false,
end_time: null,
link: {
title: '',
},
message: 'aGVyZSBpcyBhIGNvb2wgbWVzc2FnZQ==',
options: null,
start_time: '2024-01-01T08:00:00Z',
title: 'Modal title',
type: 'modal',
},
},
keys: ['02180e3f-bd5b-a851-bcc9-6f7983806df0', 'a7d7d9b1-a1ca-800c-17c5-0783be88e29c'],
},
wrap_info: null,
warnings: null,
auth: null,
mount_type: '',
};

module('Acceptance | auth custom messages auth tests', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);

test('it shows the alert banner and modal message', async function (assert) {
this.server.get('/sys/internal/ui/mounts', () => ({}));
kiannaquach marked this conversation as resolved.
Show resolved Hide resolved
this.server.get('/sys/internal/ui/unauthenticated-messages', function () {
return unauthenticatedMessageResponse;
});
await visit('/vault/auth');
const modalId = 'a7d7d9b1-a1ca-800c-17c5-0783be88e29c';
kiannaquach marked this conversation as resolved.
Show resolved Hide resolved
const alertId = '02180e3f-bd5b-a851-bcc9-6f7983806df0';
assert.dom(PAGE.modal(modalId)).exists();
assert.dom(PAGE.modalTitle(modalId)).hasText('Modal title');
assert.dom(PAGE.modalBody(modalId)).exists();
assert.dom(PAGE.modalBody(modalId)).hasText('here is a cool message');
await click(PAGE.modalButton(modalId));
assert.dom(PAGE.alertTitle(alertId)).hasText('Banner title');
assert.dom(PAGE.alertDescription(alertId)).hasText('hello world hello wolrd');
});
test('it shows the multiple modal messages', async function (assert) {
const modalIdOne = '02180e3f-bd5b-a851-bcc9-6f7983806df0';
const modalIdTwo = 'a7d7d9b1-a1ca-800c-17c5-0783be88e29c';
this.server.get('/sys/internal/ui/mounts', () => ({}));

this.server.get('/sys/internal/ui/unauthenticated-messages', function () {
unauthenticatedMessageResponse.data.key_info[modalIdOne].type = 'modal';
unauthenticatedMessageResponse.data.key_info[modalIdOne].title = 'Modal title 1';
unauthenticatedMessageResponse.data.key_info[modalIdTwo].type = 'modal';
unauthenticatedMessageResponse.data.key_info[modalIdTwo].title = 'Modal title 2';
return unauthenticatedMessageResponse;
});
await visit('/vault/auth');
assert.dom(PAGE.modal(modalIdOne)).exists();
assert.dom(PAGE.modalTitle(modalIdOne)).hasText('Modal title 1');
assert.dom(PAGE.modalBody(modalIdOne)).exists();
assert.dom(PAGE.modalBody(modalIdOne)).hasText('hello world hello wolrd');
await click(PAGE.modalButton(modalIdOne));
assert.dom(PAGE.modal(modalIdTwo)).exists();
assert.dom(PAGE.modalTitle(modalIdTwo)).hasText('Modal title 2');
assert.dom(PAGE.modalBody(modalIdTwo)).exists();
assert.dom(PAGE.modalBody(modalIdTwo)).hasText('here is a cool message');
await click(PAGE.modalButton(modalIdTwo));
});
test('it shows the multiple banner messages', async function (assert) {
const bannerIdOne = '02180e3f-bd5b-a851-bcc9-6f7983806df0';
const bannerIdTwo = 'a7d7d9b1-a1ca-800c-17c5-0783be88e29c';
this.server.get('/sys/internal/ui/mounts', () => ({}));

this.server.get('/sys/internal/ui/unauthenticated-messages', function () {
unauthenticatedMessageResponse.data.key_info[bannerIdOne].type = 'banner';
unauthenticatedMessageResponse.data.key_info[bannerIdOne].title = 'Banner title 1';
unauthenticatedMessageResponse.data.key_info[bannerIdTwo].type = 'banner';
unauthenticatedMessageResponse.data.key_info[bannerIdTwo].title = 'Banner title 2';
return unauthenticatedMessageResponse;
});
await visit('/vault/auth');
assert.dom(PAGE.alertTitle(bannerIdOne)).hasText('Banner title 1');
assert.dom(PAGE.alertDescription(bannerIdOne)).hasText('hello world hello wolrd');
assert.dom(PAGE.alertTitle(bannerIdTwo)).hasText('Banner title 2');
assert.dom(PAGE.alertDescription(bannerIdTwo)).hasText('here is a cool message');
});
});
21 changes: 21 additions & 0 deletions ui/tests/helpers/config-ui/message-selectors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

export const PAGE = {
// General selectors that are common between pages
radio: (radioName) => `[data-test-radio="${radioName}"]`,
field: (fieldName) => `[data-test-field="${fieldName}"]`,
input: (input) => `[data-test-input="${input}"]`,
button: (buttonName) => `[data-test-button="${buttonName}"]`,
inlineErrorMessage: `[data-test-inline-error-message]`,
fieldVaildation: (fieldName) => `[data-test-field-validation="${fieldName}"]`,
modal: (name) => `[data-test-modal="${name}"]`,
modalTitle: (title) => `[data-test-modal-title="${title}"]`,
modalBody: (name) => `[data-test-modal-body="${name}"]`,
modalButton: (name) => `[data-test-modal-button="${name}"]`,
alert: (name) => `data-test-alert=${name}`,
alertTitle: (name) => `[data-test-alert-title="${name}"]`,
alertDescription: (name) => `[data-test-alert-description="${name}"]`,
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,7 @@ import { render, click, fillIn } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { datetimeLocalStringFormat } from 'core/utils/date-formatters';
import { format, addDays, startOfDay } from 'date-fns';

const PAGE = {
radio: (radioName) => `[data-test-radio="${radioName}"]`,
field: (fieldName) => `[data-test-field="${fieldName}"]`,
input: (input) => `[data-test-input="${input}"]`,
button: (buttonName) => `[data-test-button="${buttonName}"]`,
inlineErrorMessage: `[data-test-inline-error-message]`,
fieldVaildation: (fieldName) => `[data-test-field-validation="${fieldName}"]`,
modal: (name) => `[data-test-modal="${name}"]`,
modalTitle: (title) => `[data-test-modal-title="${title}"]`,
modalBody: '[data-test-modal-body]',
modalButton: (name) => `[data-test-modal-button="${name}"]`,
alertTitle: (name) => `[data-test-alert-title="${name}"]`,
alertDescription: (name) => `[data-test-alert-description="${name}"]`,
};
import { PAGE } from 'vault/tests/helpers/config-ui/message-selectors';

module('Integration | Component | messages/page/create-and-edit-message', function (hooks) {
setupRenderingTest(hooks);
Expand Down Expand Up @@ -185,6 +171,6 @@ module('Integration | Component | messages/page/create-and-edit-message', functi
assert.dom(PAGE.modal('preview modal')).exists();
assert.dom(PAGE.modal('preview image')).doesNotExist();
assert.dom(PAGE.modalTitle('Preview modal title')).hasText('Preview modal title');
assert.dom(PAGE.modalBody).hasText('Some preview modal message thats super long.');
assert.dom(PAGE.modalBody('Preview modal title')).hasText('Some preview modal message thats super long.');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ module('Integration | Component | mfa-login-enforcement-form', function (hooks)
label: { enabled: false },
// TODO: add labels to enforcement targets key/value style inputs
'select-name': { enabled: false },
'aria-prohibited-attr': { enabled: false },
},
});
});
Expand Down
Loading
Loading