mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
[ui] Multi-line variable values and helios upgrades generally (#19544)
* Multi-line variable values and helios upgrades generally * Variables page titles and actions restyle * Hacky fix to keyboard shortcut otherwise bumping space on shift * Related entities heliosified * Namespace and path fields heliosed * Paths table heliosified * Variable view table * Fixups after design discussion * Monospaced editing * De-commented template placeholder * Acceptance tests updated for helios components across variables * Tests helios'd in variable-form-test * PR suggestions
This commit is contained in:
@@ -50,22 +50,21 @@
|
||||
{{/if}}
|
||||
|
||||
<div class={{if this.namespaceOptions "path-namespace"}}>
|
||||
<label>
|
||||
<span>
|
||||
Path
|
||||
</span>
|
||||
<Input
|
||||
@type="text"
|
||||
@value={{this.path}}
|
||||
placeholder="nomad/jobs/my-job/my-group/my-task"
|
||||
class="input path-input {{if this.duplicatePathWarning "error"}}"
|
||||
{{on "input" this.setModelPath}}
|
||||
disabled={{not @model.isNew}}
|
||||
{{autofocus}}
|
||||
data-test-path-input
|
||||
/>
|
||||
|
||||
<Hds::Form::TextInput::Field
|
||||
@isRequired={{true}}
|
||||
@value={{this.path}}
|
||||
placeholder="nomad/jobs/my-job/my-group/my-task"
|
||||
@isInvalid={{this.duplicatePathWarning}}
|
||||
{{on "input" (action this.setModelPath)}}
|
||||
disabled={{not @model.isNew}}
|
||||
{{autofocus}}
|
||||
data-test-path-input
|
||||
as |F|>
|
||||
<F.Label>Path</F.Label>
|
||||
|
||||
{{#if this.duplicatePathWarning}}
|
||||
<p class="duplicate-path-error help is-danger">
|
||||
<F.Error data-test-duplicate-variable-error>
|
||||
There is already a variable located at
|
||||
{{this.path}}
|
||||
.
|
||||
@@ -78,19 +77,16 @@
|
||||
edit the existing variable
|
||||
</LinkTo>
|
||||
.
|
||||
</p>
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
{{#if this.isJobTemplateVariable}}
|
||||
<p class="help job-template-hint">
|
||||
Use this variable to generate job templates with
|
||||
<code>nomad job init -template={{this.jobTemplateName}}
|
||||
<CopyButton
|
||||
@clipboardText="nomad job init -template={{this.jobTemplateName}}"
|
||||
/>
|
||||
</code>
|
||||
</p>
|
||||
<F.HelperText data-test-job-template-hint>
|
||||
Use this variable to generate job templates with <Hds::Copy::Snippet @textToCopy="nomad job init -template={{this.jobTemplateName}}" />
|
||||
</F.HelperText>
|
||||
{{/if}}
|
||||
</label>
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
|
||||
<VariableForm::NamespaceFilter
|
||||
@data={{hash
|
||||
disabled=(not @model.isNew)
|
||||
@@ -131,28 +127,20 @@
|
||||
{{else}}
|
||||
{{#each this.keyValues as |entry iter|}}
|
||||
<div class="key-value">
|
||||
<label>
|
||||
<span>
|
||||
Key
|
||||
</span>
|
||||
<Input
|
||||
data-test-var-key
|
||||
@type="text"
|
||||
@value={{entry.key}}
|
||||
class="input"
|
||||
{{autofocus ignore=(eq iter 0)}}
|
||||
{{on "input" (fn this.validateKey entry)}}
|
||||
/>
|
||||
</label>
|
||||
<Hds::Form::TextInput::Field
|
||||
@value={{entry.key}}
|
||||
data-test-var-key
|
||||
class="input"
|
||||
{{autofocus ignore=(eq iter 0)}}
|
||||
{{on "input" (action this.validateKey entry)}}
|
||||
as |F|>
|
||||
<F.Label>Key</F.Label>
|
||||
</Hds::Form::TextInput::Field>
|
||||
<VariableForm::InputGroup @entry={{entry}} />
|
||||
<button
|
||||
class="delete-row button is-danger is-inverted"
|
||||
type="button"
|
||||
{{on "click" (action this.deleteRow entry)}}
|
||||
<Hds::Button @text="Delete" @color="critical" class="delete-entry-button"
|
||||
disabled={{eq this.keyValues.length 1}}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
{{on "click" (action this.deleteRow entry)}}
|
||||
/>
|
||||
{{#each-in entry.warnings as |k v|}}
|
||||
<span class="key-value-error help is-danger">
|
||||
{{v}}
|
||||
@@ -166,25 +154,23 @@
|
||||
<footer>
|
||||
{{#unless this.isJSONView}}
|
||||
{{#unless this.isJobTemplateVariable}}
|
||||
<button
|
||||
class="add-more button is-info is-inverted"
|
||||
type="button"
|
||||
disabled={{not (and this.keyValues.lastObject.key this.keyValues.lastObject.value)}}
|
||||
{{on "click" this.appendRow}}
|
||||
<Hds::Button
|
||||
@text="Add More"
|
||||
@color="secondary"
|
||||
@size="medium"
|
||||
@icon="plus"
|
||||
{{!-- only enable if the last entry isn't empty --}}
|
||||
disabled={{not (and this.keyValues.lastObject.key this.keyValues.lastObject.value)}} {{on "click" this.appendRow}}
|
||||
data-test-add-kv
|
||||
>
|
||||
Add More
|
||||
</button>
|
||||
/>
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
<button
|
||||
disabled={{this.shouldDisableSave}}
|
||||
class="button is-primary save"
|
||||
<Hds::Button
|
||||
@text="Save {{pluralize "Variable" this.keyValues.length}}"
|
||||
@color="primary"
|
||||
type="submit"
|
||||
disabled={{this.shouldDisableSave}}
|
||||
data-test-submit-var
|
||||
>
|
||||
Save
|
||||
{{pluralize "Variable" @this.keyValues.length}}
|
||||
</button>
|
||||
/>
|
||||
</footer>
|
||||
</form>
|
||||
@@ -180,6 +180,7 @@ export default class VariableFormComponent extends Component {
|
||||
delete entry.warnings.duplicateKeyError;
|
||||
entry.warnings.notifyPropertyChange('duplicateKeyError');
|
||||
}
|
||||
set(entry, 'key', value);
|
||||
}
|
||||
|
||||
@action appendRow() {
|
||||
@@ -207,6 +208,7 @@ export default class VariableFormComponent extends Component {
|
||||
* @param {KeyboardEvent} e
|
||||
*/
|
||||
@action setModelPath(e) {
|
||||
set(this, 'path', e.target.value);
|
||||
set(this.args.model, 'path', e.target.value);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,26 +4,15 @@
|
||||
~}}
|
||||
|
||||
<label class="value-label">
|
||||
<span>
|
||||
Value
|
||||
</span>
|
||||
<Input
|
||||
@type={{this.inputType}}
|
||||
@value={{@entry.value}}
|
||||
class="input value-input"
|
||||
{{! prevent auto-fill }}
|
||||
autocomplete="new-password"
|
||||
data-test-var-value
|
||||
/>
|
||||
<button
|
||||
class="show-hide-values button is-light"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
{{on "click" this.toggleInputType}}
|
||||
>
|
||||
<FlightIcon
|
||||
@name={{if this.isObscured "eye-off" "eye"}}
|
||||
@title={{if this.isObscured "Show Value" "Hide Value"}}
|
||||
/>
|
||||
</button>
|
||||
<Hds::Form::MaskedInput::Field
|
||||
@isMultiline={{true}}
|
||||
@value={{@entry.value}}
|
||||
rows="1"
|
||||
class="hds-typography-code-200"
|
||||
{{! prevent auto-fill }}
|
||||
autocomplete="new-password"
|
||||
data-test-var-value
|
||||
as |F|>
|
||||
<F.Label>Value</F.Label>
|
||||
</Hds::Form::MaskedInput::Field>
|
||||
</label>
|
||||
@@ -10,19 +10,18 @@
|
||||
{{#if trigger.data.isSuccess}}
|
||||
{{#if trigger.data.result}}
|
||||
{{#if @data.namespaceOptions}}
|
||||
<label>
|
||||
<span>
|
||||
Namespace
|
||||
</span>
|
||||
<SingleSelectDropdown
|
||||
data-test-variable-namespace-filter
|
||||
@label="Namespace"
|
||||
@disabled={{@data.disabled}}
|
||||
@options={{@data.namespaceOptions}}
|
||||
@selection={{@data.selection}}
|
||||
@onSelect={{@fns.onSelect}}
|
||||
/>
|
||||
</label>
|
||||
<Hds::Dropdown class="namespace-dropdown" data-test-variable-namespace-filter as |dd|>
|
||||
<dd.ToggleButton @text="{{@data.selection}}" @color="secondary" disabled={{@data.disabled}} @isFullWidth={{true}} />
|
||||
{{#each @data.namespaceOptions as |option|}}
|
||||
<dd.Radio
|
||||
name={{option.key}}
|
||||
{{on "change" (action @fns.onSelect option.key)}}
|
||||
checked={{eq @data.selection option.key}}
|
||||
>
|
||||
{{option.label}}
|
||||
</dd.Radio>
|
||||
{{/each}}
|
||||
</Hds::Dropdown>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<p class="related-entities notification">
|
||||
<FlightIcon @name="info-fill" @color="var(--blue)" />
|
||||
<span>
|
||||
<Hds::Alert @type="inline" @color="highlight" @icon="info" class="related-entities notification" as |A|>
|
||||
<A.Title>Automatically-accessible variable</A.Title>
|
||||
<A.Description>
|
||||
This variable {{#if @new}}will be{{else}}is{{/if}} accessible by
|
||||
{{#if @task}}
|
||||
task <strong>{{@task}}</strong> in group <LinkTo @route="jobs.job.task-group" @models={{array (concat @job "@" @namespace) @group}}>{{@group}} <FlightIcon @name="external-link" /></LinkTo>
|
||||
task <strong>{{@task}}</strong> in group <Hds::Link::Inline @route="jobs.job.task-group" @models={{array (concat @job "@" @namespace) @group}} @icon="external-link">{{@group}}</Hds::Link::Inline>
|
||||
{{else if @group}}
|
||||
group <LinkTo @route="jobs.job.task-group" @models={{array (concat @job "@" @namespace) @group}}>{{@group}} <FlightIcon @name="external-link" /></LinkTo>
|
||||
group <Hds::Link::Inline @route="jobs.job.task-group" @models={{array (concat @job "@" @namespace) @group}} @icon="external-link">{{@group}}</Hds::Link::Inline>
|
||||
{{else if @job}}
|
||||
job <LinkTo @route="jobs.job" @model={{concat @job "@" @namespace}}>{{@job}} <FlightIcon @name="external-link" /></LinkTo>
|
||||
job <Hds::Link::Inline @route="jobs.job" @model={{concat @job "@" @namespace}} @icon="external-link">{{@job}}</Hds::Link::Inline>
|
||||
{{else}}
|
||||
all nomad jobs in this namespace
|
||||
{{/if}}
|
||||
</span>
|
||||
</p>
|
||||
all nomad jobs in this namespace
|
||||
{{/if}}
|
||||
</A.Description>
|
||||
</Hds::Alert>
|
||||
|
||||
@@ -3,22 +3,24 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<ListTable class="path-tree" @source={{@branch}} as |t|>
|
||||
<t.head>
|
||||
<th>
|
||||
Path
|
||||
</th>
|
||||
<th>
|
||||
Namespace
|
||||
</th>
|
||||
<th>
|
||||
Last Modified
|
||||
</th>
|
||||
</t.head>
|
||||
<tbody>
|
||||
<Hds::Table @caption="A list variables" class="path-tree">
|
||||
<:head as |H|>
|
||||
<H.Tr>
|
||||
<H.Th>
|
||||
Path
|
||||
</H.Th>
|
||||
<H.Th>
|
||||
Namespace
|
||||
</H.Th>
|
||||
<H.Th>
|
||||
Last Modified
|
||||
</H.Th>
|
||||
</H.Tr>
|
||||
</:head>
|
||||
<:body as |B|>
|
||||
{{#each this.folders as |folder|}}
|
||||
<tr data-test-folder-row {{on "click" (fn this.handleFolderClick folder.data.absolutePath)}}>
|
||||
<td colspan="3"
|
||||
<B.Tr data-test-folder-row {{on "click" (fn this.handleFolderClick folder.data.absolutePath)}}>
|
||||
<B.Td colspan="3"
|
||||
{{keyboard-shortcut
|
||||
enumerated=true
|
||||
action=(fn this.handleFolderClick folder.data.absolutePath)
|
||||
@@ -30,12 +32,13 @@
|
||||
{{trim-path folder.name}}
|
||||
</LinkTo>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
|
||||
{{/each}}
|
||||
|
||||
{{#each this.files as |file|}}
|
||||
<tr
|
||||
<B.Tr
|
||||
data-test-file-row="{{file.name}}"
|
||||
{{on "click" (fn this.handleFileClick file)}}
|
||||
class={{if (can "read variable" path=file.absoluteFilePath namespace=file.variable.namespace) "" "inaccessible"}}
|
||||
@@ -44,7 +47,7 @@
|
||||
action=(fn this.handleFileClick file)
|
||||
}}
|
||||
>
|
||||
<td>
|
||||
<B.Td>
|
||||
<FlightIcon @name="file-text" />
|
||||
{{#if (can "read variable" path=file.absoluteFilePath namespace=file.variable.namespace)}}
|
||||
<LinkTo
|
||||
@@ -57,17 +60,16 @@
|
||||
{{else}}
|
||||
<span title="Your access policy does not allow you to view the contents of {{file.name}}">{{file.name}}</span>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{file.variable.namespace}}
|
||||
</td>
|
||||
<td>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<span class="tooltip" aria-label="{{format-ts file.variable.modifyTime}}">
|
||||
{{moment-from-now file.variable.modifyTime}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
|
||||
</tbody>
|
||||
</ListTable>
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
{{/each}}
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
|
||||
@@ -37,6 +37,10 @@ export default class VariablesVariableIndexController extends Controller {
|
||||
this.isDeleting = false;
|
||||
}
|
||||
|
||||
@action copyVariable() {
|
||||
navigator.clipboard.writeText(JSON.stringify(this.model.items, null, 2));
|
||||
}
|
||||
|
||||
@task(function* () {
|
||||
try {
|
||||
yield this.model.deleteRecord();
|
||||
|
||||
@@ -5,28 +5,18 @@
|
||||
|
||||
.section.single-variable {
|
||||
margin-top: 1.5rem;
|
||||
|
||||
.back-link {
|
||||
text-decoration: none;
|
||||
color: #363636;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
$hdsLabelTopOffset: 26px;
|
||||
$hdsInputHeight: 35px;
|
||||
|
||||
.variable-title {
|
||||
.toggle {
|
||||
font-size: 0.8rem;
|
||||
margin-left: 1rem;
|
||||
position: relative;
|
||||
top: -0.25rem;
|
||||
.toggler {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
margin-bottom: 2rem;
|
||||
.hds-page-header__main {
|
||||
flex-direction: unset;
|
||||
}
|
||||
.copy-button {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
.copy-variable span {
|
||||
color: var(--token-color-foreground-primary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,18 +25,6 @@
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.path-input {
|
||||
height: 2.25em;
|
||||
|
||||
&:disabled {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
&.error {
|
||||
color: $red;
|
||||
border-color: $red;
|
||||
}
|
||||
}
|
||||
|
||||
.duplicate-path-error {
|
||||
position: relative;
|
||||
animation: slide-in 0.3s ease-out;
|
||||
@@ -56,13 +34,21 @@
|
||||
display: grid;
|
||||
grid-template-columns: 6fr 1fr;
|
||||
gap: 0 1rem;
|
||||
align-items: start;
|
||||
.namespace-dropdown {
|
||||
white-space: nowrap;
|
||||
width: auto;
|
||||
position: relative;
|
||||
top: $hdsLabelTopOffset;
|
||||
height: $hdsInputHeight;
|
||||
}
|
||||
}
|
||||
|
||||
.key-value {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 4fr 130px;
|
||||
gap: 0 1rem;
|
||||
align-items: end;
|
||||
align-items: start;
|
||||
|
||||
input.error {
|
||||
color: $red;
|
||||
@@ -77,6 +63,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.delete-entry-button {
|
||||
position: relative;
|
||||
top: $hdsLabelTopOffset;
|
||||
height: $hdsInputHeight;
|
||||
}
|
||||
|
||||
button.show-hide-values {
|
||||
height: 100%;
|
||||
box-shadow: none;
|
||||
@@ -131,11 +123,6 @@
|
||||
grid-auto-columns: max-content;
|
||||
grid-auto-flow: column;
|
||||
gap: 1rem;
|
||||
|
||||
.button.is-info.is-inverted.add-more[disabled] {
|
||||
border-color: #dbdbdb;
|
||||
box-shadow: 0 2px 0 0 rgb(122 122 122 / 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,20 +139,8 @@ table.path-tree {
|
||||
}
|
||||
}
|
||||
|
||||
.section .notification.related-entities {
|
||||
--blue: #1563ff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
&.notification {
|
||||
align-items: center;
|
||||
}
|
||||
a {
|
||||
color: $blue;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.related-entities {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.related-entities-hint {
|
||||
@@ -178,25 +153,6 @@ table.path-tree {
|
||||
}
|
||||
}
|
||||
|
||||
.job-template-hint {
|
||||
margin-top: 0.5rem;
|
||||
code {
|
||||
background-color: #eee;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
.copy-button {
|
||||
display: inline-block;
|
||||
padding-left: 0;
|
||||
position: relative;
|
||||
top: -5px;
|
||||
button,
|
||||
.button {
|
||||
background-color: transparent;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table.variable-items {
|
||||
// table-layout: fixed;
|
||||
td.value-cell {
|
||||
|
||||
@@ -5,46 +5,50 @@
|
||||
|
||||
{{page-title "Variables"}}
|
||||
<section class="section">
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-item is-right-aligned is-mobile-full-width">
|
||||
|
||||
<Hds::PageHeader class="variable-title" as |PH|>
|
||||
<PH.Actions>
|
||||
{{#if this.namespaceOptions}}
|
||||
<SingleSelectDropdown
|
||||
data-test-variable-namespace-filter
|
||||
@label="Namespace"
|
||||
@options={{this.namespaceOptions}}
|
||||
@selection={{this.namespaceSelection}}
|
||||
@onSelect={{this.setNamespace}}
|
||||
<Hds::Dropdown data-test-variable-namespace-filter as |dd|>
|
||||
<dd.ToggleButton @text="Namespace ({{this.namespaceSelection}})" @color="secondary" />
|
||||
{{#each this.namespaceOptions as |option|}}
|
||||
<dd.Radio
|
||||
name={{option.key}}
|
||||
{{on "change" (action this.setNamespace option.key)}}
|
||||
checked={{eq this.namespaceSelection option.key}}
|
||||
>
|
||||
{{option.label}}
|
||||
</dd.Radio>
|
||||
{{/each}}
|
||||
</Hds::Dropdown>
|
||||
{{/if}}
|
||||
|
||||
{{#if (can "write variable" path="*" namespace=this.namespaceSelection)}}
|
||||
<div
|
||||
{{keyboard-shortcut
|
||||
pattern=(array "n" "v")
|
||||
action=(action this.goToNewVariable)
|
||||
label="Create Variable"
|
||||
}}
|
||||
>
|
||||
<Hds::Button
|
||||
@text="Create Variable"
|
||||
@icon="plus"
|
||||
@route="variables.new"
|
||||
data-test-create-var
|
||||
/>
|
||||
</div>
|
||||
{{else}}
|
||||
<Hds::Button
|
||||
@text="Create Variable"
|
||||
@icon="plus"
|
||||
data-test-disabled-create-var
|
||||
disabled
|
||||
/>
|
||||
{{/if}}
|
||||
<div class="button-bar">
|
||||
{{#if (can "write variable" path="*" namespace=this.namespaceSelection)}}
|
||||
<LinkTo
|
||||
@route="variables.new"
|
||||
class="button is-primary"
|
||||
data-test-create-var
|
||||
{{keyboard-shortcut
|
||||
pattern=(array "n" "v")
|
||||
action=(action this.goToNewVariable)
|
||||
label="Create Variable"
|
||||
}}
|
||||
>
|
||||
Create Variable
|
||||
</LinkTo>
|
||||
{{else}}
|
||||
<button
|
||||
class="button is-primary is-disabled tooltip is-right-aligned"
|
||||
aria-label="You don’t have sufficient permissions"
|
||||
disabled
|
||||
type="button"
|
||||
data-test-disabled-create-var
|
||||
>
|
||||
Create Variable
|
||||
</button>
|
||||
{{/if}}
|
||||
</PH.Actions>
|
||||
</Hds::PageHeader>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{#if this.isForbidden}}
|
||||
<ForbiddenMessage />
|
||||
{{else}}
|
||||
|
||||
@@ -7,15 +7,24 @@
|
||||
<Breadcrumb @crumb={{hash label="New" args=(array "variables.new")}} />
|
||||
|
||||
<section class="section">
|
||||
<h1 class="title variable-title">
|
||||
Create a Variable
|
||||
<Toggle
|
||||
data-test-memory-toggle
|
||||
@isActive={{eq this.view "json"}}
|
||||
@onToggle={{action this.toggleView}}
|
||||
title="JSON"
|
||||
>JSON</Toggle>
|
||||
</h1>
|
||||
<Hds::PageHeader class="variable-title" as |PH|>
|
||||
<PH.Title>Create a Variable</PH.Title>
|
||||
<PH.Actions>
|
||||
<Hds::Form::Toggle::Field
|
||||
@value="enable"
|
||||
{{keyboard-shortcut
|
||||
label="Toggle View (JSON/List)"
|
||||
pattern=(array "j")
|
||||
action=(action this.toggleView)
|
||||
}}
|
||||
checked={{eq this.view "json"}}
|
||||
data-test-json-toggle
|
||||
{{on "change" (action this.toggleView)}}
|
||||
as |F|>
|
||||
<F.Label>JSON</F.Label>
|
||||
</Hds::Form::Toggle::Field>
|
||||
</PH.Actions>
|
||||
</Hds::PageHeader>
|
||||
|
||||
<VariableForm
|
||||
@model={{this.model}}
|
||||
|
||||
@@ -8,44 +8,50 @@
|
||||
<Breadcrumb @crumb={{crumb}} />
|
||||
{{/each}}
|
||||
<section class="section">
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-item is-right-aligned is-mobile-full-width">
|
||||
{{#if this.namespaceOptions}}
|
||||
<SingleSelectDropdown
|
||||
data-test-variable-namespace-filter
|
||||
@label="Namespace"
|
||||
@options={{this.namespaceOptions}}
|
||||
@selection={{this.namespaceSelection}}
|
||||
@onSelect={{this.setNamespace}}
|
||||
/>
|
||||
{{/if}}
|
||||
<div class="button-bar">
|
||||
<Hds::PageHeader class="variable-title" as |PH|>
|
||||
<PH.Title>/{{this.absolutePath}}</PH.Title>
|
||||
<PH.Actions>
|
||||
{{#if this.namespaceOptions}}
|
||||
<Hds::Dropdown data-test-variable-namespace-filter as |dd|>
|
||||
<dd.ToggleButton @text="Namespace ({{this.namespaceSelection}})" @color="secondary" />
|
||||
{{#each this.namespaceOptions as |option|}}
|
||||
<dd.Radio
|
||||
name={{option.key}}
|
||||
{{on "change" (action this.setNamespace option.key)}}
|
||||
checked={{eq this.namespaceSelection option.key}}
|
||||
>
|
||||
{{option.label}}
|
||||
</dd.Radio>
|
||||
{{/each}}
|
||||
</Hds::Dropdown>
|
||||
{{/if}}
|
||||
|
||||
{{#if (can "write variable" path=(concat this.absolutePath "/") namespace=this.namespaceSelection)}}
|
||||
<LinkTo
|
||||
@route="variables.new"
|
||||
@query={{hash path=(concat this.absolutePath "/")}}
|
||||
class="button is-primary"
|
||||
<div
|
||||
{{keyboard-shortcut
|
||||
pattern=(array "n" "v")
|
||||
action=(action this.goToNewVariable)
|
||||
label="Create Variable"
|
||||
}}
|
||||
>
|
||||
Create Variable
|
||||
</LinkTo>
|
||||
<Hds::Button
|
||||
@text="Create Variable"
|
||||
@icon="plus"
|
||||
@route="variables.new"
|
||||
@query={{hash path=(concat this.absolutePath "/")}}
|
||||
data-test-create-var
|
||||
/>
|
||||
</div>
|
||||
{{else}}
|
||||
<button
|
||||
class="button is-primary is-disabled tooltip is-right-aligned"
|
||||
aria-label="You don’t have sufficient permissions"
|
||||
<Hds::Button
|
||||
@text="Create Variable"
|
||||
@icon="plus"
|
||||
data-test-create-var
|
||||
disabled
|
||||
type="button"
|
||||
>
|
||||
Create Variable
|
||||
</button>
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PH.Actions>
|
||||
</Hds::PageHeader>
|
||||
{{#if this.isForbidden}}
|
||||
<ForbiddenMessage />
|
||||
{{else}}
|
||||
|
||||
@@ -4,25 +4,30 @@
|
||||
~}}
|
||||
|
||||
{{page-title "Edit Variable"}}
|
||||
|
||||
<h1 class="title variable-title">
|
||||
<LinkTo class="back-link" @route="variables.variable.index">
|
||||
<FlightIcon
|
||||
@name="chevron-left"
|
||||
@title="Back to {{this.model.path}}"
|
||||
@size="24"
|
||||
/>
|
||||
</LinkTo>
|
||||
Edit
|
||||
{{this.model.path}}
|
||||
<Toggle
|
||||
data-test-json-toggle
|
||||
@isActive={{eq this.view "json"}}
|
||||
@onToggle={{action this.toggleView}}
|
||||
title="JSON"
|
||||
>JSON</Toggle>
|
||||
|
||||
</h1>
|
||||
<Hds::PageHeader class="variable-title" as |PH|>
|
||||
<PH.Title>Editing {{this.model.path}}</PH.Title>
|
||||
<PH.IconTile @icon="file-text" />
|
||||
<PH.Actions>
|
||||
<Hds::Form::Toggle::Field
|
||||
@value="enable"
|
||||
{{keyboard-shortcut
|
||||
label="Toggle View (JSON/List)"
|
||||
pattern=(array "j")
|
||||
action=(action this.toggleView)
|
||||
}}
|
||||
checked={{eq this.view "json"}}
|
||||
data-test-json-toggle
|
||||
{{on "change" (action this.toggleView)}}
|
||||
as |F|>
|
||||
<F.Label>JSON</F.Label>
|
||||
</Hds::Form::Toggle::Field>
|
||||
</PH.Actions>
|
||||
<PH.Breadcrumb>
|
||||
<Hds::Breadcrumb>
|
||||
<Hds::Breadcrumb::Item @text="Back" @route="variables.variable.index" @icon="chevron-left" />
|
||||
</Hds::Breadcrumb>
|
||||
</PH.Breadcrumb>
|
||||
</Hds::PageHeader>
|
||||
|
||||
<VariableForm
|
||||
@model={{this.model}}
|
||||
|
||||
@@ -3,45 +3,54 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<h1 class="variable-title title with-flex">
|
||||
<div>
|
||||
<FlightIcon @name="file-text" />
|
||||
{{this.model.path}}
|
||||
<CopyButton
|
||||
@inset={{true}}
|
||||
@compact={{true}}
|
||||
@clipboardText={{this.model.path}}
|
||||
/>
|
||||
<Hds::PageHeader class="variable-title" as |PH|>
|
||||
<PH.Title>{{this.model.path}}</PH.Title>
|
||||
<PH.IconTile @icon="file-text" />
|
||||
<PH.Actions>
|
||||
{{#unless this.isDeleting}}
|
||||
|
||||
<Toggle
|
||||
<Hds::Form::Toggle::Field
|
||||
@value="enable"
|
||||
{{keyboard-shortcut
|
||||
label="Toggle View (JSON/List)"
|
||||
pattern=(array "j")
|
||||
action=(action this.toggleView)
|
||||
}}
|
||||
data-test-memory-toggle
|
||||
@isActive={{eq this.view "json"}}
|
||||
@onToggle={{action this.toggleView}}
|
||||
title="JSON"
|
||||
>JSON</Toggle>
|
||||
</div>
|
||||
<div>
|
||||
{{#unless this.isDeleting}}
|
||||
checked={{eq this.view "json"}}
|
||||
data-test-json-toggle
|
||||
{{on "change" (action this.toggleView)}}
|
||||
as |F|>
|
||||
<F.Label>JSON</F.Label>
|
||||
</Hds::Form::Toggle::Field>
|
||||
|
||||
<div
|
||||
{{keyboard-shortcut
|
||||
label="Copy Variable"
|
||||
pattern=(array "c" "v")
|
||||
action=(action this.copyVariable)}}
|
||||
>
|
||||
<Hds::Copy::Button
|
||||
@text="Copy"
|
||||
@textToCopy={{stringify-object this.model.items}}
|
||||
@isIconOnly={{true}}
|
||||
class="copy-variable"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{{#if (can "write variable" path=this.model.path namespace=this.model.namespace)}}
|
||||
<div class="two-step-button">
|
||||
<LinkTo
|
||||
{{autofocus}}
|
||||
data-test-edit-button
|
||||
class="button is-info is-inverted is-small"
|
||||
@model={{this.model}}
|
||||
<Hds::Button
|
||||
@icon="edit"
|
||||
@text="Edit"
|
||||
@color="secondary"
|
||||
@route="variables.variable.edit"
|
||||
@model={{this.model}}
|
||||
@query={{hash view=this.view}}
|
||||
>
|
||||
Edit
|
||||
</LinkTo>
|
||||
</div>
|
||||
data-test-edit-button
|
||||
{{autofocus}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
{{#if (can "destroy variable" path=this.model.path namespace=this.model.namespace)}}
|
||||
<TwoStepButton
|
||||
data-test-delete-button
|
||||
@@ -56,8 +65,8 @@
|
||||
@onCancel={{this.onDeleteCancel}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</h1>
|
||||
</PH.Actions>
|
||||
</Hds::PageHeader>
|
||||
|
||||
{{#if this.shouldShowLinkedEntities}}
|
||||
<VariableForm::RelatedEntities
|
||||
@@ -72,59 +81,65 @@
|
||||
<div class="boxed-section">
|
||||
<div class="boxed-section-head">
|
||||
Key/Value Data
|
||||
<CopyButton
|
||||
class="pull-right"
|
||||
@compact={{true}}
|
||||
@border={{true}}
|
||||
@clipboardText={{stringify-object this.model.items}}
|
||||
/>
|
||||
</div>
|
||||
<div class="boxed-section-body is-full-bleed">
|
||||
<JsonViewer @json={{this.model.items}} />
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<ListTable class="variable-items" @source={{this.sortedKeyValues}} @sortProperty={{this.sortProperty}} @sortDescending={{this.sortDescending}} as |t|>
|
||||
<t.head>
|
||||
<t.sort-by @prop="key">Key</t.sort-by>
|
||||
<t.sort-by @prop="value">Value</t.sort-by>
|
||||
</t.head>
|
||||
<t.body as |row|>
|
||||
<tr data-test-var={{row.model.key}}>
|
||||
<td>
|
||||
{{row.model.key}}
|
||||
</td>
|
||||
<td colspan="3" class="value-cell">
|
||||
<Hds::Table class="variable-items"
|
||||
@model={{this.sortedKeyValues}}
|
||||
@sortBy={{this.sortProperty}}
|
||||
@sortOrder={{if this.sortDescending "desc" "asc"}}
|
||||
@columns={{array
|
||||
(hash
|
||||
key="key"
|
||||
label="Key"
|
||||
isSortable=true
|
||||
width="200px"
|
||||
)
|
||||
(hash
|
||||
key="value"
|
||||
label="Value"
|
||||
isSortable=true
|
||||
)
|
||||
}}
|
||||
>
|
||||
<:body as |B|>
|
||||
<B.Tr data-test-var={{B.data.key}}>
|
||||
<B.Td>{{B.data.key}}</B.Td>
|
||||
<B.Td class="value-cell">
|
||||
<div>
|
||||
<CopyButton
|
||||
@compact={{true}}
|
||||
@clipboardText={{row.model.value}}
|
||||
@clipboardText={{B.data.value}}
|
||||
/>
|
||||
<button
|
||||
class="show-hide-values button is-borderless is-compact"
|
||||
type="button"
|
||||
{{on "click" (action this.toggleRowVisibility row.model)}}
|
||||
{{on "click" (action this.toggleRowVisibility B.data)}}
|
||||
{{keyboard-shortcut
|
||||
label="Toggle Variable Visibility"
|
||||
pattern=(array "v")
|
||||
action=(action this.toggleRowVisibility row.model)
|
||||
action=(action this.toggleRowVisibility B.data)
|
||||
}}
|
||||
|
||||
>
|
||||
<FlightIcon
|
||||
@name={{if row.model.isVisible "eye" "eye-off"}}
|
||||
@title={{if row.model.isVisible "Hide Value" "Show Value"}}
|
||||
@name={{if B.data.isVisible "eye" "eye-off"}}
|
||||
@title={{if B.data.isVisible "Hide Value" "Show Value"}}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{{#if row.model.isVisible}}
|
||||
<code>{{row.model.value}}</code>
|
||||
{{#if B.data.isVisible}}
|
||||
<code>{{B.data.value}}</code>
|
||||
{{else}}
|
||||
********
|
||||
{{/if}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</t.body>
|
||||
</ListTable>
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
|
||||
{{/if}}
|
||||
|
||||
@@ -13,10 +13,7 @@ import {
|
||||
visit,
|
||||
} from '@ember/test-helpers';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import {
|
||||
selectChoose,
|
||||
clickTrigger,
|
||||
} from 'ember-power-select/test-support/helpers';
|
||||
import { clickToggle, clickOption } from 'nomad-ui/tests/helpers/helios';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { module, test } from 'qunit';
|
||||
import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit';
|
||||
@@ -365,14 +362,13 @@ module('Acceptance | variables', function (hooks) {
|
||||
window.localStorage.nomadTokenSecret = server.db.tokens[0].secretId;
|
||||
await Variables.visitNew();
|
||||
assert.equal(currentURL(), '/variables/new');
|
||||
await typeIn('.path-input', 'foo/bar');
|
||||
await typeIn('[data-test-path-input]', 'foo/bar');
|
||||
await click('button[type="submit"]');
|
||||
assert.dom('.flash-message.alert-critical').exists();
|
||||
await click('.flash-message.alert-critical .hds-dismiss-button');
|
||||
assert.dom('.flash-message.alert-critical').doesNotExist();
|
||||
|
||||
await typeIn('.key-value label:nth-child(1) input', 'myKey');
|
||||
await typeIn('.key-value label:nth-child(2) input', 'superSecret');
|
||||
await typeIn('[data-test-var-key]', 'myKey');
|
||||
await typeIn('[data-test-var-value]', 'superSecret');
|
||||
|
||||
await percySnapshot(assert);
|
||||
|
||||
@@ -412,9 +408,12 @@ module('Acceptance | variables', function (hooks) {
|
||||
assert.equal(currentRouteName(), 'variables.new');
|
||||
|
||||
await typeIn('[data-test-path-input]', 'foo/bar');
|
||||
await clickTrigger('[data-test-variable-namespace-filter]');
|
||||
|
||||
assert.dom('.dropdown-options').exists('Namespace can be edited.');
|
||||
await clickToggle('[data-test-variable-namespace-filter]');
|
||||
assert
|
||||
.dom(
|
||||
'[data-test-variable-namespace-filter] .hds-menu-primitive__content'
|
||||
)
|
||||
.exists('Namespace can be edited.');
|
||||
assert
|
||||
.dom('[data-test-variable-namespace-filter]')
|
||||
.containsText(
|
||||
@@ -422,10 +421,7 @@ module('Acceptance | variables', function (hooks) {
|
||||
'The first alphabetically sorted namespace should be selected as the default option.'
|
||||
);
|
||||
|
||||
await selectChoose(
|
||||
'[data-test-variable-namespace-filter]',
|
||||
'namespace-1'
|
||||
);
|
||||
await clickOption('[data-test-variable-namespace-filter]', 'namespace-1');
|
||||
await typeIn('[data-test-var-key]', 'kiki');
|
||||
await typeIn('[data-test-var-value]', 'do you love me');
|
||||
await click('[data-test-submit-var]');
|
||||
@@ -555,7 +551,7 @@ module('Acceptance | variables', function (hooks) {
|
||||
.dom('.related-entities-hint')
|
||||
.doesNotExist('Hides the hint when editing a job template variable');
|
||||
assert
|
||||
.dom('.job-template-hint')
|
||||
.dom('[data-test-job-template-hint]')
|
||||
.exists('Shows a hint about job templates');
|
||||
assert
|
||||
.dom('.CodeMirror')
|
||||
@@ -574,7 +570,7 @@ module('Acceptance | variables', function (hooks) {
|
||||
|
||||
module('edit flow', function () {
|
||||
test('allows a user with correct permissions to edit a variable', async function (assert) {
|
||||
assert.expect(8);
|
||||
assert.expect(7);
|
||||
// Arrange Test Set-up
|
||||
allScenarios.variableTestCluster(server);
|
||||
server.createList('variable', 3);
|
||||
@@ -603,10 +599,6 @@ module('Acceptance | variables', function (hooks) {
|
||||
await percySnapshot(assert);
|
||||
|
||||
assert.dom('[data-test-path-input]').isDisabled('Path cannot be edited');
|
||||
await clickTrigger('[data-test-variable-namespace-filter]');
|
||||
assert
|
||||
.dom('.dropdown-options')
|
||||
.doesNotExist('Namespace cannot be edited.');
|
||||
|
||||
document.querySelector('[data-test-var-key]').value = ''; // clear current input
|
||||
await typeIn('[data-test-var-key]', 'kiki');
|
||||
@@ -884,8 +876,8 @@ module('Acceptance | variables', function (hooks) {
|
||||
});
|
||||
|
||||
// Act
|
||||
await clickTrigger('[data-test-variable-namespace-filter]');
|
||||
await selectChoose('[data-test-variable-namespace-filter]', 'default');
|
||||
await clickToggle('[data-test-variable-namespace-filter]');
|
||||
await clickOption('[data-test-variable-namespace-filter]', 'default');
|
||||
|
||||
assert
|
||||
.dom('[data-test-no-matching-variables-list-headline]')
|
||||
@@ -946,8 +938,8 @@ module('Acceptance | variables', function (hooks) {
|
||||
});
|
||||
|
||||
// Act
|
||||
await clickTrigger('[data-test-variable-namespace-filter]');
|
||||
await selectChoose('[data-test-variable-namespace-filter]', 'default');
|
||||
await clickToggle('[data-test-variable-namespace-filter]');
|
||||
await clickOption('[data-test-variable-namespace-filter]', 'default');
|
||||
|
||||
assert
|
||||
.dom('[data-test-no-matching-variables-list-headline]')
|
||||
|
||||
38
ui/tests/helpers/helios.js
Normal file
38
ui/tests/helpers/helios.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
import {
|
||||
click,
|
||||
// fillIn,
|
||||
// triggerKeyEvent,
|
||||
// triggerEvent,
|
||||
} from '@ember/test-helpers';
|
||||
|
||||
/**
|
||||
* @param {string} scope
|
||||
* @param {*} options
|
||||
*/
|
||||
export async function clickToggle(scope, options) {
|
||||
let selector = '.hds-dropdown-toggle-button';
|
||||
if (scope) {
|
||||
selector = `${scope} ${selector}`;
|
||||
}
|
||||
return click(selector, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} scope
|
||||
* @param {string} option the name of the option to click
|
||||
* @param {*} options
|
||||
*/
|
||||
export async function clickOption(scope, option, options) {
|
||||
let selector = `.hds-dropdown-list-item label input[name="${option}"]`;
|
||||
if (scope) {
|
||||
selector = `${scope} ${selector}`;
|
||||
}
|
||||
return click(selector, options);
|
||||
}
|
||||
@@ -12,10 +12,8 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import setupCodeMirror from 'nomad-ui/tests/helpers/codemirror';
|
||||
import { codeFillable, code } from 'nomad-ui/tests/pages/helpers/codemirror';
|
||||
import percySnapshot from '@percy/ember';
|
||||
import {
|
||||
selectChoose,
|
||||
clickTrigger,
|
||||
} from 'ember-power-select/test-support/helpers';
|
||||
import { clickToggle, clickOption } from 'nomad-ui/tests/helpers/helios';
|
||||
|
||||
import faker from 'nomad-ui/mirage/faker';
|
||||
|
||||
module('Integration | Component | variable-form', function (hooks) {
|
||||
@@ -57,7 +55,7 @@ module('Integration | Component | variable-form', function (hooks) {
|
||||
'The "Add More" button is disabled until key and value are filled'
|
||||
);
|
||||
|
||||
await typeIn('.key-value label:nth-child(1) input', 'foo');
|
||||
await typeIn('[data-test-var-key]', 'foo');
|
||||
|
||||
assert
|
||||
.dom('[data-test-add-kv]')
|
||||
@@ -65,7 +63,7 @@ module('Integration | Component | variable-form', function (hooks) {
|
||||
'The "Add More" button is still disabled with only key filled'
|
||||
);
|
||||
|
||||
await typeIn('.key-value label:nth-child(2) input', 'bar');
|
||||
await typeIn('[data-test-var-value]', 'bar');
|
||||
|
||||
assert
|
||||
.dom('[data-test-add-kv]')
|
||||
@@ -81,9 +79,8 @@ module('Integration | Component | variable-form', function (hooks) {
|
||||
'A second KV row exists after adding a new one'
|
||||
);
|
||||
|
||||
await typeIn('.key-value:last-of-type label:nth-child(1) input', 'foo');
|
||||
await typeIn('.key-value:last-of-type label:nth-child(2) input', 'bar');
|
||||
|
||||
await typeIn('.key-value:last-of-type [data-test-var-key]', 'foo');
|
||||
await typeIn('.key-value:last-of-type [data-test-var-value]', 'bar');
|
||||
await click('[data-test-add-kv]');
|
||||
|
||||
assert.equal(
|
||||
@@ -92,7 +89,7 @@ module('Integration | Component | variable-form', function (hooks) {
|
||||
'A third KV row exists after adding a new one'
|
||||
);
|
||||
|
||||
await click('.key-value button.delete-row');
|
||||
await click('.delete-entry-button');
|
||||
|
||||
assert.equal(
|
||||
findAll('div.key-value').length,
|
||||
@@ -116,37 +113,33 @@ module('Integration | Component | variable-form', function (hooks) {
|
||||
await render(hbs`<VariableForm @model={{this.mockedModel}} />`);
|
||||
await click('[data-test-add-kv]'); // add a second variable
|
||||
|
||||
findAll('input.value-input').forEach((input, iter) => {
|
||||
assert.equal(
|
||||
input.getAttribute('type'),
|
||||
'password',
|
||||
findAll('.value-label').forEach((label, iter) => {
|
||||
const maskedInput = label.querySelector('.hds-form-masked-input');
|
||||
assert.ok(
|
||||
maskedInput.classList.contains('hds-form-masked-input--is-masked'),
|
||||
`Value ${iter + 1} is hidden by default`
|
||||
);
|
||||
});
|
||||
|
||||
await click('.key-value button.show-hide-values');
|
||||
const [firstRow, secondRow] = findAll('input.value-input');
|
||||
await click('.hds-form-visibility-toggle');
|
||||
const [firstRow, secondRow] = findAll('.hds-form-masked-input');
|
||||
|
||||
assert.equal(
|
||||
firstRow.getAttribute('type'),
|
||||
'text',
|
||||
assert.ok(
|
||||
firstRow.classList.contains('hds-form-masked-input--is-not-masked'),
|
||||
'Only the row that is clicked on toggles visibility'
|
||||
);
|
||||
assert.equal(
|
||||
secondRow.getAttribute('type'),
|
||||
'password',
|
||||
assert.ok(
|
||||
secondRow.classList.contains('hds-form-masked-input--is-masked'),
|
||||
'Rows that are not clicked remain obscured'
|
||||
);
|
||||
|
||||
await click('.key-value button.show-hide-values');
|
||||
assert.equal(
|
||||
firstRow.getAttribute('type'),
|
||||
'password',
|
||||
await click('.hds-form-visibility-toggle');
|
||||
assert.ok(
|
||||
firstRow.classList.contains('hds-form-masked-input--is-masked'),
|
||||
'Only the row that is clicked on toggles visibility'
|
||||
);
|
||||
assert.equal(
|
||||
secondRow.getAttribute('type'),
|
||||
'password',
|
||||
assert.ok(
|
||||
secondRow.classList.contains('hds-form-masked-input--is-masked'),
|
||||
'Rows that are not clicked remain obscured'
|
||||
);
|
||||
await percySnapshot(assert);
|
||||
@@ -177,7 +170,7 @@ module('Integration | Component | variable-form', function (hooks) {
|
||||
'Shows 5 existing key values'
|
||||
);
|
||||
assert.equal(
|
||||
findAll('button.delete-row').length,
|
||||
findAll('.delete-entry-button').length,
|
||||
5,
|
||||
'Shows "delete" for all five rows'
|
||||
);
|
||||
@@ -189,13 +182,13 @@ module('Integration | Component | variable-form', function (hooks) {
|
||||
|
||||
findAll('div.key-value').forEach((row, idx) => {
|
||||
assert.equal(
|
||||
row.querySelector(`label:nth-child(1) input`).value,
|
||||
row.querySelector(`[data-test-var-key]`).value,
|
||||
keyValues[idx].key,
|
||||
`Key ${idx + 1} is correct`
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
row.querySelector(`label:nth-child(2) input`).value,
|
||||
row.querySelector(`[data-test-var-value]`).value,
|
||||
keyValues[idx].value,
|
||||
keyValues[idx].value
|
||||
);
|
||||
@@ -214,9 +207,9 @@ module('Integration | Component | variable-form', function (hooks) {
|
||||
variable.isNew = false;
|
||||
this.set('variable', variable);
|
||||
await render(hbs`<VariableForm @model={{this.variable}} />`);
|
||||
assert.dom('input.path-input').hasValue('/baz/bat', 'Path is set');
|
||||
assert.dom('[data-test-path-input]').hasValue('/baz/bat', 'Path is set');
|
||||
assert
|
||||
.dom('input.path-input')
|
||||
.dom('[data-test-path-input]')
|
||||
.isDisabled('Existing variable is in disabled state');
|
||||
|
||||
variable.isNew = true;
|
||||
@@ -224,7 +217,7 @@ module('Integration | Component | variable-form', function (hooks) {
|
||||
this.set('variable', variable);
|
||||
await render(hbs`<VariableForm @model={{this.variable}} />`);
|
||||
assert
|
||||
.dom('input.path-input')
|
||||
.dom('[data-test-path-input]')
|
||||
.isNotDisabled('New variable is not in disabled state');
|
||||
});
|
||||
|
||||
@@ -254,27 +247,36 @@ module('Integration | Component | variable-form', function (hooks) {
|
||||
hbs`<VariableForm @model={{this.mockedModel}} @existingVariables={{this.existingVariables}} />`
|
||||
);
|
||||
|
||||
await typeIn('.path-input', 'foo/bar');
|
||||
assert.dom('.duplicate-path-error').doesNotExist();
|
||||
assert.dom('.path-input').doesNotHaveClass('error');
|
||||
await typeIn('[data-test-path-input]', 'foo/bar');
|
||||
assert.dom('[data-test-duplicate-variable-error]').doesNotExist();
|
||||
assert
|
||||
.dom('[data-test-path-input]')
|
||||
.doesNotHaveClass('hds-form-text-input--is-invalid');
|
||||
|
||||
document.querySelector('.path-input').value = ''; // clear current input
|
||||
await typeIn('.path-input', 'baz/bat');
|
||||
assert.dom('.duplicate-path-error').exists();
|
||||
assert.dom('.path-input').hasClass('error');
|
||||
document.querySelector('[data-test-path-input]').value = ''; // clear current input
|
||||
await typeIn('[data-test-path-input]', 'baz/bat');
|
||||
|
||||
await clickTrigger('[data-test-variable-namespace-filter]');
|
||||
await selectChoose(
|
||||
assert.dom('[data-test-duplicate-variable-error]').exists();
|
||||
assert
|
||||
.dom('[data-test-path-input]')
|
||||
.hasClass('hds-form-text-input--is-invalid');
|
||||
|
||||
await clickToggle('[data-test-variable-namespace-filter]');
|
||||
await clickOption(
|
||||
'[data-test-variable-namespace-filter]',
|
||||
server.db.namespaces[2].id
|
||||
);
|
||||
assert.dom('.duplicate-path-error').doesNotExist();
|
||||
assert.dom('.path-input').doesNotHaveClass('error');
|
||||
assert.dom('[data-test-duplicate-variable-error]').doesNotExist();
|
||||
assert
|
||||
.dom('[data-test-path-input]')
|
||||
.doesNotHaveClass('hds-form-text-input--is-invalid');
|
||||
|
||||
document.querySelector('.path-input').value = ''; // clear current input
|
||||
await typeIn('.path-input', 'baz/bat/qux');
|
||||
assert.dom('.duplicate-path-error').exists();
|
||||
assert.dom('.path-input').hasClass('error');
|
||||
document.querySelector('[data-test-path-input]').value = ''; // clear current input
|
||||
await typeIn('[data-test-path-input]', 'baz/bat/qux');
|
||||
assert.dom('[data-test-duplicate-variable-error]').exists();
|
||||
assert
|
||||
.dom('[data-test-path-input]')
|
||||
.hasClass('hds-form-text-input--is-invalid');
|
||||
});
|
||||
|
||||
test('warns you when you set a key with . in it', async function (assert) {
|
||||
@@ -424,11 +426,8 @@ module('Integration | Component | variable-form', function (hooks) {
|
||||
|
||||
await click('[data-test-add-kv]');
|
||||
|
||||
await typeIn('.key-value:last-of-type label:nth-child(1) input', 'howdy');
|
||||
await typeIn(
|
||||
'.key-value:last-of-type label:nth-child(2) input',
|
||||
'partner'
|
||||
);
|
||||
await typeIn('.key-value:last-of-type [data-test-var-key]', 'howdy');
|
||||
await typeIn('.key-value:last-of-type [data-test-var-value]', 'partner');
|
||||
|
||||
this.set('view', 'json');
|
||||
|
||||
@@ -459,13 +458,13 @@ module('Integration | Component | variable-form', function (hooks) {
|
||||
);
|
||||
this.set('view', 'table');
|
||||
assert.equal(
|
||||
find(`.key-value:last-of-type label:nth-child(1) input`).value,
|
||||
find(`.key-value:last-of-type [data-test-var-key]`).value,
|
||||
'golden',
|
||||
'Key persists from JSON to Table'
|
||||
);
|
||||
|
||||
assert.equal(
|
||||
find(`.key-value:last-of-type label:nth-child(2) input`).value,
|
||||
find(`.key-value:last-of-type [data-test-var-value]`).value,
|
||||
'gate',
|
||||
'Value persists from JSON to Table'
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user