Skip to content

Commit

Permalink
UI: Part three bug bash custom messages (#25229)
Browse files Browse the repository at this point in the history
* Address comments

* Fix serailizer warning mesage

* Reset pageFilter when exiting

* Add start and end time validation and fix bugs

* Fix failing tests

* Add validation tests

* Set end time in contorller

* Address feedback

* Remove new date

* Put new Date back
  • Loading branch information
kiannaquach authored Feb 7, 2024
1 parent 0d83188 commit 4283caa
Show file tree
Hide file tree
Showing 14 changed files with 113 additions and 39 deletions.
26 changes: 24 additions & 2 deletions ui/app/models/config-ui/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/
import Model, { attr } from '@ember-data/model';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
import { isAfter, addDays, startOfDay, parseISO } from 'date-fns';
import { isAfter, addDays, startOfDay, parseISO, isBefore } from 'date-fns';
import { withModelValidations } from 'vault/decorators/model-validations';
import { withFormFields } from 'vault/decorators/model-form-fields';

Expand All @@ -22,6 +22,28 @@ const validations = {
message: 'Link title and url are required.',
},
],
startTime: [
{
validator(model) {
if (!model.endTime) return true;
const start = new Date(model.startTime);
const end = new Date(model.endTime);
return isBefore(start, end);
},
message: 'Start time is after end time.',
},
],
endTime: [
{
validator(model) {
if (!model.endTime) return true;
const start = new Date(model.startTime);
const end = new Date(model.endTime);
return isAfter(end, start);
},
message: 'End time is before start time.',
},
],
};

@withModelValidations(validations)
Expand Down Expand Up @@ -95,7 +117,7 @@ export default class MessageModel extends Model {
@attr('object', {
editType: 'kv',
keyPlaceholder: 'Display text (e.g. Learn more)',
valuePlaceholder: 'Link URL (e.g. https://www.learnmore.com)',
valuePlaceholder: 'Link URL (e.g. https://www.hashicorp.com/)',
label: 'Link (optional)',
isSingleRow: true,
allowWhiteSpace: true,
Expand Down
2 changes: 0 additions & 2 deletions ui/app/serializers/config-ui/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import ApplicationSerializer from '../application';
export default class MessageSerializer extends ApplicationSerializer {
attrs = {
active: { serialize: false },
start_time: { serialize: false },
end_time: { serialize: false },
};

normalizeResponse(store, primaryModelClass, payload, id, requestType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,24 @@
id="specificDate"
value="specificDate"
@value="specificDate"
@onChange={{fn (mut @message.endTime) this.formDateTime}}
@onChange={{this.specificDateChange}}
@groupValue={{this.groupValue}}
/>
<label for="specificDate" class="has-left-margin-xs has-text-black is-size-7">
<span class="has-left-margin-xs">
Specific date
</span>
<p class="has-left-margin-xs has-text-grey is-size-8">
This message will expire at midnight (local timezone) at the specific date.
This message will expire at the specified time (local timezone) and date.
</p>
<div class="has-left-margin-xs control">
<Input
@type="datetime-local"
@value={{if this.formDateTime (date-format this.formDateTime this.datetimeLocalStringFormat) ""}}
class="input has-top-margin-xs is-auto-width"
@value={{if this.messageEndTime (date-format this.messageEndTime this.datetimeLocalStringFormat) ""}}
class="input has-top-margin-xs is-auto-width {{if this.validationError 'has-error-border'}}"
name="endTime"
data-test-input="endTime"
{{on "change" (pipe (pick "target.value") (fn (mut @message.endTime)))}}
{{on "focusout" this.onFocusOut}}
/>
</div>
</label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* SPDX-License-Identifier: BUSL-1.1
*/

import { action } from '@ember/object';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { datetimeLocalStringFormat } from 'core/utils/date-formatters';
Expand All @@ -20,14 +21,33 @@ import { datetimeLocalStringFormat } from 'core/utils/date-formatters';
export default class MessageExpirationDateForm extends Component {
datetimeLocalStringFormat = datetimeLocalStringFormat;
@tracked groupValue = 'never';
@tracked formDateTime = '';
@tracked messageEndTime = '';
constructor() {
super(...arguments);
if (this.args.message.endTime) {
this.groupValue = 'specificDate';
this.formDateTime = this.args.message.endTime;
this.messageEndTime = this.args.message.endTime;
}
}
get validationError() {
const validations = this.args.modelValidations || {};
const state = validations[this.args.attr.name];
return state && !state.isValid ? state.errors.join(' ') : null;
}
@action
specificDateChange() {
this.groupValue = 'specificDate';
this.args.message.endTime = this.messageEndTime;
}
@action
onFocusOut(e) {
this.messageEndTime = e.target.value;
this.args.message.endTime = this.messageEndTime;
this.groupValue = 'specificDate';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@
/>

<form id="message-create-edit-form" {{on "submit" (perform this.save)}} data-test-form="create-and-edit">
<div class="box is-sideless is-fullwidth is-marginless has-top-padding-s">
<Hds::Text::Body @tag="p" class="has-bottom-margin-l" data-test-form-subtext>
{{if @message.isNew "Create" "Edit"}}
a custom message for all users when they access a Vault system via the UI.
</Hds::Text::Body>

<div class="box is-sideless is-fullwidth is-marginless">
<NamespaceReminder @mode={{if @message.isNew "create" "edit"}} @noun="message" />
<MessageError @errorMessage={{this.errorBanner}} class="has-top-margin-s" />

{{#each @message.formFields as |attr|}}
<FormField @attr={{attr}} @model={{@message}} @modelValidations={{this.modelValidations}} class="has-bottom-margin-m">
<Messages::MessageExpirationDateForm @message={{@message}} @attr={{attr}} />
<Messages::MessageExpirationDateForm
@message={{@message}}
@attr={{attr}}
@modelValidations={{this.modelValidations}}
/>
</FormField>
{{#if (and (eq attr.name "message") (not @message.authenticated))}}
<Hds::Alert class="has-top-margin-negative-m has-bottom-margin-l" @type="compact" @color="highlight" as |A|>
<A.Description data-test-unauth-info>Note: Do not include sensitive info in this message since users are
<A.Description data-test-unauth-info>Note: Do not include sensitive information in this message since users are
unauthenticated at this stage.</A.Description>
</Hds::Alert>
{{/if}}
Expand Down
6 changes: 1 addition & 5 deletions ui/lib/config-ui/addon/components/messages/page/list.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,7 @@
</Hds::Text::Display>
<div class="has-top-margin-xs">
<Hds::Badge @text={{message.badgeDisplayText}} @color={{message.badgeColor}} data-test-badge={{message.id}} />
<Hds::Badge
@text={{(capitalize message.type)}}
@color={{message.badgeColor}}
data-test-badge={{message.type}}
/>
<Hds::Badge @text={{(capitalize message.type)}} data-test-badge={{message.type}} />
</div>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion ui/lib/config-ui/addon/controllers/messages/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
*/

import Controller from '@ember/controller';
export default class MessagesController extends Controller {

export default class MessagesCreateController extends Controller {
queryParams = ['authenticated'];

authenticated = true;
Expand Down
6 changes: 6 additions & 0 deletions ui/lib/config-ui/addon/routes/messages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,10 @@ export default class MessagesRoute extends Route {
const label = controller.authenticated ? 'After User Logs In' : 'On Login Page';
controller.breadcrumbs = [{ label: 'Messages' }, { label }];
}

resetController(controller, isExiting) {
if (isExiting) {
controller.set('pageFilter', null);
}
}
}
5 changes: 3 additions & 2 deletions ui/lib/core/addon/components/form-field.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,12 @@
<Input
@type="datetime-local"
@value={{date-format (get @model this.valuePath) "yyyy-MM-dd'T'HH:mm"}}
class="input has-top-margin-xs is-auto-width is-block"
name={{@attr.name}}
id={{@attr.name}}
data-test-input={{@attr.name}}
{{on "change" this.onChangeWithEvent}}
class="input has-top-margin-xs has-bottom-margin-xs is-auto-width is-block
{{if this.validationError 'has-error-border'}}"
{{on "focusout" this.onChangeWithEvent}}
/>
{{else if (eq @attr.options.editType "searchSelect")}}
<div class="form-section">
Expand Down
1 change: 1 addition & 0 deletions ui/lib/core/addon/components/form-field.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { capitalize } from 'vault/helpers/capitalize';
import { humanize } from 'vault/helpers/humanize';
import { dasherize } from 'vault/helpers/dasherize';
import { assert } from '@ember/debug';

/**
* @module FormField
* `FormField` components are field elements associated with a particular model.
Expand Down
2 changes: 1 addition & 1 deletion ui/tests/acceptance/config-ui/messages/messages-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ module('Acceptance | config-ui', function (hooks) {
assert
.dom(PAGE.unauthCreateFormInfo)
.hasText(
'Note: Do not include sensitive info in this message since users are unauthenticated at this stage.'
'Note: Do not include sensitive information in this message since users are unauthenticated at this stage.'
);
});
test('it should display preview a message when all required fields are filled out', async function (assert) {
Expand Down
2 changes: 1 addition & 1 deletion ui/tests/helpers/config-ui/message-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const PAGE = {
field: (fieldName) => `[data-test-field="${fieldName}"]`,
input: (input) => `[data-test-input="${input}"]`,
button: (buttonName) => `[data-test-button="${buttonName}"]`,
fieldVaildation: (fieldName) => `[data-test-field-validation="${fieldName}"]`,
fieldValidation: (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}"]`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,13 @@ module('Integration | Component | messages/page/create-and-edit-message', functi
});

test('it should display all the create form fields and default radio button values', async function (assert) {
assert.expect(17);

await render(hbs`<Messages::Page::CreateAndEditMessageForm @message={{this.message}} />`, {
owner: this.engine,
});

assert.dom('[data-test-page-title]').hasText('Create message');
assert
.dom('[data-test-form-subtext]')
.hasText('Create a custom message for all users when they access a Vault system via the UI.');
assert.dom(PAGE.title).hasText('Create message');
assert.dom(PAGE.radio('authenticated')).exists();
assert.dom(PAGE.radio('unauthenticated')).exists();
assert.dom(PAGE.radio('authenticated')).isChecked();
Expand All @@ -53,6 +52,31 @@ module('Integration | Component | messages/page/create-and-edit-message', functi
assert.dom(PAGE.input('endTime')).hasValue('');
});

test('it should display validation errors for invalid form fields', async function (assert) {
assert.expect(8);
await render(hbs`<Messages::Page::CreateAndEditMessageForm @message={{this.message}} />`, {
owner: this.engine,
});

await fillIn(PAGE.input('startTime'), '2024-01-20T00:00');
await fillIn(PAGE.input('endTime'), '2024-01-01T00:00');
await click(PAGE.button('create-message'));
assert.dom(PAGE.input('title')).hasClass('has-error-border');
assert.dom(`${PAGE.fieldValidation('title')} ${PAGE.inlineErrorMessage}`).hasText('Title is required.');
assert.dom(PAGE.input('message')).hasClass('has-error-border');
assert
.dom(`${PAGE.fieldValidation('message')} ${PAGE.inlineErrorMessage}`)
.hasText('Message is required.');
assert.dom(PAGE.input('startTime')).hasClass('has-error-border');
assert
.dom(`${PAGE.fieldValidation('startTime')} ${PAGE.inlineErrorMessage}`)
.hasText('Start time is after end time.');
assert.dom(PAGE.input('endTime')).hasClass('has-error-border');
assert
.dom(`${PAGE.fieldValidation('endTime')} ${PAGE.inlineErrorMessage}`)
.hasText('End time is before start time.');
});

test('it should create new message', async function (assert) {
assert.expect(1);

Expand Down Expand Up @@ -83,19 +107,21 @@ module('Integration | Component | messages/page/create-and-edit-message', functi
});

test('it should have form vaildations', async function (assert) {
assert.expect(4);
await render(hbs`<Messages::Page::CreateAndEditMessageForm @message={{this.message}} />`, {
owner: this.engine,
});
await click(PAGE.button('create-message'));
assert.dom(PAGE.input('title')).hasClass('has-error-border', 'show error border for title field');
assert.dom(`${PAGE.fieldVaildation('title')} ${PAGE.inlineErrorMessage}`).hasText('Title is required.');
assert.dom(`${PAGE.fieldValidation('title')} ${PAGE.inlineErrorMessage}`).hasText('Title is required.');
assert.dom(PAGE.input('message')).hasClass('has-error-border', 'show error border for message field');
assert
.dom(`${PAGE.fieldVaildation('message')} ${PAGE.inlineErrorMessage}`)
.dom(`${PAGE.fieldValidation('message')} ${PAGE.inlineErrorMessage}`)
.hasText('Message is required.');
});

test('it should prepopulate form if form is in edit mode', async function (assert) {
assert.expect(13);
this.store.pushPayload('config-ui/message', {
modelName: 'config-ui/message',
id: 'hhhhh-iiii-lllll-dddd',
Expand All @@ -112,10 +138,7 @@ module('Integration | Component | messages/page/create-and-edit-message', functi
owner: this.engine,
});

assert.dom('[data-test-page-title]').hasText('Edit message');
assert
.dom('[data-test-form-subtext]')
.hasText('Edit a custom message for all users when they access a Vault system via the UI.');
assert.dom(PAGE.title).hasText('Edit message');
assert.dom(PAGE.radio('authenticated')).exists();
assert.dom(PAGE.radio('unauthenticated')).isChecked();
assert.dom(PAGE.radio('modal')).exists();
Expand All @@ -136,6 +159,7 @@ module('Integration | Component | messages/page/create-and-edit-message', functi
});

test('it should show a preview image modal when preview is clicked', async function (assert) {
assert.expect(6);
await render(hbs`<Messages::Page::CreateAndEditMessageForm @message={{this.message}} />`, {
owner: this.engine,
});
Expand All @@ -161,6 +185,7 @@ module('Integration | Component | messages/page/create-and-edit-message', functi
});

test('it should show a preview modal when preview is clicked', async function (assert) {
assert.expect(4);
await render(hbs`<Messages::Page::CreateAndEditMessageForm @message={{this.message}} />`, {
owner: this.engine,
});
Expand All @@ -175,6 +200,8 @@ module('Integration | Component | messages/page/create-and-edit-message', functi
});

test('it should show multiple modal message', async function (assert) {
assert.expect(2);

this.store.pushPayload('config-ui/message', {
modelName: 'config-ui/message',
id: '01234567-89ab-cdef-0123-456789abcdef',
Expand Down
2 changes: 2 additions & 0 deletions ui/tests/integration/components/form-field-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ module('Integration | Component | form field', function (hooks) {
"[data-test-input='bar']",
format(startOfDay(new Date('2023-12-17T03:24:00')), "yyyy-MM-dd'T'HH:mm")
);
// add a click label to focus out the date we filled in above
await click('.is-label');
assert.deepEqual(model.get('bar'), '2023-12-17T00:00', 'sets the value on the model');
});

Expand Down

0 comments on commit 4283caa

Please sign in to comment.