From 030191f8cd7e2e86e4573aed246ffe65bd022630 Mon Sep 17 00:00:00 2001 From: Kianna <30884335+kiannaquach@users.noreply.github.com> Date: Wed, 7 Feb 2024 10:43:57 -0800 Subject: [PATCH] UI: Part three bug bash custom messages (#25229) * 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 --- ui/app/models/config-ui/message.js | 26 +++++++++- ui/app/serializers/config-ui/message.js | 2 - .../messages/message-expiration-date-form.hbs | 10 ++-- .../messages/message-expiration-date-form.js | 24 +++++++++- .../page/create-and-edit-message-form.hbs | 16 +++---- .../addon/components/messages/page/list.hbs | 6 +-- .../addon/controllers/messages/create.js | 3 +- .../config-ui/addon/routes/messages/index.js | 6 +++ ui/lib/core/addon/components/form-field.hbs | 5 +- ui/lib/core/addon/components/form-field.js | 1 + .../config-ui/messages/messages-test.js | 2 +- .../helpers/config-ui/message-selectors.js | 2 +- .../page/create-and-edit-message-test.js | 47 +++++++++++++++---- .../integration/components/form-field-test.js | 2 + 14 files changed, 113 insertions(+), 39 deletions(-) diff --git a/ui/app/models/config-ui/message.js b/ui/app/models/config-ui/message.js index 56a95cf40eb7..0edabc4270b7 100644 --- a/ui/app/models/config-ui/message.js +++ b/ui/app/models/config-ui/message.js @@ -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'; @@ -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) @@ -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, diff --git a/ui/app/serializers/config-ui/message.js b/ui/app/serializers/config-ui/message.js index 2333eb247550..51479944e5fc 100644 --- a/ui/app/serializers/config-ui/message.js +++ b/ui/app/serializers/config-ui/message.js @@ -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) { diff --git a/ui/lib/config-ui/addon/components/messages/message-expiration-date-form.hbs b/ui/lib/config-ui/addon/components/messages/message-expiration-date-form.hbs index 60c81c555fcd..aabcd72e085d 100644 --- a/ui/lib/config-ui/addon/components/messages/message-expiration-date-form.hbs +++ b/ui/lib/config-ui/addon/components/messages/message-expiration-date-form.hbs @@ -30,7 +30,7 @@ id="specificDate" value="specificDate" @value="specificDate" - @onChange={{fn (mut @message.endTime) this.formDateTime}} + @onChange={{this.specificDateChange}} @groupValue={{this.groupValue}} /> diff --git a/ui/lib/config-ui/addon/components/messages/message-expiration-date-form.js b/ui/lib/config-ui/addon/components/messages/message-expiration-date-form.js index daaff91305cb..b85e7622a415 100644 --- a/ui/lib/config-ui/addon/components/messages/message-expiration-date-form.js +++ b/ui/lib/config-ui/addon/components/messages/message-expiration-date-form.js @@ -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'; @@ -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'; + } } diff --git a/ui/lib/config-ui/addon/components/messages/page/create-and-edit-message-form.hbs b/ui/lib/config-ui/addon/components/messages/page/create-and-edit-message-form.hbs index 6aeffedc4e22..4ea214ef6236 100644 --- a/ui/lib/config-ui/addon/components/messages/page/create-and-edit-message-form.hbs +++ b/ui/lib/config-ui/addon/components/messages/page/create-and-edit-message-form.hbs @@ -9,21 +9,21 @@ />
-
- - {{if @message.isNew "Create" "Edit"}} - a custom message for all users when they access a Vault system via the UI. - - +
+ {{#each @message.formFields as |attr|}} - + {{#if (and (eq attr.name "message") (not @message.authenticated))}} - Note: Do not include sensitive info in this message since users are + Note: Do not include sensitive information in this message since users are unauthenticated at this stage. {{/if}} diff --git a/ui/lib/config-ui/addon/components/messages/page/list.hbs b/ui/lib/config-ui/addon/components/messages/page/list.hbs index b500e55fff92..f105c25ab24e 100644 --- a/ui/lib/config-ui/addon/components/messages/page/list.hbs +++ b/ui/lib/config-ui/addon/components/messages/page/list.hbs @@ -50,11 +50,7 @@
- +
diff --git a/ui/lib/config-ui/addon/controllers/messages/create.js b/ui/lib/config-ui/addon/controllers/messages/create.js index 9c75000ab604..1f7513f8d04a 100644 --- a/ui/lib/config-ui/addon/controllers/messages/create.js +++ b/ui/lib/config-ui/addon/controllers/messages/create.js @@ -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; diff --git a/ui/lib/config-ui/addon/routes/messages/index.js b/ui/lib/config-ui/addon/routes/messages/index.js index 3d468818e83d..736dbd45ec8d 100644 --- a/ui/lib/config-ui/addon/routes/messages/index.js +++ b/ui/lib/config-ui/addon/routes/messages/index.js @@ -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); + } + } } diff --git a/ui/lib/core/addon/components/form-field.hbs b/ui/lib/core/addon/components/form-field.hbs index 6ee6f15efcce..3dfb3dd23c7b 100644 --- a/ui/lib/core/addon/components/form-field.hbs +++ b/ui/lib/core/addon/components/form-field.hbs @@ -94,11 +94,12 @@ {{else if (eq @attr.options.editType "searchSelect")}}
diff --git a/ui/lib/core/addon/components/form-field.js b/ui/lib/core/addon/components/form-field.js index 5c2991190f21..0349d10ba265 100644 --- a/ui/lib/core/addon/components/form-field.js +++ b/ui/lib/core/addon/components/form-field.js @@ -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. diff --git a/ui/tests/acceptance/config-ui/messages/messages-test.js b/ui/tests/acceptance/config-ui/messages/messages-test.js index 463fea2093f3..88fac6847c8d 100644 --- a/ui/tests/acceptance/config-ui/messages/messages-test.js +++ b/ui/tests/acceptance/config-ui/messages/messages-test.js @@ -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) { diff --git a/ui/tests/helpers/config-ui/message-selectors.js b/ui/tests/helpers/config-ui/message-selectors.js index e0c047196893..4f681678c60b 100644 --- a/ui/tests/helpers/config-ui/message-selectors.js +++ b/ui/tests/helpers/config-ui/message-selectors.js @@ -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}"]`, diff --git a/ui/tests/integration/components/config-ui/messages/page/create-and-edit-message-test.js b/ui/tests/integration/components/config-ui/messages/page/create-and-edit-message-test.js index a746c67d1eb8..3c771a292111 100644 --- a/ui/tests/integration/components/config-ui/messages/page/create-and-edit-message-test.js +++ b/ui/tests/integration/components/config-ui/messages/page/create-and-edit-message-test.js @@ -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``, { 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(); @@ -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``, { + 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); @@ -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``, { 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', @@ -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(); @@ -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``, { owner: this.engine, }); @@ -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``, { owner: this.engine, }); @@ -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', diff --git a/ui/tests/integration/components/form-field-test.js b/ui/tests/integration/components/form-field-test.js index b91157b77abb..05e6e8067aad 100644 --- a/ui/tests/integration/components/form-field-test.js +++ b/ui/tests/integration/components/form-field-test.js @@ -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'); });