mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 17:52:32 +00:00
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
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
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">
|
||||
@@ -38,16 +38,16 @@
|
||||
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>
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}"]`,
|
||||
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
@@ -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`<Messages::Page::CreateAndEditMessageForm @message={{this.message}} />`, {
|
||||
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`<Messages::Page::CreateAndEditMessageForm @message={{this.message}} />`, {
|
||||
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',
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user