mirror of
https://github.com/hashicorp/vault.git
synced 2025-11-17 16:51:45 +01:00
[UI] Ember Data Migration - Config UI Engine (#30238)
* WIP updating config-ui engine to use api service and form class * updates form-field component to support false values for radio types * updates api-error-message util to log out error in dev env * fixes issues in custom messages create and edit workflows * fixes issues in api service * updates capabilities handling * updates to custom messages form * removes store from custom messages tests * removes store as dependency from config-ui engine * removes commented out code in messages route * updates orderedKeys to displayFields in messages page component * removes unneccesary method var from message create-and-edit component * removes comment about model in message details page
This commit is contained in:
parent
294c304947
commit
e18b0485f5
@ -20,10 +20,11 @@ export default class App extends Application {
|
|||||||
'flash-messages',
|
'flash-messages',
|
||||||
'namespace',
|
'namespace',
|
||||||
{ 'app-router': 'router' },
|
{ 'app-router': 'router' },
|
||||||
'store',
|
|
||||||
'pagination',
|
'pagination',
|
||||||
'version',
|
'version',
|
||||||
'custom-messages',
|
'custom-messages',
|
||||||
|
'api',
|
||||||
|
'capabilities',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
125
ui/app/forms/custom-message.ts
Normal file
125
ui/app/forms/custom-message.ts
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) HashiCorp, Inc.
|
||||||
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
|
*/
|
||||||
|
import Form from './form';
|
||||||
|
import FormField from 'vault/utils/forms/field';
|
||||||
|
import { isBefore, isAfter } from 'date-fns';
|
||||||
|
import { encodeString } from 'core/utils/b64';
|
||||||
|
|
||||||
|
import type { CreateCustomMessageRequest } from '@hashicorp/vault-client-typescript';
|
||||||
|
import { Validations } from 'vault/vault/app-types';
|
||||||
|
|
||||||
|
type CustomMessageFormData = Partial<CreateCustomMessageRequest>;
|
||||||
|
|
||||||
|
export default class CustomMessageForm extends Form {
|
||||||
|
declare data: CustomMessageFormData;
|
||||||
|
|
||||||
|
formFields = [
|
||||||
|
new FormField('authenticated', undefined, {
|
||||||
|
label: 'Where should we display this message?',
|
||||||
|
editType: 'radio',
|
||||||
|
possibleValues: [
|
||||||
|
{
|
||||||
|
label: 'After the user logs in',
|
||||||
|
subText: 'Display to users after they have successfully logged in to Vault.',
|
||||||
|
value: true,
|
||||||
|
id: 'authenticated',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'On the login page',
|
||||||
|
subText: 'Display to users on the login page before they have authenticated.',
|
||||||
|
value: false,
|
||||||
|
id: 'unauthenticated',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
|
||||||
|
new FormField('type', 'string', {
|
||||||
|
label: 'Type',
|
||||||
|
editType: 'radio',
|
||||||
|
possibleValues: [
|
||||||
|
{
|
||||||
|
label: 'Alert message',
|
||||||
|
subText:
|
||||||
|
'A banner that appears on the top of every page to display brief but high-signal messages like an update or system alert.',
|
||||||
|
value: 'banner',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Modal',
|
||||||
|
subText:
|
||||||
|
'A pop-up window used to bring immediate attention for important notifications or actions.',
|
||||||
|
value: 'modal',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
|
||||||
|
new FormField('title', 'string'),
|
||||||
|
|
||||||
|
new FormField('message', 'string', { editType: 'textarea' }),
|
||||||
|
|
||||||
|
new FormField('link', 'string', {
|
||||||
|
editType: 'kv',
|
||||||
|
keyPlaceholder: 'Display text (e.g. Learn more)',
|
||||||
|
valuePlaceholder: 'Link URL (e.g. https://www.hashicorp.com/)',
|
||||||
|
label: 'Link (optional)',
|
||||||
|
isSingleRow: true,
|
||||||
|
allowWhiteSpace: true,
|
||||||
|
}),
|
||||||
|
|
||||||
|
new FormField('startTime', 'dateTimeLocal', {
|
||||||
|
editType: 'dateTimeLocal',
|
||||||
|
label: 'Message starts',
|
||||||
|
subText: 'Defaults to 12:00 a.m. the following day (local timezone).',
|
||||||
|
}),
|
||||||
|
|
||||||
|
new FormField('endTime', 'dateTimeLocal', {
|
||||||
|
editType: 'yield',
|
||||||
|
label: 'Message expires',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
validations: Validations = {
|
||||||
|
title: [{ type: 'presence', message: 'Title is required.' }],
|
||||||
|
message: [{ type: 'presence', message: 'Message is required.' }],
|
||||||
|
link: [
|
||||||
|
{
|
||||||
|
validator({ link }: CustomMessageFormData) {
|
||||||
|
if (!link) return true;
|
||||||
|
const [title] = Object.keys(link);
|
||||||
|
const [href] = Object.values(link);
|
||||||
|
return title || href ? !!(title && href) : true;
|
||||||
|
},
|
||||||
|
message: 'Link title and url are required.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
startTime: [
|
||||||
|
{
|
||||||
|
validator({ startTime, endTime }: CustomMessageFormData) {
|
||||||
|
if (!startTime || !endTime) return true;
|
||||||
|
return isBefore(new Date(startTime), new Date(endTime));
|
||||||
|
},
|
||||||
|
message: 'Start time is after end time.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
endTime: [
|
||||||
|
{
|
||||||
|
validator({ startTime, endTime }: CustomMessageFormData) {
|
||||||
|
if (!startTime || !endTime) return true;
|
||||||
|
return isAfter(new Date(endTime), new Date(startTime));
|
||||||
|
},
|
||||||
|
message: 'End time is before start time.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
// overriding to do some date serialization
|
||||||
|
// form sets dates as strings but client expects Date objects
|
||||||
|
const startTime = this.data.startTime ? new Date(this.data.startTime as unknown as string) : undefined;
|
||||||
|
const endTime = this.data.endTime ? new Date(this.data.endTime as unknown as string) : undefined;
|
||||||
|
// encode message to base64
|
||||||
|
const message = this.data.message ? encodeString(this.data.message) : undefined;
|
||||||
|
return super.toJSON({ ...this.data, startTime, endTime, message });
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,8 +12,12 @@ import {
|
|||||||
IdentityApi,
|
IdentityApi,
|
||||||
SecretsApi,
|
SecretsApi,
|
||||||
SystemApi,
|
SystemApi,
|
||||||
|
HTTPQuery,
|
||||||
|
HTTPRequestInit,
|
||||||
|
RequestOpts,
|
||||||
} from '@hashicorp/vault-client-typescript';
|
} from '@hashicorp/vault-client-typescript';
|
||||||
import config from '../config/environment';
|
import config from '../config/environment';
|
||||||
|
import { waitForPromise } from '@ember/test-waiters';
|
||||||
|
|
||||||
import type AuthService from 'vault/services/auth';
|
import type AuthService from 'vault/services/auth';
|
||||||
import type NamespaceService from 'vault/services/namespace';
|
import type NamespaceService from 'vault/services/namespace';
|
||||||
@ -80,13 +84,16 @@ export default class ApiService extends Service {
|
|||||||
// -- Post Request Middleware --
|
// -- Post Request Middleware --
|
||||||
showWarnings = async (context: ResponseContext) => {
|
showWarnings = async (context: ResponseContext) => {
|
||||||
const response = context.response.clone();
|
const response = context.response.clone();
|
||||||
const json = await response?.json();
|
// if the response is empty, don't try to parse it
|
||||||
|
if (response.headers.get('Content-Length')) {
|
||||||
|
const json = await response.json();
|
||||||
|
|
||||||
if (json?.warnings) {
|
if (json?.warnings) {
|
||||||
json.warnings.forEach((message: string) => {
|
json.warnings.forEach((message: string) => {
|
||||||
this.flashMessages.info(message);
|
this.flashMessages.info(message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteControlGroupToken = async (context: ResponseContext) => {
|
deleteControlGroupToken = async (context: ResponseContext) => {
|
||||||
@ -129,6 +136,9 @@ export default class ApiService extends Service {
|
|||||||
{ post: this.deleteControlGroupToken },
|
{ post: this.deleteControlGroupToken },
|
||||||
{ post: this.formatErrorResponse },
|
{ post: this.formatErrorResponse },
|
||||||
],
|
],
|
||||||
|
fetchApi: (...args: [Request]) => {
|
||||||
|
return waitForPromise(window.fetch(...args));
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
auth = new AuthApi(this.configuration);
|
auth = new AuthApi(this.configuration);
|
||||||
@ -154,4 +164,12 @@ export default class ApiService extends Service {
|
|||||||
|
|
||||||
return { headers };
|
return { headers };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// convenience method for updating the query params object on the request context
|
||||||
|
// eg. this.api.sys.uiConfigListCustomMessages(true, ({ context: { query } }) => { query.authenticated = true });
|
||||||
|
// -> this.api.sys.uiConfigListCustomMessages(true, (context) => this.api.addQueryParams(context, { authenticated: true }));
|
||||||
|
addQueryParams(requestContext: { init: HTTPRequestInit; context: RequestOpts }, params: HTTPQuery = {}) {
|
||||||
|
const { context } = requestContext;
|
||||||
|
context.query = { ...context.query, ...params };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,11 +10,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ErrorContext, ApiError } from 'vault/api';
|
import { ErrorContext, ApiError } from 'vault/api';
|
||||||
|
import ENV from 'vault/config/environment';
|
||||||
|
|
||||||
// accepts an error and returns error.errors joined with a comma, error.message or a fallback message
|
// accepts an error and returns error.errors joined with a comma, error.message or a fallback message
|
||||||
export default async function (error: unknown, fallbackMessage = 'An error occurred, please try again') {
|
export default async function (error: unknown, fallbackMessage = 'An error occurred, please try again') {
|
||||||
const messageOrFallback = (message?: string) => message || fallbackMessage;
|
const messageOrFallback = (message?: string) => message || fallbackMessage;
|
||||||
|
|
||||||
|
// log out the error for ease of debugging in dev env
|
||||||
|
if (ENV.environment === 'development') {
|
||||||
|
console.error('API Error:', error); // eslint-disable-line no-console
|
||||||
|
}
|
||||||
|
|
||||||
if ((error as ErrorContext).response instanceof Response) {
|
if ((error as ErrorContext).response instanceof Response) {
|
||||||
const apiError: ApiError = await (error as ErrorContext).response?.json();
|
const apiError: ApiError = await (error as ErrorContext).response?.json();
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { datetimeLocalStringFormat } from 'core/utils/date-formatters';
|
|||||||
* ```js
|
* ```js
|
||||||
* <Messages::MessageExpirationDateForm @message={{this.message}} @attr={{attr}} />
|
* <Messages::MessageExpirationDateForm @message={{this.message}} @attr={{attr}} />
|
||||||
* ```
|
* ```
|
||||||
* @param {array} messages - array message objects
|
* @param {array} message - message form data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default class MessageExpirationDateForm extends Component {
|
export default class MessageExpirationDateForm extends Component {
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
import Component from '@glimmer/component';
|
import Component from '@glimmer/component';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import { task, timeout } from 'ember-concurrency';
|
import { task, timeout } from 'ember-concurrency';
|
||||||
import errorMessage from 'vault/utils/error-message';
|
import apiErrorMessage from 'vault/utils/api-error-message';
|
||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
@ -20,16 +20,18 @@ import timestamp from 'core/utils/timestamp';
|
|||||||
* ```js
|
* ```js
|
||||||
* <Page::CreateAndEditMessageForm @message={{this.message}} />
|
* <Page::CreateAndEditMessageForm @message={{this.message}} />
|
||||||
* ```
|
* ```
|
||||||
* @param {model} message - message model to pass to form components
|
* @param message - message to pass to form component
|
||||||
|
* @param messages - array of all created messages
|
||||||
|
* @param breadcrumbs - breadcrumbs to pass to the TabPageHeader component
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default class MessagesList extends Component {
|
export default class MessagesList extends Component {
|
||||||
@service('app-router') router;
|
@service('app-router') router;
|
||||||
@service store;
|
|
||||||
@service pagination;
|
@service pagination;
|
||||||
@service flashMessages;
|
@service flashMessages;
|
||||||
@service customMessages;
|
@service customMessages;
|
||||||
@service namespace;
|
@service namespace;
|
||||||
|
@service api;
|
||||||
|
|
||||||
@tracked errorBanner = '';
|
@tracked errorBanner = '';
|
||||||
@tracked modelValidations;
|
@tracked modelValidations;
|
||||||
@ -38,17 +40,21 @@ export default class MessagesList extends Component {
|
|||||||
@tracked showMultipleModalsMessage = false;
|
@tracked showMultipleModalsMessage = false;
|
||||||
@tracked userConfirmation = '';
|
@tracked userConfirmation = '';
|
||||||
|
|
||||||
willDestroy() {
|
get hasSomeActiveModals() {
|
||||||
const noTeardown = this.store && !this.store.isDestroying;
|
const { messages } = this.args;
|
||||||
const { model } = this;
|
return messages?.some((message) => message.type === 'modal' && message.active);
|
||||||
if (noTeardown && model && model.isDirty && !model.isDestroyed && !model.isDestroying) {
|
|
||||||
model.rollbackAttributes();
|
|
||||||
}
|
}
|
||||||
super.willDestroy();
|
|
||||||
|
get hasExpiredModalMessages() {
|
||||||
|
const modalMessages = this.args.messages?.filter((message) => message.type === 'modal') || [];
|
||||||
|
return modalMessages.every((message) => {
|
||||||
|
if (!message.endTime) return false;
|
||||||
|
return isAfter(timestamp.now(), new Date(message.endTime));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
validate() {
|
validate() {
|
||||||
const { isValid, state, invalidFormMessage } = this.args.message.validate();
|
const { isValid, state, invalidFormMessage } = this.args.message.toJSON();
|
||||||
this.modelValidations = isValid ? null : state;
|
this.modelValidations = isValid ? null : state;
|
||||||
this.invalidFormAlert = invalidFormMessage;
|
this.invalidFormAlert = invalidFormMessage;
|
||||||
return isValid;
|
return isValid;
|
||||||
@ -60,29 +66,32 @@ export default class MessagesList extends Component {
|
|||||||
try {
|
try {
|
||||||
this.userConfirmation = '';
|
this.userConfirmation = '';
|
||||||
|
|
||||||
|
const { message } = this.args;
|
||||||
const isValid = this.validate();
|
const isValid = this.validate();
|
||||||
const modalMessages = this.args.messages?.filter((message) => message.type === 'modal') || [];
|
|
||||||
const hasExpiredModalMessages = modalMessages.every((message) => {
|
|
||||||
if (!message.endTime) return false;
|
|
||||||
return isAfter(timestamp.now(), new Date(message.endTime));
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!hasExpiredModalMessages && this.args.hasSomeActiveModals && this.args.message.type === 'modal') {
|
if (!this.hasExpiredModalMessages && this.hasSomeActiveModals && message.type === 'modal') {
|
||||||
this.showMultipleModalsMessage = true;
|
this.showMultipleModalsMessage = true;
|
||||||
const isConfirmed = yield this.getUserConfirmation.perform();
|
const isConfirmed = yield this.getUserConfirmation.perform();
|
||||||
if (!isConfirmed) return;
|
if (!isConfirmed) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
const { isNew } = this.args.message;
|
const { data } = message.toJSON();
|
||||||
const { id, title } = yield this.args.message.save();
|
let id = data.id;
|
||||||
this.flashMessages.success(`Successfully ${isNew ? 'created' : 'updated'} ${title} message.`);
|
|
||||||
this.pagination.clearDataset('config-ui/message');
|
if (message.isNew) {
|
||||||
|
const response = yield this.api.sys.createCustomMessage(data);
|
||||||
|
id = response.data.id;
|
||||||
|
} else {
|
||||||
|
yield this.api.sys.uiConfigUpdateCustomMessage(id, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.flashMessages.success(`Successfully saved ${data.title} message.`);
|
||||||
this.customMessages.fetchMessages(this.namespace.path);
|
this.customMessages.fetchMessages(this.namespace.path);
|
||||||
this.router.transitionTo('vault.cluster.config-ui.messages.message.details', id);
|
this.router.transitionTo('vault.cluster.config-ui.messages.message.details', id);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.errorBanner = errorMessage(error);
|
this.errorBanner = yield apiErrorMessage(error);
|
||||||
this.invalidFormAlert = 'There was an error submitting this form.';
|
this.invalidFormAlert = 'There was an error submitting this form.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<ToolbarActions aria-label="message delete and edit">
|
<ToolbarActions aria-label="message delete and edit">
|
||||||
{{#if @message.canDeleteCustomMessages}}
|
{{#if @capabilities.canDelete}}
|
||||||
<ConfirmAction
|
<ConfirmAction
|
||||||
class="toolbar-button"
|
class="toolbar-button"
|
||||||
@buttonColor="secondary"
|
@buttonColor="secondary"
|
||||||
@ -23,7 +23,7 @@
|
|||||||
/>
|
/>
|
||||||
<div class="toolbar-separator"></div>
|
<div class="toolbar-separator"></div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if @message.canEditCustomMessages}}
|
{{#if @capabilities.canUpdate}}
|
||||||
<LinkTo class="toolbar-link" @route="messages.message.edit" @model={{@message.id}} data-test-link="edit">
|
<LinkTo class="toolbar-link" @route="messages.message.edit" @model={{@message.id}} data-test-link="edit">
|
||||||
Edit message
|
Edit message
|
||||||
<Icon @name="chevron-right" />
|
<Icon @name="chevron-right" />
|
||||||
@ -32,18 +32,18 @@
|
|||||||
</ToolbarActions>
|
</ToolbarActions>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
|
|
||||||
{{#each @message.allFields as |attr|}}
|
{{#each this.displayFields as |field|}}
|
||||||
{{#if (or (eq attr.name "endTime") (eq attr.name "startTime"))}}
|
{{#if (or (eq field "endTime") (eq field "startTime"))}}
|
||||||
{{! if the attr is an endTime and is falsy, we want to show a 'Never' text value }}
|
{{! if the attr is an endTime and is falsy, we want to show a 'Never' text value }}
|
||||||
<InfoTableRow
|
<InfoTableRow
|
||||||
@label={{capitalize (humanize (dasherize attr.name))}}
|
@label={{capitalize (humanize (dasherize field))}}
|
||||||
@value={{if
|
@value={{if
|
||||||
(and (eq attr.name "endTime") (not (get @message attr.name)))
|
(and (eq field "endTime") (not (get @message field)))
|
||||||
"Never"
|
"Never"
|
||||||
(date-format (get @message attr.name) "MMM d, yyyy hh:mm aaa" withTimeZone=true)
|
(date-format (get @message field) "MMM d, yyyy hh:mm aaa" withTimeZone=true)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{{else if (eq attr.name "link")}}
|
{{else if (eq field "link")}}
|
||||||
{{#if (is-empty-value @message.link)}}
|
{{#if (is-empty-value @message.link)}}
|
||||||
<InfoTableRow @label="Link" @value="None" />
|
<InfoTableRow @label="Link" @value="None" />
|
||||||
{{else}}
|
{{else}}
|
||||||
@ -54,7 +54,7 @@
|
|||||||
{{/each-in}}
|
{{/each-in}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<InfoTableRow @label={{capitalize (humanize (dasherize attr.name))}} @value={{get @message attr.name}} />
|
<InfoTableRow @label={{capitalize (humanize (dasherize field))}} @value={{get @message field}} />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
@ -6,16 +6,17 @@
|
|||||||
import Component from '@glimmer/component';
|
import Component from '@glimmer/component';
|
||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import errorMessage from 'vault/utils/error-message';
|
import apiErrorMessage from 'vault/utils/api-error-message';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @module Page::MessageDetails
|
* @module Page::MessageDetails
|
||||||
* Page::MessageDetails components are used to display a message
|
* Page::MessageDetails components are used to display a message
|
||||||
* @example
|
* @example
|
||||||
* ```js
|
* ```js
|
||||||
* <Page::MessageDetails @message={{this.message}} />
|
* <Page::MessageDetails @message={{this.model.message}} @capabilities={{this.model.capabilities}} />
|
||||||
* ```
|
* ```
|
||||||
* @param {model} message - message model
|
* @param message
|
||||||
|
* @param capabilities - capabilities for the message
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default class MessageDetails extends Component {
|
export default class MessageDetails extends Component {
|
||||||
@ -24,18 +25,21 @@ export default class MessageDetails extends Component {
|
|||||||
@service customMessages;
|
@service customMessages;
|
||||||
@service namespace;
|
@service namespace;
|
||||||
@service pagination;
|
@service pagination;
|
||||||
|
@service api;
|
||||||
|
|
||||||
|
displayFields = ['active', 'type', 'authenticated', 'title', 'message', 'startTime', 'endTime', 'link'];
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async deleteMessage() {
|
async deleteMessage() {
|
||||||
try {
|
try {
|
||||||
await this.args.message.destroyRecord(this.args.message.id);
|
const { message } = this.args;
|
||||||
this.pagination.clearDataset('config-ui/message');
|
await this.api.sys.uiConfigDeleteCustomMessage(message.id);
|
||||||
this.router.transitionTo('vault.cluster.config-ui.messages');
|
this.router.transitionTo('vault.cluster.config-ui.messages');
|
||||||
this.customMessages.fetchMessages(this.namespace.path);
|
this.customMessages.fetchMessages(this.namespace.path);
|
||||||
this.flashMessages.success(`Successfully deleted ${this.args.message.title}.`);
|
this.flashMessages.success(`Successfully deleted ${message.title}.`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const message = errorMessage(e);
|
const errorMessage = await apiErrorMessage(e);
|
||||||
this.flashMessages.danger(message);
|
this.flashMessages.danger(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,7 +104,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="level-right is-flex is-paddingless is-marginless">
|
<div class="level-right is-flex is-paddingless is-marginless">
|
||||||
<div class="level-item">
|
<div class="level-item">
|
||||||
{{#if (or message.canEditCustomMessages message.canDeleteCustomMessages)}}
|
{{#if (has-capability @capabilities "update" "delete" id=message.id)}}
|
||||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||||
<dd.ToggleIcon
|
<dd.ToggleIcon
|
||||||
@icon="more-horizontal"
|
@icon="more-horizontal"
|
||||||
@ -112,10 +112,10 @@
|
|||||||
@hasChevron={{false}}
|
@hasChevron={{false}}
|
||||||
data-test-popup-menu-trigger
|
data-test-popup-menu-trigger
|
||||||
/>
|
/>
|
||||||
{{#if message.canEditCustomMessages}}
|
{{#if (has-capability @capabilities "update" id=message.id)}}
|
||||||
<dd.Interactive @route="messages.message.edit" @model={{message.id}}>Edit</dd.Interactive>
|
<dd.Interactive @route="messages.message.edit" @model={{message.id}}>Edit</dd.Interactive>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if message.canDeleteCustomMessages}}
|
{{#if (has-capability @capabilities "delete" id=message.id)}}
|
||||||
<dd.Interactive
|
<dd.Interactive
|
||||||
@color="critical"
|
@color="critical"
|
||||||
{{on "click" (fn (mut this.messageToDelete) message)}}
|
{{on "click" (fn (mut this.messageToDelete) message)}}
|
||||||
|
|||||||
@ -10,7 +10,9 @@ import { task, timeout } from 'ember-concurrency';
|
|||||||
import { dateFormat } from 'core/helpers/date-format';
|
import { dateFormat } from 'core/helpers/date-format';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import errorMessage from 'vault/utils/error-message';
|
import apiErrorMessage from 'vault/utils/api-error-message';
|
||||||
|
import { isAfter } from 'date-fns';
|
||||||
|
import timestamp from 'core/utils/timestamp';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @module Page::MessagesList
|
* @module Page::MessagesList
|
||||||
@ -28,10 +30,15 @@ export default class MessagesList extends Component {
|
|||||||
@service namespace;
|
@service namespace;
|
||||||
@service pagination;
|
@service pagination;
|
||||||
@service('app-router') router;
|
@service('app-router') router;
|
||||||
|
@service api;
|
||||||
|
|
||||||
@tracked showMaxMessageModal = false;
|
@tracked showMaxMessageModal = false;
|
||||||
@tracked messageToDelete = null;
|
@tracked messageToDelete = null;
|
||||||
|
|
||||||
|
isStartTimeAfterToday = (message) => {
|
||||||
|
return isAfter(message.startTime, timestamp.now());
|
||||||
|
};
|
||||||
|
|
||||||
get formattedMessages() {
|
get formattedMessages() {
|
||||||
return this.args.messages.map((message) => {
|
return this.args.messages.map((message) => {
|
||||||
let badgeDisplayText = '';
|
let badgeDisplayText = '';
|
||||||
@ -47,7 +54,7 @@ export default class MessagesList extends Component {
|
|||||||
}
|
}
|
||||||
badgeColor = 'success';
|
badgeColor = 'success';
|
||||||
} else {
|
} else {
|
||||||
if (message.isStartTimeAfterToday) {
|
if (this.isStartTimeAfterToday(message)) {
|
||||||
badgeDisplayText = `Scheduled: ${dateFormat([message.startTime, 'MMM d, yyyy hh:mm aaa'], {
|
badgeDisplayText = `Scheduled: ${dateFormat([message.startTime, 'MMM d, yyyy hh:mm aaa'], {
|
||||||
withTimeZone: true,
|
withTimeZone: true,
|
||||||
})}`;
|
})}`;
|
||||||
@ -90,14 +97,13 @@ export default class MessagesList extends Component {
|
|||||||
@task
|
@task
|
||||||
*deleteMessage(message) {
|
*deleteMessage(message) {
|
||||||
try {
|
try {
|
||||||
yield message.destroyRecord(message.id);
|
yield this.api.sys.uiConfigDeleteCustomMessage(message.id);
|
||||||
this.pagination.clearDataset('config-ui/message');
|
|
||||||
this.router.transitionTo('vault.cluster.config-ui.messages');
|
this.router.transitionTo('vault.cluster.config-ui.messages');
|
||||||
this.customMessages.fetchMessages(this.namespace.path);
|
this.customMessages.fetchMessages(this.namespace.path);
|
||||||
this.flashMessages.success(`Successfully deleted ${message.title}.`);
|
this.flashMessages.success(`Successfully deleted ${message.title}.`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const message = errorMessage(e);
|
const errorMessage = yield apiErrorMessage(e);
|
||||||
this.flashMessages.danger(message);
|
this.flashMessages.danger(errorMessage);
|
||||||
} finally {
|
} finally {
|
||||||
this.messageToDelete = null;
|
this.messageToDelete = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,13 +18,14 @@ export default class ConfigUiEngine extends Engine {
|
|||||||
dependencies = {
|
dependencies = {
|
||||||
services: [
|
services: [
|
||||||
'auth',
|
'auth',
|
||||||
'store',
|
|
||||||
'pagination',
|
'pagination',
|
||||||
'flash-messages',
|
'flash-messages',
|
||||||
'namespace',
|
'namespace',
|
||||||
'app-router',
|
'app-router',
|
||||||
'version',
|
'version',
|
||||||
'custom-messages',
|
'custom-messages',
|
||||||
|
'api',
|
||||||
|
'capabilities',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,9 +5,12 @@
|
|||||||
|
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
import CustomMessage from 'vault/forms/custom-message';
|
||||||
|
import { addDays, startOfDay } from 'date-fns';
|
||||||
|
import timestamp from 'core/utils/timestamp';
|
||||||
|
|
||||||
export default class MessagesCreateRoute extends Route {
|
export default class MessagesCreateRoute extends Route {
|
||||||
@service store;
|
@service api;
|
||||||
|
|
||||||
queryParams = {
|
queryParams = {
|
||||||
authenticated: {
|
authenticated: {
|
||||||
@ -17,9 +20,8 @@ export default class MessagesCreateRoute extends Route {
|
|||||||
|
|
||||||
async getMessages(authenticated) {
|
async getMessages(authenticated) {
|
||||||
try {
|
try {
|
||||||
return await this.store.query('config-ui/message', {
|
const { keyInfo } = await this.api.sys.uiConfigListCustomMessages(true, undefined, authenticated);
|
||||||
authenticated,
|
return Object.values(keyInfo);
|
||||||
});
|
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -27,17 +29,21 @@ export default class MessagesCreateRoute extends Route {
|
|||||||
|
|
||||||
async model(params) {
|
async model(params) {
|
||||||
const { authenticated } = params;
|
const { authenticated } = params;
|
||||||
const message = this.store.createRecord('config-ui/message', {
|
const message = new CustomMessage(
|
||||||
|
{
|
||||||
authenticated,
|
authenticated,
|
||||||
});
|
type: 'banner',
|
||||||
|
startTime: addDays(startOfDay(timestamp.now()), 1).toISOString(),
|
||||||
|
},
|
||||||
|
{ isNew: true }
|
||||||
|
);
|
||||||
|
|
||||||
const messages = await this.getMessages(authenticated);
|
const messages = await this.getMessages(authenticated);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message,
|
message,
|
||||||
messages,
|
messages,
|
||||||
authenticated,
|
authenticated,
|
||||||
hasSomeActiveModals:
|
|
||||||
messages.length && messages?.some((message) => message.type === 'modal' && message.active),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,10 +5,12 @@
|
|||||||
|
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { hash } from 'rsvp';
|
import { paginate } from 'core/utils/paginate-list';
|
||||||
|
import { PATH_MAP } from 'core/utils/capabilities';
|
||||||
|
|
||||||
export default class MessagesRoute extends Route {
|
export default class MessagesRoute extends Route {
|
||||||
@service pagination;
|
@service api;
|
||||||
|
@service capabilities;
|
||||||
|
|
||||||
queryParams = {
|
queryParams = {
|
||||||
page: {
|
page: {
|
||||||
@ -28,36 +30,49 @@ export default class MessagesRoute extends Route {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
model(params) {
|
async model(params) {
|
||||||
const { authenticated, page, pageFilter, status, type } = params;
|
const { authenticated, page, pageFilter, status, type } = params;
|
||||||
const filter = pageFilter
|
const active = {
|
||||||
? (dataset) => dataset.filter((item) => item?.title.toLowerCase().includes(pageFilter.toLowerCase()))
|
active: true,
|
||||||
: null;
|
inactive: false,
|
||||||
let active;
|
}[status];
|
||||||
|
|
||||||
if (status === 'active') active = true;
|
try {
|
||||||
if (status === 'inactive') active = false;
|
const { keyInfo, keys } = await this.api.sys.uiConfigListCustomMessages(
|
||||||
|
true,
|
||||||
const messages = this.pagination
|
|
||||||
.lazyPaginatedQuery('config-ui/message', {
|
|
||||||
authenticated,
|
|
||||||
pageFilter: filter,
|
|
||||||
active,
|
active,
|
||||||
type,
|
authenticated,
|
||||||
responsePath: 'data.keys',
|
type
|
||||||
page: page || 1,
|
);
|
||||||
size: 10,
|
// ids are in the keys array and can be mapped to the object in keyInfo
|
||||||
})
|
// map and set id property on keyInfo object
|
||||||
.catch((e) => {
|
const data = keys.map((id) => {
|
||||||
if (e.httpStatus === 404) {
|
const { startTime, endTime, ...message } = keyInfo[id];
|
||||||
return [];
|
// dates returned from list endpoint are strings -- convert to date
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
...message,
|
||||||
|
startTime: startTime ? new Date(startTime) : startTime,
|
||||||
|
endTime: endTime ? new Date(endTime) : endTime,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const messages = paginate(data, {
|
||||||
|
page,
|
||||||
|
pageSize: 2,
|
||||||
|
filter: pageFilter,
|
||||||
|
filterKey: 'title',
|
||||||
|
});
|
||||||
|
// fetch capabilities for each message path
|
||||||
|
const paths = messages.map((message) => `${PATH_MAP.customMessages}/${message.id}`);
|
||||||
|
const capabilities = await this.capabilities.fetch(paths);
|
||||||
|
|
||||||
|
return { params, messages, capabilities };
|
||||||
|
} catch (e) {
|
||||||
|
if (e.response?.status === 404) {
|
||||||
|
return { params, messages: [] };
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
});
|
}
|
||||||
return hash({
|
|
||||||
params,
|
|
||||||
messages,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupController(controller, resolvedModel) {
|
setupController(controller, resolvedModel) {
|
||||||
|
|||||||
@ -5,22 +5,36 @@
|
|||||||
|
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
|
import { decodeString } from 'core/utils/b64';
|
||||||
|
import { PATH_MAP } from 'core/utils/capabilities';
|
||||||
|
|
||||||
export default class MessagesMessageDetailsRoute extends Route {
|
export default class MessagesMessageDetailsRoute extends Route {
|
||||||
@service store;
|
@service api;
|
||||||
|
@service capabilities;
|
||||||
|
|
||||||
model() {
|
async model() {
|
||||||
const { id } = this.paramsFor('messages.message');
|
const { id } = this.paramsFor('messages.message');
|
||||||
|
|
||||||
return this.store.queryRecord('config-ui/message', id);
|
const requests = [
|
||||||
|
this.api.sys.uiConfigReadCustomMessage(id),
|
||||||
|
this.capabilities.fetchPathCapabilities(`${PATH_MAP.customMessages}/${id}`),
|
||||||
|
];
|
||||||
|
const [customMessage, capabilities] = await Promise.all(requests);
|
||||||
|
customMessage.message = decodeString(customMessage.message);
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: customMessage,
|
||||||
|
capabilities,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setupController(controller, resolvedModel) {
|
setupController(controller, resolvedModel) {
|
||||||
super.setupController(controller, resolvedModel);
|
super.setupController(controller, resolvedModel);
|
||||||
|
const { message } = resolvedModel;
|
||||||
|
|
||||||
controller.breadcrumbs = [
|
controller.breadcrumbs = [
|
||||||
{ label: 'Messages', route: 'messages', query: { authenticated: resolvedModel.authenticated } },
|
{ label: 'Messages', route: 'messages', query: { authenticated: message.authenticated } },
|
||||||
{ label: resolvedModel.title },
|
{ label: message.title },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,25 +5,24 @@
|
|||||||
|
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { hash } from 'rsvp';
|
import CustomMessage from 'vault/forms/custom-message';
|
||||||
|
import { decodeString } from 'core/utils/b64';
|
||||||
|
|
||||||
export default class MessagesMessageEditRoute extends Route {
|
export default class MessagesMessageEditRoute extends Route {
|
||||||
@service store;
|
@service api;
|
||||||
|
|
||||||
getMessages(authenticated = true) {
|
|
||||||
return this.store.query('config-ui/message', { authenticated }).catch(() => []);
|
|
||||||
}
|
|
||||||
|
|
||||||
async model() {
|
async model() {
|
||||||
const { id } = this.paramsFor('messages.message');
|
const { id } = this.paramsFor('messages.message');
|
||||||
const message = await this.store.queryRecord('config-ui/message', id);
|
const data = await this.api.sys.uiConfigReadCustomMessage(id);
|
||||||
const messages = await this.getMessages(message.authenticated);
|
const { keyInfo, keys } = await this.api.sys.uiConfigListCustomMessages(
|
||||||
return hash({
|
true,
|
||||||
message,
|
undefined,
|
||||||
messages,
|
data.authenticated
|
||||||
hasSomeActiveModals:
|
);
|
||||||
messages.length && messages?.some((message) => message.type === 'modal' && message.active),
|
return {
|
||||||
});
|
message: new CustomMessage({ ...data, message: decodeString(data.message) }),
|
||||||
|
messages: keys.map((id) => ({ ...keyInfo[id], id })),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setupController(controller, resolvedModel) {
|
setupController(controller, resolvedModel) {
|
||||||
|
|||||||
@ -6,6 +6,5 @@
|
|||||||
<Messages::Page::CreateAndEdit
|
<Messages::Page::CreateAndEdit
|
||||||
@message={{this.model.message}}
|
@message={{this.model.message}}
|
||||||
@messages={{this.model.messages}}
|
@messages={{this.model.messages}}
|
||||||
@hasSomeActiveModals={{this.model.hasSomeActiveModals}}
|
|
||||||
@breadcrumbs={{this.breadcrumbs}}
|
@breadcrumbs={{this.breadcrumbs}}
|
||||||
/>
|
/>
|
||||||
@ -3,4 +3,8 @@
|
|||||||
SPDX-License-Identifier: BUSL-1.1
|
SPDX-License-Identifier: BUSL-1.1
|
||||||
}}
|
}}
|
||||||
|
|
||||||
<Messages::Page::Details @message={{this.model}} @breadcrumbs={{this.breadcrumbs}} />
|
<Messages::Page::Details
|
||||||
|
@message={{this.model.message}}
|
||||||
|
@capabilities={{this.model.capabilities}}
|
||||||
|
@breadcrumbs={{this.breadcrumbs}}
|
||||||
|
/>
|
||||||
@ -6,6 +6,5 @@
|
|||||||
<Messages::Page::CreateAndEdit
|
<Messages::Page::CreateAndEdit
|
||||||
@message={{this.model.message}}
|
@message={{this.model.message}}
|
||||||
@messages={{this.model.messages}}
|
@messages={{this.model.messages}}
|
||||||
@hasSomeActiveModals={{this.model.hasSomeActiveModals}}
|
|
||||||
@breadcrumbs={{this.breadcrumbs}}
|
@breadcrumbs={{this.breadcrumbs}}
|
||||||
/>
|
/>
|
||||||
@ -152,17 +152,18 @@
|
|||||||
<RadioButton
|
<RadioButton
|
||||||
class="radio"
|
class="radio"
|
||||||
name={{@attr.name}}
|
name={{@attr.name}}
|
||||||
id={{or val.value val}}
|
id={{or val.id (this.radioValue val)}}
|
||||||
value={{or val.value val}}
|
value={{this.radioValue val}}
|
||||||
@value={{or val.value val}}
|
@value={{this.radioValue val}}
|
||||||
@onChange={{this.setAndBroadcast}}
|
@onChange={{this.setAndBroadcast}}
|
||||||
@groupValue={{get @model this.valuePath}}
|
@groupValue={{get @model this.valuePath}}
|
||||||
@disabled={{and @attr.options.editDisabled (not @model.isNew)}}
|
@disabled={{and @attr.options.editDisabled (not @model.isNew)}}
|
||||||
data-test-radio={{or val.value val}}
|
data-test-radio={{or val.id (this.radioValue val)}}
|
||||||
/>
|
/>
|
||||||
<div class="has-left-margin-xs">
|
<div class="has-left-margin-xs">
|
||||||
<label
|
<label
|
||||||
for="{{or val.value val}}"
|
for={{or val.id (this.radioValue val)}}
|
||||||
|
value={{this.radioValue val}}
|
||||||
class="has-left-margin-xs is-size-7"
|
class="has-left-margin-xs is-size-7"
|
||||||
data-test-radio-label={{or val.label val.value val}}
|
data-test-radio-label={{or val.label val.value val}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { dasherize } from 'vault/helpers/dasherize';
|
|||||||
import { assert } from '@ember/debug';
|
import { assert } from '@ember/debug';
|
||||||
import { addToArray } from 'vault/helpers/add-to-array';
|
import { addToArray } from 'vault/helpers/add-to-array';
|
||||||
import { removeFromArray } from 'vault/helpers/remove-from-array';
|
import { removeFromArray } from 'vault/helpers/remove-from-array';
|
||||||
|
import { isEmpty } from '@ember/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @module FormField
|
* @module FormField
|
||||||
@ -68,6 +69,8 @@ export default class FormFieldComponent extends Component {
|
|||||||
@tracked showToggleTextInput = false;
|
@tracked showToggleTextInput = false;
|
||||||
@tracked toggleInputEnabled = false;
|
@tracked toggleInputEnabled = false;
|
||||||
|
|
||||||
|
radioValue = (item) => (isEmpty(item.value) ? item : item.value);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
const { attr, model } = this.args;
|
const { attr, model } = this.args;
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { CUSTOM_MESSAGES } from 'vault/tests/helpers/config-ui/message-selectors
|
|||||||
import timestamp from 'core/utils/timestamp';
|
import timestamp from 'core/utils/timestamp';
|
||||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
import CustomMessage from 'vault/forms/custom-message';
|
||||||
|
|
||||||
module('Integration | Component | messages/page/create-and-edit', function (hooks) {
|
module('Integration | Component | messages/page/create-and-edit', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
@ -24,15 +25,24 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
|
|||||||
hooks.beforeEach(function () {
|
hooks.beforeEach(function () {
|
||||||
const now = new Date('2023-07-02T00:00:00Z'); // stub "now" for testing
|
const now = new Date('2023-07-02T00:00:00Z'); // stub "now" for testing
|
||||||
sinon.replace(timestamp, 'now', sinon.fake.returns(now));
|
sinon.replace(timestamp, 'now', sinon.fake.returns(now));
|
||||||
this.context = { owner: this.engine };
|
|
||||||
this.store = this.owner.lookup('service:store');
|
this.message = new CustomMessage(
|
||||||
this.message = this.store.createRecord('config-ui/message');
|
{
|
||||||
|
authenticated: true,
|
||||||
|
type: 'banner',
|
||||||
|
startTime: addDays(startOfDay(timestamp.now()), 1).toISOString(),
|
||||||
|
},
|
||||||
|
{ isNew: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
this.renderComponent = () =>
|
||||||
|
render(hbs`<Messages::Page::CreateAndEdit @message={{this.message}} @messages={{this.messages}} />`, {
|
||||||
|
owner: this.engine,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it should display all the create form fields and default radio button values', async function (assert) {
|
test('it should display all the create form fields and default radio button values', async function (assert) {
|
||||||
await render(hbs`<Messages::Page::CreateAndEdit @message={{this.message}} />`, {
|
await this.renderComponent();
|
||||||
owner: this.engine,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.dom(GENERAL.title).hasText('Create message');
|
assert.dom(GENERAL.title).hasText('Create message');
|
||||||
assert.dom(CUSTOM_MESSAGES.radio('authenticated')).exists();
|
assert.dom(CUSTOM_MESSAGES.radio('authenticated')).exists();
|
||||||
@ -60,9 +70,8 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
|
|||||||
|
|
||||||
test('it should display validation errors for invalid form fields', async function (assert) {
|
test('it should display validation errors for invalid form fields', async function (assert) {
|
||||||
assert.expect(8);
|
assert.expect(8);
|
||||||
await render(hbs`<Messages::Page::CreateAndEdit @message={{this.message}} />`, {
|
|
||||||
owner: this.engine,
|
await this.renderComponent();
|
||||||
});
|
|
||||||
|
|
||||||
await fillIn(CUSTOM_MESSAGES.input('startTime'), '2024-01-20T00:00');
|
await fillIn(CUSTOM_MESSAGES.input('startTime'), '2024-01-20T00:00');
|
||||||
await fillIn(CUSTOM_MESSAGES.input('endTime'), '2024-01-01T00:00');
|
await fillIn(CUSTOM_MESSAGES.input('endTime'), '2024-01-01T00:00');
|
||||||
@ -92,9 +101,8 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
|
|||||||
assert.ok(true, 'POST request made to create message');
|
assert.ok(true, 'POST request made to create message');
|
||||||
});
|
});
|
||||||
|
|
||||||
await render(hbs`<Messages::Page::CreateAndEdit @message={{this.message}} />`, {
|
await this.renderComponent();
|
||||||
owner: this.engine,
|
|
||||||
});
|
|
||||||
await fillIn(CUSTOM_MESSAGES.input('title'), 'Awesome custom message title');
|
await fillIn(CUSTOM_MESSAGES.input('title'), 'Awesome custom message title');
|
||||||
await fillIn(
|
await fillIn(
|
||||||
CUSTOM_MESSAGES.input('message'),
|
CUSTOM_MESSAGES.input('message'),
|
||||||
@ -116,9 +124,9 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
|
|||||||
|
|
||||||
test('it should have form vaildations', async function (assert) {
|
test('it should have form vaildations', async function (assert) {
|
||||||
assert.expect(4);
|
assert.expect(4);
|
||||||
await render(hbs`<Messages::Page::CreateAndEdit @message={{this.message}} />`, {
|
|
||||||
owner: this.engine,
|
await this.renderComponent();
|
||||||
});
|
|
||||||
await click(CUSTOM_MESSAGES.button('create-message'));
|
await click(CUSTOM_MESSAGES.button('create-message'));
|
||||||
assert
|
assert
|
||||||
.dom(CUSTOM_MESSAGES.input('title'))
|
.dom(CUSTOM_MESSAGES.input('title'))
|
||||||
@ -136,21 +144,19 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
|
|||||||
|
|
||||||
test('it should prepopulate form if form is in edit mode', async function (assert) {
|
test('it should prepopulate form if form is in edit mode', async function (assert) {
|
||||||
assert.expect(13);
|
assert.expect(13);
|
||||||
this.store.pushPayload('config-ui/message', {
|
|
||||||
modelName: 'config-ui/message',
|
this.message = new CustomMessage({
|
||||||
id: 'hhhhh-iiii-lllll-dddd',
|
id: 'hhhhh-iiii-lllll-dddd',
|
||||||
type: 'modal',
|
type: 'modal',
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
title: 'Hello world',
|
title: 'Hello world',
|
||||||
message: 'Blah blah blah. Some super long message.',
|
message: 'Blah blah blah. Some super long message.',
|
||||||
start_time: '2023-12-12T08:00:00.000Z',
|
startTime: new Date('2023-12-12T08:00:00.000Z'),
|
||||||
end_time: '2023-12-21T08:00:00.000Z',
|
endTime: new Date('2023-12-21T08:00:00.000Z'),
|
||||||
link: { 'Learn more': 'www.learnmore.com' },
|
link: { 'Learn more': 'www.learnmore.com' },
|
||||||
});
|
});
|
||||||
this.message = this.store.peekRecord('config-ui/message', 'hhhhh-iiii-lllll-dddd');
|
|
||||||
await render(hbs`<Messages::Page::CreateAndEdit @message={{this.message}} />`, {
|
await this.renderComponent();
|
||||||
owner: this.engine,
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.dom(GENERAL.title).hasText('Edit message');
|
assert.dom(GENERAL.title).hasText('Edit message');
|
||||||
assert.dom(CUSTOM_MESSAGES.radio('authenticated')).exists();
|
assert.dom(CUSTOM_MESSAGES.radio('authenticated')).exists();
|
||||||
@ -174,9 +180,9 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
|
|||||||
|
|
||||||
test('it should show a preview image modal when preview is clicked', async function (assert) {
|
test('it should show a preview image modal when preview is clicked', async function (assert) {
|
||||||
assert.expect(6);
|
assert.expect(6);
|
||||||
await render(hbs`<Messages::Page::CreateAndEdit @message={{this.message}} />`, {
|
|
||||||
owner: this.engine,
|
await this.renderComponent();
|
||||||
});
|
|
||||||
await fillIn(CUSTOM_MESSAGES.input('title'), 'Awesome custom message title');
|
await fillIn(CUSTOM_MESSAGES.input('title'), 'Awesome custom message title');
|
||||||
await fillIn(
|
await fillIn(
|
||||||
CUSTOM_MESSAGES.input('message'),
|
CUSTOM_MESSAGES.input('message'),
|
||||||
@ -202,9 +208,9 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
|
|||||||
|
|
||||||
test('it should show a preview modal when preview is clicked', async function (assert) {
|
test('it should show a preview modal when preview is clicked', async function (assert) {
|
||||||
assert.expect(4);
|
assert.expect(4);
|
||||||
await render(hbs`<Messages::Page::CreateAndEdit @message={{this.message}} />`, {
|
|
||||||
owner: this.engine,
|
await this.renderComponent();
|
||||||
});
|
|
||||||
await click(CUSTOM_MESSAGES.radio('modal'));
|
await click(CUSTOM_MESSAGES.radio('modal'));
|
||||||
await fillIn(CUSTOM_MESSAGES.input('title'), 'Preview modal title');
|
await fillIn(CUSTOM_MESSAGES.input('title'), 'Preview modal title');
|
||||||
await fillIn(CUSTOM_MESSAGES.input('message'), 'Some preview modal message thats super long.');
|
await fillIn(CUSTOM_MESSAGES.input('message'), 'Some preview modal message thats super long.');
|
||||||
@ -220,8 +226,8 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
|
|||||||
test('it should show multiple modal message', async function (assert) {
|
test('it should show multiple modal message', async function (assert) {
|
||||||
assert.expect(2);
|
assert.expect(2);
|
||||||
|
|
||||||
this.store.pushPayload('config-ui/message', {
|
this.messages = [
|
||||||
modelName: 'config-ui/message',
|
{
|
||||||
id: '01234567-89ab-cdef-0123-456789abcdef',
|
id: '01234567-89ab-cdef-0123-456789abcdef',
|
||||||
active: true,
|
active: true,
|
||||||
type: 'modal',
|
type: 'modal',
|
||||||
@ -229,11 +235,10 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
|
|||||||
title: 'Message title 1',
|
title: 'Message title 1',
|
||||||
message: 'Some long long long message',
|
message: 'Some long long long message',
|
||||||
link: { here: 'www.example.com' },
|
link: { here: 'www.example.com' },
|
||||||
startTime: '2021-08-01T00:00:00Z',
|
startTime: new Date('2021-08-01T00:00:00Z'),
|
||||||
endTime: '',
|
endTime: '',
|
||||||
});
|
},
|
||||||
this.store.pushPayload('config-ui/message', {
|
{
|
||||||
modelName: 'config-ui/message',
|
|
||||||
id: '01234567-89ab-vvvv-0123-456789abcdef',
|
id: '01234567-89ab-vvvv-0123-456789abcdef',
|
||||||
active: true,
|
active: true,
|
||||||
type: 'modal',
|
type: 'modal',
|
||||||
@ -241,18 +246,13 @@ module('Integration | Component | messages/page/create-and-edit', function (hook
|
|||||||
title: 'Message title 2',
|
title: 'Message title 2',
|
||||||
message: 'Some long long long message',
|
message: 'Some long long long message',
|
||||||
link: { here: 'www.example.com' },
|
link: { here: 'www.example.com' },
|
||||||
startTime: '2021-08-01T00:00:00Z',
|
startTime: new Date('2021-08-01T00:00:00Z'),
|
||||||
endTime: '2090-08-01T00:00:00Z',
|
endTime: new Date('2090-08-01T00:00:00Z'),
|
||||||
});
|
},
|
||||||
|
];
|
||||||
|
|
||||||
this.messages = this.store.peekAll('config-ui/message');
|
await this.renderComponent();
|
||||||
|
|
||||||
await render(
|
|
||||||
hbs`<Messages::Page::CreateAndEdit @message={{this.message}} @messages={{this.messages}} @hasSomeActiveModals={{true}} />`,
|
|
||||||
{
|
|
||||||
owner: this.engine,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await fillIn(CUSTOM_MESSAGES.input('title'), 'Awesome custom message title');
|
await fillIn(CUSTOM_MESSAGES.input('title'), 'Awesome custom message title');
|
||||||
await fillIn(
|
await fillIn(
|
||||||
CUSTOM_MESSAGES.input('message'),
|
CUSTOM_MESSAGES.input('message'),
|
||||||
|
|||||||
@ -29,7 +29,6 @@ module('Integration | Component | messages/page/details', function (hooks) {
|
|||||||
|
|
||||||
hooks.beforeEach(function () {
|
hooks.beforeEach(function () {
|
||||||
this.context = { owner: this.engine };
|
this.context = { owner: this.engine };
|
||||||
this.store = this.owner.lookup('service:store');
|
|
||||||
|
|
||||||
this.server.post('/sys/capabilities-self', () => ({
|
this.server.post('/sys/capabilities-self', () => ({
|
||||||
data: {
|
data: {
|
||||||
@ -37,8 +36,7 @@ module('Integration | Component | messages/page/details', function (hooks) {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.store.pushPayload('config-ui/message', {
|
this.message = {
|
||||||
modelName: 'config-ui/message',
|
|
||||||
id: '01234567-89ab-cdef-0123-456789abcdef',
|
id: '01234567-89ab-cdef-0123-456789abcdef',
|
||||||
active: true,
|
active: true,
|
||||||
type: 'banner',
|
type: 'banner',
|
||||||
@ -46,19 +44,18 @@ module('Integration | Component | messages/page/details', function (hooks) {
|
|||||||
title: 'Message title 1',
|
title: 'Message title 1',
|
||||||
message: 'Some long long long message',
|
message: 'Some long long long message',
|
||||||
link: { here: 'www.example.com' },
|
link: { here: 'www.example.com' },
|
||||||
start_time: '2021-08-01T00:00:00Z',
|
startTime: new Date('2021-08-01T00:00:00Z'),
|
||||||
end_time: '',
|
endTime: undefined,
|
||||||
canDeleteCustomMessages: true,
|
|
||||||
canEditCustomMessages: true,
|
canEditCustomMessages: true,
|
||||||
});
|
};
|
||||||
|
this.capabilities = { canDelete: true, canUpdate: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it should show the message details', async function (assert) {
|
test('it should show the message details', async function (assert) {
|
||||||
this.message = await this.store.peekRecord('config-ui/message', '01234567-89ab-cdef-0123-456789abcdef');
|
await render(
|
||||||
|
hbs`<Messages::Page::Details @message={{this.message}} @capabilities={{this.capabilities}} />`,
|
||||||
await render(hbs`<Messages::Page::Details @message={{this.message}} />`, {
|
this.context
|
||||||
owner: this.engine,
|
);
|
||||||
});
|
|
||||||
assert.dom('[data-test-page-title]').hasText('Message title 1');
|
assert.dom('[data-test-page-title]').hasText('Message title 1');
|
||||||
assert
|
assert
|
||||||
.dom('[data-test-component="info-table-row"]')
|
.dom('[data-test-component="info-table-row"]')
|
||||||
@ -88,5 +85,8 @@ module('Integration | Component | messages/page/details', function (hooks) {
|
|||||||
.hasText(this.message[field.key], `${field.label} value renders`);
|
.hasText(this.message[field.key], `${field.label} value renders`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
assert.dom('[data-test-confirm-action="Delete message"]').exists();
|
||||||
|
assert.dom('[data-test-link="edit"]').exists();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -11,14 +11,18 @@ import { render, click } from '@ember/test-helpers';
|
|||||||
import { hbs } from 'ember-cli-htmlbars';
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
import { CUSTOM_MESSAGES } from 'vault/tests/helpers/config-ui/message-selectors';
|
import { CUSTOM_MESSAGES } from 'vault/tests/helpers/config-ui/message-selectors';
|
||||||
import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs';
|
import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs';
|
||||||
|
import { addDays, startOfDay } from 'date-fns';
|
||||||
|
import timestamp from 'core/utils/timestamp';
|
||||||
|
|
||||||
const META = {
|
const META = {
|
||||||
|
value: {
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
lastPage: 1,
|
lastPage: 1,
|
||||||
nextPage: 1,
|
nextPage: 1,
|
||||||
prevPage: 1,
|
prevPage: 1,
|
||||||
total: 3,
|
total: 3,
|
||||||
pageSize: 15,
|
pageSize: 15,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
module('Integration | Component | messages/page/list', function (hooks) {
|
module('Integration | Component | messages/page/list', function (hooks) {
|
||||||
@ -28,10 +32,9 @@ module('Integration | Component | messages/page/list', function (hooks) {
|
|||||||
|
|
||||||
hooks.beforeEach(function () {
|
hooks.beforeEach(function () {
|
||||||
this.server.post('/sys/capabilities-self', allowAllCapabilitiesStub());
|
this.server.post('/sys/capabilities-self', allowAllCapabilitiesStub());
|
||||||
this.store = this.owner.lookup('service:store');
|
|
||||||
|
|
||||||
this.store.pushPayload('config-ui/message', {
|
this.messages = [
|
||||||
modelName: 'config-ui/message',
|
{
|
||||||
id: '0',
|
id: '0',
|
||||||
active: true,
|
active: true,
|
||||||
type: 'banner',
|
type: 'banner',
|
||||||
@ -39,11 +42,10 @@ module('Integration | Component | messages/page/list', function (hooks) {
|
|||||||
title: 'Message title 1',
|
title: 'Message title 1',
|
||||||
message: 'Some long long long message',
|
message: 'Some long long long message',
|
||||||
link: { title: 'here', href: 'www.example.com' },
|
link: { title: 'here', href: 'www.example.com' },
|
||||||
start_time: '2021-08-01T00:00:00Z',
|
startTime: new Date('2021-08-01T00:00:00Z'),
|
||||||
end_time: '',
|
endTime: undefined,
|
||||||
});
|
},
|
||||||
this.store.pushPayload('config-ui/message', {
|
{
|
||||||
modelName: 'config-ui/message',
|
|
||||||
id: '1',
|
id: '1',
|
||||||
active: false,
|
active: false,
|
||||||
type: 'modal',
|
type: 'modal',
|
||||||
@ -51,11 +53,10 @@ module('Integration | Component | messages/page/list', function (hooks) {
|
|||||||
title: 'Message title 2',
|
title: 'Message title 2',
|
||||||
message: 'Some long long long message blah blah blah',
|
message: 'Some long long long message blah blah blah',
|
||||||
link: { title: 'here', href: 'www.example2.com' },
|
link: { title: 'here', href: 'www.example2.com' },
|
||||||
start_time: '2023-07-01T00:00:00Z',
|
startTime: new Date('2023-07-01T00:00:00Z'),
|
||||||
end_time: '2023-08-01T00:00:00Z',
|
endTime: new Date('2023-08-01T00:00:00Z'),
|
||||||
});
|
},
|
||||||
this.store.pushPayload('config-ui/message', {
|
{
|
||||||
modelName: 'config-ui/message',
|
|
||||||
id: '2',
|
id: '2',
|
||||||
active: false,
|
active: false,
|
||||||
type: 'banner',
|
type: 'banner',
|
||||||
@ -63,7 +64,9 @@ module('Integration | Component | messages/page/list', function (hooks) {
|
|||||||
title: 'Message title 3',
|
title: 'Message title 3',
|
||||||
message: 'Some long long long message',
|
message: 'Some long long long message',
|
||||||
link: { title: 'here', href: 'www.example.com' },
|
link: { title: 'here', href: 'www.example.com' },
|
||||||
});
|
},
|
||||||
|
];
|
||||||
|
Object.defineProperty(this.messages, 'meta', META);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it should show the messages empty state', async function (assert) {
|
test('it should show the messages empty state', async function (assert) {
|
||||||
@ -82,8 +85,6 @@ module('Integration | Component | messages/page/list', function (hooks) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('it should show the list of custom messages', async function (assert) {
|
test('it should show the list of custom messages', async function (assert) {
|
||||||
this.messages = this.store.peekAll('config-ui/message', {});
|
|
||||||
this.messages.meta = META;
|
|
||||||
await render(hbs`<Messages::Page::List @messages={{this.messages}} />`, {
|
await render(hbs`<Messages::Page::List @messages={{this.messages}} />`, {
|
||||||
owner: this.engine,
|
owner: this.engine,
|
||||||
});
|
});
|
||||||
@ -96,8 +97,7 @@ module('Integration | Component | messages/page/list', function (hooks) {
|
|||||||
|
|
||||||
test('it should show max message warning modal', async function (assert) {
|
test('it should show max message warning modal', async function (assert) {
|
||||||
for (let i = 0; i < 97; i++) {
|
for (let i = 0; i < 97; i++) {
|
||||||
this.store.pushPayload('config-ui/message', {
|
this.messages.push({
|
||||||
modelName: 'config-ui/message',
|
|
||||||
id: `${i}-a`,
|
id: `${i}-a`,
|
||||||
active: true,
|
active: true,
|
||||||
type: 'banner',
|
type: 'banner',
|
||||||
@ -105,19 +105,12 @@ module('Integration | Component | messages/page/list', function (hooks) {
|
|||||||
title: `Message title ${i}`,
|
title: `Message title ${i}`,
|
||||||
message: 'Some long long long message',
|
message: 'Some long long long message',
|
||||||
link: { title: 'here', href: 'www.example.com' },
|
link: { title: 'here', href: 'www.example.com' },
|
||||||
start_time: '2021-08-01T00:00:00Z',
|
startTime: new Date('2021-08-01T00:00:00Z'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
this.messages.meta.total = this.messages.length;
|
||||||
|
this.messages.meta.pageSize = 100;
|
||||||
|
|
||||||
this.messages = this.store.peekAll('config-ui/message', {});
|
|
||||||
this.messages.meta = {
|
|
||||||
currentPage: 1,
|
|
||||||
lastPage: 1,
|
|
||||||
nextPage: 1,
|
|
||||||
prevPage: 1,
|
|
||||||
total: this.messages.length,
|
|
||||||
pageSize: 100,
|
|
||||||
};
|
|
||||||
await render(hbs`<Messages::Page::List @messages={{this.messages}} />`, {
|
await render(hbs`<Messages::Page::List @messages={{this.messages}} />`, {
|
||||||
owner: this.engine,
|
owner: this.engine,
|
||||||
});
|
});
|
||||||
@ -134,8 +127,8 @@ module('Integration | Component | messages/page/list', function (hooks) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('it should show the correct badge colors based on badge status', async function (assert) {
|
test('it should show the correct badge colors based on badge status', async function (assert) {
|
||||||
this.messages = this.store.peekAll('config-ui/message', {});
|
this.messages[2].startTime = addDays(startOfDay(timestamp.now()), 1);
|
||||||
this.messages.meta = META;
|
|
||||||
await render(hbs`<Messages::Page::List @messages={{this.messages}} />`, {
|
await render(hbs`<Messages::Page::List @messages={{this.messages}} />`, {
|
||||||
owner: this.engine,
|
owner: this.engine,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -231,6 +231,26 @@ module('Integration | Component | form field', function (hooks) {
|
|||||||
assert.strictEqual(model.get('foo'), selectedValue);
|
assert.strictEqual(model.get('foo'), selectedValue);
|
||||||
assert.ok(spy.calledWith('foo', selectedValue), 'onChange called with correct args');
|
assert.ok(spy.calledWith('foo', selectedValue), 'onChange called with correct args');
|
||||||
});
|
});
|
||||||
|
test('it renders: radio buttons false value and id', async function (assert) {
|
||||||
|
const [model, spy] = await setup.call(
|
||||||
|
this,
|
||||||
|
createAttr('foo', null, {
|
||||||
|
editType: 'radio',
|
||||||
|
possibleValues: [
|
||||||
|
{ label: 'True option', value: true, id: 'true-option' },
|
||||||
|
{ label: 'False option', value: false, id: 'false-option' },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.dom('[data-test-radio-label="True option"]').hasTextContaining('True option');
|
||||||
|
assert.dom('[data-test-radio-label="False option"]').hasTextContaining('False option');
|
||||||
|
assert.dom('[data-test-radio="true-option"]').hasAttribute('id', 'true-option');
|
||||||
|
assert.dom('[data-test-radio="false-option"]').hasAttribute('id', 'false-option');
|
||||||
|
await component.selectRadioInput('false-option');
|
||||||
|
assert.false(model.get('foo'));
|
||||||
|
assert.ok(spy.calledWith('foo', false), 'onChange called with correct args');
|
||||||
|
});
|
||||||
test('it renders: datetimelocal', async function (assert) {
|
test('it renders: datetimelocal', async function (assert) {
|
||||||
const [model] = await setup.call(
|
const [model] = await setup.call(
|
||||||
this,
|
this,
|
||||||
|
|||||||
@ -111,13 +111,19 @@ module('Unit | Service | api', function (hooks) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('it should show warnings', async function (assert) {
|
test('it should show warnings', async function (assert) {
|
||||||
const warnings = ['warning1', 'warning2'];
|
const warnings = JSON.stringify({ warnings: ['warning1', 'warning2'] });
|
||||||
const response = new Response(JSON.stringify({ warnings }));
|
const response = new Response(warnings, { headers: { 'Content-Length': warnings.length } });
|
||||||
|
|
||||||
await this.apiService.showWarnings({ response });
|
await this.apiService.showWarnings({ response });
|
||||||
|
|
||||||
assert.true(this.info.firstCall.calledWith(warnings[0]), 'First warning message is shown');
|
assert.true(this.info.firstCall.calledWith('warning1'), 'First warning message is shown');
|
||||||
assert.true(this.info.secondCall.calledWith(warnings[1]), 'Second warning message is shown');
|
assert.true(this.info.secondCall.calledWith('warning2'), 'Second warning message is shown');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should not attempt to set warnings for empty response', async function (assert) {
|
||||||
|
const response = new Response();
|
||||||
|
await this.apiService.showWarnings({ response });
|
||||||
|
assert.true(this.info.notCalled, 'No warning messages are shown');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it should delete control group token', async function (assert) {
|
test('it should delete control group token', async function (assert) {
|
||||||
|
|||||||
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import apiErrorMessage from 'vault/utils/api-error-message';
|
import apiErrorMessage from 'vault/utils/api-error-message';
|
||||||
|
import ENV from 'vault/config/environment';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
module('Unit | Util | api-error-message', function (hooks) {
|
module('Unit | Util | api-error-message', function (hooks) {
|
||||||
hooks.beforeEach(function () {
|
hooks.beforeEach(function () {
|
||||||
@ -48,4 +50,13 @@ module('Unit | Util | api-error-message', function (hooks) {
|
|||||||
const message = await apiErrorMessage('some random error', fallback);
|
const message = await apiErrorMessage('some random error', fallback);
|
||||||
assert.strictEqual(message, fallback);
|
assert.strictEqual(message, fallback);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it should log out error in development environment', async function (assert) {
|
||||||
|
const consoleStub = sinon.stub(console, 'error');
|
||||||
|
sinon.stub(ENV, 'environment').value('development');
|
||||||
|
const error = new Error('some js type error');
|
||||||
|
await apiErrorMessage(error);
|
||||||
|
assert.true(consoleStub.calledWith('API Error:', error));
|
||||||
|
sinon.restore();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user