-
+
-
Hide details
+
Hide details
{{#yield-slot 'details'}}
{{yield item index}}
diff --git a/ui-v2/app/templates/components/tag-list.hbs b/ui-v2/app/templates/components/tag-list.hbs
new file mode 100644
index 0000000000..c51ea2a418
--- /dev/null
+++ b/ui-v2/app/templates/components/tag-list.hbs
@@ -0,0 +1,8 @@
+{{#if (gt items.length 0)}}
+
Tags
+
+ {{#each items as |item|}}
+ {{item}}
+ {{/each}}
+
+{{/if}}
diff --git a/ui-v2/app/templates/components/templated-anchor.hbs b/ui-v2/app/templates/components/templated-anchor.hbs
new file mode 100644
index 0000000000..fb5c4b157d
--- /dev/null
+++ b/ui-v2/app/templates/components/templated-anchor.hbs
@@ -0,0 +1 @@
+{{yield}}
\ No newline at end of file
diff --git a/ui-v2/app/templates/components/token-list.hbs b/ui-v2/app/templates/components/token-list.hbs
index 5cd449ce5c..0462a1fea3 100644
--- a/ui-v2/app/templates/components/token-list.hbs
+++ b/ui-v2/app/templates/components/token-list.hbs
@@ -2,6 +2,7 @@
{{#if (gt items.length 0)}}
{{#tabular-collection
data-test-tokens
+ class='token-list'
items=(sort-by 'AccessorID:asc' items) as |item index|
}}
{{#if caption}}
diff --git a/ui-v2/app/templates/dc/acls/-form.hbs b/ui-v2/app/templates/dc/acls/-form.hbs
index b0ec83363c..c6dac4d759 100644
--- a/ui-v2/app/templates/dc/acls/-form.hbs
+++ b/ui-v2/app/templates/dc/acls/-form.hbs
@@ -14,7 +14,7 @@
Policy (HCL Format)
- {{code-editor class=(if item.error.Rules 'error') name='Rules' value=item.Rules syntax="hcl" onkeyup=(action 'change')}}
+ {{code-editor class=(if item.error.Rules 'error') name='Rules' value=item.Rules syntax="hcl" onkeyup=(action 'change' 'Rules')}}
{{#if create }}
diff --git a/ui-v2/app/templates/dc/acls/-nav.hbs b/ui-v2/app/templates/dc/acls/-nav.hbs
index 4b7625a82e..9d794f4443 100644
--- a/ui-v2/app/templates/dc/acls/-nav.hbs
+++ b/ui-v2/app/templates/dc/acls/-nav.hbs
@@ -3,11 +3,17 @@
(hash
label='Tokens'
href=(href-to 'dc.acls.tokens')
+ selected=(is-href 'dc.acls.tokens')
+ )
+ (hash
+ label='Roles'
+ href=(href-to 'dc.acls.roles')
+ selected=(is-href 'dc.acls.roles')
)
(hash
label='Policies'
href=(href-to 'dc.acls.policies')
+ selected=(is-href 'dc.acls.policies')
)
)
- selected=(if (is-href 'dc.acls.policies') 'policies' 'tokens')
}}
diff --git a/ui-v2/app/templates/dc/acls/index.hbs b/ui-v2/app/templates/dc/acls/index.hbs
index 52cc48f51a..497ec5bffa 100644
--- a/ui-v2/app/templates/dc/acls/index.hbs
+++ b/ui-v2/app/templates/dc/acls/index.hbs
@@ -4,7 +4,7 @@
{{/block-slot}}
{{#block-slot 'header'}}
- ACL Tokens
+ ACL Tokens {{format-number items.length}} total
{{/block-slot}}
@@ -13,87 +13,90 @@
{{/block-slot}}
{{#block-slot 'toolbar'}}
{{#if (gt items.length 0) }}
- {{acl-filter filters=typeFilters search=filters.s type=filters.type onchange=(action 'filter')}}
+ {{acl-filter searchable=searchable filters=typeFilters search=filters.s type=filters.type onchange=(action 'filter')}}
{{/if}}
{{/block-slot}}
{{#block-slot 'content'}}
-{{#if (gt filtered.length 0)}}
- {{#tabular-collection
- items=(sort-by 'Name:asc' filtered) as |item index|
- }}
- {{#block-slot 'header'}}
- Name
- Type
- {{/block-slot}}
- {{#block-slot 'row'}}
-
- {{item.Name}}
-
-
- {{#if (eq item.Type 'management')}}
- {{item.Type}}
- {{else}}
- {{item.Type}}
- {{/if}}
-
- {{/block-slot}}
- {{#block-slot 'actions' as |index change checked|}}
- {{#confirmation-dialog confirming=false index=index}}
- {{#block-slot 'action' as |confirm|}}
- {{#action-group index=index onchange=(action change) checked=(if (eq checked index) 'checked')}}
-
- {{/action-group}}
- {{/block-slot}}
- {{#block-slot 'dialog' as |execute cancel message name|}}
-
- {{#if (eq name 'delete')}}
- Are you sure you want to delete this ACL token?
- {{else if (eq name 'logout')}}
- Are you sure you want to stop using this ACL token? This will log you out.
- {{ else if (eq name 'use')}}
- Are you sure you want to use this ACL token?
- {{/if}}
-
-
- {{#if (eq name 'delete')}}
- Confirm Delete
- {{else if (eq name 'logout')}}
- Confirm Logout
- {{ else if (eq name 'use')}}
- Confirm Use
- {{/if}}
-
- Cancel
- {{/block-slot}}
- {{/confirmation-dialog}}
- {{/block-slot}}
- {{/tabular-collection}}
-{{else}}
-
- There are no ACLs.
-
+
+ Delete
+
{{/if}}
+
+ {{/action-group}}
+ {{/block-slot}}
+ {{#block-slot 'dialog' as |execute cancel message name|}}
+
+ {{#if (eq name 'delete')}}
+ Are you sure you want to delete this ACL token?
+ {{else if (eq name 'logout')}}
+ Are you sure you want to stop using this ACL token? This will log you out.
+ {{ else if (eq name 'use')}}
+ Are you sure you want to use this ACL token?
+ {{/if}}
+
+
+ {{#if (eq name 'delete')}}
+ Confirm Delete
+ {{else if (eq name 'logout')}}
+ Confirm Logout
+ {{ else if (eq name 'use')}}
+ Confirm Use
+ {{/if}}
+
+ Cancel
+ {{/block-slot}}
+ {{/confirmation-dialog}}
+ {{/block-slot}}
+ {{/tabular-collection}}
+ {{/block-slot}}
+ {{#block-slot 'empty'}}
+
+ There are no ACLs.
+
+ {{/block-slot}}
+ {{/changeable-set}}
{{/block-slot}}
{{/app-view}}
\ No newline at end of file
diff --git a/ui-v2/app/templates/dc/acls/policies/-fieldsets.hbs b/ui-v2/app/templates/dc/acls/policies/-fieldsets.hbs
deleted file mode 100644
index a2755edbfb..0000000000
--- a/ui-v2/app/templates/dc/acls/policies/-fieldsets.hbs
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
- Name
- {{input value=item.Name name='policy[Name]' autofocus='autofocus'}}
-
- Maximum 128 characters. May only include letters (uppercase and/or lowercase) and/or numbers. Must be unique.
-
- {{#if item.error.Name}}
- {{item.error.Name.validation}}
- {{/if}}
-
-
- Rules (HCL Format)
- {{code-editor id="policy_rules" syntax='hcl' class=(if item.error.Rules 'error') name='policy[Rules]' value=item.Rules onkeyup=(action 'change' 'policy[Rules]')}}
- {{#if item.error.Rules}}
- {{item.error.Rules.validation}}
- {{/if}}
-
-
- Valid datacenters
-
-
- All
-
-
-{{#if isScoped }}
-
- {{#each datacenters as |dc| }}
-
-
- {{dc.Name}}
-
- {{/each}}
- {{#each item.Datacenters as |dc| }}
- {{#if (not (find-by 'Name' dc datacenters))}}
-
-
- {{dc}}
-
- {{/if}}
- {{/each}}
-
-{{/if}}
-
- Description (Optional)
-
-
-
-
diff --git a/ui-v2/app/templates/dc/acls/policies/-form.hbs b/ui-v2/app/templates/dc/acls/policies/-form.hbs
index 7896e80b25..593a9764ed 100644
--- a/ui-v2/app/templates/dc/acls/policies/-form.hbs
+++ b/ui-v2/app/templates/dc/acls/policies/-form.hbs
@@ -1,5 +1,8 @@
{{/if}}
-{{#if (gt filtered.length 0)}}
- {{#tabular-collection
- items=(sort-by 'CreateIndex:desc' 'Name:asc' filtered) as |item index|
- }}
- {{#block-slot 'header'}}
- Name
- Datacenters
- Description
- {{/block-slot}}
- {{#block-slot 'row' }}
-
- {{item.Name}}
-
-
- {{join ', ' (policy/datacenters item)}}
-
-
- {{item.Description}}
-
- {{/block-slot}}
- {{#block-slot 'actions' as |index change checked|}}
- {{#confirmation-dialog confirming=false index=index message="Are you sure you want to delete this Policy?"}}
- {{#block-slot 'action' as |confirm|}}
- {{#action-group index=index onchange=(action change) checked=(if (eq checked index) 'checked')}}
-
-{{#if (policy/is-management item)}}
-
- View
-
-{{else}}
+ {{#changeable-set dispatcher=searchable}}
+ {{#block-slot 'set' as |filtered|}}
+ {{#tabular-collection
+ items=(sort-by 'CreateIndex:desc' 'Name:asc' filtered) as |item index|
+ }}
+ {{#block-slot 'header'}}
+ Name
+ Datacenters
+ Description
+ {{/block-slot}}
+ {{#block-slot 'row' }}
+
+ {{item.Name}}
+
+
+ {{join ', ' (policy/datacenters item)}}
+
+
+ {{item.Description}}
+
+ {{/block-slot}}
+ {{#block-slot 'actions' as |index change checked|}}
+ {{#confirmation-dialog confirming=false index=index message="Are you sure you want to delete this Policy?"}}
+ {{#block-slot 'action' as |confirm|}}
+ {{#action-group index=index onchange=(action change) checked=(if (eq checked index) 'checked')}}
+
+ {{#if (eq (policy/typeof item) 'policy-management')}}
+
+ View
+
+ {{else}}
-
- Edit
-
-
- Delete
-
-{{/if}}
-
- {{/action-group}}
- {{/block-slot}}
- {{#block-slot 'dialog' as |execute cancel message name|}}
- {{delete-confirmation message=message execute=execute cancel=cancel}}
- {{/block-slot}}
- {{/confirmation-dialog}}
- {{/block-slot}}
- {{/tabular-collection}}
-{{else}}
-
- There are no Policies.
-
-{{/if}}
+
+ Edit
+
+
+ Delete
+
+ {{/if}}
+
+ {{/action-group}}
+ {{/block-slot}}
+ {{#block-slot 'dialog' as |execute cancel message name|}}
+ {{delete-confirmation message=message execute=execute cancel=cancel}}
+ {{/block-slot}}
+ {{/confirmation-dialog}}
+ {{/block-slot}}
+ {{/tabular-collection}}
+ {{/block-slot}}
+ {{#block-slot 'empty'}}
+
+ There are no Policies.
+
+ {{/block-slot}}
+ {{/changeable-set}}
{{/block-slot}}
{{/app-view}}
\ No newline at end of file
diff --git a/ui-v2/app/templates/dc/acls/roles/-form.hbs b/ui-v2/app/templates/dc/acls/roles/-form.hbs
new file mode 100644
index 0000000000..effee582aa
--- /dev/null
+++ b/ui-v2/app/templates/dc/acls/roles/-form.hbs
@@ -0,0 +1,50 @@
+
diff --git a/ui-v2/app/templates/dc/acls/roles/-notifications.hbs b/ui-v2/app/templates/dc/acls/roles/-notifications.hbs
new file mode 100644
index 0000000000..fbbf8aa656
--- /dev/null
+++ b/ui-v2/app/templates/dc/acls/roles/-notifications.hbs
@@ -0,0 +1,20 @@
+{{#if (eq type 'create')}}
+ {{#if (eq status 'success') }}
+ Your role has been added.
+ {{else}}
+ There was an error adding your role.
+ {{/if}}
+{{else if (eq type 'update') }}
+ {{#if (eq status 'success') }}
+ Your role has been saved.
+ {{else}}
+ There was an error saving your role.
+ {{/if}}
+{{ else if (eq type 'delete')}}
+ {{#if (eq status 'success') }}
+ Your role was deleted.
+ {{else}}
+ There was an error deleting your role.
+ {{/if}}
+{{/if}}
+
diff --git a/ui-v2/app/templates/dc/acls/roles/edit.hbs b/ui-v2/app/templates/dc/acls/roles/edit.hbs
new file mode 100644
index 0000000000..1c71ea2134
--- /dev/null
+++ b/ui-v2/app/templates/dc/acls/roles/edit.hbs
@@ -0,0 +1,32 @@
+{{#app-view class=(concat 'role ' (if (or isAuthorized isEnabled) 'edit' 'list')) loading=isLoading authorized=isAuthorized enabled=isEnabled}}
+ {{#block-slot 'notification' as |status type|}}
+ {{partial 'dc/acls/roles/notifications'}}
+ {{/block-slot}}
+ {{#block-slot 'disabled'}}
+ {{partial 'dc/acls/disabled'}}
+ {{/block-slot}}
+ {{#block-slot 'authorization'}}
+ {{partial 'dc/acls/authorization'}}
+ {{/block-slot}}
+ {{#block-slot 'breadcrumbs'}}
+
+ All Roles
+
+ {{/block-slot}}
+ {{#block-slot 'header'}}
+
+{{#if isAuthorized }}
+ {{#if create }}
+ New Role
+ {{else}}
+ Edit Role
+ {{/if}}
+{{else}}
+ Access Controls
+{{/if}}
+
+ {{/block-slot}}
+ {{#block-slot 'content'}}
+ {{ partial 'dc/acls/roles/form'}}
+ {{/block-slot}}
+{{/app-view}}
\ No newline at end of file
diff --git a/ui-v2/app/templates/dc/acls/roles/index.hbs b/ui-v2/app/templates/dc/acls/roles/index.hbs
new file mode 100644
index 0000000000..e1fb71ec8c
--- /dev/null
+++ b/ui-v2/app/templates/dc/acls/roles/index.hbs
@@ -0,0 +1,79 @@
+{{#app-view class=(concat 'role ' (if (not isAuthorized) 'edit' 'list')) loading=isLoading authorized=isAuthorized enabled=isEnabled}}
+ {{#block-slot 'notification' as |status type|}}
+ {{partial 'dc/acls/roles/notifications'}}
+ {{/block-slot}}
+ {{#block-slot 'header'}}
+
+ Access Controls
+
+ {{#if isAuthorized }}
+ {{partial 'dc/acls/nav'}}
+ {{/if}}
+ {{/block-slot}}
+ {{#block-slot 'disabled'}}
+ {{partial 'dc/acls/disabled'}}
+ {{/block-slot}}
+ {{#block-slot 'authorization'}}
+ {{partial 'dc/acls/authorization'}}
+ {{/block-slot}}
+ {{#block-slot 'actions'}}
+ Create
+ {{/block-slot}}
+ {{#block-slot 'content'}}
+{{#if (gt items.length 0) }}
+
+{{/if}}
+ {{#changeable-set dispatcher=searchable}}
+ {{#block-slot 'set' as |filtered|}}
+ {{#tabular-collection
+ items=(sort-by 'CreateIndex:desc' 'Name:asc' filtered) as |item index|
+ }}
+ {{#block-slot 'header'}}
+ Name
+ Description
+ Policies
+ {{/block-slot}}
+ {{#block-slot 'row' }}
+
+ {{item.Name}}
+
+
+ {{item.Description}}
+
+
+ {{#each item.Policies as |item|}}
+ {{item.Name}}
+ {{/each}}
+
+ {{/block-slot}}
+ {{#block-slot 'actions' as |index change checked|}}
+ {{#confirmation-dialog confirming=false index=index message="Are you sure you want to delete this Role?"}}
+ {{#block-slot 'action' as |confirm|}}
+ {{#action-group index=index onchange=(action change) checked=(if (eq checked index) 'checked')}}
+
+
+ Edit
+
+
+ Delete
+
+
+ {{/action-group}}
+ {{/block-slot}}
+ {{#block-slot 'dialog' as |execute cancel message name|}}
+ {{delete-confirmation message=message execute=execute cancel=cancel}}
+ {{/block-slot}}
+ {{/confirmation-dialog}}
+ {{/block-slot}}
+ {{/tabular-collection}}
+ {{/block-slot}}
+ {{#block-slot 'empty'}}
+
+ There are no Roles.
+
+ {{/block-slot}}
+ {{/changeable-set}}
+ {{/block-slot}}
+{{/app-view}}
\ No newline at end of file
diff --git a/ui-v2/app/templates/dc/acls/tokens/-fieldsets.hbs b/ui-v2/app/templates/dc/acls/tokens/-fieldsets.hbs
index 96ff233eaf..d279d6b1fb 100644
--- a/ui-v2/app/templates/dc/acls/tokens/-fieldsets.hbs
+++ b/ui-v2/app/templates/dc/acls/tokens/-fieldsets.hbs
@@ -14,7 +14,11 @@
-
- Policies
- {{partial 'dc/acls/tokens/policies'}}
+
+ Roles
+ {{role-selector dc=dc items=item.Roles}}
+
+
+ Policies
+ {{policy-selector dc=dc items=item.Policies}}
diff --git a/ui-v2/app/templates/dc/acls/tokens/-policies.hbs b/ui-v2/app/templates/dc/acls/tokens/-policies.hbs
deleted file mode 100644
index c1cb54d632..0000000000
--- a/ui-v2/app/templates/dc/acls/tokens/-policies.hbs
+++ /dev/null
@@ -1,77 +0,0 @@
-
- Create new policy
- {{#modal-dialog data-test-policy-form onclose=(action 'sendClearPolicy') onopen=(action 'refreshCodeEditor' '#policy_rules') name="new-policy-toggle"}}
- {{#block-slot 'header'}}
- New Policy
- {{/block-slot}}
- {{#block-slot 'body'}}
- {{#with policy as |item|}}
- {{partial 'dc/acls/policies/fieldsets'}}
- {{/with}}
- {{/block-slot}}
- {{#block-slot 'actions' as |close|}}
-
- {{#if policy.isSaving }}
-
- {{/if}}
- Create and apply
-
- Cancel
- {{/block-slot}}
- {{/modal-dialog}}
-
-
- Apply an existing policy
- {{#power-select
- options=(difference items item.Policies item.Policies.length)
- searchField='Name'
- selected=DestinationName
- searchPlaceholder='Type policy name'
- onchange=(action 'change' 'Policy') as |policy|
- }}
- {{policy.Name}}
- {{/power-select}}
-
-{{#if (gt item.Policies.length 0)}}
- {{#with item as |token| }}
- {{#tabular-details
- data-test-policies
- onchange=(action 'change')
- name="Details"
- items=(sort-by 'CreateTime:desc' 'Name:asc' item.Policies) as |item index|
- }}
- {{#block-slot 'header'}}
- Name
- Datacenters
- {{/block-slot}}
- {{#block-slot 'row'}}
-
- {{item.Name}}
-
-
- {{if (not item.isSaving) (join ', ' (policy/datacenters item)) 'Saving...'}}
-
- {{/block-slot}}
- {{#block-slot 'details'}}
-
- Rules (HCL Format)
- {{code-editor readonly=true value=item.Rules}}
-
-
- {{#confirmation-dialog message='Are you sure you want to remove this policy from this token?'}}
- {{#block-slot 'action' as |confirm|}}
-
Remove
- {{/block-slot}}
- {{#block-slot 'dialog' as |execute cancel message|}}
-
- {{message}}
-
-
Confirm remove
-
Cancel
- {{/block-slot}}
- {{/confirmation-dialog}}
-
- {{/block-slot}}
- {{/tabular-details}}
- {{/with}}
-{{/if}}
diff --git a/ui-v2/app/templates/dc/acls/tokens/index.hbs b/ui-v2/app/templates/dc/acls/tokens/index.hbs
index 4dad5c7e92..f2d783cfce 100644
--- a/ui-v2/app/templates/dc/acls/tokens/index.hbs
+++ b/ui-v2/app/templates/dc/acls/tokens/index.hbs
@@ -1,131 +1,137 @@
{{#app-view class=(concat 'token ' (if (and isEnabled (not isAuthorized)) 'edit' 'list')) loading=isLoading authorized=isAuthorized enabled=isEnabled}}
- {{#block-slot 'notification' as |status type subject|}}
- {{partial 'dc/acls/tokens/notifications'}}
- {{/block-slot}}
- {{#block-slot 'header'}}
-
- Access Controls
-
- {{#if isAuthorized }}
- {{partial 'dc/acls/nav'}}
- {{/if}}
- {{/block-slot}}
- {{#block-slot 'disabled'}}
- {{partial 'dc/acls/disabled'}}
- {{/block-slot}}
- {{#block-slot 'authorization'}}
- {{partial 'dc/acls/authorization'}}
- {{/block-slot}}
- {{#block-slot 'actions'}}
- Create
- {{/block-slot}}
- {{#block-slot 'content'}}
-{{#if (gt items.length 0) }}
-
+ {{#block-slot 'notification' as |status type subject|}}
+ {{partial 'dc/acls/tokens/notifications'}}
+ {{/block-slot}}
+ {{#block-slot 'header'}}
+
+ Access Controls
+
+{{#if isAuthorized }}
+ {{partial 'dc/acls/nav'}}
{{/if}}
- {{#if (token/is-legacy items)}}
- Update. We have upgraded our ACL System to allow the creation of reusable policies that can be applied to tokens. Read more about the changes and how to upgrade legacy tokens in our documentation .
- {{/if}}
-{{#if (gt filtered.length 0)}}
+ {{/block-slot}}
+ {{#block-slot 'disabled'}}
+ {{partial 'dc/acls/disabled'}}
+ {{/block-slot}}
+ {{#block-slot 'authorization'}}
+ {{partial 'dc/acls/authorization'}}
+ {{/block-slot}}
+ {{#block-slot 'actions'}}
+ Create
+ {{/block-slot}}
+ {{#block-slot 'content'}}
+{{#if (gt items.length 0) }}
+
+{{/if}}
+{{#if (token/is-legacy items)}}
+ Update. We have upgraded our ACL System to allow the creation of reusable policies that can be applied to tokens. Read more about the changes and how to upgrade legacy tokens in our documentation .
+{{/if}}
+ {{#changeable-set dispatcher=searchable}}
+ {{#block-slot 'set' as |filtered|}}
{{#tabular-collection
items=(sort-by 'CreateTime:desc' filtered) as |item index|
}}
- {{#block-slot 'header'}}
- Accessor ID
- Scope
- Description
- Policies
-
- {{/block-slot}}
- {{#block-slot 'row'}}
-
- {{truncate item.AccessorID 8 false}}
-
-
- {{if item.Local 'local' 'global' }}
-
-
- {{default item.Description item.Name}}
-
-
- {{#if (token/is-legacy item) }}
- Legacy tokens have embedded policies.
- {{ else }}
- {{#each item.Policies as |item|}}
- {{item.Name}}
- {{/each}}
- {{/if}}
-
- {{#if (eq item.AccessorID token.AccessorID)}}
- Your token
- {{/if}}
- {{/block-slot}}
- {{#block-slot 'actions' as |index change checked|}}
- {{#confirmation-dialog confirming=false index=index message="Are you sure you want to delete this Token?"}}
- {{#block-slot 'action' as |confirm|}}
- {{#action-group index=index onchange=(action change) checked=(if (eq checked index) 'checked')}}
-
- {{#if false}}
-
- {{#copy-button-feedback title="Copy AccessorID to the clipboard" copy=item.AccessorID name="AccessorID"}}Copy AccessorID{{/copy-button-feedback}}
-
- {{/if}}
-
- Edit
-
+ {{#block-slot 'header'}}
+ Accessor ID
+ Scope
+ Description
+ Roles & Policies
+
+ {{/block-slot}}
+ {{#block-slot 'row'}}
+
+ {{truncate item.AccessorID 8 false}}
+
+
+ {{if item.Local 'local' 'global' }}
+
+
+ {{default item.Description item.Name}}
+
+
+{{#if (token/is-legacy item) }}
+ Legacy tokens have embedded rules.
+{{ else }}
+ {{#each (append item.Policies item.Roles) as |item|}}
+ {{item.Name}}
+ {{/each}}
+{{/if}}
+
+{{#if (eq item.AccessorID token.AccessorID)}}
+ Your token
+{{/if}}
+ {{/block-slot}}
+ {{#block-slot 'actions' as |index change checked|}}
+ {{#confirmation-dialog confirming=false index=index message="Are you sure you want to delete this Token?"}}
+ {{#block-slot 'action' as |confirm|}}
+ {{#action-group index=index onchange=(action change) checked=(if (eq checked index) 'checked')}}
+
+{{#if false}}
+
+ {{#copy-button-feedback title="Copy AccessorID to the clipboard" copy=item.AccessorID name="AccessorID"}}Copy AccessorID{{/copy-button-feedback}}
+
+{{/if}}
+
+ Edit
+
{{#if (not (token/is-legacy item))}}
-
- Duplicate
-
+
+ Duplicate
+
{{/if}}
{{#if (eq item.AccessorID token.AccessorID) }}
-
- Stop using
-
+
+ Stop using
+
{{else}}
-
- Use
-
+
+ Use
+
{{/if}}
{{#unless (or (token/is-anonymous item) (eq item.AccessorID token.AccessorID)) }}
-
- Delete
-
+
+ Delete
+
{{/unless}}
-
- {{/action-group}}
- {{/block-slot}}
- {{#block-slot 'dialog' as |execute cancel message name|}}
-
- {{#if (eq name 'delete')}}
- {{message}}
- {{else if (eq name 'logout')}}
- Are you sure you want to stop using this ACL token? This will log you out.
- {{else if (eq name 'use')}}
- Are you sure you want to use this ACL token?
- {{/if}}
-
-
- {{#if (eq name 'delete')}}
- Confirm Delete
- {{else if (eq name 'logout')}}
- Confirm Logout
- {{ else if (eq name 'use')}}
- Confirm Use
- {{/if}}
-
- Cancel
- {{/block-slot}}
- {{/confirmation-dialog}}
- {{/block-slot}}
- {{/tabular-collection}}
-{{else}}
-
- There are no Tokens.
-
+
+ {{/action-group}}
+ {{/block-slot}}
+ {{#block-slot 'dialog' as |execute cancel message name|}}
+
+ {{#if (eq name 'delete')}}
+ {{message}}
+{{#if (eq item.AccessorID token.AccessorID)}}
+ Warning: This is the token you are currently using!
{{/if}}
- {{/block-slot}}
+ {{else if (eq name 'logout')}}
+ Are you sure you want to stop using this ACL token? This will log you out.
+ {{else if (eq name 'use')}}
+ Are you sure you want to use this ACL token?
+ {{/if}}
+
+
+ {{#if (eq name 'delete')}}
+ Confirm Delete
+ {{else if (eq name 'logout')}}
+ Confirm Logout
+ {{ else if (eq name 'use')}}
+ Confirm Use
+ {{/if}}
+
+ Cancel
+ {{/block-slot}}
+ {{/confirmation-dialog}}
+ {{/block-slot}}
+ {{/tabular-collection}}
+ {{/block-slot}}
+ {{#block-slot 'empty'}}
+
+ There are no Tokens.
+
+ {{/block-slot}}
+ {{/changeable-set}}
+ {{/block-slot}}
{{/app-view}}
diff --git a/ui-v2/app/templates/dc/intentions/-form.hbs b/ui-v2/app/templates/dc/intentions/-form.hbs
index 6a1123cbfd..f85fb8e3ab 100644
--- a/ui-v2/app/templates/dc/intentions/-form.hbs
+++ b/ui-v2/app/templates/dc/intentions/-form.hbs
@@ -7,7 +7,7 @@
searchField='Name'
selected=SourceName
searchPlaceholder='Type service name'
- buildSuggestion=(action 'createNewLabel')
+ buildSuggestion=(action 'createNewLabel' "Use a future Consul Service called '{{term}}'")
showCreateWhen=(action "isUnique")
oncreate=(action 'change' 'SourceName')
onchange=(action 'change' 'SourceName') as |service search|
@@ -27,7 +27,7 @@
searchField='Name'
selected=DestinationName
searchPlaceholder='Type service name'
- buildSuggestion=(action 'createNewLabel')
+ buildSuggestion=(action 'createNewLabel' "Use a future Consul Service called '{{term}}'")
showCreateWhen=(action "isUnique")
oncreate=(action 'change' 'DestinationName')
onchange=(action 'change' 'DestinationName') as |service|
diff --git a/ui-v2/app/templates/dc/intentions/index.hbs b/ui-v2/app/templates/dc/intentions/index.hbs
index bd509d85a8..b521d8b6e7 100644
--- a/ui-v2/app/templates/dc/intentions/index.hbs
+++ b/ui-v2/app/templates/dc/intentions/index.hbs
@@ -4,7 +4,7 @@
{{/block-slot}}
{{#block-slot 'header'}}
- Intentions
+ Intentions {{format-number items.length}} total
{{/block-slot}}
@@ -13,70 +13,73 @@
{{/block-slot}}
{{#block-slot 'toolbar'}}
{{#if (gt items.length 0) }}
- {{intention-filter filters=actionFilters search=filters.s type=filters.action onchange=(action 'filter')}}
+ {{intention-filter searchable=searchable filters=actionFilters search=filters.s type=filters.action onchange=(action 'filter')}}
{{/if}}
{{/block-slot}}
{{#block-slot 'content'}}
-{{#if (gt filtered.length 0) }}
- {{#tabular-collection
- route='dc.intentions.edit'
- key='SourceName'
- items=filtered as |item index|
- }}
- {{#block-slot 'header'}}
- Source
-
- Destination
- Precedence
- {{/block-slot}}
- {{#block-slot 'row'}}
-
-
- {{#if (eq item.SourceName '*') }}
- All Services (*)
- {{else}}
- {{item.SourceName}}
- {{/if}}
-
-
-
- {{item.Action}}
-
-
- {{#if (eq item.DestinationName '*') }}
- All Services (*)
- {{else}}
- {{item.DestinationName}}
- {{/if}}
-
-
- {{item.Precedence}}
-
- {{/block-slot}}
- {{#block-slot 'actions' as |index change checked|}}
- {{#confirmation-dialog confirming=false index=index message='Are you sure you want to delete this intention?'}}
- {{#block-slot 'action' as |confirm|}}
- {{#action-group index=index onchange=(action change) checked=(if (eq checked index) 'checked')}}
-
- {{/action-group}}
- {{/block-slot}}
- {{#block-slot 'dialog' as |execute cancel message|}}
- {{delete-confirmation message=message execute=execute cancel=cancel}}
- {{/block-slot}}
- {{/confirmation-dialog}}
- {{/block-slot}}
- {{/tabular-collection}}
-{{else}}
-
- There are no intentions.
-
-{{/if}}
+ {{#changeable-set dispatcher=searchable}}
+ {{#block-slot 'set' as |filtered|}}
+ {{#tabular-collection
+ route='dc.intentions.edit'
+ key='SourceName'
+ items=filtered as |item index|
+ }}
+ {{#block-slot 'header'}}
+ Source
+
+ Destination
+ Precedence
+ {{/block-slot}}
+ {{#block-slot 'row'}}
+
+
+ {{#if (eq item.SourceName '*') }}
+ All Services (*)
+ {{else}}
+ {{item.SourceName}}
+ {{/if}}
+
+
+
+ {{item.Action}}
+
+
+ {{#if (eq item.DestinationName '*') }}
+ All Services (*)
+ {{else}}
+ {{item.DestinationName}}
+ {{/if}}
+
+
+ {{item.Precedence}}
+
+ {{/block-slot}}
+ {{#block-slot 'actions' as |index change checked|}}
+ {{#confirmation-dialog confirming=false index=index message='Are you sure you want to delete this intention?'}}
+ {{#block-slot 'action' as |confirm|}}
+ {{#action-group index=index onchange=(action change) checked=(if (eq checked index) 'checked')}}
+
+
+ Edit
+
+
+ Delete
+
+
+ {{/action-group}}
+ {{/block-slot}}
+ {{#block-slot 'dialog' as |execute cancel message|}}
+ {{delete-confirmation message=message execute=execute cancel=cancel}}
+ {{/block-slot}}
+ {{/confirmation-dialog}}
+ {{/block-slot}}
+ {{/tabular-collection}}
+ {{/block-slot}}
+ {{#block-slot 'empty'}}
+
+ There are no intentions.
+
+ {{/block-slot}}
+ {{/changeable-set}}
{{/block-slot}}
{{/app-view}}
\ No newline at end of file
diff --git a/ui-v2/app/templates/dc/kv/-form.hbs b/ui-v2/app/templates/dc/kv/-form.hbs
index a500e3d3d9..5caf028c58 100644
--- a/ui-v2/app/templates/dc/kv/-form.hbs
+++ b/ui-v2/app/templates/dc/kv/-form.hbs
@@ -18,7 +18,7 @@
Value
{{#if json}}
- {{ code-editor value=(atob item.Value) onkeyup=(action 'change') }}
+ {{code-editor value=(atob item.Value) onkeyup=(action 'change' 'value')}}
{{else}}
{{/if}}
diff --git a/ui-v2/app/templates/dc/kv/edit.hbs b/ui-v2/app/templates/dc/kv/edit.hbs
index 7bf538afb7..cfcb18de92 100644
--- a/ui-v2/app/templates/dc/kv/edit.hbs
+++ b/ui-v2/app/templates/dc/kv/edit.hbs
@@ -39,10 +39,10 @@
ID
{{session.ID}}
Behavior
- <{{session.Behavior}}/dd>
+ {{session.Behavior}}
{{#if session.Delay }}
Delay
- <{{session.LockDelay}}/dd>
+ {{session.LockDelay}}
{{/if}}
{{#if session.TTL }}
TTL
diff --git a/ui-v2/app/templates/dc/kv/index.hbs b/ui-v2/app/templates/dc/kv/index.hbs
index ecb6ac2ef7..50aca95063 100644
--- a/ui-v2/app/templates/dc/kv/index.hbs
+++ b/ui-v2/app/templates/dc/kv/index.hbs
@@ -25,7 +25,7 @@
{{#block-slot 'toolbar'}}
{{#if (gt items.length 0) }}
{{/if}}
{{/block-slot}}
@@ -37,40 +37,45 @@
{{/if}}
{{/block-slot}}
{{#block-slot 'content'}}
-{{#if (gt filtered.length 0)}}
- {{#tabular-collection
- items=(sort-by 'isFolder:desc' 'Key:asc' filtered) as |item index|
- }}
- {{#block-slot 'header'}}
- Name
- {{/block-slot}}
- {{#block-slot 'row'}}
-
- {{right-trim (left-trim item.Key parent.Key) '/'}}
-
- {{/block-slot}}
- {{#block-slot 'actions' as |index change checked|}}
- {{#confirmation-dialog confirming=false index=index message='Are you sure you want to delete this key?'}}
- {{#block-slot 'action' as |confirm|}}
- {{#action-group index=index onchange=(action change) checked=(if (eq checked index) 'checked')}}
-
- {{/action-group}}
- {{/block-slot}}
- {{#block-slot 'dialog' as |execute cancel message|}}
- {{delete-confirmation message=message execute=execute cancel=cancel}}
- {{/block-slot}}
- {{/confirmation-dialog}}
- {{/block-slot}}
- {{/tabular-collection}}
-{{else}}
- There are no Key / Value pairs.
-{{/if}}
+ {{#changeable-set dispatcher=searchable}}
+ {{#block-slot 'set' as |filtered|}}
+ {{#tabular-collection
+ items=(sort-by 'isFolder:desc' 'Key:asc' filtered) as |item index|
+ }}
+ {{#block-slot 'header'}}
+ Name
+ {{/block-slot}}
+ {{#block-slot 'row'}}
+
+ {{right-trim (left-trim item.Key parent.Key) '/'}}
+
+ {{/block-slot}}
+ {{#block-slot 'actions' as |index change checked|}}
+ {{#confirmation-dialog confirming=false index=index message='Are you sure you want to delete this key?'}}
+ {{#block-slot 'action' as |confirm|}}
+ {{#action-group index=index onchange=(action change) checked=(if (eq checked index) 'checked')}}
+
+ {{/action-group}}
+ {{/block-slot}}
+ {{#block-slot 'dialog' as |execute cancel message|}}
+ {{delete-confirmation message=message execute=execute cancel=cancel}}
+ {{/block-slot}}
+ {{/confirmation-dialog}}
+ {{/block-slot}}
+ {{/tabular-collection}}
+ {{/block-slot}}
+ {{#block-slot 'empty'}}
+
+ There are no Key / Value pairs.
+
+ {{/block-slot}}
+ {{/changeable-set}}
{{/block-slot}}
{{/app-view}}
\ No newline at end of file
diff --git a/ui-v2/app/templates/dc/nodes/-healthchecks.hbs b/ui-v2/app/templates/dc/nodes/-healthchecks.hbs
index a956fad631..19acae8c38 100644
--- a/ui-v2/app/templates/dc/nodes/-healthchecks.hbs
+++ b/ui-v2/app/templates/dc/nodes/-healthchecks.hbs
@@ -1,9 +1,5 @@
{{#if (gt item.Checks.length 0) }}
-
-{{#each (sort-by (action 'sortChecksByImportance') item.Checks) as |check| }}
- {{healthcheck-status data-test-node-healthcheck=check.Name tagName='li' name=check.Name class=check.Status status=check.Status notes=check.Notes output=check.Output}}
-{{/each}}
-
+ {{healthcheck-list items=item.Checks}}
{{else}}
This node has no health checks.
diff --git a/ui-v2/app/templates/dc/nodes/-notifications.hbs b/ui-v2/app/templates/dc/nodes/-notifications.hbs
index 70f90530b4..cbc36249f0 100644
--- a/ui-v2/app/templates/dc/nodes/-notifications.hbs
+++ b/ui-v2/app/templates/dc/nodes/-notifications.hbs
@@ -4,5 +4,9 @@
{{else}}
There was an error invalidating the session.
{{/if}}
+{{else if (eq type 'update')}}
+ {{#if (eq status 'warning') }}
+ This node no longer exists in the catalog.
+ {{else}}
+ {{/if}}
{{/if}}
-
diff --git a/ui-v2/app/templates/dc/nodes/-services.hbs b/ui-v2/app/templates/dc/nodes/-services.hbs
index cf809393bd..de00507b3b 100644
--- a/ui-v2/app/templates/dc/nodes/-services.hbs
+++ b/ui-v2/app/templates/dc/nodes/-services.hbs
@@ -1,40 +1,39 @@
{{#if (gt items.length 0) }}
-
-
+
+
{{/if}}
-{{#if (gt filtered.length 0)}}
- {{#tabular-collection
- data-test-services
- items=filtered as |item index|
- }}
- {{#block-slot 'header'}}
- Service
- Port
- Tags
- {{/block-slot}}
- {{#block-slot 'row'}}
-
-
-
- {{item.Service}}{{#if (not-eq item.ID item.Service) }}({{item.ID}}) {{/if}}
-
-
-
- {{item.Port}}
-
-
- {{#if (gt item.Tags.length 0)}}
- {{#each item.Tags as |item|}}
- {{item}}
- {{/each}}
- {{/if}}
-
- {{/block-slot}}
- {{/tabular-collection}}
-{{else}}
-
+ {{#changeable-set dispatcher=searchable}}
+ {{#block-slot 'set' as |filtered|}}
+ {{#tabular-collection
+ data-test-services
+ items=filtered as |item index|
+ }}
+ {{#block-slot 'header'}}
+
Service
+ Port
+ Tags
+ {{/block-slot}}
+ {{#block-slot 'row'}}
+
+
+
+ {{item.Service}}{{#if (not-eq item.ID item.Service) }} ({{item.ID}}) {{/if}}
+
+
+
+ {{item.Port}}
+
+
+ {{tag-list items=item.Tags}}
+
+ {{/block-slot}}
+ {{/tabular-collection}}
+ {{/block-slot}}
+ {{#block-slot 'empty'}}
+
There are no services.
-
-{{/if}}
+
+ {{/block-slot}}
+ {{/changeable-set}}
diff --git a/ui-v2/app/templates/dc/nodes/index.hbs b/ui-v2/app/templates/dc/nodes/index.hbs
index b49368aef4..82d93a5260 100644
--- a/ui-v2/app/templates/dc/nodes/index.hbs
+++ b/ui-v2/app/templates/dc/nodes/index.hbs
@@ -1,13 +1,13 @@
{{#app-view class="node list"}}
{{#block-slot 'header'}}
- Nodes
+ Nodes {{format-number items.length}} total
{{/block-slot}}
{{#block-slot 'toolbar'}}
{{#if (gt items.length 0) }}
- {{catalog-filter filters=healthFilters search=filters.s status=filters.status onchange=(action 'filter')}}
+ {{catalog-filter searchable=(array searchableHealthy searchableUnhealthy) search=s status=filters.status onchange=(action 'filter')}}
{{/if}}
{{/block-slot}}
{{#block-slot 'content'}}
@@ -16,33 +16,51 @@
Unhealthy Nodes
{{! think about 2 differing views here }}
-
- {{#each unhealthy as |item|}}
- {{healthchecked-resource
- tagName='li'
- data-test-node=item.Node
- href=(href-to 'dc.nodes.show' item.Node)
- name=item.Node
- address=item.Address
- checks=item.Checks
- }}
- {{/each}}
-
+
{{/if}}
{{#if (gt healthy.length 0) }}
Healthy Nodes
- {{#list-collection cellHeight=92 items=healthy as |item index|}}
- {{healthchecked-resource
- data-test-node=item.Node
- href=(href-to 'dc.nodes.show' item.Node)
- name=item.Node
- address=item.Address
- status=item.Checks.[0].Status
- }}
- {{/list-collection}}
+ {{#changeable-set dispatcher=searchableHealthy}}
+ {{#block-slot 'set' as |healthy|}}
+ {{#list-collection cellHeight=92 items=healthy as |item index|}}
+ {{healthchecked-resource
+ data-test-node=item.Node
+ href=(href-to 'dc.nodes.show' item.Node)
+ name=item.Node
+ address=item.Address
+ status=item.Checks.[0].Status
+ }}
+ {{/list-collection}}
+ {{/block-slot}}
+ {{#block-slot 'empty'}}
+
+ There are no healthy nodes for that search.
+
+ {{/block-slot}}
+ {{/changeable-set}}
{{/if}}
{{#if (and (eq healthy.length 0) (eq unhealthy.length 0)) }}
diff --git a/ui-v2/app/templates/dc/nodes/show.hbs b/ui-v2/app/templates/dc/nodes/show.hbs
index 10a12fc576..627de3e5bd 100644
--- a/ui-v2/app/templates/dc/nodes/show.hbs
+++ b/ui-v2/app/templates/dc/nodes/show.hbs
@@ -18,7 +18,7 @@
(array
'Health Checks'
'Services'
- (if tomography 'Round Trip Time' '')
+ (if tomography.distances 'Round Trip Time' '')
'Lock Sessions'
)
)
@@ -26,32 +26,32 @@
}}
{{/block-slot}}
{{#block-slot 'actions'}}
- {{#feedback-dialog type='inline'}}
- {{#block-slot 'action' as |success error|}}
- {{#copy-button success=(action success) error=(action error) clipboardText=item.Address title='copy IP address to clipboard'}}
- {{item.Address}}
- {{/copy-button}}
- {{/block-slot}}
- {{#block-slot 'success' as |transition|}}
-
- Copied IP Address!
-
- {{/block-slot}}
- {{#block-slot 'error' as |transition|}}
-
- Sorry, something went wrong!
-
- {{/block-slot}}
- {{/feedback-dialog}}
+ {{#feedback-dialog type='inline'}}
+ {{#block-slot 'action' as |success error|}}
+ {{#copy-button success=(action success) error=(action error) clipboardText=item.Address title='copy IP address to clipboard'}}
+ {{item.Address}}
+ {{/copy-button}}
+ {{/block-slot}}
+ {{#block-slot 'success' as |transition|}}
+
+ Copied IP Address!
+
+ {{/block-slot}}
+ {{#block-slot 'error' as |transition|}}
+
+ Sorry, something went wrong!
+
+ {{/block-slot}}
+ {{/feedback-dialog}}
{{/block-slot}}
{{#block-slot 'content'}}
{{#each
(compact
(array
- (hash id=(slugify 'Health Checks') partial='dc/nodes/healthchecks')
- (hash id=(slugify 'Services') partial='dc/nodes/services')
- (if tomography (hash id=(slugify 'Round Trip Time') partial='dc/nodes/rtt') '')
- (hash id=(slugify 'Lock Sessions') partial='dc/nodes/sessions')
+ (hash id=(slugify 'Health Checks') partial='dc/nodes/healthchecks')
+ (hash id=(slugify 'Services') partial='dc/nodes/services')
+ (if tomography.distances (hash id=(slugify 'Round Trip Time') partial='dc/nodes/rtt') '')
+ (hash id=(slugify 'Lock Sessions') partial='dc/nodes/sessions')
)
) as |panel|
}}
diff --git a/ui-v2/app/templates/dc/services/-instances.hbs b/ui-v2/app/templates/dc/services/-instances.hbs
new file mode 100644
index 0000000000..7ef34c3400
--- /dev/null
+++ b/ui-v2/app/templates/dc/services/-instances.hbs
@@ -0,0 +1,55 @@
+{{#if (gt items.length 0) }}
+
+
+{{/if}}
+ {{#changeable-set dispatcher=searchable}}
+ {{#block-slot 'set' as |filtered|}}
+ {{#tabular-collection
+ data-test-instances
+ items=filtered as |item index|
+ }}
+ {{#block-slot 'header'}}
+ ID
+ Node
+ Address
+ Node Checks
+ Service Checks
+ {{/block-slot}}
+ {{#block-slot 'row'}}
+
+
+
+ {{ or item.Service.ID item.Service.Service }}
+
+
+
+ {{item.Node.Node}}
+
+
+ {{item.Service.Address}}:{{item.Service.Port}}
+
+
+ {{#with (reject-by 'ServiceID' '' item.Checks) as |checks|}}
+ {{healthcheck-info
+ passing=(filter-by 'Status' 'passing' checks) warning=(filter-by 'Status' 'warning' checks) critical=(filter-by 'Status' 'critical' checks)
+ }}
+ {{/with}}
+
+
+ {{#with (filter-by 'ServiceID' '' item.Checks) as |checks|}}
+ {{healthcheck-info
+ passing=(filter-by 'Status' 'passing' checks) warning=(filter-by 'Status' 'warning' checks) critical=(filter-by 'Status' 'critical' checks)
+ }}
+ {{/with}}
+
+ {{/block-slot}}
+ {{/tabular-collection}}
+ {{/block-slot}}
+ {{#block-slot 'empty'}}
+
+ There are no services.
+
+ {{/block-slot}}
+ {{/changeable-set}}
diff --git a/ui-v2/app/templates/dc/services/-nodechecks.hbs b/ui-v2/app/templates/dc/services/-nodechecks.hbs
new file mode 100644
index 0000000000..487db34fc9
--- /dev/null
+++ b/ui-v2/app/templates/dc/services/-nodechecks.hbs
@@ -0,0 +1,8 @@
+{{#if (gt item.NodeChecks.length 0) }}
+ {{healthcheck-list items=item.NodeChecks}}
+{{else}}
+
+ This instance has no node health checks.
+
+{{/if}}
+
diff --git a/ui-v2/app/templates/dc/services/-notifications.hbs b/ui-v2/app/templates/dc/services/-notifications.hbs
new file mode 100644
index 0000000000..9dee0a3ec9
--- /dev/null
+++ b/ui-v2/app/templates/dc/services/-notifications.hbs
@@ -0,0 +1,7 @@
+{{#if (eq type 'update')}}
+ {{#if (eq status 'warning') }}
+ This service has been deregistered and no longer exists in the catalog.
+ {{else}}
+ {{/if}}
+{{/if}}
+
diff --git a/ui-v2/app/templates/dc/services/-servicechecks.hbs b/ui-v2/app/templates/dc/services/-servicechecks.hbs
new file mode 100644
index 0000000000..424772e705
--- /dev/null
+++ b/ui-v2/app/templates/dc/services/-servicechecks.hbs
@@ -0,0 +1,8 @@
+{{#if (gt item.ServiceChecks.length 0) }}
+ {{healthcheck-list items=item.ServiceChecks}}
+{{else}}
+
+ This instance has no service health checks.
+
+{{/if}}
+
diff --git a/ui-v2/app/templates/dc/services/-tags.hbs b/ui-v2/app/templates/dc/services/-tags.hbs
new file mode 100644
index 0000000000..c0a3a0f783
--- /dev/null
+++ b/ui-v2/app/templates/dc/services/-tags.hbs
@@ -0,0 +1,7 @@
+{{#if (gt item.Tags.length 0) }}
+{{tag-list items=item.Tags}}
+{{else}}
+
+ There are no tags.
+
+{{/if}}
diff --git a/ui-v2/app/templates/dc/services/-upstreams.hbs b/ui-v2/app/templates/dc/services/-upstreams.hbs
new file mode 100644
index 0000000000..62031ba28e
--- /dev/null
+++ b/ui-v2/app/templates/dc/services/-upstreams.hbs
@@ -0,0 +1,31 @@
+{{#if (gt item.Proxy.Upstreams.length 0) }}
+{{#tabular-collection
+ data-test-upstreams
+ items=item.Proxy.Upstreams as |item index|
+}}
+ {{#block-slot 'header'}}
+ Upstream
+ Datacenter
+ Type
+ Local Bind Address
+ {{/block-slot}}
+ {{#block-slot 'row'}}
+
+ {{item.DestinationName}}
+
+
+ {{item.Datacenter}}
+
+
+ {{item.DestinationType}}
+
+
+ {{item.LocalBindAddress}}:{{item.LocalBindPort}}
+
+ {{/block-slot}}
+{{/tabular-collection}}
+{{else}}
+
+ There are no upstreams.
+
+{{/if}}
diff --git a/ui-v2/app/templates/dc/services/index.hbs b/ui-v2/app/templates/dc/services/index.hbs
index f3029d046f..6812f5a6af 100644
--- a/ui-v2/app/templates/dc/services/index.hbs
+++ b/ui-v2/app/templates/dc/services/index.hbs
@@ -1,67 +1,64 @@
{{#app-view class="service list"}}
- {{!TODO: Look at the item passed through to figure what partial to show, also move into its own service partial, for the moment keeping here for visibility}}
{{#block-slot 'notification' as |status type|}}
- {{partial 'dc/acls/notifications'}}
+ {{partial 'dc/services/notifications'}}
{{/block-slot}}
{{#block-slot 'header'}}
- Services
+ Services {{format-number items.length}} total
{{/block-slot}}
{{#block-slot 'toolbar'}}
{{#if (gt items.length 0) }}
- {{catalog-filter filters=healthFilters search=filters.s status=filters.status onchange=(action 'filter')}}
+ {{#phrase-editor placeholder=(if (eq terms.length 0) 'service:name tag:name status:critical search-term' '') items=terms searchable=searchable}}{{/phrase-editor}}
{{/if}}
{{/block-slot}}
{{#block-slot 'content'}}
-{{#if (gt filtered.length 0) }}
- {{#tabular-collection
- route='dc.services.show'
- key='Name'
- items=filtered as |item index|
- }}
- {{#block-slot 'header'}}
- Service
- Health ChecksThe number of health checks for the service on all nodes
- Tags
- {{/block-slot}}
- {{#block-slot 'row'}}
-
-
-
-
- {{item.Name}}
-
-
-
-{{#if (and (lt item.ChecksPassing 1) (lt item.ChecksWarning 1) (lt item.ChecksCritical 1) )}}
- 0
+ {{#changeable-set dispatcher=searchable}}
+ {{#block-slot 'set' as |filtered|}}
+ {{#tabular-collection
+ route='dc.services.show'
+ key='Name'
+ items=filtered as |item index|
+ }}
+ {{#block-slot 'header'}}
+ Service
+ Type
+ Health ChecksThe number of health checks for the service on all nodes
+ Tags
+ {{/block-slot}}
+ {{#block-slot 'row'}}
+
+
+
+ {{item.Name}}
+
+
+
+{{#if (eq item.Kind 'connect-proxy')}}
+ Proxy
{{else}}
-
- Healthchecks Passing
- {{format-number item.ChecksPassing}}
- Healthchecks Warning
- {{format-number item.ChecksWarning}}
- Healthchecks Critical
- {{format-number item.ChecksCritical}}
-
-{{/if}}
-
-
- {{#if (gt item.Tags.length 0)}}
- {{#each item.Tags as |item|}}
- {{item}}
- {{/each}}
- {{/if}}
-
- {{/block-slot}}
- {{/tabular-collection}}
-{{else}}
-
- There are no services.
-
+
{{/if}}
+
+
+ {{healthcheck-info
+ passing=item.ChecksPassing warning=item.ChecksWarning critical=item.ChecksCritical
+ passingWidth=passingWidth warningWidth=warningWidth criticalWidth=criticalWidth
+ }}
+
+
+ {{tag-list items=item.Tags}}
+
+ {{/block-slot}}
+ {{/tabular-collection}}
+ {{/block-slot}}
+ {{#block-slot 'empty'}}
+
+ There are no services.
+
+ {{/block-slot}}
+ {{/changeable-set}}
{{/block-slot}}
{{/app-view}}
diff --git a/ui-v2/app/templates/dc/services/instance.hbs b/ui-v2/app/templates/dc/services/instance.hbs
new file mode 100644
index 0000000000..b5aae2b9ea
--- /dev/null
+++ b/ui-v2/app/templates/dc/services/instance.hbs
@@ -0,0 +1,85 @@
+{{#app-view class="instance show"}}
+ {{#block-slot 'notification' as |status type|}}
+ {{partial 'dc/services/notifications'}}
+ {{/block-slot}}
+ {{#block-slot 'breadcrumbs'}}
+
+ All Services
+ Service ({{item.Service}})
+ Instance
+
+ {{/block-slot}}
+ {{#block-slot 'header'}}
+
+ {{ item.ID }}
+{{#with (service/external-source item) as |externalSource| }}
+ {{#with (css-var (concat '--' externalSource '-color-svg') 'none') as |bg| }}
+ {{#if (not-eq bg 'none') }}
+ Registered via {{externalSource}}
+ {{/if}}
+ {{/with}}
+{{/with}}
+{{#if (eq item.Kind 'connect-proxy')}}
+ Proxy
+{{/if}}
+
+
+ Service Name
+ {{item.Service}}
+
+
+ Node Name
+ {{item.Node.Node}}
+
+{{#if proxy.ServiceName}}
+
+ {{if proxy.ServiceProxy.DestinationServiceID "Sidecar " ""}}Proxy
+ {{proxy.ServiceID}}
+
+{{/if}}
+{{#if (eq item.Kind 'connect-proxy')}}
+ {{#if item.Proxy.DestinationServiceID}}
+
+ Dest. Service Instance
+ {{item.Proxy.DestinationServiceID}}
+
+
+ Local Service Address
+ {{item.Proxy.LocalServiceAddress}}:{{item.Proxy.LocalServicePort}}
+
+ {{else}}
+
+ Dest. Service
+ {{item.Proxy.DestinationServiceName}}
+
+ {{/if}}
+{{/if}}
+ {{/block-slot}}
+ {{#block-slot 'content'}}
+ {{tab-nav
+ items=(compact
+ (array
+ 'Service Checks'
+ 'Node Checks'
+(if (eq item.Kind 'connect-proxy') 'Upstreams' '')
+ 'Tags'
+ )
+ )
+ selected=selectedTab
+ }}
+ {{#each
+ (compact
+ (array
+ (hash id=(slugify 'Service Checks') partial='dc/services/servicechecks')
+ (hash id=(slugify 'Node Checks') partial='dc/services/nodechecks')
+(if (eq item.Kind 'connect-proxy') (hash id=(slugify 'Upstreams') partial='dc/services/upstreams') '')
+ (hash id=(slugify 'Tags') partial='dc/services/tags')
+ )
+ ) as |panel|
+ }}
+ {{#tab-section id=panel.id selected=(eq (if selectedTab selectedTab '') panel.id) onchange=(action "change")}}
+ {{partial panel.partial}}
+ {{/tab-section}}
+ {{/each}}
+ {{/block-slot}}
+{{/app-view}}
\ No newline at end of file
diff --git a/ui-v2/app/templates/dc/services/show.hbs b/ui-v2/app/templates/dc/services/show.hbs
index 130c18ce80..452017b2a5 100644
--- a/ui-v2/app/templates/dc/services/show.hbs
+++ b/ui-v2/app/templates/dc/services/show.hbs
@@ -1,4 +1,7 @@
{{#app-view class="service show"}}
+ {{#block-slot 'notification' as |status type|}}
+ {{partial 'dc/services/notifications'}}
+ {{/block-slot}}
{{#block-slot 'breadcrumbs'}}
All Services
@@ -14,59 +17,38 @@
{{/if}}
{{/with}}
{{/with}}
-
- {{/block-slot}}
- {{#block-slot 'toolbar'}}
-{{#if (gt items.length 0) }}
- {{catalog-filter filters=healthFilters search=filters.s status=filters.status onchange=(action 'filter')}}
+{{#if (eq item.Service.Kind 'connect-proxy')}}
+ Proxy
{{/if}}
+
+
+ {{tab-nav
+ items=(compact
+ (array
+ 'Instances'
+ 'Tags'
+ )
+ )
+ selected=selectedTab
+ }}
+ {{/block-slot}}
+ {{#block-slot 'actions'}}
+ {{#if urls.service}}
+ {{#templated-anchor href=urls.service vars=(hash Service=(hash Name=item.Service.Service)) rel="external"}}Open Dashboard{{/templated-anchor}}
+ {{/if}}
{{/block-slot}}
{{#block-slot 'content'}}
-{{#if (gt item.Tags.length 0)}}
-
- Tags
-
- {{#each item.Tags as |item|}}
- {{item}}
- {{/each}}
-
-
-{{/if}}
-{{#if (gt unhealthy.length 0) }}
-
-
Unhealthy Nodes
-
-
- {{#each unhealthy as |item|}}
- {{healthchecked-resource
- tagName='li'
- data-test-node=item.Node.Node
- href=(href-to 'dc.nodes.show' item.Node.Node)
- name=item.Node.Node
- service=item.Service.ID
- address=(concat (default item.Service.Address item.Node.Address) ':' item.Service.Port)
- checks=item.Checks
- }}
- {{/each}}
-
-
-
-{{/if}}
-{{#if (gt healthy.length 0) }}
-
-
Healthy Nodes
- {{#list-collection cellHeight=113 items=healthy as |item index|}}
- {{healthchecked-resource
- href=(href-to 'dc.nodes.show' item.Node.Node)
- data-test-node=item.Node.Node
- name=item.Node.Node
- service=item.Service.ID
- address=(concat (default item.Service.Address item.Node.Address) ':' item.Service.Port)
- checks=item.Checks
- status=item.Checks.[0].Status
- }}
- {{/list-collection}}
-
-{{/if}}
+ {{#each
+ (compact
+ (array
+ (hash id=(slugify 'Instances') partial='dc/services/instances')
+ (hash id=(slugify 'Tags') partial='dc/services/tags')
+ )
+ ) as |panel|
+ }}
+ {{#tab-section id=panel.id selected=(eq (if selectedTab selectedTab '') panel.id) onchange=(action "change")}}
+ {{partial panel.partial}}
+ {{/tab-section}}
+ {{/each}}
{{/block-slot}}
{{/app-view}}
diff --git a/ui-v2/app/templates/error.hbs b/ui-v2/app/templates/error.hbs
index a1e279d1fb..ea2027efe5 100644
--- a/ui-v2/app/templates/error.hbs
+++ b/ui-v2/app/templates/error.hbs
@@ -13,7 +13,7 @@
Consul returned an error.
You may have visited a URL that is loading an unknown resource, so you can try going back to the root or try re-submitting your ACL Token/SecretID by going back to ACLs.
- Try looking in our documentation
+ Try looking in our documentation
Go back to root
{{/block-slot}}
diff --git a/ui-v2/app/templates/settings.hbs b/ui-v2/app/templates/settings.hbs
index 97390b6a6f..4a4874cc21 100644
--- a/ui-v2/app/templates/settings.hbs
+++ b/ui-v2/app/templates/settings.hbs
@@ -1,39 +1,40 @@
{{#hashicorp-consul id="wrapper" dcs=dcs dc=dc}}
- {{#app-view class="settings show"}}
- {{#block-slot 'notification' as |status type|}}
- {{#if (eq type 'update')}}
- {{#if (eq status 'success') }}
- Your settings were saved.
- {{else}}
- There was an error saving your settings.
- {{/if}}
- {{ else if (eq type 'delete')}}
- {{#if (eq status 'success') }}
- You settings have been reset.
- {{else}}
- There was an error resetting your settings.
- {{/if}}
- {{/if}}
- {{/block-slot}}
- {{#block-slot 'header'}}
-
- Settings
-
- {{/block-slot}}
- {{#block-slot 'content'}}
-
- These settings allow you to configure your browser for the Consul Web UI. Everything is saved to localstorage, and persists through visits and browser usage.
-
-
- {{/block-slot}}
- {{/app-view}}
+ {{#app-view class="settings show"}}
+ {{#block-slot 'header'}}
+
+ Settings
+
+ {{/block-slot}}
+ {{#block-slot 'content'}}
+
+
Local Storage
+
+ These settings are immediately saved to local storage and persisted through browser usage.
+
+
+
+ {{/block-slot}}
+ {{/app-view}}
{{/hashicorp-consul}}
\ No newline at end of file
diff --git a/ui-v2/app/utils/acls-status.js b/ui-v2/app/utils/acls-status.js
index 9712e5820c..acc34dc596 100644
--- a/ui-v2/app/utils/acls-status.js
+++ b/ui-v2/app/utils/acls-status.js
@@ -23,29 +23,36 @@ export default function(isValidServerError, P = Promise) {
}),
[propName]: p
.catch(function(e) {
- switch (e.errors[0].status) {
- case '500':
- if (isValidServerError(e)) {
+ if (e.errors && e.errors[0]) {
+ switch (e.errors[0].status) {
+ case '500':
+ if (isValidServerError(e)) {
+ enable(true);
+ authorize(false);
+ } else {
+ enable(false);
+ authorize(false);
+ return P.reject(e);
+ }
+ break;
+ case '403':
enable(true);
authorize(false);
- } else {
- return P.reject(e);
- }
- break;
- case '403':
- enable(true);
- authorize(false);
- break;
- case '401':
- enable(false);
- authorize(false);
- break;
- default:
- enable(false);
- authorize(false);
- throw e;
+ break;
+ case '401':
+ enable(false);
+ authorize(false);
+ break;
+ default:
+ enable(false);
+ authorize(false);
+ throw e;
+ }
+ return [];
}
- return [];
+ enable(false);
+ authorize(false);
+ throw e;
})
.then(function(res) {
enable(true);
diff --git a/ui-v2/app/utils/computed/factory.js b/ui-v2/app/utils/computed/factory.js
new file mode 100644
index 0000000000..72539972ee
--- /dev/null
+++ b/ui-v2/app/utils/computed/factory.js
@@ -0,0 +1,18 @@
+/**
+ * Gives you factory function to create a specified type of ComputedProperty
+ * Largely taken from https://github.com/emberjs/ember.js/blob/v2.18.2/packages/ember-metal/lib/computed.js#L529
+ * but configurable from the outside (IoC) so its reuseable
+ *
+ * @param {Class} ComputedProperty - ComputedProperty to use for the factory
+ * @returns {function} - Ember-like `computed` function (see https://www.emberjs.com/api/ember/2.18/classes/ComputedProperty)
+ */
+export default function(ComputedProperty) {
+ return function() {
+ const args = [...arguments];
+ const cp = new ComputedProperty(args.pop());
+ if (args.length > 0) {
+ cp.property(...args);
+ }
+ return cp;
+ };
+}
diff --git a/ui-v2/app/utils/computed/purify.js b/ui-v2/app/utils/computed/purify.js
new file mode 100644
index 0000000000..1008ed8c47
--- /dev/null
+++ b/ui-v2/app/utils/computed/purify.js
@@ -0,0 +1,42 @@
+import { get, computed } from '@ember/object';
+
+/**
+ * Converts a conventional non-pure Ember `computed` function into a pure one
+ * (see https://github.com/emberjs/rfcs/blob/be351b059f08ac0fe709bc7697860d5064717a7f/text/0000-tracked-properties.md#avoiding-dependency-hell)
+ *
+ * @param {function} computed - a computed function to 'purify' (convert to a pure function)
+ * @param {function} filter - Optional string filter function to pre-process the names of computed properties
+ * @returns {function} - A pure `computed` function
+ */
+const _success = function(value) {
+ return value;
+};
+const purify = function(computed, filter = args => args) {
+ return function() {
+ let args = [...arguments];
+ let success = _success;
+ // pop the user function off the end
+ if (typeof args[args.length - 1] === 'function') {
+ success = args.pop();
+ }
+ args = filter(args);
+ // this is the 'conventional' `computed`
+ const cb = function(name) {
+ return success.apply(
+ this,
+ args.map(item => {
+ // Right now this just takes the first part of the path so:
+ // `items.[]` or `items.@each.prop` etc
+ // gives you `items` which is 'probably' what you expect
+ // it won't work with something like `item.objects.[]`
+ // it could potentially be made to do so, but we don't need that right now at least
+ return get(this, item.split('.')[0]);
+ })
+ );
+ };
+ // concat/push the user function back on
+ return computed(...args.concat([cb]));
+ };
+};
+export const subscribe = purify(computed);
+export default purify;
diff --git a/ui-v2/app/utils/dom/create-listeners.js b/ui-v2/app/utils/dom/create-listeners.js
new file mode 100644
index 0000000000..623046198f
--- /dev/null
+++ b/ui-v2/app/utils/dom/create-listeners.js
@@ -0,0 +1,32 @@
+export default function(listeners = []) {
+ const add = function(target, event, handler) {
+ let addEventListener = 'addEventListener';
+ let removeEventListener = 'removeEventListener';
+ if (typeof target[addEventListener] === 'undefined') {
+ addEventListener = 'on';
+ removeEventListener = 'off';
+ }
+ target[addEventListener](event, handler);
+ const remove = function() {
+ target[removeEventListener](event, handler);
+ return handler;
+ };
+ listeners.push(remove);
+ return remove;
+ };
+ // TODO: Allow passing of a 'listener remove' in here
+ // call it, find in the array and remove
+ // Post-thoughts, pretty sure this is covered now by returning the remove
+ // function above, use-case for wanting to use this method to remove individual
+ // listeners is probably pretty limited, this method itself could be easily implemented
+ // from the outside also, but I suppose its handy to keep here
+ const remove = function() {
+ const handlers = listeners.map(item => item());
+ listeners.splice(0, listeners.length);
+ return handlers;
+ };
+ return {
+ add: add,
+ remove: remove,
+ };
+}
diff --git a/ui-v2/app/utils/dom/event-source/blocking.js b/ui-v2/app/utils/dom/event-source/blocking.js
new file mode 100644
index 0000000000..afc315896f
--- /dev/null
+++ b/ui-v2/app/utils/dom/event-source/blocking.js
@@ -0,0 +1,116 @@
+import { get } from '@ember/object';
+import { Promise } from 'rsvp';
+
+const pause = 2000;
+// native EventSource retry is ~3s wait
+export const create5xxBackoff = function(ms = 3000, P = Promise, wait = setTimeout) {
+ // This expects an ember-data like error
+ return function(err) {
+ const status = get(err, 'errors.firstObject.status');
+ if (typeof status !== 'undefined') {
+ switch (true) {
+ // Any '5xx' (not 500) errors should back off and try again
+ case status.indexOf('5') === 0 && status.length === 3 && status !== '500':
+ return new P(function(resolve) {
+ wait(function() {
+ resolve(err);
+ }, ms);
+ });
+ }
+ }
+ // any other errors should throw to be picked up by an error listener/catch
+ throw err;
+ };
+};
+export const validateCursor = function(current, prev = null) {
+ let cursor = parseInt(current);
+ if (!isNaN(cursor)) {
+ // if cursor is less than the current cursor, reset to zero
+ if (prev !== null && cursor < prev) {
+ cursor = 0;
+ }
+ // if cursor is less than 0, its always safe to use 1
+ return Math.max(cursor, 1);
+ }
+};
+const throttle = function(configuration, prev, current) {
+ return function(obj) {
+ return new Promise(function(resolve, reject) {
+ setTimeout(function() {
+ resolve(obj);
+ }, pause);
+ });
+ };
+};
+const defaultCreateEvent = function(result, configuration) {
+ return {
+ type: 'message',
+ data: result,
+ };
+};
+/**
+ * Wraps an EventSource with functionality to add native EventSource-like functionality
+ *
+ * @param {Class} [CallableEventSource] - CallableEventSource Class
+ * @param {Function} [backoff] - Default backoff function for all instances, defaults to create5xxBackoff
+ */
+export default function(EventSource, backoff = create5xxBackoff()) {
+ /**
+ * An EventSource implementation to add native EventSource-like functionality with just callbacks (`cursor` and 5xx backoff)
+ *
+ * This includes:
+ * 1. 5xx backoff support (uses a 3 second reconnect like native implementations). You can add to this via `Promise.catch`
+ * 2. A `cursor` configuration value. Current `cursor` is taken from the `meta` property of the event (i.e. `event.data.meta.cursor`)
+ * 3. Event data can be customized by adding a `configuration.createEvent`
+ *
+ * @param {Function} [source] - Promise returning function that resolves your data
+ * @param {Object} [configuration] - Plain configuration object:
+ * `cursor` - Cursor position of the EventSource
+ * `createEvent` - A data filter, giving you the opportunity to filter or replace the event data, such as removing/replacing records
+ */
+ return class extends EventSource {
+ constructor(source, configuration = {}) {
+ super(configuration => {
+ const { createEvent, ...superConfiguration } = configuration;
+ return source
+ .apply(this, [superConfiguration])
+ .catch(backoff)
+ .then(result => {
+ if (result instanceof Error) {
+ return result;
+ }
+ const _createEvent =
+ typeof createEvent === 'function' ? createEvent : defaultCreateEvent;
+ let event = _createEvent(result, configuration);
+ // allow custom types, but make a default of `message`, ideally this would check for CustomEvent
+ // but keep this flexible for the moment
+ if (!event.type) {
+ event = {
+ type: 'message',
+ data: event,
+ };
+ }
+ // meta is also configurable by using createEvent
+ const meta = get(event.data || {}, 'meta');
+ if (meta) {
+ // pick off the `cursor` from the meta and add it to configuration
+ // along with cursor validation
+ configuration.cursor = validateCursor(meta.cursor, configuration.cursor);
+ }
+ this.currentEvent = event;
+ this.dispatchEvent(this.currentEvent);
+ const throttledResolve = throttle(configuration, this.currentEvent, this.previousEvent);
+ this.previousEvent = this.currentEvent;
+ return throttledResolve(result);
+ });
+ }, configuration);
+ }
+ // if we are having these props, at least make getters
+ getCurrentEvent() {
+ return this.currentEvent;
+ }
+ getPreviousEvent() {
+ return this.previousEvent;
+ }
+ };
+}
diff --git a/ui-v2/app/utils/dom/event-source/cache.js b/ui-v2/app/utils/dom/event-source/cache.js
new file mode 100644
index 0000000000..4e7d9dee58
--- /dev/null
+++ b/ui-v2/app/utils/dom/event-source/cache.js
@@ -0,0 +1,31 @@
+export default function(source, DefaultEventSource, P = Promise) {
+ return function(sources) {
+ return function(cb, configuration) {
+ const key = configuration.key;
+ if (typeof sources[key] !== 'undefined' && configuration.settings.enabled) {
+ if (typeof sources[key].configuration === 'undefined') {
+ sources[key].configuration = {};
+ }
+ sources[key].configuration.settings = configuration.settings;
+ return source(sources[key]);
+ } else {
+ const EventSource = configuration.type || DefaultEventSource;
+ const eventSource = (sources[key] = new EventSource(cb, configuration));
+ return source(eventSource)
+ .catch(function(e) {
+ // any errors, delete from the cache for next time
+ delete sources[key];
+ return P.reject(e);
+ })
+ .then(function(eventSource) {
+ // make sure we cancel everything out if there is no cursor
+ if (typeof eventSource.configuration.cursor === 'undefined') {
+ eventSource.close();
+ delete sources[key];
+ }
+ return eventSource;
+ });
+ }
+ };
+ };
+}
diff --git a/ui-v2/app/utils/dom/event-source/callable.js b/ui-v2/app/utils/dom/event-source/callable.js
new file mode 100644
index 0000000000..a25633c1bf
--- /dev/null
+++ b/ui-v2/app/utils/dom/event-source/callable.js
@@ -0,0 +1,71 @@
+export const defaultRunner = function(target, configuration, isClosed) {
+ if (isClosed(target)) {
+ target.dispatchEvent({ type: 'close' });
+ return;
+ }
+ // TODO Consider wrapping this is a promise for none thenable returns
+ return target.source
+ .bind(target)(configuration)
+ .then(function(res) {
+ return defaultRunner(target, configuration, isClosed);
+ });
+};
+const errorEvent = function(e) {
+ return new ErrorEvent('error', {
+ error: e,
+ message: e.message,
+ });
+};
+const isClosed = function(target) {
+ switch (target.readyState) {
+ case 2: // CLOSED
+ case 3: // CLOSING
+ return true;
+ }
+ return false;
+};
+export default function(
+ EventTarget,
+ P = Promise,
+ run = defaultRunner,
+ createErrorEvent = errorEvent
+) {
+ return class extends EventTarget {
+ constructor(source, configuration = {}) {
+ super();
+ this.readyState = 2;
+ this.source =
+ typeof source !== 'function'
+ ? function(configuration) {
+ this.close();
+ return P.resolve();
+ }
+ : source;
+ this.readyState = 0; // connecting
+ P.resolve()
+ .then(() => {
+ this.readyState = 1; // open
+ // ...that the connection _was just_ opened
+ this.dispatchEvent({ type: 'open' });
+ return run(this, configuration, isClosed);
+ })
+ .catch(e => {
+ this.dispatchEvent(createErrorEvent(e));
+ // close after the dispatch so we can tell if it was an error whilst closed or not
+ // but make sure its before the promise tick
+ this.readyState = 2; // CLOSE
+ })
+ .then(() => {
+ // This only gets called when the promise chain completely finishes
+ // so only when its completely closed.
+ this.readyState = 2; // CLOSE
+ });
+ }
+ close() {
+ // additional readyState 3 = CLOSING
+ if (this.readyState !== 2) {
+ this.readyState = 3;
+ }
+ }
+ };
+}
diff --git a/ui-v2/app/utils/dom/event-source/index.js b/ui-v2/app/utils/dom/event-source/index.js
new file mode 100644
index 0000000000..d7168ad913
--- /dev/null
+++ b/ui-v2/app/utils/dom/event-source/index.js
@@ -0,0 +1,81 @@
+import ObjectProxy from '@ember/object/proxy';
+import ArrayProxy from '@ember/array/proxy';
+import { Promise } from 'rsvp';
+
+import createListeners from 'consul-ui/utils/dom/create-listeners';
+
+import EventTarget from 'consul-ui/utils/dom/event-target/rsvp';
+
+import cacheFactory from 'consul-ui/utils/dom/event-source/cache';
+import proxyFactory from 'consul-ui/utils/dom/event-source/proxy';
+import firstResolverFactory from 'consul-ui/utils/dom/event-source/resolver';
+
+import CallableEventSourceFactory from 'consul-ui/utils/dom/event-source/callable';
+import ReopenableEventSourceFactory from 'consul-ui/utils/dom/event-source/reopenable';
+import BlockingEventSourceFactory from 'consul-ui/utils/dom/event-source/blocking';
+import StorageEventSourceFactory from 'consul-ui/utils/dom/event-source/storage';
+
+// All The EventSource-i
+export const CallableEventSource = CallableEventSourceFactory(EventTarget, Promise);
+export const ReopenableEventSource = ReopenableEventSourceFactory(CallableEventSource);
+export const BlockingEventSource = BlockingEventSourceFactory(ReopenableEventSource);
+export const StorageEventSource = StorageEventSourceFactory(EventTarget, Promise);
+
+// various utils
+export const proxy = proxyFactory(ObjectProxy, ArrayProxy, createListeners);
+export const resolve = firstResolverFactory(Promise);
+
+export const source = function(source) {
+ // create API needed for conventional promise blocked, loading, Routes
+ // i.e. resolve/reject on first response
+ return resolve(source, createListeners()).then(function(data) {
+ // create API needed for conventional DD/computed and Controllers
+ return proxy(source, data);
+ });
+};
+export const cache = cacheFactory(source, BlockingEventSource, Promise);
+
+const errorEvent = function(e) {
+ return new ErrorEvent('error', {
+ error: e,
+ message: e.message,
+ });
+};
+export const fromPromise = function(promise) {
+ return new CallableEventSource(function(configuration) {
+ const dispatch = this.dispatchEvent.bind(this);
+ const close = () => {
+ this.close();
+ };
+ return promise
+ .then(function(result) {
+ close();
+ dispatch({ type: 'message', data: result });
+ })
+ .catch(function(e) {
+ close();
+ dispatch(errorEvent(e));
+ });
+ });
+};
+export const toPromise = function(target, cb, eventName = 'message', errorName = 'error') {
+ return new Promise(function(resolve, reject) {
+ // TODO: e.target.data
+ const message = function(e) {
+ resolve(e.data);
+ };
+ const error = function(e) {
+ reject(e.error);
+ };
+ const remove = function() {
+ if (typeof target.close === 'function') {
+ target.close();
+ }
+ target.removeEventListener(eventName, message);
+ target.removeEventListener(errorName, error);
+ };
+ target.addEventListener(eventName, message);
+ target.addEventListener(errorName, error);
+ cb(remove);
+ });
+};
diff --git a/ui-v2/app/utils/dom/event-source/proxy.js b/ui-v2/app/utils/dom/event-source/proxy.js
new file mode 100644
index 0000000000..19f0d3d5cb
--- /dev/null
+++ b/ui-v2/app/utils/dom/event-source/proxy.js
@@ -0,0 +1,56 @@
+import { get, set } from '@ember/object';
+
+export default function(ObjProxy, ArrProxy, createListeners) {
+ return function(source, data = []) {
+ let Proxy = ObjProxy;
+ // TODO: Why are these two separate?
+ // And when is data ever a string?
+ if (typeof data !== 'string' && typeof get(data, 'length') !== 'undefined') {
+ data = data.filter(function(item) {
+ return !get(item, 'isDestroyed') && !get(item, 'isDeleted') && get(item, 'isLoaded');
+ });
+ }
+ if (typeof data !== 'string' && typeof get(data, 'length') !== 'undefined') {
+ Proxy = ArrProxy;
+ }
+ const proxy = Proxy.create({
+ content: data,
+ closed: false,
+ error: null,
+ init: function() {
+ this.listeners = createListeners();
+ this.listeners.add(source, 'message', e => set(this, 'content', e.data));
+ this.listeners.add(source, 'open', () => set(this, 'closed', false));
+ this.listeners.add(source, 'close', () => set(this, 'closed', true));
+ this.listeners.add(source, 'error', e => set(this, 'error', e.error));
+ },
+ configuration: source.configuration,
+ addEventListener: function(type, handler) {
+ // Force use of computed for messages
+ // Temporarily disable this restriction
+ // if (type !== 'message') {
+ this.listeners.add(source, type, handler);
+ // }
+ },
+ getCurrentEvent: function() {
+ return source.getCurrentEvent(...arguments);
+ },
+ removeEventListener: function() {
+ return source.removeEventListener(...arguments);
+ },
+ dispatchEvent: function() {
+ return source.dispatchEvent(...arguments);
+ },
+ close: function() {
+ return source.close(...arguments);
+ },
+ reopen: function() {
+ return source.reopen(...arguments);
+ },
+ willDestroy: function() {
+ this.listeners.remove();
+ },
+ });
+ return proxy;
+ };
+}
diff --git a/ui-v2/app/utils/dom/event-source/reopenable.js b/ui-v2/app/utils/dom/event-source/reopenable.js
new file mode 100644
index 0000000000..c1f362c5de
--- /dev/null
+++ b/ui-v2/app/utils/dom/event-source/reopenable.js
@@ -0,0 +1,23 @@
+/**
+ * Wraps an EventSource so that you can `close` and `reopen`
+ *
+ * @param {Class} eventSource - EventSource class to extend from
+ */
+export default function(eventSource = EventSource) {
+ return class extends eventSource {
+ constructor(source, configuration) {
+ super(...arguments);
+ this.configuration = configuration;
+ }
+ reopen() {
+ switch (this.readyState) {
+ case 3: // CLOSING
+ this.readyState = 1;
+ break;
+ case 2: // CLOSED
+ eventSource.apply(this, [this.source, this.configuration]);
+ break;
+ }
+ }
+ };
+}
diff --git a/ui-v2/app/utils/dom/event-source/resolver.js b/ui-v2/app/utils/dom/event-source/resolver.js
new file mode 100644
index 0000000000..a5984295aa
--- /dev/null
+++ b/ui-v2/app/utils/dom/event-source/resolver.js
@@ -0,0 +1,29 @@
+export default function(P = Promise) {
+ return function(source, listeners) {
+ let current;
+ if (typeof source.getCurrentEvent === 'function') {
+ current = source.getCurrentEvent();
+ }
+ if (current != null) {
+ // immediately resolve if we have previous cached data
+ return P.resolve(current.data).then(function(cached) {
+ source.reopen();
+ return cached;
+ });
+ }
+ // if we have no previously cached data, listen for the first response
+ return new P(function(resolve, reject) {
+ // close, cleanup and reject if we get an error
+ listeners.add(source, 'error', function(e) {
+ listeners.remove();
+ e.target.close();
+ reject(e.error);
+ });
+ // ...or cleanup and respond with the first lot of data
+ listeners.add(source, 'message', function(e) {
+ listeners.remove();
+ resolve(e.data);
+ });
+ });
+ };
+}
diff --git a/ui-v2/app/utils/dom/event-source/storage.js b/ui-v2/app/utils/dom/event-source/storage.js
new file mode 100644
index 0000000000..b5cba3b618
--- /dev/null
+++ b/ui-v2/app/utils/dom/event-source/storage.js
@@ -0,0 +1,40 @@
+export default function(EventTarget, P = Promise) {
+ const handler = function(e) {
+ if (e.key === this.configuration.key) {
+ P.resolve(this.getCurrentEvent()).then(event => {
+ this.configuration.cursor++;
+ this.dispatchEvent(event);
+ });
+ }
+ };
+ return class extends EventTarget {
+ constructor(cb, configuration) {
+ super(...arguments);
+ this.source = cb;
+ this.handler = handler.bind(this);
+ this.configuration = configuration;
+ this.configuration.cursor = 1;
+ this.dispatcher = configuration.dispatcher;
+ this.reopen();
+ }
+ dispatchEvent() {
+ if (this.readyState === 1) {
+ return super.dispatchEvent(...arguments);
+ }
+ }
+ close() {
+ this.dispatcher.removeEventListener('storage', this.handler);
+ this.readyState = 2;
+ }
+ reopen() {
+ this.dispatcher.addEventListener('storage', this.handler);
+ this.readyState = 1;
+ }
+ getCurrentEvent() {
+ return {
+ type: 'message',
+ data: this.source(this.configuration),
+ };
+ }
+ };
+}
diff --git a/ui-v2/app/utils/dom/event-target/event-target-shim/LICENSE b/ui-v2/app/utils/dom/event-target/event-target-shim/LICENSE
new file mode 100644
index 0000000000..c39e6949ed
--- /dev/null
+++ b/ui-v2/app/utils/dom/event-target/event-target-shim/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Toru Nagashima
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/ui-v2/app/utils/dom/event-target/event-target-shim/event.js b/ui-v2/app/utils/dom/event-target/event-target-shim/event.js
new file mode 100644
index 0000000000..13a46fd5a1
--- /dev/null
+++ b/ui-v2/app/utils/dom/event-target/event-target-shim/event.js
@@ -0,0 +1,470 @@
+/**
+ * @typedef {object} PrivateData
+ * @property {EventTarget} eventTarget The event target.
+ * @property {{type:string}} event The original event object.
+ * @property {number} eventPhase The current event phase.
+ * @property {EventTarget|null} currentTarget The current event target.
+ * @property {boolean} canceled The flag to prevent default.
+ * @property {boolean} stopped The flag to stop propagation.
+ * @property {boolean} immediateStopped The flag to stop propagation immediately.
+ * @property {Function|null} passiveListener The listener if the current listener is passive. Otherwise this is null.
+ * @property {number} timeStamp The unix time.
+ * @private
+ */
+
+/**
+ * Private data for event wrappers.
+ * @type {WeakMap}
+ * @private
+ */
+const privateData = new WeakMap();
+
+/**
+ * Cache for wrapper classes.
+ * @type {WeakMap}
+ * @private
+ */
+const wrappers = new WeakMap();
+
+/**
+ * Get private data.
+ * @param {Event} event The event object to get private data.
+ * @returns {PrivateData} The private data of the event.
+ * @private
+ */
+function pd(event) {
+ const retv = privateData.get(event);
+ console.assert(retv != null, "'this' is expected an Event object, but got", event);
+ return retv;
+}
+
+/**
+ * https://dom.spec.whatwg.org/#set-the-canceled-flag
+ * @param data {PrivateData} private data.
+ */
+function setCancelFlag(data) {
+ if (data.passiveListener != null) {
+ if (typeof console !== 'undefined' && typeof console.error === 'function') {
+ console.error(
+ 'Unable to preventDefault inside passive event listener invocation.',
+ data.passiveListener
+ );
+ }
+ return;
+ }
+ if (!data.event.cancelable) {
+ return;
+ }
+
+ data.canceled = true;
+ if (typeof data.event.preventDefault === 'function') {
+ data.event.preventDefault();
+ }
+}
+
+/**
+ * @see https://dom.spec.whatwg.org/#interface-event
+ * @private
+ */
+/**
+ * The event wrapper.
+ * @constructor
+ * @param {EventTarget} eventTarget The event target of this dispatching.
+ * @param {Event|{type:string}} event The original event to wrap.
+ */
+function Event(eventTarget, event) {
+ privateData.set(this, {
+ eventTarget,
+ event,
+ eventPhase: 2,
+ currentTarget: eventTarget,
+ canceled: false,
+ stopped: false,
+ immediateStopped: false,
+ passiveListener: null,
+ timeStamp: event.timeStamp || Date.now(),
+ });
+
+ // https://heycam.github.io/webidl/#Unforgeable
+ Object.defineProperty(this, 'isTrusted', { value: false, enumerable: true });
+
+ // Define accessors
+ const keys = Object.keys(event);
+ for (let i = 0; i < keys.length; ++i) {
+ const key = keys[i];
+ if (!(key in this)) {
+ Object.defineProperty(this, key, defineRedirectDescriptor(key));
+ }
+ }
+}
+
+// Should be enumerable, but class methods are not enumerable.
+Event.prototype = {
+ /**
+ * The type of this event.
+ * @type {string}
+ */
+ get type() {
+ return pd(this).event.type;
+ },
+
+ /**
+ * The target of this event.
+ * @type {EventTarget}
+ */
+ get target() {
+ return pd(this).eventTarget;
+ },
+
+ /**
+ * The target of this event.
+ * @type {EventTarget}
+ */
+ get currentTarget() {
+ return pd(this).currentTarget;
+ },
+
+ /**
+ * @returns {EventTarget[]} The composed path of this event.
+ */
+ composedPath() {
+ const currentTarget = pd(this).currentTarget;
+ if (currentTarget == null) {
+ return [];
+ }
+ return [currentTarget];
+ },
+
+ /**
+ * Constant of NONE.
+ * @type {number}
+ */
+ get NONE() {
+ return 0;
+ },
+
+ /**
+ * Constant of CAPTURING_PHASE.
+ * @type {number}
+ */
+ get CAPTURING_PHASE() {
+ return 1;
+ },
+
+ /**
+ * Constant of AT_TARGET.
+ * @type {number}
+ */
+ get AT_TARGET() {
+ return 2;
+ },
+
+ /**
+ * Constant of BUBBLING_PHASE.
+ * @type {number}
+ */
+ get BUBBLING_PHASE() {
+ return 3;
+ },
+
+ /**
+ * The target of this event.
+ * @type {number}
+ */
+ get eventPhase() {
+ return pd(this).eventPhase;
+ },
+
+ /**
+ * Stop event bubbling.
+ * @returns {void}
+ */
+ stopPropagation() {
+ const data = pd(this);
+
+ data.stopped = true;
+ if (typeof data.event.stopPropagation === 'function') {
+ data.event.stopPropagation();
+ }
+ },
+
+ /**
+ * Stop event bubbling.
+ * @returns {void}
+ */
+ stopImmediatePropagation() {
+ const data = pd(this);
+
+ data.stopped = true;
+ data.immediateStopped = true;
+ if (typeof data.event.stopImmediatePropagation === 'function') {
+ data.event.stopImmediatePropagation();
+ }
+ },
+
+ /**
+ * The flag to be bubbling.
+ * @type {boolean}
+ */
+ get bubbles() {
+ return Boolean(pd(this).event.bubbles);
+ },
+
+ /**
+ * The flag to be cancelable.
+ * @type {boolean}
+ */
+ get cancelable() {
+ return Boolean(pd(this).event.cancelable);
+ },
+
+ /**
+ * Cancel this event.
+ * @returns {void}
+ */
+ preventDefault() {
+ setCancelFlag(pd(this));
+ },
+
+ /**
+ * The flag to indicate cancellation state.
+ * @type {boolean}
+ */
+ get defaultPrevented() {
+ return pd(this).canceled;
+ },
+
+ /**
+ * The flag to be composed.
+ * @type {boolean}
+ */
+ get composed() {
+ return Boolean(pd(this).event.composed);
+ },
+
+ /**
+ * The unix time of this event.
+ * @type {number}
+ */
+ get timeStamp() {
+ return pd(this).timeStamp;
+ },
+
+ /**
+ * The target of this event.
+ * @type {EventTarget}
+ * @deprecated
+ */
+ get srcElement() {
+ return pd(this).eventTarget;
+ },
+
+ /**
+ * The flag to stop event bubbling.
+ * @type {boolean}
+ * @deprecated
+ */
+ get cancelBubble() {
+ return pd(this).stopped;
+ },
+ set cancelBubble(value) {
+ if (!value) {
+ return;
+ }
+ const data = pd(this);
+
+ data.stopped = true;
+ if (typeof data.event.cancelBubble === 'boolean') {
+ data.event.cancelBubble = true;
+ }
+ },
+
+ /**
+ * The flag to indicate cancellation state.
+ * @type {boolean}
+ * @deprecated
+ */
+ get returnValue() {
+ return !pd(this).canceled;
+ },
+ set returnValue(value) {
+ if (!value) {
+ setCancelFlag(pd(this));
+ }
+ },
+
+ /**
+ * Initialize this event object. But do nothing under event dispatching.
+ * @param {string} type The event type.
+ * @param {boolean} [bubbles=false] The flag to be possible to bubble up.
+ * @param {boolean} [cancelable=false] The flag to be possible to cancel.
+ * @deprecated
+ */
+ initEvent() {
+ // Do nothing.
+ },
+};
+
+// `constructor` is not enumerable.
+Object.defineProperty(Event.prototype, 'constructor', {
+ value: Event,
+ configurable: true,
+ writable: true,
+});
+
+// Ensure `event instanceof window.Event` is `true`.
+if (typeof window !== 'undefined' && typeof window.Event !== 'undefined') {
+ Object.setPrototypeOf(Event.prototype, window.Event.prototype);
+
+ // Make association for wrappers.
+ wrappers.set(window.Event.prototype, Event);
+}
+
+/**
+ * Get the property descriptor to redirect a given property.
+ * @param {string} key Property name to define property descriptor.
+ * @returns {PropertyDescriptor} The property descriptor to redirect the property.
+ * @private
+ */
+function defineRedirectDescriptor(key) {
+ return {
+ get() {
+ return pd(this).event[key];
+ },
+ set(value) {
+ pd(this).event[key] = value;
+ },
+ configurable: true,
+ enumerable: true,
+ };
+}
+
+/**
+ * Get the property descriptor to call a given method property.
+ * @param {string} key Property name to define property descriptor.
+ * @returns {PropertyDescriptor} The property descriptor to call the method property.
+ * @private
+ */
+function defineCallDescriptor(key) {
+ return {
+ value() {
+ const event = pd(this).event;
+ return event[key].apply(event, arguments);
+ },
+ configurable: true,
+ enumerable: true,
+ };
+}
+
+/**
+ * Define new wrapper class.
+ * @param {Function} BaseEvent The base wrapper class.
+ * @param {Object} proto The prototype of the original event.
+ * @returns {Function} The defined wrapper class.
+ * @private
+ */
+function defineWrapper(BaseEvent, proto) {
+ const keys = Object.keys(proto);
+ if (keys.length === 0) {
+ return BaseEvent;
+ }
+
+ /** CustomEvent */
+ function CustomEvent(eventTarget, event) {
+ BaseEvent.call(this, eventTarget, event);
+ }
+
+ CustomEvent.prototype = Object.create(BaseEvent.prototype, {
+ constructor: { value: CustomEvent, configurable: true, writable: true },
+ });
+
+ // Define accessors.
+ for (let i = 0; i < keys.length; ++i) {
+ const key = keys[i];
+ if (!(key in BaseEvent.prototype)) {
+ const descriptor = Object.getOwnPropertyDescriptor(proto, key);
+ const isFunc = typeof descriptor.value === 'function';
+ Object.defineProperty(
+ CustomEvent.prototype,
+ key,
+ isFunc ? defineCallDescriptor(key) : defineRedirectDescriptor(key)
+ );
+ }
+ }
+
+ return CustomEvent;
+}
+
+/**
+ * Get the wrapper class of a given prototype.
+ * @param {Object} proto The prototype of the original event to get its wrapper.
+ * @returns {Function} The wrapper class.
+ * @private
+ */
+function getWrapper(proto) {
+ if (proto == null || proto === Object.prototype) {
+ return Event;
+ }
+
+ let wrapper = wrappers.get(proto);
+ if (wrapper == null) {
+ wrapper = defineWrapper(getWrapper(Object.getPrototypeOf(proto)), proto);
+ wrappers.set(proto, wrapper);
+ }
+ return wrapper;
+}
+
+/**
+ * Wrap a given event to management a dispatching.
+ * @param {EventTarget} eventTarget The event target of this dispatching.
+ * @param {Object} event The event to wrap.
+ * @returns {Event} The wrapper instance.
+ * @private
+ */
+export function wrapEvent(eventTarget, event) {
+ const Wrapper = getWrapper(Object.getPrototypeOf(event));
+ return new Wrapper(eventTarget, event);
+}
+
+/**
+ * Get the immediateStopped flag of a given event.
+ * @param {Event} event The event to get.
+ * @returns {boolean} The flag to stop propagation immediately.
+ * @private
+ */
+export function isStopped(event) {
+ return pd(event).immediateStopped;
+}
+
+/**
+ * Set the current event phase of a given event.
+ * @param {Event} event The event to set current target.
+ * @param {number} eventPhase New event phase.
+ * @returns {void}
+ * @private
+ */
+export function setEventPhase(event, eventPhase) {
+ pd(event).eventPhase = eventPhase;
+}
+
+/**
+ * Set the current target of a given event.
+ * @param {Event} event The event to set current target.
+ * @param {EventTarget|null} currentTarget New current target.
+ * @returns {void}
+ * @private
+ */
+export function setCurrentTarget(event, currentTarget) {
+ pd(event).currentTarget = currentTarget;
+}
+
+/**
+ * Set a passive listener of a given event.
+ * @param {Event} event The event to set current target.
+ * @param {Function|null} passiveListener New passive listener.
+ * @returns {void}
+ * @private
+ */
+export function setPassiveListener(event, passiveListener) {
+ pd(event).passiveListener = passiveListener;
+}
diff --git a/ui-v2/app/utils/dom/event-target/rsvp.js b/ui-v2/app/utils/dom/event-target/rsvp.js
new file mode 100644
index 0000000000..d1a108663e
--- /dev/null
+++ b/ui-v2/app/utils/dom/event-target/rsvp.js
@@ -0,0 +1,63 @@
+// Simple RSVP.EventTarget wrapper to make it more like a standard EventTarget
+import RSVP from 'rsvp';
+// See https://github.com/mysticatea/event-target-shim/blob/v4.0.2/src/event.mjs
+// The MIT License (MIT) - Copyright (c) 2015 Toru Nagashima
+import { setCurrentTarget, wrapEvent } from './event-target-shim/event';
+
+const EventTarget = function() {};
+function callbacksFor(object) {
+ let callbacks = object._promiseCallbacks;
+
+ if (!callbacks) {
+ callbacks = object._promiseCallbacks = {};
+ }
+
+ return callbacks;
+}
+EventTarget.prototype = Object.assign(
+ Object.create(Object.prototype, {
+ constructor: {
+ value: EventTarget,
+ configurable: true,
+ writable: true,
+ },
+ }),
+ {
+ dispatchEvent: function(obj) {
+ // borrow just what I need from event-target-shim
+ // to make true events even ErrorEvents with targets
+ const wrappedEvent = wrapEvent(this, obj);
+ setCurrentTarget(wrappedEvent, null);
+ // RSVP trigger doesn't bind to `this`
+ // the rest is pretty much the contents of `trigger`
+ // but with a `.bind(this)` to make it compatible
+ // with standard EventTarget
+ // we use `let` and `callbacksFor` above, just to keep things the same as rsvp.js
+ const eventName = obj.type;
+ const options = wrappedEvent;
+ let allCallbacks = callbacksFor(this);
+
+ let callbacks = allCallbacks[eventName];
+ if (callbacks) {
+ // Don't cache the callbacks.length since it may grow
+ let callback;
+ for (let i = 0; i < callbacks.length; i++) {
+ callback = callbacks[i];
+ callback.bind(this)(options);
+ }
+ }
+ },
+ addEventListener: function(event, cb) {
+ this.on(event, cb);
+ },
+ removeEventListener: function(event, cb) {
+ try {
+ this.off(event, cb);
+ } catch (e) {
+ // passthrough
+ }
+ },
+ }
+);
+RSVP.EventTarget.mixin(EventTarget.prototype);
+export default EventTarget;
diff --git a/ui-v2/app/utils/get-component-factory.js b/ui-v2/app/utils/dom/get-component-factory.js
similarity index 100%
rename from ui-v2/app/utils/get-component-factory.js
rename to ui-v2/app/utils/dom/get-component-factory.js
diff --git a/ui-v2/app/utils/form/builder.js b/ui-v2/app/utils/form/builder.js
index b7a8e8c5fd..feba1adbfd 100644
--- a/ui-v2/app/utils/form/builder.js
+++ b/ui-v2/app/utils/form/builder.js
@@ -14,7 +14,7 @@ const defaultChangeset = function(data, validators) {
return changeset;
};
/**
- * Form builder/Form factory (WIP)
+ * Form builder/Form factory
* Deals with handling (generally change) events and updating data in response to the change
* in a typical data down event up manner
* validations are included currently using ember-changeset-validations
@@ -32,26 +32,25 @@ const defaultChangeset = function(data, validators) {
*/
export default function(changeset = defaultChangeset, getFormNameProperty = parseElementName) {
return function(name = '', obj = {}) {
- let _data;
- const _name = name;
const _children = {};
let _validators = null;
// TODO make this into a class to reuse prototype
- return {
+ const form = {
+ data: null,
+ name: name,
getName: function() {
- return _name;
+ return this.name;
},
setData: function(data) {
// Array check temporarily for when we get an empty array from repo.status
if (_validators && !Array.isArray(data)) {
- _data = changeset(data, _validators);
- } else {
- _data = data;
+ data = changeset(data, _validators);
}
+ set(this, 'data', data);
return this;
},
getData: function() {
- return _data;
+ return this.data;
},
add: function(child) {
_children[child.getName()] = child;
@@ -66,7 +65,7 @@ export default function(changeset = defaultChangeset, getFormNameProperty = pars
//
let config = obj;
// if the name (usually the name of the model) isn't this form, look at its children
- if (name !== _name) {
+ if (name !== this.getName()) {
if (this.has(name)) {
// is its a child form then use the child form
return this.form(name).handleEvent(e);
@@ -77,9 +76,13 @@ export default function(changeset = defaultChangeset, getFormNameProperty = pars
}
const data = this.getData();
// ember-data/changeset dance
+ // TODO: This works for ember-data RecordSets and Changesets but not for plain js Objects
+ // see settings
const json = typeof data.toJSON === 'function' ? data.toJSON() : get(data, 'data').toJSON();
// if the form doesn't include a property then throw so it can be
// caught outside, therefore the user can deal with things that aren't in the data
+ // TODO: possibly need to add support for deeper properties using `get` here
+ // for example `client.blocking` instead of just `blocking`
if (!Object.keys(json).includes(prop)) {
const error = new Error(`${prop} property doesn't exist`);
error.target = target;
@@ -124,6 +127,20 @@ export default function(changeset = defaultChangeset, getFormNameProperty = pars
}
return this;
},
+ clear: function(cb = {}) {
+ if (typeof cb === 'function') {
+ return (this.clearer = cb);
+ } else {
+ return this.setData(this.clearer(cb)).getData();
+ }
+ },
+ submit: function(cb = {}) {
+ if (typeof cb === 'function') {
+ return (this.submitter = cb);
+ } else {
+ this.submitter(this.getData());
+ }
+ },
setValidators: function(validators) {
_validators = validators;
return this;
@@ -152,5 +169,8 @@ export default function(changeset = defaultChangeset, getFormNameProperty = pars
return typeof _children[name] !== 'undefined';
},
};
+ form.submit = form.submit.bind(form);
+ form.reset = form.reset.bind(form);
+ return form;
};
}
diff --git a/ui-v2/app/utils/get-object-pool.js b/ui-v2/app/utils/get-object-pool.js
new file mode 100644
index 0000000000..ad9f0ebe83
--- /dev/null
+++ b/ui-v2/app/utils/get-object-pool.js
@@ -0,0 +1,52 @@
+export default function(dispose = function() {}, max, objects = []) {
+ return {
+ acquire: function(obj, id) {
+ // TODO: what should happen if an ID already exists
+ // should we ignore and release both? Or prevent from acquiring? Or generate a unique ID?
+ // what happens if we can't get an id via getId or .id?
+ // could potentially use Set
+ objects.push(obj);
+ if (typeof max !== 'undefined') {
+ if (objects.length > max) {
+ return dispose(objects.shift());
+ }
+ }
+ return id;
+ },
+ // release releases the obj from the pool but **doesn't** dispose it
+ release: function(obj) {
+ let index = -1;
+ let id;
+ if (typeof obj === 'string') {
+ id = obj;
+ } else {
+ id = obj.id;
+ }
+ objects.forEach(function(item, i) {
+ let itemId;
+ if (typeof item.getId === 'function') {
+ itemId = item.getId();
+ } else {
+ itemId = item.id;
+ }
+ if (itemId === id) {
+ index = i;
+ }
+ });
+ if (index !== -1) {
+ return objects.splice(index, 1)[0];
+ }
+ },
+ purge: function() {
+ let obj;
+ const objs = [];
+ while ((obj = objects.shift())) {
+ objs.push(dispose(obj));
+ }
+ return objs;
+ },
+ dispose: function(id) {
+ return dispose(this.release(id));
+ },
+ };
+}
diff --git a/ui-v2/app/utils/http/consul.js b/ui-v2/app/utils/http/consul.js
new file mode 100644
index 0000000000..f81e47d366
--- /dev/null
+++ b/ui-v2/app/utils/http/consul.js
@@ -0,0 +1,3 @@
+export const HEADERS_SYMBOL = '__consul_ui_http_headers__';
+export const HEADERS_INDEX = 'x-consul-index';
+export const HEADERS_DIGEST = 'x-consul-contenthash';
diff --git a/ui-v2/app/utils/http/request.js b/ui-v2/app/utils/http/request.js
new file mode 100644
index 0000000000..1a9643d514
--- /dev/null
+++ b/ui-v2/app/utils/http/request.js
@@ -0,0 +1,29 @@
+export default class {
+ constructor(method, url, headers, xhr) {
+ this._xhr = xhr;
+ this._url = url;
+ this._method = method;
+ this._headers = headers;
+ this._headers = {
+ ...headers,
+ 'content-type': 'application/json',
+ 'x-request-id': `${this._method} ${this._url}?${JSON.stringify(headers.body)}`,
+ };
+ if (typeof this._headers.body.index !== 'undefined') {
+ // this should probably be in a response
+ this._headers['content-type'] = 'text/event-stream';
+ }
+ }
+ headers() {
+ return this._headers;
+ }
+ getId() {
+ return this._headers['x-request-id'];
+ }
+ abort() {
+ this._xhr.abort();
+ }
+ connection() {
+ return this._xhr;
+ }
+}
diff --git a/ui-v2/app/utils/minimizeModel.js b/ui-v2/app/utils/minimizeModel.js
new file mode 100644
index 0000000000..6411ad8ce7
--- /dev/null
+++ b/ui-v2/app/utils/minimizeModel.js
@@ -0,0 +1,17 @@
+import { get } from '@ember/object';
+
+export default function(arr) {
+ if (Array.isArray(arr)) {
+ return arr
+ .filter(function(item) {
+ // Just incase, don't save any models that aren't saved
+ return !get(item, 'isNew');
+ })
+ .map(function(item) {
+ return {
+ ID: get(item, 'ID'),
+ Name: get(item, 'Name'),
+ };
+ });
+ }
+}
diff --git a/ui-v2/app/utils/search/filterable.js b/ui-v2/app/utils/search/filterable.js
new file mode 100644
index 0000000000..a0bec4a99a
--- /dev/null
+++ b/ui-v2/app/utils/search/filterable.js
@@ -0,0 +1,47 @@
+import RSVP, { Promise } from 'rsvp';
+export default function(EventTarget = RSVP.EventTarget, P = Promise) {
+ // TODO: Class-ify
+ return function(filter) {
+ return EventTarget.mixin({
+ value: '',
+ add: function(data) {
+ this.data = data;
+ return this;
+ },
+ find: function(terms = []) {
+ this.value = terms
+ .filter(function(item) {
+ return typeof item === 'string' && item !== '';
+ })
+ .map(function(term) {
+ return term.trim();
+ });
+ return P.resolve(
+ this.value.reduce(function(prev, term) {
+ return prev.filter(item => {
+ return filter(item, { s: term });
+ });
+ }, this.data)
+ );
+ },
+ search: function(terms = []) {
+ // specifically no return here we return `this` instead
+ // right now filtering is sync but we introduce an async
+ // flow now for later on
+ this.find(Array.isArray(terms) ? terms : [terms]).then(data => {
+ // TODO: For the moment, lets just fake a target
+ this.trigger('change', {
+ target: {
+ value: this.value.join('\n'),
+ // TODO: selectedOptions is what uses, consider that
+ data: data,
+ },
+ });
+ // not returned
+ return data;
+ });
+ return this;
+ },
+ });
+ };
+}
diff --git a/ui-v2/app/utils/tomography.js b/ui-v2/app/utils/tomography.js
index de60056bc5..4f9611086d 100644
--- a/ui-v2/app/utils/tomography.js
+++ b/ui-v2/app/utils/tomography.js
@@ -44,7 +44,6 @@ export default function(distance) {
}
return {
distances: distances,
- n: distances.length,
min: parseInt(min * 100) / 100,
median: parseInt(median * 100) / 100,
max: parseInt(max * 100) / 100,
diff --git a/ui-v2/app/utils/update-array-object.js b/ui-v2/app/utils/update-array-object.js
index 4bdee8c8be..ef834c81e8 100644
--- a/ui-v2/app/utils/update-array-object.js
+++ b/ui-v2/app/utils/update-array-object.js
@@ -1,12 +1,21 @@
import { get, set } from '@ember/object';
-export default function(arr, item, prop, value) {
+import ObjectProxy from '@ember/object/proxy';
+export default function(items, item, prop, value) {
value = typeof value === 'undefined' ? get(item, prop) : value;
- const current = arr.findBy(prop, value);
- if (current) {
- // TODO: This is reliant on changeset?
- Object.keys(get(item, 'data')).forEach(function(prop) {
- set(current, prop, get(item, prop));
- });
- return current;
+ const pos = items.findIndex(function(item) {
+ return get(item, prop) === value;
+ });
+ if (pos !== -1) {
+ // TODO: We only currently use this with EventSources
+ // would be good to check this doesn't do anything unexpected
+ // with other proxies.
+ // Before we get there, we might have figured a better way to do
+ // this anyway
+ if (item instanceof ObjectProxy) {
+ set(item, 'content', items.objectAt(pos));
+ }
+ items.replace(pos, 1, [item]);
+ items.enumerableContentDidChange();
}
+ return item;
}
diff --git a/ui-v2/app/validations/role.js b/ui-v2/app/validations/role.js
new file mode 100644
index 0000000000..eabd7b6557
--- /dev/null
+++ b/ui-v2/app/validations/role.js
@@ -0,0 +1,4 @@
+import { validateFormat } from 'ember-changeset-validations/validators';
+export default {
+ Name: validateFormat({ regex: /^[A-Za-z0-9\-_]{1,256}$/ }),
+};
diff --git a/ui-v2/config/environment.js b/ui-v2/config/environment.js
index 0d937aeafb..aaeed171fd 100644
--- a/ui-v2/config/environment.js
+++ b/ui-v2/config/environment.js
@@ -27,7 +27,9 @@ module.exports = function(environment) {
injectionFactories: ['view', 'controller', 'component'],
},
};
+ // TODO: These should probably go onto APP
ENV = Object.assign({}, ENV, {
+ CONSUL_UI_DISABLE_REALTIME: false,
CONSUL_GIT_SHA: (function() {
if (process.env.CONSUL_GIT_SHA) {
return process.env.CONSUL_GIT_SHA;
diff --git a/ui-v2/ember-cli-build.js b/ui-v2/ember-cli-build.js
index 3ec0935578..ce6d212f6b 100644
--- a/ui-v2/ember-cli-build.js
+++ b/ui-v2/ember-cli-build.js
@@ -5,7 +5,7 @@ module.exports = function(defaults) {
const env = EmberApp.env();
const prodlike = ['production', 'staging'];
const isProd = env === 'production';
- // leave this in for now for when I start a proper staging env
+ // if we ever need a 'prodlike' staging environment with staging settings
// const isProdLike = prodlike.indexOf(env) > -1;
const sourcemaps = !isProd;
let app = new EmberApp(
diff --git a/ui-v2/lib/commands/index.js b/ui-v2/lib/commands/index.js
new file mode 100644
index 0000000000..c8c558db54
--- /dev/null
+++ b/ui-v2/lib/commands/index.js
@@ -0,0 +1,19 @@
+/* eslint no-console: "off" */
+/* eslint-env node */
+'use strict';
+module.exports = {
+ name: 'commands',
+ includedCommands: function() {
+ return {
+ 'steps:list': {
+ name: 'steps:list',
+ run: function(config, args) {
+ require('./lib/list.js')(`${process.cwd()}/tests/steps.js`);
+ },
+ },
+ };
+ },
+ isDevelopingAddon() {
+ return true;
+ },
+};
diff --git a/ui-v2/lib/commands/lib/list.js b/ui-v2/lib/commands/lib/list.js
new file mode 100644
index 0000000000..db868141d6
--- /dev/null
+++ b/ui-v2/lib/commands/lib/list.js
@@ -0,0 +1,67 @@
+/* eslint no-console: "off" */
+/* eslint-env node */
+'use strict';
+const babel = require('@babel/core');
+const read = require('fs').readFileSync;
+const path = require('path');
+const vm = require('vm');
+const color = require('chalk');
+
+const out = function(prefix, step, desc) {
+ if (!Array.isArray(step)) {
+ step = [step];
+ }
+ step.forEach(function(item) {
+ const str =
+ prefix +
+ item.replace('\n', ' | ').replace(/\$\w+/g, function(match) {
+ return color.cyan(match);
+ });
+ console.log(color.green(str));
+ });
+};
+const library = {
+ given: function(step, cb, desc) {
+ out('Given ', step, desc);
+ return this;
+ },
+ desc: function(desc) {
+ console.log(color.yellow(`- ${desc.trim()}`));
+ },
+ section: function() {
+ console.log(color.yellow(`##`));
+ },
+ then: function(step, cb, desc) {
+ out('Then ', step, desc);
+ return this;
+ },
+ when: function(step, cb, desc) {
+ out('When ', step, desc);
+ return this;
+ },
+};
+const exec = function(filename) {
+ const js = read(filename);
+ const code = babel.transform(js.toString(), {
+ filename: filename,
+ presets: [require('babel-preset-env')],
+ }).code;
+ const exports = {};
+ vm.runInNewContext(
+ code,
+ {
+ exports: exports,
+ require: function(str) {
+ return exec(path.resolve(`${process.cwd()}/tests`, `${str}.js`)).default;
+ },
+ },
+ {
+ filename: filename,
+ }
+ );
+ return exports;
+};
+
+module.exports = function(filename) {
+ exec(filename).default(function() {}, library, {}, {}, {}, function() {});
+};
diff --git a/ui-v2/lib/commands/package.json b/ui-v2/lib/commands/package.json
new file mode 100644
index 0000000000..722b9500ec
--- /dev/null
+++ b/ui-v2/lib/commands/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "commands",
+ "keywords": [
+ "ember-addon"
+ ]
+}
diff --git a/ui-v2/package.json b/ui-v2/package.json
index ccdf44f5d1..c7eec5373f 100644
--- a/ui-v2/package.json
+++ b/ui-v2/package.json
@@ -20,7 +20,8 @@
"test-parallel": "EMBER_EXAM_PARALLEL=true ember exam --split=4 --parallel",
"test:view": "ember test --server --test-port=${EMBER_TEST_PORT:-7357}",
"test:coverage": "COVERAGE=true ember test --test-port=${EMBER_TEST_PORT:-7357}",
- "test:view:coverage": "COVERAGE=true ember test --server --test-port=${EMBER_TEST_PORT:-7357}"
+ "test:view:coverage": "COVERAGE=true ember test --server --test-port=${EMBER_TEST_PORT:-7357}",
+ "steps:list": "node ./lib/commands/bin/list.js"
},
"husky": {
"hooks": {
@@ -38,15 +39,17 @@
]
},
"devDependencies": {
+ "@babel/core": "^7.2.2",
"@hashicorp/consul-api-double": "^2.0.1",
"@hashicorp/ember-cli-api-double": "^1.3.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"base64-js": "^1.3.0",
"broccoli-asset-rev": "^2.4.5",
+ "chalk": "^2.4.2",
"dart-sass": "^1.14.1",
"ember-ajax": "^3.0.0",
"ember-browserify": "^1.2.2",
- "ember-changeset-validations": "^1.2.11",
+ "ember-changeset-validations": "^2.1.0",
"ember-cli": "~2.18.2",
"ember-cli-app-version": "^3.0.0",
"ember-cli-autoprefixer": "^0.8.1",
@@ -104,7 +107,8 @@
"ember-addon": {
"paths": [
"lib/startup",
- "lib/block-slots"
+ "lib/block-slots",
+ "lib/commands"
]
}
}
diff --git a/ui-v2/tests/acceptance/components/catalog-filter.feature b/ui-v2/tests/acceptance/components/catalog-filter.feature
index ed11e247bd..9eb37f1bcf 100644
--- a/ui-v2/tests/acceptance/components/catalog-filter.feature
+++ b/ui-v2/tests/acceptance/components/catalog-filter.feature
@@ -1,3 +1,6 @@
+# TODO: If we keep separate types of catalog filters then
+# these tests need splitting out, if we are moving nodes
+# to use the name filter UI also, then they can stay together
@setupApplicationTest
Feature: components / catalog-filter
Scenario: Filtering [Model]
@@ -60,7 +63,6 @@ Feature: components / catalog-filter
Where:
-------------------------------------------------
| Model | Page | Url |
- | service | services | /dc-1/services |
| node | nodes | /dc-1/nodes |
-------------------------------------------------
Scenario: Filtering [Model] in [Page]
@@ -123,38 +125,22 @@ Feature: components / catalog-filter
| Model | Page | Url |
| service | node | /dc-1/nodes/node-0 |
-------------------------------------------------
- Scenario: Filtering [Model] in [Page]
- Given 1 datacenter model with the value "dc1"
- And 2 [Model] models from yaml
- ---
- - ID: node-0
- ---
- When I visit the [Page] page for yaml
- ---
- dc: dc1
- service: service-0
- ---
- Then I fill in with yaml
- ---
- s: service-0-with-id
- ---
- And I see 1 [Model] model
- Then I see id on the unhealthy like yaml
- ---
- - service-0-with-id
- ---
- Where:
- -------------------------------------------------
- | Model | Page | Url |
- | nodes | service | /dc-1/services/service-0 |
- -------------------------------------------------
- Scenario:
+ Scenario: Freetext filtering the service listing
Given 1 datacenter model with the value "dc-1"
And 3 service models from yaml
---
- Tags: ['one', 'two', 'three']
+ ChecksPassing: 0
+ ChecksWarning: 0
+ ChecksCritical: 1
- Tags: ['two', 'three']
+ ChecksPassing: 0
+ ChecksWarning: 1
+ ChecksCritical: 0
- Tags: ['three']
+ ChecksPassing: 1
+ ChecksWarning: 0
+ ChecksCritical: 0
---
When I visit the services page for yaml
---
@@ -164,21 +150,16 @@ Feature: components / catalog-filter
Then I see 3 service models
Then I fill in with yaml
---
- s: one
- ---
- And I see 1 service model with the name "service-0"
- Then I fill in with yaml
- ---
- s: two
- ---
- And I see 2 service models
- Then I fill in with yaml
- ---
s: three
---
And I see 3 service models
Then I fill in with yaml
---
- s: wothre
+ s: 'tag:two'
---
- And I see 0 service models
+ And I see 2 service models
+ Then I fill in with yaml
+ ---
+ s: 'status:critical'
+ ---
+ And I see 1 service model
diff --git a/ui-v2/tests/acceptance/dc/acls/policies/as-many/add-existing.feature b/ui-v2/tests/acceptance/dc/acls/policies/as-many/add-existing.feature
new file mode 100644
index 0000000000..caf09020bb
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/acls/policies/as-many/add-existing.feature
@@ -0,0 +1,46 @@
+@setupApplicationTest
+Feature: dc / acls / policies / as many / add existing: Add existing policy
+ Scenario: Adding an existing policy as a child of [Model]
+ Given 1 datacenter model with the value "datacenter"
+ And 1 [Model] model from yaml
+ ---
+ Policies: ~
+ ServiceIdentities: ~
+ ---
+ And 2 policy models from yaml
+ ---
+ - ID: policy-1
+ Name: Policy 1
+ - ID: policy-2
+ Name: Policy 2
+ ---
+ When I visit the [Model] page for yaml
+ ---
+ dc: datacenter
+ [Model]: key
+ ---
+ Then the url should be /datacenter/acls/[Model]s/key
+ And I click "#policies .ember-power-select-trigger"
+ And I click ".ember-power-select-option:first-child"
+ And I see 1 policy model on the policies component
+ And I click "#policies .ember-power-select-trigger"
+ And I click ".ember-power-select-option:nth-child(1)"
+ And I see 2 policy models on the policies component
+ And I submit
+ Then a PUT request is made to "/v1/acl/[Model]/key?dc=datacenter" with the body from yaml
+ ---
+ Policies:
+ - ID: policy-1
+ Name: Policy 1
+ - ID: policy-2
+ Name: Policy 2
+ ---
+ Then the url should be /datacenter/acls/[Model]s
+ And "[data-notification]" has the "notification-update" class
+ And "[data-notification]" has the "success" class
+ Where:
+ -------------
+ | Model |
+ | token |
+ | role |
+ -------------
diff --git a/ui-v2/tests/acceptance/dc/acls/policies/as-many/add-new.feature b/ui-v2/tests/acceptance/dc/acls/policies/as-many/add-new.feature
new file mode 100644
index 0000000000..e1d79522fe
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/acls/policies/as-many/add-new.feature
@@ -0,0 +1,79 @@
+@setupApplicationTest
+Feature: dc / acls / policies / as many / add new: Add new policy
+ Background:
+ Given 1 datacenter model with the value "datacenter"
+ And 1 [Model] model from yaml
+ ---
+ Policies: ~
+ ServiceIdentities: ~
+ ---
+ When I visit the [Model] page for yaml
+ ---
+ dc: datacenter
+ [Model]: key
+ ---
+ Then the url should be /datacenter/acls/[Model]s/key
+ And I click policies.create
+ Scenario: Adding a new policy as a child of [Model]
+ Then I fill in the policies.form with yaml
+ ---
+ Name: New-Policy
+ Description: New Policy Description
+ Rules: key {}
+ ---
+ And I click submit on the policies.form
+ Then the last PUT request was made to "/v1/acl/policy?dc=datacenter" with the body from yaml
+ ---
+ Name: New-Policy
+ Description: New Policy Description
+ Rules: key {}
+ ---
+ And I submit
+ Then a PUT request is made to "/v1/acl/[Model]/key?dc=datacenter" with the body from yaml
+ ---
+ Policies:
+ - Name: New-Policy
+ ID: ee52203d-989f-4f7a-ab5a-2bef004164ca-1
+ ---
+ Then the url should be /datacenter/acls/[Model]s
+ And "[data-notification]" has the "notification-update" class
+ And "[data-notification]" has the "success" class
+ Where:
+ -------------
+ | Model |
+ | token |
+ | role |
+ -------------
+ Scenario: Adding a new service identity as a child of [Model]
+ Then I fill in the policies.form with yaml
+ ---
+ Name: New-Service-Identity
+ Description: New Service Identity Description
+ ---
+ And I click serviceIdentity on the policies.form
+ And I click submit on the policies.form
+ And I submit
+ Then a PUT request is made to "/v1/acl/[Model]/key?dc=datacenter" with the body from yaml
+ ---
+ ServiceIdentities:
+ - ServiceName: New-Service-Identity
+ ---
+ Then the url should be /datacenter/acls/[Model]s
+ And "[data-notification]" has the "notification-update" class
+ And "[data-notification]" has the "success" class
+ Where:
+ -------------
+ | Model |
+ | token |
+ | role |
+ -------------
+@ignore:
+ Scenario: Click the cancel form
+ Then ok
+ # And I click cancel on the policyForm
+ Where:
+ -------------
+ | Model |
+ | token |
+ | role |
+ -------------
diff --git a/ui-v2/tests/acceptance/dc/acls/policies/as-many/list.feature b/ui-v2/tests/acceptance/dc/acls/policies/as-many/list.feature
new file mode 100644
index 0000000000..31a9f32c43
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/acls/policies/as-many/list.feature
@@ -0,0 +1,32 @@
+@setupApplicationTest
+Feature: dc / acls / policies / as many / list: List
+ Scenario: Listing policies as children of the [Model] page
+ Given 1 datacenter model with the value "datacenter"
+ And 1 [Model] model from yaml
+ ---
+ ServiceIdentities:
+ - ServiceName: Service-Identity
+ - ServiceName: Service-Identity 2
+ - ServiceName: Service-Identity 3
+ Policies:
+ - Name: Policy
+ ID: 0000
+ - Name: Policy 2
+ ID: 0002
+ - Name: Policy 3
+ ID: 0003
+ ---
+ When I visit the [Model] page for yaml
+ ---
+ dc: datacenter
+ [Model]: key
+ ---
+ Then the url should be /datacenter/acls/[Model]s/key
+ # ServiceIdentities turn into policies
+ Then I see 6 policy models on the policies component
+ Where:
+ -------------
+ | Model |
+ | token |
+ | role |
+ -------------
diff --git a/ui-v2/tests/acceptance/dc/acls/policies/as-many/remove.feature b/ui-v2/tests/acceptance/dc/acls/policies/as-many/remove.feature
new file mode 100644
index 0000000000..c45cd5199f
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/acls/policies/as-many/remove.feature
@@ -0,0 +1,35 @@
+@setupApplicationTest
+Feature: dc / acls / policies / as many / remove: Remove
+ Scenario: Removing policies as children of the [Model] page
+ Given 1 datacenter model with the value "datacenter"
+ And 1 [Model] model from yaml
+ ---
+ ServiceIdentities: ~
+ Policies:
+ - Name: Policy
+ ID: 00000000-0000-0000-0000-000000000001
+ ---
+ When I visit the [Model] page for yaml
+ ---
+ dc: datacenter
+ [Model]: key
+ ---
+ Then the url should be /datacenter/acls/[Model]s/key
+ And I see 1 policy model on the policies component
+ And I click expand on the policies.selectedOptions
+ And the last GET request was made to "/v1/acl/policy/00000000-0000-0000-0000-000000000001?dc=datacenter"
+ And I click delete on the policies.selectedOptions
+ And I click confirmDelete on the policies.selectedOptions
+ And I see 0 policy models on the policies component
+ And I submit
+ Then a PUT request is made to "/v1/acl/[Model]/key?dc=datacenter" with the body from yaml
+ ---
+ Policies: [[]]
+ ---
+ Then the url should be /datacenter/acls/[Model]s
+ Where:
+ -------------
+ | Model |
+ | token |
+ | role |
+ -------------
diff --git a/ui-v2/tests/acceptance/dc/acls/policies/delete.feature b/ui-v2/tests/acceptance/dc/acls/policies/delete.feature
new file mode 100644
index 0000000000..5b296d6b93
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/acls/policies/delete.feature
@@ -0,0 +1,46 @@
+@setupApplicationTest
+Feature: dc / acls / policies / delete: Policy Delete
+ Background:
+ Given 1 datacenter model with the value "datacenter"
+ Scenario: Deleting a policy model from the policies listing page
+ Given 1 policy model from yaml
+ ---
+ ID: 1981f51d-301a-497b-89a0-05112ef02b4b
+ ---
+ When I visit the policies page for yaml
+ ---
+ dc: datacenter
+ ---
+ And I click actions on the policies
+ And I click delete on the policies
+ And I click confirmDelete on the policies
+ Then a DELETE request is made to "/v1/acl/policy/1981f51d-301a-497b-89a0-05112ef02b4b?dc=datacenter"
+ And "[data-notification]" has the "notification-delete" class
+ And "[data-notification]" has the "success" class
+ Given the url "/v1/acl/policy/1981f51d-301a-497b-89a0-05112ef02b4b?dc=datacenter" responds with a 500 status
+ And I click actions on the policies
+ And I click delete on the policies
+ And I click confirmDelete on the policies
+ And "[data-notification]" has the "notification-delete" class
+ And "[data-notification]" has the "error" class
+ Scenario: Deleting a policy from the policy detail page
+ When I visit the policy page for yaml
+ ---
+ dc: datacenter
+ policy: 1981f51d-301a-497b-89a0-05112ef02b4b
+ ---
+ And I click delete
+ And I click confirmDelete on the deleteModal
+ Then a DELETE request is made to "/v1/acl/policy/1981f51d-301a-497b-89a0-05112ef02b4b?dc=datacenter"
+ And "[data-notification]" has the "notification-delete" class
+ And "[data-notification]" has the "success" class
+ When I visit the policy page for yaml
+ ---
+ dc: datacenter
+ policy: 1981f51d-301a-497b-89a0-05112ef02b4b
+ ---
+ Given the url "/v1/acl/policy/1981f51d-301a-497b-89a0-05112ef02b4b?dc=datacenter" responds with a 500 status
+ And I click delete
+ And I click confirmDelete on the deleteModal
+ And "[data-notification]" has the "notification-delete" class
+ And "[data-notification]" has the "error" class
diff --git a/ui-v2/tests/acceptance/dc/acls/tokens/policies/add-existing.feature b/ui-v2/tests/acceptance/dc/acls/roles/as-many/add-existing.feature
similarity index 63%
rename from ui-v2/tests/acceptance/dc/acls/tokens/policies/add-existing.feature
rename to ui-v2/tests/acceptance/dc/acls/roles/as-many/add-existing.feature
index 4cbdeec827..1eda3497a6 100644
--- a/ui-v2/tests/acceptance/dc/acls/tokens/policies/add-existing.feature
+++ b/ui-v2/tests/acceptance/dc/acls/roles/as-many/add-existing.feature
@@ -1,19 +1,19 @@
@setupApplicationTest
-Feature: dc / acls / tokens / policies: ACL Token add existing policy
+Feature: dc / acls / roles / as many / add existing: Add existing
Scenario:
Given 1 datacenter model with the value "datacenter"
And 1 token model from yaml
---
AccessorID: key
Description: The Description
- Policies: ~
+ Roles: ~
---
- And 2 policy models from yaml
+ And 2 role models from yaml
---
- - ID: policy-1
- Name: Policy 1
- - ID: policy-2
- Name: Policy 2
+ - ID: role-1
+ Name: Role 1
+ - ID: role-2
+ Name: Role 2
---
When I visit the token page for yaml
---
@@ -21,12 +21,12 @@ Feature: dc / acls / tokens / policies: ACL Token add existing policy
token: key
---
Then the url should be /datacenter/acls/tokens/key
- And I click "[data-test-policy-element] .ember-power-select-trigger"
+ And I click "#roles .ember-power-select-trigger"
And I click ".ember-power-select-option:first-child"
- And I see 1 policy model
- And I click "[data-test-policy-element] .ember-power-select-trigger"
+ And I see 1 role model on the roles component
+ And I click "#roles .ember-power-select-trigger"
And I click ".ember-power-select-option:nth-child(1)"
- And I see 2 policy models
+ And I see 2 role models on the roles component
Then I fill in with yaml
---
Description: The Description
@@ -35,11 +35,11 @@ Feature: dc / acls / tokens / policies: ACL Token add existing policy
Then a PUT request is made to "/v1/acl/token/key?dc=datacenter" with the body from yaml
---
Description: The Description
- Policies:
- - ID: policy-1
- Name: Policy 1
- - ID: policy-2
- Name: Policy 2
+ Roles:
+ - ID: role-1
+ Name: Role 1
+ - ID: role-2
+ Name: Role 2
---
Then the url should be /datacenter/acls/tokens
And "[data-notification]" has the "notification-update" class
diff --git a/ui-v2/tests/acceptance/dc/acls/roles/as-many/add-new.feature b/ui-v2/tests/acceptance/dc/acls/roles/as-many/add-new.feature
new file mode 100644
index 0000000000..32ef8a6923
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/acls/roles/as-many/add-new.feature
@@ -0,0 +1,139 @@
+@setupApplicationTest
+Feature: dc / acls / roles / as many / add new: Add new
+ Background:
+ Given 1 datacenter model with the value "datacenter"
+ And 1 token model from yaml
+ ---
+ AccessorID: key
+ Description: The Description
+ Roles: ~
+ Policies: ~
+ ServiceIdentities: ~
+ ---
+ And 1 policy model from yaml
+ ---
+ ID: policy-1
+ Name: policy
+ ---
+ When I visit the token page for yaml
+ ---
+ dc: datacenter
+ token: key
+ ---
+ Then the url should be /datacenter/acls/tokens/key
+ And I click roles.create
+ Then I fill in the roles.form with yaml
+ ---
+ Name: New-Role
+ Description: New Role Description
+ ---
+ Scenario: Add Policy-less Role
+ And I click submit on the roles.form
+ Then the last PUT request was made to "/v1/acl/role?dc=datacenter" with the body from yaml
+ ---
+ Name: New-Role
+ Description: New Role Description
+ ---
+ And I submit
+ Then a PUT request is made to "/v1/acl/token/key?dc=datacenter" with the body from yaml
+ ---
+ Description: The Description
+ Roles:
+ - Name: New-Role
+ ID: ee52203d-989f-4f7a-ab5a-2bef004164ca-1
+ ---
+ Then the url should be /datacenter/acls/tokens
+ And "[data-notification]" has the "notification-update" class
+ And "[data-notification]" has the "success" class
+ Scenario: Add Role that has an existing Policy
+ And I click "#new-role-toggle + div .ember-power-select-trigger"
+ And I click ".ember-power-select-option:first-child"
+ And I click submit on the roles.form
+ Then the last PUT request was made to "/v1/acl/role?dc=datacenter" with the body from yaml
+ ---
+ Name: New-Role
+ Description: New Role Description
+ Policies:
+ - ID: policy-1
+ Name: policy
+ ---
+ And I submit
+ Then a PUT request is made to "/v1/acl/token/key?dc=datacenter" with the body from yaml
+ ---
+ Description: The Description
+ Roles:
+ - Name: New-Role
+ ID: ee52203d-989f-4f7a-ab5a-2bef004164ca-1
+ ---
+ Then the url should be /datacenter/acls/tokens
+ And "[data-notification]" has the "notification-update" class
+ And "[data-notification]" has the "success" class
+ Scenario: Add Role and add a new Policy
+ And I click roles.form.policies.create
+ Then I fill in the roles.form.policies.form with yaml
+ ---
+ Name: New-Policy
+ Description: New Policy Description
+ Rules: key {}
+ ---
+ # This next line is actually the popped up policyForm due to the way things currently work
+ And I click submit on the roles.form
+ Then the last PUT request was made to "/v1/acl/policy?dc=datacenter" with the body from yaml
+ ---
+ Name: New-Policy
+ Description: New Policy Description
+ Rules: key {}
+ ---
+ And I click submit on the roles.form
+ Then the last PUT request was made to "/v1/acl/role?dc=datacenter" with the body from yaml
+ ---
+ Name: New-Role
+ Description: New Role Description
+ Policies:
+ # TODO: Ouch, we need to do deep partial comparisons here
+ - ID: ee52203d-989f-4f7a-ab5a-2bef004164ca-1
+ Name: New-Policy
+ ---
+ And I submit
+ Then a PUT request is made to "/v1/acl/token/key?dc=datacenter" with the body from yaml
+ ---
+ Description: The Description
+ Roles:
+ - Name: New-Role
+ ID: ee52203d-989f-4f7a-ab5a-2bef004164ca-1
+ ---
+ Then the url should be /datacenter/acls/tokens
+ And "[data-notification]" has the "notification-update" class
+ And "[data-notification]" has the "success" class
+ Scenario: Add Role and add a new Service Identity
+ And I click roles.form.policies.create
+ Then I fill in the roles.form.policies.form with yaml
+ ---
+ Name: New-Service-Identity
+ ---
+ And I click "[value='service-identity']"
+ # This next line is actually the popped up policyForm due to the way things currently work
+ And I click submit on the roles.form
+ And I click submit on the roles.form
+ Then the last PUT request was made to "/v1/acl/role?dc=datacenter" with the body from yaml
+ ---
+ Name: New-Role
+ Description: New Role Description
+ ServiceIdentities:
+ - ServiceName: New-Service-Identity
+ ---
+ And I submit
+ Then a PUT request is made to "/v1/acl/token/key?dc=datacenter" with the body from yaml
+ ---
+ Description: The Description
+ Roles:
+ - Name: New-Role
+ ID: ee52203d-989f-4f7a-ab5a-2bef004164ca-1
+ ---
+ Then the url should be /datacenter/acls/tokens
+ And "[data-notification]" has the "notification-update" class
+ And "[data-notification]" has the "success" class
+@ignore:
+ Scenario: Click the cancel form
+ Then ok
+ # And I click cancel on the policyForm
diff --git a/ui-v2/tests/acceptance/dc/acls/tokens/policies/list.feature b/ui-v2/tests/acceptance/dc/acls/roles/as-many/list.feature
similarity index 66%
rename from ui-v2/tests/acceptance/dc/acls/tokens/policies/list.feature
rename to ui-v2/tests/acceptance/dc/acls/roles/as-many/list.feature
index 8f70c32b21..430f36e6a4 100644
--- a/ui-v2/tests/acceptance/dc/acls/tokens/policies/list.feature
+++ b/ui-v2/tests/acceptance/dc/acls/roles/as-many/list.feature
@@ -1,16 +1,16 @@
@setupApplicationTest
-Feature: dc / acls / tokens / policies: List
+Feature: dc / acls / roles / as many / list: List
Scenario:
Given 1 datacenter model with the value "datacenter"
And 1 token model from yaml
---
AccessorID: key
- Policies:
- - Name: Policy
+ Roles:
+ - Name: Role
ID: 0000
- - Name: Policy 2
+ - Name: Role 2
ID: 0002
- - Name: Policy 3
+ - Name: Role 3
ID: 0003
---
When I visit the token page for yaml
@@ -19,4 +19,4 @@ Feature: dc / acls / tokens / policies: List
token: key
---
Then the url should be /datacenter/acls/tokens/key
- Then I see 3 policy models
+ Then I see 3 role models on the roles component
diff --git a/ui-v2/tests/acceptance/dc/acls/tokens/policies/remove.feature b/ui-v2/tests/acceptance/dc/acls/roles/as-many/remove.feature
similarity index 57%
rename from ui-v2/tests/acceptance/dc/acls/tokens/policies/remove.feature
rename to ui-v2/tests/acceptance/dc/acls/roles/as-many/remove.feature
index 90b681ae25..f8052e4721 100644
--- a/ui-v2/tests/acceptance/dc/acls/tokens/policies/remove.feature
+++ b/ui-v2/tests/acceptance/dc/acls/roles/as-many/remove.feature
@@ -1,12 +1,12 @@
@setupApplicationTest
-Feature: dc / acls / tokens / policies: Remove
+Feature: dc / acls / roles / as many / remove: Remove
Scenario:
Given 1 datacenter model with the value "datacenter"
And 1 token model from yaml
---
AccessorID: key
- Policies:
- - Name: Policy
+ Roles:
+ - Name: Role
ID: 00000000-0000-0000-0000-000000000001
---
When I visit the token page for yaml
@@ -15,15 +15,14 @@ Feature: dc / acls / tokens / policies: Remove
token: key
---
Then the url should be /datacenter/acls/tokens/key
- And I see 1 policy model
- And I click expand on the policies
- And the last GET request was made to "/v1/acl/policy/00000000-0000-0000-0000-000000000001?dc=datacenter"
- And I click delete on the policies
- And I click confirmDelete on the policies
- And I see 0 policy models
+ And I see 1 role model on the roles component
+ And I click actions on the roles.selectedOptions
+ And I click delete on the roles.selectedOptions
+ And I click confirmDelete on the roles.selectedOptions
+ And I see 0 role models on the roles component
And I submit
Then a PUT request is made to "/v1/acl/token/key?dc=datacenter" with the body from yaml
---
- Policies: []
+ Roles: []
---
Then the url should be /datacenter/acls/tokens
diff --git a/ui-v2/tests/acceptance/dc/acls/roles/index.feature b/ui-v2/tests/acceptance/dc/acls/roles/index.feature
new file mode 100644
index 0000000000..244e4539ae
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/acls/roles/index.feature
@@ -0,0 +1,12 @@
+@setupApplicationTest
+Feature: dc / acls / roles / index: ACL Roles List
+
+ Scenario:
+ Given 1 datacenter model with the value "dc-1"
+ And 3 role models
+ When I visit the roles page for yaml
+ ---
+ dc: dc-1
+ ---
+ Then the url should be /dc-1/acls/roles
+ Then I see 3 role models
diff --git a/ui-v2/tests/acceptance/dc/acls/roles/update.feature b/ui-v2/tests/acceptance/dc/acls/roles/update.feature
new file mode 100644
index 0000000000..ed2d00c416
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/acls/roles/update.feature
@@ -0,0 +1,44 @@
+@setupApplicationTest
+Feature: dc / acls / roles / update: ACL Role Update
+ Background:
+ Given 1 datacenter model with the value "datacenter"
+ And 1 role model from yaml
+ ---
+ ID: role-id
+ ---
+ And 3 token models
+ When I visit the role page for yaml
+ ---
+ dc: datacenter
+ role: role-id
+ ---
+ Then the url should be /datacenter/acls/roles/role-id
+ Then I see 3 token models
+ Scenario: Update to [Name], [Rules], [Description]
+ Then I fill in the role form with yaml
+ ---
+ Name: [Name]
+ Description: [Description]
+ ---
+ And I submit
+ Then a PUT request is made to "/v1/acl/role/role-id?dc=datacenter" with the body from yaml
+ ---
+ Name: [Name]
+ Description: [Description]
+ ---
+ Then the url should be /datacenter/acls/roles
+ And "[data-notification]" has the "notification-update" class
+ And "[data-notification]" has the "success" class
+ Where:
+ ------------------------------------------
+ | Name | Description |
+ | role-name | role-name description |
+ | role | role name description |
+ | roleName | role%20name description |
+ ------------------------------------------
+ Scenario: There was an error saving the key
+ Given the url "/v1/acl/role/role-id" responds with a 500 status
+ And I submit
+ Then the url should be /datacenter/acls/roles/role-id
+ Then "[data-notification]" has the "notification-update" class
+ And "[data-notification]" has the "error" class
diff --git a/ui-v2/tests/acceptance/dc/acls/tokens/policies/add-new.feature b/ui-v2/tests/acceptance/dc/acls/tokens/policies/add-new.feature
deleted file mode 100644
index d7ee2b715b..0000000000
--- a/ui-v2/tests/acceptance/dc/acls/tokens/policies/add-new.feature
+++ /dev/null
@@ -1,45 +0,0 @@
-@setupApplicationTest
-Feature: dc / acls / tokens / policies: Add new
- Scenario:
- Given 1 datacenter model with the value "datacenter"
- And 1 token model from yaml
- ---
- AccessorID: key
- Description: The Description
- Policies: ~
- ---
- When I visit the token page for yaml
- ---
- dc: datacenter
- token: key
- ---
- Then the url should be /datacenter/acls/tokens/key
- And I click newPolicy
- Then I fill in the policy form with yaml
- ---
- Name: New-Policy
- Description: New Description
- Rules: key {}
- ---
- And I click submit on the policyForm
- Then the last PUT request was made to "/v1/acl/policy?dc=datacenter" with the body from yaml
- ---
- Name: New-Policy
- Description: New Description
- Rules: key {}
- ---
- And I submit
- Then a PUT request is made to "/v1/acl/token/key?dc=datacenter" with the body from yaml
- ---
- Description: The Description
- Policies:
- - Name: New-Policy
- ID: ee52203d-989f-4f7a-ab5a-2bef004164ca-1
- ---
- Then the url should be /datacenter/acls/tokens
- And "[data-notification]" has the "notification-update" class
- And "[data-notification]" has the "success" class
-@pending:
- Scenario: Click the cancel form
- Then ok
- # And I click cancel on the policyForm
diff --git a/ui-v2/tests/acceptance/dc/list-blocking.feature b/ui-v2/tests/acceptance/dc/list-blocking.feature
new file mode 100644
index 0000000000..7413ae8a7b
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/list-blocking.feature
@@ -0,0 +1,54 @@
+@setupApplicationTest
+Feature: dc / list-blocking
+ In order to see updates without refreshing the page
+ As a user
+ I want to see changes if I change consul externally
+ Background:
+ Given 1 datacenter model with the value "dc-1"
+ And settings from yaml
+ ---
+ consul:client:
+ blocking: 1
+ ---
+ Scenario: Viewing the listing pages
+ Given 3 [Model] models
+ And a network latency of 100
+ When I visit the [Page] page for yaml
+ ---
+ dc: dc-1
+ ---
+ Then the url should be /dc-1/[Url]
+ And pause until I see 3 [Model] models
+ And an external edit results in 5 [Model] models
+ And pause until I see 5 [Model] models
+ And an external edit results in 1 [Model] model
+ And pause until I see 1 [Model] model
+ And an external edit results in 0 [Model] models
+ And pause until I see 0 [Model] models
+ Where:
+ ------------------------------------------------
+ | Page | Model | Url |
+ | services | service | services |
+ | nodes | node | nodes |
+ ------------------------------------------------
+ Scenario: Viewing detail pages with a listing
+ Given 3 [Model] models
+ And a network latency of 100
+ When I visit the [Page] page for yaml
+ ---
+ dc: dc-1
+ service: service-0
+ ---
+ Then the url should be /dc-1/[Url]
+ And pause until I see 3 [Model] models
+ And an external edit results in 5 [Model] models
+ And pause until I see 5 [Model] models
+ And an external edit results in 1 [Model] model
+ And pause until I see 1 [Model] model
+ And an external edit results in 0 [Model] models
+ And pause until I see the text "deregistered" in "[data-notification]"
+ Where:
+ -------------------------------------------------
+ | Page | Model | Url |
+ | service | instance | services/service-0 |
+ -------------------------------------------------
\ No newline at end of file
diff --git a/ui-v2/tests/acceptance/dc/nodes/show.feature b/ui-v2/tests/acceptance/dc/nodes/show.feature
index 2ea28bb3e2..eca463a6bc 100644
--- a/ui-v2/tests/acceptance/dc/nodes/show.feature
+++ b/ui-v2/tests/acceptance/dc/nodes/show.feature
@@ -1,8 +1,9 @@
@setupApplicationTest
Feature: dc / nodes / show: Show node
- Scenario: Given 2 nodes all the tabs are visible and clickable
+ Background:
Given 1 datacenter model with the value "dc1"
- And 2 node models from yaml
+ Scenario: Given 2 nodes all the tabs are visible and clickable
+ Given 2 node models from yaml
When I visit the node page for yaml
---
dc: dc1
@@ -19,8 +20,7 @@ Feature: dc / nodes / show: Show node
When I click lockSessions on the tabs
And I see lockSessionsIsSelected on the tabs
Scenario: Given 1 node all the tabs are visible and clickable and the RTT one isn't there
- Given 1 datacenter model with the value "dc1"
- And 1 node models from yaml
+ Given 1 node models from yaml
---
ID: node-0
---
@@ -39,8 +39,7 @@ Feature: dc / nodes / show: Show node
When I click lockSessions on the tabs
And I see lockSessionsIsSelected on the tabs
Scenario: Given 1 node with no checks all the tabs are visible but the Services tab is selected
- Given 1 datacenter model with the value "dc1"
- And 1 node models from yaml
+ Given 1 node models from yaml
---
ID: node-0
Checks: []
@@ -55,3 +54,30 @@ Feature: dc / nodes / show: Show node
And I see roundTripTime on the tabs
And I see lockSessions on the tabs
And I see servicesIsSelected on the tabs
+ Scenario: A node warns when deregistered whilst blocking
+ Given 1 node model from yaml
+ ---
+ ID: node-0
+ ---
+ And settings from yaml
+ ---
+ consul:client:
+ blocking: 1
+ throttle: 200
+ ---
+ And a network latency of 100
+ When I visit the node page for yaml
+ ---
+ dc: dc1
+ node: node-0
+ ---
+ Then the url should be /dc1/nodes/node-0
+ And the url "/v1/internal/ui/node/node-0" responds with a 404 status
+ And pause until I see the text "no longer exists" in "[data-notification]"
+ @ignore
+ Scenario: The RTT for the node is displayed properly
+ Then ok
+ @ignore
+ Scenario: The RTT for the node displays properly whilst blocking
+ Then ok
+
diff --git a/ui-v2/tests/acceptance/dc/services/instances/error.feature b/ui-v2/tests/acceptance/dc/services/instances/error.feature
new file mode 100644
index 0000000000..bf4ef1dc6e
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/services/instances/error.feature
@@ -0,0 +1,15 @@
+@setupApplicationTest
+Feature: dc / services / instances / error: Visit Service Instance what doesn't exist
+ Scenario: No instance can be found in the API response
+ Given 1 datacenter model with the value "dc1"
+ And 1 service model
+ When I visit the instance page for yaml
+ ---
+ dc: dc1
+ service: service-0
+ id: id-that-doesnt-exist
+ ---
+ Then the url should be /dc1/services/service-0/id-that-doesnt-exist
+ And I see the text "404 (Unable to find instance)" in "[data-test-error]"
+
+
diff --git a/ui-v2/tests/acceptance/dc/services/instances/proxy.feature b/ui-v2/tests/acceptance/dc/services/instances/proxy.feature
new file mode 100644
index 0000000000..1d69efb6b2
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/services/instances/proxy.feature
@@ -0,0 +1,48 @@
+@setupApplicationTest
+Feature: dc / services / instances / proxy: Show Proxy Service Instance
+ Scenario: A Proxy Service instance
+ Given 1 datacenter model with the value "dc1"
+ And 1 service model from yaml
+ ---
+ - Service:
+ Kind: connect-proxy
+ Name: service-0-proxy
+ ID: service-0-proxy-with-id
+ Proxy:
+ DestinationServiceName: service-0
+ Upstreams:
+ - DestinationType: service
+ DestinationName: service-1
+ LocalBindAddress: 127.0.0.1
+ LocalBindPort: 1111
+ - DestinationType: prepared_query
+ DestinationName: service-group
+ LocalBindAddress: 127.0.0.1
+ LocalBindPort: 1112
+ ---
+ When I visit the instance page for yaml
+ ---
+ dc: dc1
+ service: service-0-proxy
+ id: service-0-proxy-with-id
+ ---
+ Then the url should be /dc1/services/service-0-proxy/service-0-proxy-with-id
+ And I see destination on the proxy like "service"
+
+ And I see serviceChecksIsSelected on the tabs
+
+ When I click upstreams on the tabs
+ And I see upstreamsIsSelected on the tabs
+ And I see 2 of the upstreams object
+ And I see name on the upstreams like yaml
+ ---
+ - service-1
+ - service-group
+ ---
+ And I see type on the upstreams like yaml
+ ---
+ - service
+ - prepared_query
+ ---
+
+
diff --git a/ui-v2/tests/acceptance/dc/services/instances/show.feature b/ui-v2/tests/acceptance/dc/services/instances/show.feature
new file mode 100644
index 0000000000..d03d59f68a
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/services/instances/show.feature
@@ -0,0 +1,88 @@
+@setupApplicationTest
+Feature: dc / services / instances / show: Show Service Instance
+ Background:
+ Given 1 datacenter model with the value "dc1"
+ And 1 service model from yaml
+ ---
+ - Service:
+ ID: service-0-with-id
+ Tags: ['Tag1', 'Tag2']
+ Meta:
+ external-source: nomad
+ Checks:
+ - Name: Service check
+ ServiceID: service-0
+ Output: Output of check
+ Status: passing
+ - Name: Service check
+ ServiceID: service-0
+ Output: Output of check
+ Status: warning
+ - Name: Service check
+ ServiceID: service-0
+ Output: Output of check
+ Status: critical
+ - Name: Node check
+ ServiceID: ""
+ Output: Output of check
+ Status: passing
+ - Name: Node check
+ ServiceID: ""
+ Output: Output of check
+ Status: warning
+ - Name: Node check
+ ServiceID: ""
+ Output: Output of check
+ Status: critical
+ ---
+ And 1 proxy model from yaml
+ ---
+ - ServiceProxy:
+ DestinationServiceName: service-1
+ DestinationServiceID: ~
+ ---
+ Scenario: A Service instance has no Proxy
+ When I visit the instance page for yaml
+ ---
+ dc: dc1
+ service: service-0
+ id: service-0-with-id
+ ---
+ Then the url should be /dc1/services/service-0/service-0-with-id
+ Then I don't see type on the proxy
+ Then I see externalSource like "nomad"
+
+ And I don't see upstreams on the tabs
+ And I see serviceChecksIsSelected on the tabs
+ And I see 3 of the serviceChecks object
+
+ When I click nodeChecks on the tabs
+ And I see nodeChecksIsSelected on the tabs
+ And I see 3 of the nodeChecks object
+
+ When I click tags on the tabs
+ And I see tagsIsSelected on the tabs
+
+ Then I see the text "Tag1" in "[data-test-tags] span:nth-child(1)"
+ Then I see the text "Tag2" in "[data-test-tags] span:nth-child(2)"
+ Scenario: A Service instance warns when deregistered whilst blocking
+ Given settings from yaml
+ ---
+ consul:client:
+ blocking: 1
+ throttle: 200
+ ---
+ And a network latency of 100
+ When I visit the instance page for yaml
+ ---
+ dc: dc1
+ service: service-0
+ id: service-0-with-id
+ ---
+ Then the url should be /dc1/services/service-0/service-0-with-id
+ And an external edit results in 0 instance models
+ And pause until I see the text "deregistered" in "[data-notification]"
+ @ignore
+ Scenario: A Service Instance's proxy blocking query is closed when the instance is deregistered
+ Then ok
+
diff --git a/ui-v2/tests/acceptance/dc/services/instances/sidecar-proxy.feature b/ui-v2/tests/acceptance/dc/services/instances/sidecar-proxy.feature
new file mode 100644
index 0000000000..3a650565d9
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/services/instances/sidecar-proxy.feature
@@ -0,0 +1,29 @@
+@setupApplicationTest
+Feature: dc / services / instances / sidecar-proxy: Show Sidecar Proxy Service Instance
+ Scenario: A Sidecar Proxy Service instance
+ Given 1 datacenter model with the value "dc1"
+ And 1 service model from yaml
+ ---
+ - Service:
+ Kind: connect-proxy
+ Name: service-0-sidecar-proxy
+ ID: service-0-sidecar-proxy-with-id
+ Proxy:
+ DestinationServiceName: service-0
+ DestinationServiceID: service-0-with-id
+ ---
+ When I visit the instance page for yaml
+ ---
+ dc: dc1
+ service: service-0-sidecar-proxy
+ id: service-0-sidecar-proxy-with-id
+ ---
+ Then the url should be /dc1/services/service-0-sidecar-proxy/service-0-sidecar-proxy-with-id
+ And I see destination on the proxy like "instance"
+
+ And I see serviceChecksIsSelected on the tabs
+
+ When I click upstreams on the tabs
+ And I see upstreamsIsSelected on the tabs
+
+
diff --git a/ui-v2/tests/acceptance/dc/services/instances/with-proxy.feature b/ui-v2/tests/acceptance/dc/services/instances/with-proxy.feature
new file mode 100644
index 0000000000..145bf95cdd
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/services/instances/with-proxy.feature
@@ -0,0 +1,20 @@
+@setupApplicationTest
+Feature: dc / services / instances / with-proxy: Show Service Instance with a proxy
+ Scenario: A Service instance has a Proxy (no DestinationServiceID)
+ Given 1 datacenter model with the value "dc1"
+ And 1 proxy model from yaml
+ ---
+ - ServiceProxy:
+ DestinationServiceID: ~
+ ---
+ When I visit the instance page for yaml
+ ---
+ dc: dc1
+ service: service-0
+ id: service-0-with-id
+ ---
+ Then the url should be /dc1/services/service-0/service-0-with-id
+ And I see type on the proxy like "proxy"
+
+ And I see serviceChecksIsSelected on the tabs
+ And I don't see upstreams on the tabs
diff --git a/ui-v2/tests/acceptance/dc/services/instances/with-sidecar.feature b/ui-v2/tests/acceptance/dc/services/instances/with-sidecar.feature
new file mode 100644
index 0000000000..5e7c6c3f5c
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/services/instances/with-sidecar.feature
@@ -0,0 +1,22 @@
+@setupApplicationTest
+Feature: dc / services / instances / with-sidecar: Show Service Instance with a Sidecar Proxy
+ Scenario: A Service instance has a Sidecar Proxy (a DestinationServiceID)
+ Given 1 datacenter model with the value "dc1"
+ And 1 proxy model from yaml
+ ---
+ - ServiceProxy:
+ DestinationServiceID: service-1
+ ---
+ When I visit the instance page for yaml
+ ---
+ dc: dc1
+ service: service-0
+ id: service-0-with-id
+ ---
+ Then the url should be /dc1/services/service-0/service-0-with-id
+ And I see type on the proxy like "sidecar-proxy"
+
+ And I see serviceChecksIsSelected on the tabs
+ And I don't see upstreams on the tabs
+
+
diff --git a/ui-v2/tests/acceptance/dc/services/show.feature b/ui-v2/tests/acceptance/dc/services/show.feature
index 5fa48f0def..0c707f31b9 100644
--- a/ui-v2/tests/acceptance/dc/services/show.feature
+++ b/ui-v2/tests/acceptance/dc/services/show.feature
@@ -52,7 +52,7 @@ Feature: dc / services / show: Show Service
Then I see the text "Tag1" in "[data-test-tags] span:nth-child(1)"
Then I see the text "Tag2" in "[data-test-tags] span:nth-child(2)"
Then I see the text "Tag3" in "[data-test-tags] span:nth-child(3)"
- Scenario: Given various services the various ports on their nodes are displayed
+ Scenario: Given various services the various nodes on their instances are displayed
Given 1 datacenter model with the value "dc1"
And 3 node models
And 1 service model from yaml
@@ -83,21 +83,9 @@ Feature: dc / services / show: Show Service
dc: dc1
service: service-0
---
- Then I see address on the healthy like yaml
+ Then I see address on the instances like yaml
---
- "1.1.1.1:8080"
- ---
- Then I see address on the unhealthy like yaml
- ---
- "2.2.2.2:8000"
- "3.3.3.3:8888"
---
- Then I see id on the healthy like yaml
- ---
- - "passing-service-8080"
- ---
- Then I see id on the unhealthy like yaml
- ---
- - "service-8000"
- - "service-8888"
- ---
diff --git a/ui-v2/tests/acceptance/deleting.feature b/ui-v2/tests/acceptance/deleting.feature
index 41fce5a37b..9f18d94533 100644
--- a/ui-v2/tests/acceptance/deleting.feature
+++ b/ui-v2/tests/acceptance/deleting.feature
@@ -37,7 +37,6 @@ Feature: deleting: Deleting items with confirmations, success and error notifica
| kv | kvs | DELETE | /v1/kv/key-name?dc=datacenter | ["key-name"] | kv: key-name |
| intention | intentions | DELETE | /v1/connect/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=datacenter | {"SourceName": "name", "ID": "ee52203d-989f-4f7a-ab5a-2bef004164ca"} | intention: ee52203d-989f-4f7a-ab5a-2bef004164ca |
| token | tokens | DELETE | /v1/acl/token/001fda31-194e-4ff1-a5ec-589abf2cafd0?dc=datacenter | {"AccessorID": "001fda31-194e-4ff1-a5ec-589abf2cafd0"} | token: 001fda31-194e-4ff1-a5ec-589abf2cafd0 |
- | policy | policies | DELETE | /v1/acl/policy/1981f51d-301a-497b-89a0-05112ef02b4b?dc=datacenter | {"ID": "1981f51d-301a-497b-89a0-05112ef02b4b"} | policy: 1981f51d-301a-497b-89a0-05112ef02b4b |
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Scenario: Deleting a [Model] from the [Model] detail page
When I visit the [Model] page for yaml
diff --git a/ui-v2/tests/acceptance/page-navigation.feature b/ui-v2/tests/acceptance/page-navigation.feature
index c90c4b5765..1a13332223 100644
--- a/ui-v2/tests/acceptance/page-navigation.feature
+++ b/ui-v2/tests/acceptance/page-navigation.feature
@@ -44,11 +44,12 @@ Feature: Page Navigation
| Item | Model | URL | Endpoint | Back |
| service | services | /dc-1/services/service-0 | /v1/health/service/service-0?dc=dc-1 | /dc-1/services |
| node | nodes | /dc-1/nodes/node-0 | /v1/session/node/node-0?dc=dc-1 | /dc-1/nodes |
- | kv | kvs | /dc-1/kv/necessitatibus-0/edit | /v1/session/info/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=dc-1 | /dc-1/kv |
+ | kv | kvs | /dc-1/kv/0-key-value/edit | /v1/session/info/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=dc-1 | /dc-1/kv |
# | acl | acls | /dc-1/acls/anonymous | /v1/acl/info/anonymous?dc=dc-1 | /dc-1/acls |
| intention | intentions | /dc-1/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca | /v1/internal/ui/services?dc=dc-1 | /dc-1/intentions |
- | token | tokens | /dc-1/acls/tokens/ee52203d-989f-4f7a-ab5a-2bef004164ca | /v1/acl/policies?dc=dc-1 | /dc-1/acls/tokens |
- | policy | policies | /dc-1/acls/policies/ee52203d-989f-4f7a-ab5a-2bef004164ca | /v1/acl/tokens?policy=ee52203d-989f-4f7a-ab5a-2bef004164ca&dc=dc-1 | /dc-1/acls/policies |
+# These Endpoints will be datacenters due to the datacenters checkbox selectors
+ | token | tokens | /dc-1/acls/tokens/ee52203d-989f-4f7a-ab5a-2bef004164ca | /v1/catalog/datacenters | /dc-1/acls/tokens |
+ | policy | policies | /dc-1/acls/policies/ee52203d-989f-4f7a-ab5a-2bef004164ca | /v1/catalog/datacenters | /dc-1/acls/policies |
# | token | tokens | /dc-1/acls/tokens/00000000-0000-0000-0000-000000000000 | /v1/acl/token/00000000-0000-0000-0000-000000000000?dc=dc-1 | /dc-1/acls/tokens |
# | policy | policies | /dc-1/acls/policies/ee52203d-989f-4f7a-ab5a-2bef004164ca | /v1/acl/policy/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=dc-1 | /dc-1/acls/policies |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -116,7 +117,7 @@ Feature: Page Navigation
Where:
--------------------------------------------------------------------------------------------------------
| Item | Model | URL | Back |
- | kv | kvs | /dc-1/kv/necessitatibus-0/edit | /dc-1/kv |
+ | kv | kvs | /dc-1/kv/0-key-value/edit | /dc-1/kv |
# | acl | acls | /dc-1/acls/anonymous | /dc-1/acls |
| intention | intentions | /dc-1/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca | /dc-1/intentions |
--------------------------------------------------------------------------------------------------------
diff --git a/ui-v2/tests/acceptance/steps/dc/acls/tokens/policies/add-new-steps.js b/ui-v2/tests/acceptance/steps/dc/acls/policies/as-many/add-existing-steps.js
similarity index 100%
rename from ui-v2/tests/acceptance/steps/dc/acls/tokens/policies/add-new-steps.js
rename to ui-v2/tests/acceptance/steps/dc/acls/policies/as-many/add-existing-steps.js
diff --git a/ui-v2/tests/acceptance/steps/dc/acls/policies/as-many/add-new-steps.js b/ui-v2/tests/acceptance/steps/dc/acls/policies/as-many/add-new-steps.js
new file mode 100644
index 0000000000..550f688585
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/acls/policies/as-many/add-new-steps.js
@@ -0,0 +1,10 @@
+import steps from '../../../../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/acceptance/steps/dc/acls/tokens/policies/list-steps.js b/ui-v2/tests/acceptance/steps/dc/acls/policies/as-many/list-steps.js
similarity index 61%
rename from ui-v2/tests/acceptance/steps/dc/acls/tokens/policies/list-steps.js
rename to ui-v2/tests/acceptance/steps/dc/acls/policies/as-many/list-steps.js
index 4ce41c3555..550f688585 100644
--- a/ui-v2/tests/acceptance/steps/dc/acls/tokens/policies/list-steps.js
+++ b/ui-v2/tests/acceptance/steps/dc/acls/policies/as-many/list-steps.js
@@ -4,8 +4,7 @@ import steps from '../../../../steps';
// tests/acceptance/steps/steps.js file
export default function(assert) {
- return steps(assert)
- .then('I should find a file', function() {
- assert.ok(true, this.step);
- });
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
}
diff --git a/ui-v2/tests/acceptance/steps/dc/acls/tokens/policies/remove-steps.js b/ui-v2/tests/acceptance/steps/dc/acls/policies/as-many/remove-steps.js
similarity index 61%
rename from ui-v2/tests/acceptance/steps/dc/acls/tokens/policies/remove-steps.js
rename to ui-v2/tests/acceptance/steps/dc/acls/policies/as-many/remove-steps.js
index 4ce41c3555..550f688585 100644
--- a/ui-v2/tests/acceptance/steps/dc/acls/tokens/policies/remove-steps.js
+++ b/ui-v2/tests/acceptance/steps/dc/acls/policies/as-many/remove-steps.js
@@ -4,8 +4,7 @@ import steps from '../../../../steps';
// tests/acceptance/steps/steps.js file
export default function(assert) {
- return steps(assert)
- .then('I should find a file', function() {
- assert.ok(true, this.step);
- });
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
}
diff --git a/ui-v2/tests/acceptance/steps/dc/acls/policies/delete-steps.js b/ui-v2/tests/acceptance/steps/dc/acls/policies/delete-steps.js
new file mode 100644
index 0000000000..9bfbe9ac9b
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/acls/policies/delete-steps.js
@@ -0,0 +1,10 @@
+import steps from '../../../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/acceptance/steps/dc/acls/tokens/policies/add-existing-steps.js b/ui-v2/tests/acceptance/steps/dc/acls/roles/as-many/add-existing-steps.js
similarity index 61%
rename from ui-v2/tests/acceptance/steps/dc/acls/tokens/policies/add-existing-steps.js
rename to ui-v2/tests/acceptance/steps/dc/acls/roles/as-many/add-existing-steps.js
index 4ce41c3555..550f688585 100644
--- a/ui-v2/tests/acceptance/steps/dc/acls/tokens/policies/add-existing-steps.js
+++ b/ui-v2/tests/acceptance/steps/dc/acls/roles/as-many/add-existing-steps.js
@@ -4,8 +4,7 @@ import steps from '../../../../steps';
// tests/acceptance/steps/steps.js file
export default function(assert) {
- return steps(assert)
- .then('I should find a file', function() {
- assert.ok(true, this.step);
- });
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
}
diff --git a/ui-v2/tests/acceptance/steps/dc/acls/roles/as-many/add-new-steps.js b/ui-v2/tests/acceptance/steps/dc/acls/roles/as-many/add-new-steps.js
new file mode 100644
index 0000000000..550f688585
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/acls/roles/as-many/add-new-steps.js
@@ -0,0 +1,10 @@
+import steps from '../../../../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/acceptance/steps/dc/acls/roles/as-many/list-steps.js b/ui-v2/tests/acceptance/steps/dc/acls/roles/as-many/list-steps.js
new file mode 100644
index 0000000000..550f688585
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/acls/roles/as-many/list-steps.js
@@ -0,0 +1,10 @@
+import steps from '../../../../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/acceptance/steps/dc/acls/roles/as-many/remove-steps.js b/ui-v2/tests/acceptance/steps/dc/acls/roles/as-many/remove-steps.js
new file mode 100644
index 0000000000..550f688585
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/acls/roles/as-many/remove-steps.js
@@ -0,0 +1,10 @@
+import steps from '../../../../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/acceptance/steps/dc/acls/roles/index-steps.js b/ui-v2/tests/acceptance/steps/dc/acls/roles/index-steps.js
new file mode 100644
index 0000000000..9bfbe9ac9b
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/acls/roles/index-steps.js
@@ -0,0 +1,10 @@
+import steps from '../../../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/acceptance/steps/dc/acls/roles/update-steps.js b/ui-v2/tests/acceptance/steps/dc/acls/roles/update-steps.js
new file mode 100644
index 0000000000..9bfbe9ac9b
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/acls/roles/update-steps.js
@@ -0,0 +1,10 @@
+import steps from '../../../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/acceptance/steps/dc/list-blocking-steps.js b/ui-v2/tests/acceptance/steps/dc/list-blocking-steps.js
new file mode 100644
index 0000000000..3c9a76f69f
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/list-blocking-steps.js
@@ -0,0 +1,10 @@
+import steps from '../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/acceptance/steps/dc/services/instances/error-steps.js b/ui-v2/tests/acceptance/steps/dc/services/instances/error-steps.js
new file mode 100644
index 0000000000..9bfbe9ac9b
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/services/instances/error-steps.js
@@ -0,0 +1,10 @@
+import steps from '../../../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/acceptance/steps/dc/services/instances/proxy-steps.js b/ui-v2/tests/acceptance/steps/dc/services/instances/proxy-steps.js
new file mode 100644
index 0000000000..9bfbe9ac9b
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/services/instances/proxy-steps.js
@@ -0,0 +1,10 @@
+import steps from '../../../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/acceptance/steps/dc/services/instances/show-steps.js b/ui-v2/tests/acceptance/steps/dc/services/instances/show-steps.js
new file mode 100644
index 0000000000..9bfbe9ac9b
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/services/instances/show-steps.js
@@ -0,0 +1,10 @@
+import steps from '../../../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/acceptance/steps/dc/services/instances/sidecar-proxy-steps.js b/ui-v2/tests/acceptance/steps/dc/services/instances/sidecar-proxy-steps.js
new file mode 100644
index 0000000000..9bfbe9ac9b
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/services/instances/sidecar-proxy-steps.js
@@ -0,0 +1,10 @@
+import steps from '../../../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/acceptance/steps/dc/services/instances/with-proxy-steps.js b/ui-v2/tests/acceptance/steps/dc/services/instances/with-proxy-steps.js
new file mode 100644
index 0000000000..9bfbe9ac9b
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/services/instances/with-proxy-steps.js
@@ -0,0 +1,10 @@
+import steps from '../../../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/acceptance/steps/dc/services/instances/with-sidecar-steps.js b/ui-v2/tests/acceptance/steps/dc/services/instances/with-sidecar-steps.js
new file mode 100644
index 0000000000..3231912b98
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/services/instances/with-sidecar-steps.js
@@ -0,0 +1,10 @@
+import steps from '../../../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/acceptance/steps/steps.js b/ui-v2/tests/acceptance/steps/steps.js
index df45d3f284..c8da3b0a2b 100644
--- a/ui-v2/tests/acceptance/steps/steps.js
+++ b/ui-v2/tests/acceptance/steps/steps.js
@@ -1,2 +1,83 @@
+/* eslint no-console: "off" */
+import Inflector from 'ember-inflector';
+import utils from '@ember/test-helpers';
+import getDictionary from '@hashicorp/ember-cli-api-double/dictionary';
+
+import yadda from 'consul-ui/tests/helpers/yadda';
+import pages from 'consul-ui/tests/pages';
+import api from 'consul-ui/tests/helpers/api';
+
import steps from 'consul-ui/tests/steps';
-export default steps;
+
+const pluralize = function(str) {
+ return Inflector.inflector.pluralize(str);
+};
+export default function(assert) {
+ const library = yadda.localisation.English.library(
+ getDictionary(function(model, cb) {
+ switch (model) {
+ case 'datacenter':
+ case 'datacenters':
+ case 'dcs':
+ model = 'dc';
+ break;
+ case 'services':
+ model = 'service';
+ break;
+ case 'nodes':
+ model = 'node';
+ break;
+ case 'kvs':
+ model = 'kv';
+ break;
+ case 'acls':
+ model = 'acl';
+ break;
+ case 'sessions':
+ model = 'session';
+ break;
+ case 'intentions':
+ model = 'intention';
+ break;
+ }
+ cb(null, model);
+ }, yadda)
+ );
+ const create = function(number, name, value) {
+ // don't return a promise here as
+ // I don't need it to wait
+ api.server.createList(name, number, value);
+ };
+ const respondWith = function(url, data) {
+ api.server.respondWith(url.split('?')[0], data);
+ };
+ const setCookie = function(key, value) {
+ api.server.setCookie(key, value);
+ };
+ const getLastNthRequest = function(arr) {
+ return function(n, method) {
+ let requests = arr.slice(0).reverse();
+ if (method) {
+ requests = requests.filter(function(item) {
+ return item.method === method;
+ });
+ }
+ if (n == null) {
+ return requests;
+ }
+ return requests[n];
+ };
+ };
+ return steps(assert, library, pages, {
+ pluralize: pluralize,
+ triggerKeyEvent: utils.triggerKeyEvent,
+ currentURL: utils.currentURL,
+ click: utils.click,
+ fillIn: utils.fillIn,
+ find: utils.find,
+ lastNthRequest: getLastNthRequest(api.server.history),
+ respondWith: respondWith,
+ create: create,
+ set: setCookie,
+ });
+}
diff --git a/ui-v2/tests/helpers/module-for-acceptance.js b/ui-v2/tests/helpers/module-for-acceptance.js
index 364089288e..eac9400b98 100644
--- a/ui-v2/tests/helpers/module-for-acceptance.js
+++ b/ui-v2/tests/helpers/module-for-acceptance.js
@@ -4,8 +4,20 @@ import startApp from '../helpers/start-app';
import destroyApp from '../helpers/destroy-app';
export default function(name, options = {}) {
+ let setTimeout = window.setTimeout;
+ let setInterval = window.setInterval;
module(name, {
beforeEach() {
+ const speedup = function(func) {
+ return function(cb, interval = 0) {
+ if (interval > 10) {
+ interval = Math.max(Math.round(interval / 10), 10);
+ }
+ return func(cb, interval);
+ };
+ };
+ window.setTimeout = speedup(window.setTimeout);
+ window.setInterval = speedup(window.setInterval);
this.application = startApp();
if (options.beforeEach) {
@@ -14,6 +26,8 @@ export default function(name, options = {}) {
},
afterEach() {
+ window.setTimeout = setTimeout;
+ window.setInterval = setInterval;
let afterEach = options.afterEach && options.afterEach.apply(this, arguments);
return resolve(afterEach).then(() => destroyApp(this.application));
},
diff --git a/ui-v2/tests/helpers/normalizers.js b/ui-v2/tests/helpers/normalizers.js
new file mode 100644
index 0000000000..256d5ddc8d
--- /dev/null
+++ b/ui-v2/tests/helpers/normalizers.js
@@ -0,0 +1,19 @@
+export const createPolicies = function(item) {
+ return item.Policies.map(function(item) {
+ return {
+ template: '',
+ ...item,
+ };
+ }).concat(
+ item.ServiceIdentities.map(function(item) {
+ const policy = {
+ template: 'service-identity',
+ Name: item.ServiceName,
+ };
+ if (typeof item.Datacenters !== 'undefined') {
+ policy.Datacenters = item.Datacenters;
+ }
+ return policy;
+ })
+ );
+};
diff --git a/ui-v2/tests/helpers/set-cookies.js b/ui-v2/tests/helpers/set-cookies.js
index 0cfa58313b..6312e85ea0 100644
--- a/ui-v2/tests/helpers/set-cookies.js
+++ b/ui-v2/tests/helpers/set-cookies.js
@@ -9,6 +9,7 @@ export default function(type, count, obj) {
key = 'CONSUL_SERVICE_COUNT';
break;
case 'node':
+ case 'instance':
key = 'CONSUL_NODE_COUNT';
break;
case 'kv':
@@ -28,6 +29,10 @@ export default function(type, count, obj) {
key = 'CONSUL_POLICY_COUNT';
obj['CONSUL_ACLS_ENABLE'] = 1;
break;
+ case 'role':
+ key = 'CONSUL_ROLE_COUNT';
+ obj['CONSUL_ACLS_ENABLE'] = 1;
+ break;
case 'token':
key = 'CONSUL_TOKEN_COUNT';
obj['CONSUL_ACLS_ENABLE'] = 1;
diff --git a/ui-v2/tests/helpers/type-to-url.js b/ui-v2/tests/helpers/type-to-url.js
index 1b217dcb4d..abd0fb5766 100644
--- a/ui-v2/tests/helpers/type-to-url.js
+++ b/ui-v2/tests/helpers/type-to-url.js
@@ -5,8 +5,12 @@ export default function(type) {
requests = ['/v1/catalog/datacenters'];
break;
case 'service':
+ case 'instance':
requests = ['/v1/internal/ui/services', '/v1/health/service/'];
break;
+ case 'proxy':
+ requests = ['/v1/catalog/connect'];
+ break;
case 'node':
requests = ['/v1/internal/ui/nodes', '/v1/internal/ui/node/'];
break;
@@ -22,6 +26,9 @@ export default function(type) {
case 'policy':
requests = ['/v1/acl/policies', '/v1/acl/policy/'];
break;
+ case 'role':
+ requests = ['/v1/acl/roles', '/v1/acl/role/'];
+ break;
case 'token':
requests = ['/v1/acl/tokens', '/v1/acl/token/'];
break;
diff --git a/ui-v2/tests/integration/adapters/acl/response-test.js b/ui-v2/tests/integration/adapters/acl/response-test.js
index f1dc795aa5..5da7808da1 100644
--- a/ui-v2/tests/integration/adapters/acl/response-test.js
+++ b/ui-v2/tests/integration/adapters/acl/response-test.js
@@ -1,6 +1,7 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { get } from 'consul-ui/tests/helpers/api';
+import { HEADERS_SYMBOL as META } from 'consul-ui/utils/http/consul';
module('Integration | Adapter | acl | response', function(hooks) {
setupTest(hooks);
const dc = 'dc-1';
@@ -29,6 +30,7 @@ module('Integration | Adapter | acl | response', function(hooks) {
return get(request.url).then(function(payload) {
const expected = Object.assign({}, payload[0], {
Datacenter: dc,
+ [META]: {},
uid: `["${dc}","${id}"]`,
});
const actual = adapter.handleResponse(200, {}, payload, request);
diff --git a/ui-v2/tests/integration/adapters/intention/response-test.js b/ui-v2/tests/integration/adapters/intention/response-test.js
index 48944273a6..62c583cb6a 100644
--- a/ui-v2/tests/integration/adapters/intention/response-test.js
+++ b/ui-v2/tests/integration/adapters/intention/response-test.js
@@ -1,6 +1,7 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { get } from 'consul-ui/tests/helpers/api';
+import { HEADERS_SYMBOL as META } from 'consul-ui/utils/http/consul';
module('Integration | Adapter | intention | response', function(hooks) {
setupTest(hooks);
const dc = 'dc-1';
@@ -31,6 +32,7 @@ module('Integration | Adapter | intention | response', function(hooks) {
return get(request.url).then(function(payload) {
const expected = Object.assign({}, payload, {
Datacenter: dc,
+ [META]: {},
uid: `["${dc}","${id}"]`,
});
const actual = adapter.handleResponse(200, {}, payload, request);
diff --git a/ui-v2/tests/integration/adapters/kv/response-test.js b/ui-v2/tests/integration/adapters/kv/response-test.js
index 8b5dd8e729..db6e5def7f 100644
--- a/ui-v2/tests/integration/adapters/kv/response-test.js
+++ b/ui-v2/tests/integration/adapters/kv/response-test.js
@@ -1,6 +1,7 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { get } from 'consul-ui/tests/helpers/api';
+import { HEADERS_SYMBOL as META } from 'consul-ui/utils/http/consul';
module('Integration | Adapter | kv | response', function(hooks) {
setupTest(hooks);
const dc = 'dc-1';
@@ -35,6 +36,7 @@ module('Integration | Adapter | kv | response', function(hooks) {
return get(request.url).then(function(payload) {
const expected = Object.assign({}, payload[0], {
Datacenter: dc,
+ [META]: {},
uid: `["${dc}","${id}"]`,
});
const actual = adapter.handleResponse(200, {}, payload, request);
diff --git a/ui-v2/tests/integration/adapters/node/response-test.js b/ui-v2/tests/integration/adapters/node/response-test.js
index dcddc2359a..c3a3ebf49a 100644
--- a/ui-v2/tests/integration/adapters/node/response-test.js
+++ b/ui-v2/tests/integration/adapters/node/response-test.js
@@ -1,6 +1,7 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { get } from 'consul-ui/tests/helpers/api';
+import { HEADERS_SYMBOL as META } from 'consul-ui/utils/http/consul';
module('Integration | Adapter | node | response', function(hooks) {
setupTest(hooks);
test('handleResponse returns the correct data for list endpoint', function(assert) {
@@ -30,6 +31,7 @@ module('Integration | Adapter | node | response', function(hooks) {
return get(request.url).then(function(payload) {
const expected = Object.assign({}, payload, {
Datacenter: dc,
+ [META]: {},
uid: `["${dc}","${id}"]`,
});
const actual = adapter.handleResponse(200, {}, payload, request);
diff --git a/ui-v2/tests/integration/adapters/policy/response-test.js b/ui-v2/tests/integration/adapters/policy/response-test.js
index dc90fa1079..03ef04ea06 100644
--- a/ui-v2/tests/integration/adapters/policy/response-test.js
+++ b/ui-v2/tests/integration/adapters/policy/response-test.js
@@ -1,6 +1,7 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { get } from 'consul-ui/tests/helpers/api';
+import { HEADERS_SYMBOL as META } from 'consul-ui/utils/http/consul';
module('Integration | Adapter | policy | response', function(hooks) {
setupTest(hooks);
const dc = 'dc-1';
@@ -29,6 +30,7 @@ module('Integration | Adapter | policy | response', function(hooks) {
return get(request.url).then(function(payload) {
const expected = Object.assign({}, payload, {
Datacenter: dc,
+ [META]: {},
uid: `["${dc}","${id}"]`,
});
const actual = adapter.handleResponse(200, {}, payload, request);
diff --git a/ui-v2/tests/integration/adapters/role/response-test.js b/ui-v2/tests/integration/adapters/role/response-test.js
new file mode 100644
index 0000000000..5ffde99990
--- /dev/null
+++ b/ui-v2/tests/integration/adapters/role/response-test.js
@@ -0,0 +1,44 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+import { get } from 'consul-ui/tests/helpers/api';
+import { HEADERS_SYMBOL as META } from 'consul-ui/utils/http/consul';
+import { createPolicies } from 'consul-ui/tests/helpers/normalizers';
+
+module('Integration | Adapter | role | response', function(hooks) {
+ setupTest(hooks);
+ const dc = 'dc-1';
+ const id = 'role-name';
+ test('handleResponse returns the correct data for list endpoint', function(assert) {
+ const adapter = this.owner.lookup('adapter:role');
+ const request = {
+ url: `/v1/acl/roles?dc=${dc}`,
+ };
+ return get(request.url).then(function(payload) {
+ const expected = payload.map(item =>
+ Object.assign({}, item, {
+ Datacenter: dc,
+ uid: `["${dc}","${item.ID}"]`,
+ Policies: createPolicies(item),
+ })
+ );
+ const actual = adapter.handleResponse(200, {}, payload, request);
+ assert.deepEqual(actual, expected);
+ });
+ });
+ test('handleResponse returns the correct data for item endpoint', function(assert) {
+ const adapter = this.owner.lookup('adapter:role');
+ const request = {
+ url: `/v1/acl/role/${id}?dc=${dc}`,
+ };
+ return get(request.url).then(function(payload) {
+ const expected = Object.assign({}, payload, {
+ Datacenter: dc,
+ [META]: {},
+ uid: `["${dc}","${id}"]`,
+ Policies: createPolicies(payload),
+ });
+ const actual = adapter.handleResponse(200, {}, payload, request);
+ assert.deepEqual(actual, expected);
+ });
+ });
+});
diff --git a/ui-v2/tests/integration/adapters/role/url-test.js b/ui-v2/tests/integration/adapters/role/url-test.js
new file mode 100644
index 0000000000..3b3bae452d
--- /dev/null
+++ b/ui-v2/tests/integration/adapters/role/url-test.js
@@ -0,0 +1,70 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+import makeAttrable from 'consul-ui/utils/makeAttrable';
+module('Integration | Adapter | role | url', function(hooks) {
+ setupTest(hooks);
+ const dc = 'dc-1';
+ const id = 'role-name';
+ test('urlForQuery returns the correct url', function(assert) {
+ const adapter = this.owner.lookup('adapter:role');
+ const expected = `/v1/acl/roles?dc=${dc}`;
+ const actual = adapter.urlForQuery({
+ dc: dc,
+ });
+ assert.equal(actual, expected);
+ });
+ test('urlForQueryRecord returns the correct url', function(assert) {
+ const adapter = this.owner.lookup('adapter:role');
+ const expected = `/v1/acl/role/${id}?dc=${dc}`;
+ const actual = adapter.urlForQueryRecord({
+ dc: dc,
+ id: id,
+ });
+ assert.equal(actual, expected);
+ });
+ test("urlForQueryRecord throws if you don't specify an id", function(assert) {
+ const adapter = this.owner.lookup('adapter:role');
+ assert.throws(function() {
+ adapter.urlForQueryRecord({
+ dc: dc,
+ });
+ });
+ });
+ test('urlForCreateRecord returns the correct url', function(assert) {
+ const adapter = this.owner.lookup('adapter:role');
+ const expected = `/v1/acl/role?dc=${dc}`;
+ const actual = adapter.urlForCreateRecord(
+ 'role',
+ makeAttrable({
+ Datacenter: dc,
+ })
+ );
+ assert.equal(actual, expected);
+ });
+ test('urlForUpdateRecord returns the correct url', function(assert) {
+ const adapter = this.owner.lookup('adapter:role');
+ const expected = `/v1/acl/role/${id}?dc=${dc}`;
+ const actual = adapter.urlForUpdateRecord(
+ id,
+ 'role',
+ makeAttrable({
+ Datacenter: dc,
+ ID: id,
+ })
+ );
+ assert.equal(actual, expected);
+ });
+ test('urlForDeleteRecord returns the correct url', function(assert) {
+ const adapter = this.owner.lookup('adapter:role');
+ const expected = `/v1/acl/role/${id}?dc=${dc}`;
+ const actual = adapter.urlForDeleteRecord(
+ id,
+ 'role',
+ makeAttrable({
+ Datacenter: dc,
+ ID: id,
+ })
+ );
+ assert.equal(actual, expected);
+ });
+});
diff --git a/ui-v2/tests/integration/adapters/service/response-test.js b/ui-v2/tests/integration/adapters/service/response-test.js
index a9641b6687..827dc5c72e 100644
--- a/ui-v2/tests/integration/adapters/service/response-test.js
+++ b/ui-v2/tests/integration/adapters/service/response-test.js
@@ -1,6 +1,7 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { get } from 'consul-ui/tests/helpers/api';
+import { HEADERS_SYMBOL as META } from 'consul-ui/utils/http/consul';
module('Integration | Adapter | service | response', function(hooks) {
setupTest(hooks);
test('handleResponse returns the correct data for list endpoint', function(assert) {
@@ -30,6 +31,7 @@ module('Integration | Adapter | service | response', function(hooks) {
return get(request.url).then(function(payload) {
const expected = {
Datacenter: dc,
+ [META]: {},
uid: `["${dc}","${id}"]`,
Nodes: payload,
};
diff --git a/ui-v2/tests/integration/adapters/session/response-test.js b/ui-v2/tests/integration/adapters/session/response-test.js
index 9af7fde388..9626bc5517 100644
--- a/ui-v2/tests/integration/adapters/session/response-test.js
+++ b/ui-v2/tests/integration/adapters/session/response-test.js
@@ -1,6 +1,7 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { get } from 'consul-ui/tests/helpers/api';
+import { HEADERS_SYMBOL as META } from 'consul-ui/utils/http/consul';
module('Integration | Adapter | session | response', function(hooks) {
setupTest(hooks);
const dc = 'dc-1';
@@ -30,6 +31,7 @@ module('Integration | Adapter | session | response', function(hooks) {
return get(request.url).then(function(payload) {
const expected = Object.assign({}, payload[0], {
Datacenter: dc,
+ [META]: {},
uid: `["${dc}","${id}"]`,
});
const actual = adapter.handleResponse(200, {}, payload, request);
diff --git a/ui-v2/tests/integration/adapters/token/response-test.js b/ui-v2/tests/integration/adapters/token/response-test.js
index 0a60965b8d..7d2412262e 100644
--- a/ui-v2/tests/integration/adapters/token/response-test.js
+++ b/ui-v2/tests/integration/adapters/token/response-test.js
@@ -1,6 +1,10 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { get } from 'consul-ui/tests/helpers/api';
+import { HEADERS_SYMBOL as META } from 'consul-ui/utils/http/consul';
+
+import { createPolicies } from 'consul-ui/tests/helpers/normalizers';
+
module('Integration | Adapter | token | response', function(hooks) {
setupTest(hooks);
const dc = 'dc-1';
@@ -15,6 +19,7 @@ module('Integration | Adapter | token | response', function(hooks) {
Object.assign({}, item, {
Datacenter: dc,
uid: `["${dc}","${item.AccessorID}"]`,
+ Policies: createPolicies(item),
})
);
const actual = adapter.handleResponse(200, {}, payload, request);
@@ -29,7 +34,9 @@ module('Integration | Adapter | token | response', function(hooks) {
return get(request.url).then(function(payload) {
const expected = Object.assign({}, payload, {
Datacenter: dc,
+ [META]: {},
uid: `["${dc}","${id}"]`,
+ Policies: createPolicies(payload),
});
const actual = adapter.handleResponse(200, {}, payload, request);
assert.deepEqual(actual, expected);
diff --git a/ui-v2/tests/integration/components/catalog-filter-test.js b/ui-v2/tests/integration/components/catalog-filter-test.js
index e4ba4793f9..df2e4fd750 100644
--- a/ui-v2/tests/integration/components/catalog-filter-test.js
+++ b/ui-v2/tests/integration/components/catalog-filter-test.js
@@ -11,22 +11,12 @@ test('it renders', function(assert) {
this.render(hbs`{{catalog-filter}}`);
- assert.equal(
- this.$()
- .text()
- .trim(),
- 'Search'
- );
+ assert.equal(this.$().find('form').length, 1);
// Template block usage:
this.render(hbs`
{{#catalog-filter}}{{/catalog-filter}}
`);
- assert.equal(
- this.$()
- .text()
- .trim(),
- 'Search'
- );
+ assert.equal(this.$().find('form').length, 1);
});
diff --git a/ui-v2/tests/integration/components/changeable-set-test.js b/ui-v2/tests/integration/components/changeable-set-test.js
new file mode 100644
index 0000000000..e495809a8c
--- /dev/null
+++ b/ui-v2/tests/integration/components/changeable-set-test.js
@@ -0,0 +1,33 @@
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+
+moduleForComponent('changeable-set', 'Integration | Component | changeable set', {
+ integration: true,
+});
+
+test('it renders', function(assert) {
+ // Set any properties with this.set('myProperty', 'value');
+ // Handle any actions with this.on('myAction', function(val) { ... });
+
+ this.render(hbs`{{changeable-set}}`);
+
+ assert.equal(
+ this.$()
+ .text()
+ .trim(),
+ ''
+ );
+
+ // Template block usage:
+ this.render(hbs`
+ {{#changeable-set}}
+ {{/changeable-set}}
+ `);
+
+ assert.equal(
+ this.$()
+ .text()
+ .trim(),
+ ''
+ );
+});
diff --git a/ui-v2/tests/integration/components/healthcheck-info-test.js b/ui-v2/tests/integration/components/healthcheck-info-test.js
new file mode 100644
index 0000000000..613a650657
--- /dev/null
+++ b/ui-v2/tests/integration/components/healthcheck-info-test.js
@@ -0,0 +1,22 @@
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+
+moduleForComponent('healthcheck-info', 'Integration | Component | healthcheck info', {
+ integration: true,
+});
+
+test('it renders', function(assert) {
+ // Set any properties with this.set('myProperty', 'value');
+ // Handle any actions with this.on('myAction', function(val) { ... });
+
+ this.render(hbs`{{healthcheck-info}}`);
+
+ assert.equal(this.$('dl').length, 1);
+
+ // Template block usage:
+ this.render(hbs`
+ {{#healthcheck-info}}
+ {{/healthcheck-info}}
+ `);
+ assert.equal(this.$('dl').length, 1);
+});
diff --git a/ui-v2/tests/integration/components/healthcheck-list-test.js b/ui-v2/tests/integration/components/healthcheck-list-test.js
new file mode 100644
index 0000000000..a85c4866de
--- /dev/null
+++ b/ui-v2/tests/integration/components/healthcheck-list-test.js
@@ -0,0 +1,23 @@
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+
+moduleForComponent('healthcheck-list', 'Integration | Component | healthcheck list', {
+ integration: true,
+});
+
+test('it renders', function(assert) {
+ // Set any properties with this.set('myProperty', 'value');
+ // Handle any actions with this.on('myAction', function(val) { ... });
+
+ this.render(hbs`{{healthcheck-list}}`);
+
+ assert.equal(this.$('ul').length, 1);
+
+ // Template block usage:
+ this.render(hbs`
+ {{#healthcheck-list}}
+ {{/healthcheck-list}}
+ `);
+
+ assert.equal(this.$('ul').length, 1);
+});
diff --git a/ui-v2/tests/integration/components/healthcheck-output-test.js b/ui-v2/tests/integration/components/healthcheck-output-test.js
new file mode 100644
index 0000000000..b72e7412f9
--- /dev/null
+++ b/ui-v2/tests/integration/components/healthcheck-output-test.js
@@ -0,0 +1,34 @@
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+
+moduleForComponent('healthcheck-output', 'Integration | Component | healthcheck output', {
+ integration: true,
+});
+
+test('it renders', function(assert) {
+ // Set any properties with this.set('myProperty', 'value');
+ // Handle any actions with this.on('myAction', function(val) { ... });
+
+ this.render(hbs`{{healthcheck-output}}`);
+
+ assert.notEqual(
+ this.$()
+ .text()
+ .trim()
+ .indexOf('Output'),
+ -1
+ );
+
+ // Template block usage:
+ this.render(hbs`
+ {{#healthcheck-output}}{{/healthcheck-output}}
+ `);
+
+ assert.notEqual(
+ this.$()
+ .text()
+ .trim()
+ .indexOf('Output'),
+ -1
+ );
+});
diff --git a/ui-v2/tests/integration/components/healthcheck-status-test.js b/ui-v2/tests/integration/components/healthcheck-status-test.js
index b19207e5a4..f4e9bd78ff 100644
--- a/ui-v2/tests/integration/components/healthcheck-status-test.js
+++ b/ui-v2/tests/integration/components/healthcheck-status-test.js
@@ -10,25 +10,11 @@ test('it renders', function(assert) {
// Handle any actions with this.on('myAction', function(val) { ... });
this.render(hbs`{{healthcheck-status}}`);
-
- assert.notEqual(
- this.$()
- .text()
- .trim()
- .indexOf('Output'),
- -1
- );
+ assert.equal(this.$('dt').length, 1);
// Template block usage:
this.render(hbs`
{{#healthcheck-status}}{{/healthcheck-status}}
`);
-
- assert.notEqual(
- this.$()
- .text()
- .trim()
- .indexOf('Output'),
- -1
- );
+ assert.equal(this.$('dt').length, 1);
});
diff --git a/ui-v2/tests/integration/components/phrase-editor-test.js b/ui-v2/tests/integration/components/phrase-editor-test.js
new file mode 100644
index 0000000000..1ce41299bd
--- /dev/null
+++ b/ui-v2/tests/integration/components/phrase-editor-test.js
@@ -0,0 +1,34 @@
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+
+moduleForComponent('phrase-editor', 'Integration | Component | phrase editor', {
+ integration: true,
+});
+
+test('it renders', function(assert) {
+ // Set any properties with this.set('myProperty', 'value');
+ // Handle any actions with this.on('myAction', function(val) { ... });
+
+ this.render(hbs`{{phrase-editor}}`);
+
+ assert.equal(
+ this.$()
+ .text()
+ .trim(),
+ 'Search'
+ );
+
+ // Template block usage:
+ this.render(hbs`
+ {{#phrase-editor}}
+ template block text
+ {{/phrase-editor}}
+ `);
+
+ assert.equal(
+ this.$()
+ .text()
+ .trim(),
+ 'Search'
+ );
+});
diff --git a/ui-v2/tests/integration/components/service-identity-test.js b/ui-v2/tests/integration/components/service-identity-test.js
new file mode 100644
index 0000000000..31c08c5c98
--- /dev/null
+++ b/ui-v2/tests/integration/components/service-identity-test.js
@@ -0,0 +1,34 @@
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+
+moduleForComponent('service-identity', 'Integration | Component | service identity', {
+ integration: true,
+});
+
+test('it renders', function(assert) {
+ // Set any properties with this.set('myProperty', 'value');
+ // Handle any actions with this.on('myAction', function(val) { ... });
+
+ this.render(hbs`{{service-identity}}`);
+
+ assert.ok(
+ this.$()
+ .text()
+ .trim()
+ .indexOf('service_prefix') !== -1,
+ ''
+ );
+
+ // Template block usage:
+ this.render(hbs`
+ {{#service-identity}}{{/service-identity}}
+ `);
+
+ assert.ok(
+ this.$()
+ .text()
+ .trim()
+ .indexOf('service_prefix') !== -1,
+ ''
+ );
+});
diff --git a/ui-v2/tests/integration/components/tag-list-test.js b/ui-v2/tests/integration/components/tag-list-test.js
new file mode 100644
index 0000000000..6924c8562c
--- /dev/null
+++ b/ui-v2/tests/integration/components/tag-list-test.js
@@ -0,0 +1,33 @@
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+
+moduleForComponent('tag-list', 'Integration | Component | tag list', {
+ integration: true,
+});
+
+test('it renders', function(assert) {
+ // Set any properties with this.set('myProperty', 'value');
+ // Handle any actions with this.on('myAction', function(val) { ... });
+
+ this.render(hbs`{{tag-list}}`);
+
+ assert.equal(
+ this.$()
+ .text()
+ .trim(),
+ ''
+ );
+
+ // Template block usage:
+ this.render(hbs`
+ {{#tag-list}}
+ {{/tag-list}}
+ `);
+
+ assert.equal(
+ this.$()
+ .text()
+ .trim(),
+ ''
+ );
+});
diff --git a/ui-v2/tests/integration/components/templated-anchor-test.js b/ui-v2/tests/integration/components/templated-anchor-test.js
new file mode 100644
index 0000000000..17b9d74ac2
--- /dev/null
+++ b/ui-v2/tests/integration/components/templated-anchor-test.js
@@ -0,0 +1,98 @@
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+
+moduleForComponent('templated-anchor', 'Integration | Component | templated anchor', {
+ integration: true,
+});
+
+test('it renders', function(assert) {
+ [
+ {
+ href: 'http://localhost/?={{Name}}/{{ID}}',
+ vars: {
+ Name: 'name',
+ ID: 'id',
+ },
+ result: 'http://localhost/?=name/id',
+ },
+ {
+ href: 'http://localhost/?={{Name}}/{{ID}}',
+ vars: {
+ Name: '{{Name}}',
+ ID: '{{ID}}',
+ },
+ result: 'http://localhost/?={{Name}}/{{ID}}',
+ },
+ {
+ href: 'http://localhost/?={{deep.Name}}/{{deep.ID}}',
+ vars: {
+ deep: {
+ Name: '{{Name}}',
+ ID: '{{ID}}',
+ },
+ },
+ result: 'http://localhost/?={{Name}}/{{ID}}',
+ },
+ {
+ href: 'http://localhost/?={{}}/{{}}',
+ vars: {
+ Name: 'name',
+ ID: 'id',
+ },
+ result: 'http://localhost/?={{}}/{{}}',
+ },
+ {
+ href: 'http://localhost/?={{Service_Name}}/{{Meta-Key}}',
+ vars: {
+ Service_Name: 'name',
+ ['Meta-Key']: 'id',
+ },
+ result: 'http://localhost/?=name/id',
+ },
+ {
+ href: 'http://localhost/?={{Service_Name}}/{{Meta-Key}}',
+ vars: {
+ WrongPropertyName: 'name',
+ ['Meta-Key']: 'id',
+ },
+ result: 'http://localhost/?=/id',
+ },
+ {
+ href: 'http://localhost/?={{.Name}}',
+ vars: {
+ ['.Name']: 'name',
+ },
+ result: 'http://localhost/?=',
+ },
+ {
+ href: 'http://localhost/?={{.}}',
+ vars: {
+ ['.']: 'name',
+ },
+ result: 'http://localhost/?=',
+ },
+ {
+ href: 'http://localhost/?={{deep..Name}}',
+ vars: {
+ deep: {
+ Name: 'Name',
+ ID: 'ID',
+ },
+ },
+ result: 'http://localhost/?=',
+ },
+ ].forEach(item => {
+ this.set('item', item);
+ this.render(hbs`
+ {{#templated-anchor href=item.href vars=item.vars}}
+ Dashboard link
+ {{/templated-anchor}}
+ `);
+ assert.equal(
+ this.$()
+ .find('a')
+ .attr('href'),
+ item.result
+ );
+ });
+});
diff --git a/ui-v2/tests/integration/helpers/policy/is-management-test.js b/ui-v2/tests/integration/helpers/policy/typeof-test.js
similarity index 61%
rename from ui-v2/tests/integration/helpers/policy/is-management-test.js
rename to ui-v2/tests/integration/helpers/policy/typeof-test.js
index 92e89df912..11302f57eb 100644
--- a/ui-v2/tests/integration/helpers/policy/is-management-test.js
+++ b/ui-v2/tests/integration/helpers/policy/typeof-test.js
@@ -1,20 +1,20 @@
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
-moduleForComponent('policy/is-management', 'helper:policy/is-management', {
+moduleForComponent('policy/typeof', 'helper:policy/typeof', {
integration: true,
});
// Replace this with your real tests.
test('it renders', function(assert) {
- this.set('inputValue', {});
+ this.set('inputValue', '1234');
- this.render(hbs`{{policy/is-management inputValue}}`);
+ this.render(hbs`{{policy/typeof inputValue}}`);
assert.equal(
this.$()
.text()
.trim(),
- 'false'
+ 'role'
);
});
diff --git a/ui-v2/tests/integration/mixins/with-resizing-test.js b/ui-v2/tests/integration/mixins/with-resizing-test.js
index 8ebbee85ac..213b151b13 100644
--- a/ui-v2/tests/integration/mixins/with-resizing-test.js
+++ b/ui-v2/tests/integration/mixins/with-resizing-test.js
@@ -12,8 +12,13 @@ module('Integration | Mixin | with-resizing', function(hooks) {
addEventListener: this.stub(),
removeEventListener: this.stub(),
};
+ const dom = {
+ viewport: function() {
+ return win;
+ },
+ };
const subject = EmberObject.extend(Mixin, {
- win: win,
+ dom: dom,
}).create();
const resize = this.stub(subject, 'resize');
subject.didInsertElement();
diff --git a/ui-v2/tests/integration/services/repository/intention-test.js b/ui-v2/tests/integration/services/repository/intention-test.js
index bedfbb9f2b..59797baa41 100644
--- a/ui-v2/tests/integration/services/repository/intention-test.js
+++ b/ui-v2/tests/integration/services/repository/intention-test.js
@@ -2,14 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
import repo from 'consul-ui/tests/helpers/repo';
const NAME = 'intention';
moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, {
- // Specify the other units that are required for this test.
- needs: [
- 'service:settings',
- 'service:store',
- `adapter:${NAME}`,
- `serializer:${NAME}`,
- `model:${NAME}`,
- ],
+ integration: true,
});
const dc = 'dc-1';
diff --git a/ui-v2/tests/integration/services/repository/node-test.js b/ui-v2/tests/integration/services/repository/node-test.js
index 8a6b4816da..d3086c04fc 100644
--- a/ui-v2/tests/integration/services/repository/node-test.js
+++ b/ui-v2/tests/integration/services/repository/node-test.js
@@ -55,6 +55,10 @@ test('findBySlug returns the correct data for item endpoint', function(assert) {
return Object.assign({}, item, {
Datacenter: dc,
uid: `["${dc}","${item.ID}"]`,
+ meta: {
+ date: undefined,
+ cursor: undefined,
+ },
});
})
);
diff --git a/ui-v2/tests/integration/services/repository/policy-test.js b/ui-v2/tests/integration/services/repository/policy-test.js
index 6fa640a795..96fd154c5b 100644
--- a/ui-v2/tests/integration/services/repository/policy-test.js
+++ b/ui-v2/tests/integration/services/repository/policy-test.js
@@ -1,6 +1,6 @@
import { moduleFor, test, skip } from 'ember-qunit';
-const NAME = 'policy';
import repo from 'consul-ui/tests/helpers/repo';
+const NAME = 'policy';
moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, {
// Specify the other units that are required for this test.
integration: true,
diff --git a/ui-v2/tests/integration/services/repository/role-test.js b/ui-v2/tests/integration/services/repository/role-test.js
new file mode 100644
index 0000000000..bcaaeb7d6e
--- /dev/null
+++ b/ui-v2/tests/integration/services/repository/role-test.js
@@ -0,0 +1,66 @@
+import { moduleFor, test } from 'ember-qunit';
+import repo from 'consul-ui/tests/helpers/repo';
+import { createPolicies } from 'consul-ui/tests/helpers/normalizers';
+
+const NAME = 'role';
+moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, {
+ // Specify the other units that are required for this test.
+ integration: true,
+});
+const dc = 'dc-1';
+const id = 'role-name';
+test('findByDatacenter returns the correct data for list endpoint', function(assert) {
+ return repo(
+ 'Role',
+ 'findAllByDatacenter',
+ this.subject(),
+ function retrieveStub(stub) {
+ return stub(`/v1/acl/roles?dc=${dc}`, {
+ CONSUL_ROLE_COUNT: '100',
+ });
+ },
+ function performTest(service) {
+ return service.findAllByDatacenter(dc);
+ },
+ function performAssertion(actual, expected) {
+ assert.deepEqual(
+ actual,
+ expected(function(payload) {
+ return payload.map(item =>
+ Object.assign({}, item, {
+ Datacenter: dc,
+ uid: `["${dc}","${item.ID}"]`,
+ Policies: createPolicies(item),
+ })
+ );
+ })
+ );
+ }
+ );
+});
+test('findBySlug returns the correct data for item endpoint', function(assert) {
+ return repo(
+ 'Role',
+ 'findBySlug',
+ this.subject(),
+ function retrieveStub(stub) {
+ return stub(`/v1/acl/role/${id}?dc=${dc}`);
+ },
+ function performTest(service) {
+ return service.findBySlug(id, dc);
+ },
+ function performAssertion(actual, expected) {
+ assert.deepEqual(
+ actual,
+ expected(function(payload) {
+ const item = payload;
+ return Object.assign({}, item, {
+ Datacenter: dc,
+ uid: `["${dc}","${item.ID}"]`,
+ Policies: createPolicies(item),
+ });
+ })
+ );
+ }
+ );
+});
diff --git a/ui-v2/tests/integration/services/repository/service-test.js b/ui-v2/tests/integration/services/repository/service-test.js
index 630aa38e4a..cac6967922 100644
--- a/ui-v2/tests/integration/services/repository/service-test.js
+++ b/ui-v2/tests/integration/services/repository/service-test.js
@@ -68,6 +68,10 @@ test('findBySlug returns the correct data for item endpoint', function(assert) {
const service = payload.Nodes[0];
service.Nodes = nodes;
service.Tags = payload.Nodes[0].Service.Tags;
+ service.meta = {
+ date: undefined,
+ cursor: undefined,
+ };
return service;
})
diff --git a/ui-v2/tests/integration/services/repository/token-test.js b/ui-v2/tests/integration/services/repository/token-test.js
index 7e7706b5c8..1f73805f25 100644
--- a/ui-v2/tests/integration/services/repository/token-test.js
+++ b/ui-v2/tests/integration/services/repository/token-test.js
@@ -1,5 +1,7 @@
import { moduleFor, test, skip } from 'ember-qunit';
import repo from 'consul-ui/tests/helpers/repo';
+import { createPolicies } from 'consul-ui/tests/helpers/normalizers';
+
const NAME = 'token';
moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, {
// Specify the other units that are required for this test.
@@ -24,13 +26,14 @@ test('findByDatacenter returns the correct data for list endpoint', function(ass
assert.deepEqual(
actual,
expected(function(payload) {
- return payload.map(item =>
- Object.assign({}, item, {
+ return payload.map(function(item) {
+ return Object.assign({}, item, {
Datacenter: dc,
CreateTime: new Date(item.CreateTime),
uid: `["${dc}","${item.AccessorID}"]`,
- })
- );
+ Policies: createPolicies(item),
+ });
+ });
})
);
}
@@ -56,6 +59,7 @@ test('findBySlug returns the correct data for item endpoint', function(assert) {
Datacenter: dc,
CreateTime: new Date(item.CreateTime),
uid: `["${dc}","${item.AccessorID}"]`,
+ Policies: createPolicies(item),
});
})
);
diff --git a/ui-v2/tests/integration/utils/dom/event-source/callable-test.js b/ui-v2/tests/integration/utils/dom/event-source/callable-test.js
new file mode 100644
index 0000000000..02605d6f66
--- /dev/null
+++ b/ui-v2/tests/integration/utils/dom/event-source/callable-test.js
@@ -0,0 +1,84 @@
+import domEventSourceCallable from 'consul-ui/utils/dom/event-source/callable';
+import EventTarget from 'consul-ui/utils/dom/event-target/rsvp';
+import { Promise } from 'rsvp';
+
+import { module } from 'qunit';
+import { setupTest } from 'ember-qunit';
+import test from 'ember-sinon-qunit/test-support/test';
+
+module('Integration | Utility | dom/event-source/callable', function(hooks) {
+ setupTest(hooks);
+ test('it dispatches messages', function(assert) {
+ assert.expect(1);
+ const EventSource = domEventSourceCallable(EventTarget);
+ const listener = this.stub();
+ const source = new EventSource(
+ function(configuration) {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ this.dispatchEvent({
+ type: 'message',
+ data: null,
+ });
+ resolve();
+ }, configuration.milliseconds);
+ });
+ },
+ {
+ milliseconds: 100,
+ }
+ );
+ source.addEventListener('message', function() {
+ listener();
+ });
+ return new Promise(function(resolve) {
+ setTimeout(function() {
+ source.close();
+ assert.equal(listener.callCount, 5);
+ resolve();
+ }, 550);
+ });
+ });
+ test('it dispatches a single open event and closes when called with no callable', function(assert) {
+ assert.expect(4);
+ const EventSource = domEventSourceCallable(EventTarget);
+ const listener = this.stub();
+ const source = new EventSource();
+ source.addEventListener('open', function(e) {
+ assert.deepEqual(e.target, this);
+ assert.equal(e.target.readyState, 1);
+ listener();
+ });
+ return Promise.resolve().then(function() {
+ assert.ok(listener.calledOnce);
+ assert.equal(source.readyState, 2);
+ });
+ });
+ test('it dispatches a single open event, and calls the specified callable that can dispatch an event', function(assert) {
+ assert.expect(1);
+ const EventSource = domEventSourceCallable(EventTarget);
+ const listener = this.stub();
+ const source = new EventSource(function() {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ this.dispatchEvent({
+ type: 'message',
+ data: {},
+ });
+ this.close();
+ }, 190);
+ });
+ });
+ source.addEventListener('open', function() {
+ // open is called first
+ listener();
+ });
+ return new Promise(function(resolve) {
+ source.addEventListener('message', function() {
+ // message is called second
+ assert.ok(listener.calledOnce);
+ resolve();
+ });
+ });
+ });
+});
diff --git a/ui-v2/tests/lib/page-object/createDeletable.js b/ui-v2/tests/lib/page-object/createDeletable.js
index 88a328d96b..9bbe357da8 100644
--- a/ui-v2/tests/lib/page-object/createDeletable.js
+++ b/ui-v2/tests/lib/page-object/createDeletable.js
@@ -1,5 +1,5 @@
export default function(clickable) {
- return function(obj, scope = '') {
+ return function(obj = {}, scope = '') {
if (scope !== '') {
scope = scope + ' ';
}
diff --git a/ui-v2/tests/lib/page-object/createSubmitable.js b/ui-v2/tests/lib/page-object/createSubmitable.js
index aabd562900..2d6282114d 100644
--- a/ui-v2/tests/lib/page-object/createSubmitable.js
+++ b/ui-v2/tests/lib/page-object/createSubmitable.js
@@ -1,5 +1,5 @@
export default function(clickable, is) {
- return function(obj, scope = '') {
+ return function(obj = {}, scope = '') {
if (scope !== '') {
scope = scope + ' ';
}
diff --git a/ui-v2/tests/lib/page-object/radiogroup.js b/ui-v2/tests/lib/page-object/radiogroup.js
index b5344ce7a3..b9802f56ca 100644
--- a/ui-v2/tests/lib/page-object/radiogroup.js
+++ b/ui-v2/tests/lib/page-object/radiogroup.js
@@ -1,12 +1,12 @@
import { is, clickable } from 'ember-cli-page-object';
import ucfirst from 'consul-ui/utils/ucfirst';
-export default function(name, items) {
+export default function(name, items, blankKey = 'all') {
return items.reduce(function(prev, item, i, arr) {
// if item is empty then it means 'all'
// otherwise camelCase based on something-here = somethingHere for the key
const key =
item === ''
- ? 'all'
+ ? blankKey
: item.split('-').reduce(function(prev, item, i, arr) {
if (i === 0) {
return item;
diff --git a/ui-v2/tests/pages.js b/ui-v2/tests/pages.js
index 69e890b32a..d3c5c2f752 100644
--- a/ui-v2/tests/pages.js
+++ b/ui-v2/tests/pages.js
@@ -1,4 +1,5 @@
import { create, clickable, is, attribute, collection, text } from 'ember-cli-page-object';
+import { alias } from 'ember-cli-page-object/macros';
import { visitable } from 'consul-ui/tests/lib/page-object/visitable';
import createDeletable from 'consul-ui/tests/lib/page-object/createDeletable';
import createSubmitable from 'consul-ui/tests/lib/page-object/createSubmitable';
@@ -11,6 +12,11 @@ import freetextFilter from 'consul-ui/tests/pages/components/freetext-filter';
import catalogFilter from 'consul-ui/tests/pages/components/catalog-filter';
import aclFilter from 'consul-ui/tests/pages/components/acl-filter';
import intentionFilter from 'consul-ui/tests/pages/components/intention-filter';
+import tokenListFactory from 'consul-ui/tests/pages/components/token-list';
+import policyFormFactory from 'consul-ui/tests/pages/components/policy-form';
+import policySelectorFactory from 'consul-ui/tests/pages/components/policy-selector';
+import roleFormFactory from 'consul-ui/tests/pages/components/role-form';
+import roleSelectorFactory from 'consul-ui/tests/pages/components/role-selector';
// TODO: should this specifically be modal or form?
// should all forms be forms?
@@ -19,6 +25,7 @@ import dcs from 'consul-ui/tests/pages/dc';
import settings from 'consul-ui/tests/pages/settings';
import services from 'consul-ui/tests/pages/dc/services/index';
import service from 'consul-ui/tests/pages/dc/services/show';
+import instance from 'consul-ui/tests/pages/dc/services/instance';
import nodes from 'consul-ui/tests/pages/dc/nodes/index';
import node from 'consul-ui/tests/pages/dc/nodes/show';
import kvs from 'consul-ui/tests/pages/dc/kv/index';
@@ -27,6 +34,8 @@ import acls from 'consul-ui/tests/pages/dc/acls/index';
import acl from 'consul-ui/tests/pages/dc/acls/edit';
import policies from 'consul-ui/tests/pages/dc/acls/policies/index';
import policy from 'consul-ui/tests/pages/dc/acls/policies/edit';
+import roles from 'consul-ui/tests/pages/dc/acls/roles/index';
+import role from 'consul-ui/tests/pages/dc/acls/roles/edit';
import tokens from 'consul-ui/tests/pages/dc/acls/tokens/index';
import token from 'consul-ui/tests/pages/dc/acls/tokens/edit';
import intentions from 'consul-ui/tests/pages/dc/intentions/index';
@@ -36,11 +45,21 @@ const deletable = createDeletable(clickable);
const submitable = createSubmitable(clickable, is);
const creatable = createCreatable(clickable, is);
const cancelable = createCancelable(clickable, is);
+
+const tokenList = tokenListFactory(clickable, attribute, collection, deletable);
+
+const policyForm = policyFormFactory(submitable, cancelable, radiogroup);
+const policySelector = policySelectorFactory(clickable, deletable, collection, alias, policyForm);
+
+const roleForm = roleFormFactory(submitable, cancelable, policySelector);
+const roleSelector = roleSelectorFactory(clickable, deletable, collection, alias, roleForm);
+
export default {
index: create(index(visitable, collection)),
dcs: create(dcs(visitable, clickable, attribute, collection)),
services: create(services(visitable, clickable, attribute, collection, page, catalogFilter)),
service: create(service(visitable, attribute, collection, text, catalogFilter)),
+ instance: create(instance(visitable, attribute, collection, text, radiogroup)),
nodes: create(nodes(visitable, clickable, attribute, collection, catalogFilter)),
node: create(node(visitable, deletable, clickable, attribute, collection, radiogroup)),
kvs: create(kvs(visitable, deletable, creatable, clickable, attribute, collection)),
@@ -50,9 +69,12 @@ export default {
policies: create(
policies(visitable, deletable, creatable, clickable, attribute, collection, freetextFilter)
),
- policy: create(
- policy(visitable, submitable, deletable, cancelable, clickable, attribute, collection)
+ policy: create(policy(visitable, submitable, deletable, cancelable, tokenList)),
+ roles: create(
+ roles(visitable, deletable, creatable, clickable, attribute, collection, freetextFilter)
),
+ // TODO: This needs a policyList
+ role: create(role(visitable, submitable, deletable, cancelable, policySelector, tokenList)),
tokens: create(
tokens(
visitable,
@@ -67,7 +89,7 @@ export default {
)
),
token: create(
- token(visitable, submitable, deletable, cancelable, clickable, attribute, collection)
+ token(visitable, submitable, deletable, cancelable, clickable, policySelector, roleSelector)
),
intentions: create(
intentions(visitable, deletable, creatable, clickable, attribute, collection, intentionFilter)
diff --git a/ui-v2/tests/pages/components/policy-form.js b/ui-v2/tests/pages/components/policy-form.js
new file mode 100644
index 0000000000..71c5e0a37a
--- /dev/null
+++ b/ui-v2/tests/pages/components/policy-form.js
@@ -0,0 +1,11 @@
+export default (submitable, cancelable, radiogroup) => () => {
+ return {
+ // this should probably be settable
+ resetScope: true,
+ scope: '[data-test-policy-form]',
+ prefix: 'policy',
+ ...submitable(),
+ ...cancelable(),
+ ...radiogroup('template', ['', 'service-identity'], 'policy'),
+ };
+};
diff --git a/ui-v2/tests/pages/components/policy-selector.js b/ui-v2/tests/pages/components/policy-selector.js
new file mode 100644
index 0000000000..25c7a4a420
--- /dev/null
+++ b/ui-v2/tests/pages/components/policy-selector.js
@@ -0,0 +1,20 @@
+export default (clickable, deletable, collection, alias, policyForm) => (
+ scope = '#policies',
+ createSelector = '[for="new-policy-toggle"]'
+) => {
+ return {
+ scope: scope,
+ create: clickable(createSelector),
+ form: policyForm(),
+ policies: alias('selectedOptions'),
+ selectedOptions: collection(
+ '[data-test-policies] [data-test-tabular-row]',
+ deletable(
+ {
+ expand: clickable('label'),
+ },
+ '+ tr'
+ )
+ ),
+ };
+};
diff --git a/ui-v2/tests/pages/components/role-form.js b/ui-v2/tests/pages/components/role-form.js
new file mode 100644
index 0000000000..e45baedcdd
--- /dev/null
+++ b/ui-v2/tests/pages/components/role-form.js
@@ -0,0 +1,11 @@
+export default (submitable, cancelable, policySelector) => () => {
+ return {
+ // this should probably be settable
+ resetScope: true,
+ scope: '[data-test-role-form]',
+ prefix: 'role',
+ ...submitable(),
+ ...cancelable(),
+ policies: policySelector('', '[data-test-create-policy]'),
+ };
+};
diff --git a/ui-v2/tests/pages/components/role-selector.js b/ui-v2/tests/pages/components/role-selector.js
new file mode 100644
index 0000000000..d2771e2233
--- /dev/null
+++ b/ui-v2/tests/pages/components/role-selector.js
@@ -0,0 +1,14 @@
+export default (clickable, deletable, collection, alias, roleForm) => (scope = '#roles') => {
+ return {
+ scope: scope,
+ create: clickable('[for="new-role-toggle"]'),
+ form: roleForm(),
+ roles: alias('selectedOptions'),
+ selectedOptions: collection(
+ '[data-test-roles] [data-test-tabular-row]',
+ deletable({
+ actions: clickable('label'),
+ })
+ ),
+ };
+};
diff --git a/ui-v2/tests/pages/components/token-list.js b/ui-v2/tests/pages/components/token-list.js
new file mode 100644
index 0000000000..a941c0b2d7
--- /dev/null
+++ b/ui-v2/tests/pages/components/token-list.js
@@ -0,0 +1,7 @@
+export default (clickable, attribute, collection, deletable) => () => {
+ return collection('[data-test-tokens] [data-test-tabular-row]', {
+ id: attribute('data-test-token', '[data-test-token]'),
+ token: clickable('a'),
+ ...deletable(),
+ });
+};
diff --git a/ui-v2/tests/pages/dc/acls/policies/edit.js b/ui-v2/tests/pages/dc/acls/policies/edit.js
index 437fc4beb6..847c7261bf 100644
--- a/ui-v2/tests/pages/dc/acls/policies/edit.js
+++ b/ui-v2/tests/pages/dc/acls/policies/edit.js
@@ -1,16 +1,14 @@
-export default function(visitable, submitable, deletable, cancelable, clickable, attribute, collection) {
- return submitable(
- cancelable(
- deletable({
- visit: visitable(['/:dc/acls/policies/:policy', '/:dc/acls/policies/create']),
- tokens: collection(
- '[data-test-tabular-row]',
- deletable({
- id: attribute('data-test-token', '[data-test-token]'),
- token: clickable('a'),
- })
- ),
- })
- )
- );
+export default function(visitable, submitable, deletable, cancelable, tokenList) {
+ return {
+ visit: visitable(['/:dc/acls/policies/:policy', '/:dc/acls/policies/create']),
+ ...submitable({}, 'form > div'),
+ ...cancelable({}, 'form > div'),
+ ...deletable({}, 'form > div'),
+ tokens: tokenList(),
+ deleteModal: {
+ resetScope: true,
+ scope: '[data-test-delete-modal]',
+ ...deletable({}),
+ },
+ };
}
diff --git a/ui-v2/tests/pages/dc/acls/roles/edit.js b/ui-v2/tests/pages/dc/acls/roles/edit.js
new file mode 100644
index 0000000000..85a794801f
--- /dev/null
+++ b/ui-v2/tests/pages/dc/acls/roles/edit.js
@@ -0,0 +1,10 @@
+export default function(visitable, submitable, deletable, cancelable, policySelector, tokenList) {
+ return {
+ visit: visitable(['/:dc/acls/roles/:role', '/:dc/acls/roles/create']),
+ ...submitable({}, 'form > div'),
+ ...cancelable({}, 'form > div'),
+ ...deletable({}, 'form > div'),
+ policies: policySelector(''),
+ tokens: tokenList(),
+ };
+}
diff --git a/ui-v2/tests/pages/dc/acls/roles/index.js b/ui-v2/tests/pages/dc/acls/roles/index.js
new file mode 100644
index 0000000000..4082349265
--- /dev/null
+++ b/ui-v2/tests/pages/dc/acls/roles/index.js
@@ -0,0 +1,14 @@
+export default function(visitable, deletable, creatable, clickable, attribute, collection, filter) {
+ return creatable({
+ visit: visitable('/:dc/acls/roles'),
+ roles: collection(
+ '[data-test-tabular-row]',
+ deletable({
+ name: attribute('data-test-role', '[data-test-role]'),
+ policy: clickable('a'),
+ actions: clickable('label'),
+ })
+ ),
+ filter: filter,
+ });
+}
diff --git a/ui-v2/tests/pages/dc/acls/tokens/edit.js b/ui-v2/tests/pages/dc/acls/tokens/edit.js
index 862c9eee23..15a3e560d9 100644
--- a/ui-v2/tests/pages/dc/acls/tokens/edit.js
+++ b/ui-v2/tests/pages/dc/acls/tokens/edit.js
@@ -4,33 +4,17 @@ export default function(
deletable,
cancelable,
clickable,
- attribute,
- collection
+ policySelector,
+ roleSelector
) {
- return submitable(
- cancelable(
- deletable(
- {
- visit: visitable(['/:dc/acls/tokens/:token', '/:dc/acls/tokens/create']),
- use: clickable('[data-test-use]'),
- confirmUse: clickable('button.type-delete'),
- newPolicy: clickable('[data-test-new-policy]'),
- policyForm: submitable(
- cancelable({}, '[data-test-policy-form]'),
- '[data-test-policy-form]'
- ),
- policies: collection(
- '[data-test-tabular-row]',
- deletable(
- {
- expand: clickable('label'),
- },
- '+ tr'
- )
- ),
- },
- 'form > div'
- )
- )
- );
+ return {
+ visit: visitable(['/:dc/acls/tokens/:token', '/:dc/acls/tokens/create']),
+ ...submitable({}, 'form > div'),
+ ...cancelable({}, 'form > div'),
+ ...deletable({}, 'form > div'),
+ use: clickable('[data-test-use]'),
+ confirmUse: clickable('button.type-delete'),
+ policies: policySelector(),
+ roles: roleSelector(),
+ };
}
diff --git a/ui-v2/tests/pages/dc/services/instance.js b/ui-v2/tests/pages/dc/services/instance.js
new file mode 100644
index 0000000000..69a707780d
--- /dev/null
+++ b/ui-v2/tests/pages/dc/services/instance.js
@@ -0,0 +1,19 @@
+export default function(visitable, attribute, collection, text, radiogroup) {
+ return {
+ visit: visitable('/:dc/services/:service/:id'),
+ externalSource: attribute('data-test-external-source', 'h1 span'),
+ tabs: radiogroup('tab', ['service-checks', 'node-checks', 'upstreams', 'tags']),
+ serviceChecks: collection('#service-checks [data-test-healthchecks] li', {}),
+ nodeChecks: collection('#node-checks [data-test-healthchecks] li', {}),
+ upstreams: collection('#upstreams [data-test-tabular-row]', {
+ name: text('[data-test-destination-name]'),
+ datacenter: text('[data-test-destination-datacenter]'),
+ type: text('[data-test-destination-type]'),
+ address: text('[data-test-local-bind-address]'),
+ }),
+ proxy: {
+ type: attribute('data-test-proxy-type', '[data-test-proxy-type]'),
+ destination: attribute('data-test-proxy-destination', '[data-test-proxy-destination]'),
+ },
+ };
+}
diff --git a/ui-v2/tests/pages/dc/services/show.js b/ui-v2/tests/pages/dc/services/show.js
index f50c60c834..5b5fb2e381 100644
--- a/ui-v2/tests/pages/dc/services/show.js
+++ b/ui-v2/tests/pages/dc/services/show.js
@@ -2,18 +2,8 @@ export default function(visitable, attribute, collection, text, filter) {
return {
visit: visitable('/:dc/services/:service'),
externalSource: attribute('data-test-external-source', 'h1 span'),
- nodes: collection('[data-test-node]', {
- name: attribute('data-test-node'),
- }),
- healthy: collection('[data-test-healthy] [data-test-node]', {
- name: attribute('data-test-node'),
- address: text('header strong'),
- id: text('header em'),
- }),
- unhealthy: collection('[data-test-unhealthy] [data-test-node]', {
- name: attribute('data-test-node'),
- address: text('header strong'),
- id: text('header em'),
+ instances: collection('#instances [data-test-tabular-row]', {
+ address: text('[data-test-address]'),
}),
filter: filter,
};
diff --git a/ui-v2/tests/steps.js b/ui-v2/tests/steps.js
index 859cfe830b..3bec041741 100644
--- a/ui-v2/tests/steps.js
+++ b/ui-v2/tests/steps.js
@@ -1,487 +1,86 @@
-/* eslint no-console: "off" */
-import Inflector from 'ember-inflector';
-import yadda from './helpers/yadda';
-import { currentURL, click, triggerKeyEvent, fillIn, find } from '@ember/test-helpers';
-import getDictionary from '@hashicorp/ember-cli-api-double/dictionary';
-import pages from 'consul-ui/tests/pages';
-import api from 'consul-ui/tests/helpers/api';
+import models from './steps/doubles/model';
+import http from './steps/doubles/http';
+import visit from './steps/interactions/visit';
+import click from './steps/interactions/click';
+import form from './steps/interactions/form';
+import debug from './steps/debug/index';
+import assertHttp from './steps/assertions/http';
+import assertModel from './steps/assertions/model';
+import assertPage from './steps/assertions/page';
+import assertDom from './steps/assertions/dom';
+
// const dont = `( don't| shouldn't| can't)?`;
-const pluralize = function(str) {
- return Inflector.inflector.pluralize(str);
-};
-const create = function(number, name, value) {
- // don't return a promise here as
- // I don't need it to wait
- api.server.createList(name, number, value);
-};
-const lastRequest = function(method) {
- return api.server.history
- .slice(0)
- .reverse()
- .find(function(item) {
- return item.method === method;
- });
-};
-const fillInElement = function(page, name, value) {
- const cm = document.querySelector(`textarea[name="${name}"] + .CodeMirror`);
- if (cm) {
- cm.CodeMirror.setValue(value);
+
+export default function(assert, library, pages, utils) {
+ var currentPage;
+ const getCurrentPage = function() {
+ return currentPage;
+ };
+ const setCurrentPage = function(page) {
+ currentPage = page;
return page;
- } else {
- return page.fillIn(name, value);
- }
-};
-var currentPage;
-export default function(assert) {
- return (
- yadda.localisation.English.library(
- getDictionary(function(model, cb) {
- switch (model) {
- case 'datacenter':
- case 'datacenters':
- case 'dcs':
- model = 'dc';
- break;
- case 'services':
- model = 'service';
- break;
- case 'nodes':
- model = 'node';
- break;
- case 'kvs':
- model = 'kv';
- break;
- case 'acls':
- model = 'acl';
- break;
- case 'sessions':
- model = 'session';
- break;
- case 'intentions':
- model = 'intention';
- break;
- }
- cb(null, model);
- }, yadda)
- )
- // doubles
- .given(['$number $model model[s]?', '$number $model models'], function(number, model) {
- return create(number, model);
- })
- .given(['$number $model model[s]? with the value "$value"'], function(number, model, value) {
- return create(number, model, value);
- })
- .given(
- ['$number $model model[s]? from yaml\n$yaml', '$number $model model[s]? from json\n$json'],
- function(number, model, data) {
- return create(number, model, data);
- }
- )
- .given(["I'm using a legacy token"], function(number, model, data) {
- window.localStorage['consul:token'] = JSON.stringify({ AccessorID: null, SecretID: 'id' });
- })
- // TODO: Abstract this away from HTTP
- .given(['the url "$url" responds with a $status status'], function(url, status) {
- return api.server.respondWithStatus(url.split('?')[0], parseInt(status));
- })
- .given(['the url "$url" responds with from yaml\n$yaml'], function(url, data) {
- api.server.respondWith(url.split('?')[0], data);
- })
- // interactions
- .when('I visit the $name page', function(name) {
- currentPage = pages[name];
- return currentPage.visit();
- })
- .when('I visit the $name page for the "$id" $model', function(name, id, model) {
- currentPage = pages[name];
- return currentPage.visit({
- [model]: id,
- });
- })
- .when(
- ['I visit the $name page for yaml\n$yaml', 'I visit the $name page for json\n$json'],
- function(name, data) {
- currentPage = pages[name];
- // TODO: Consider putting an assertion here for testing the current url
- // do I absolutely definitely need that all the time?
- return pages[name].visit(data);
- }
- )
- .when('I click "$selector"', function(selector) {
- return click(selector);
- })
- // TODO: Probably nicer to think of better vocab than having the 'without " rule'
- .when('I click (?!")$property(?!")', function(property) {
- try {
- return currentPage[property]();
- } catch (e) {
- console.error(e);
- throw new Error(`The '${property}' property on the page object doesn't exist`);
- }
- })
- .when('I click $prop on the $component', function(prop, component) {
- // Collection
- var obj;
- if (typeof currentPage[component].objectAt === 'function') {
- obj = currentPage[component].objectAt(0);
- } else {
- obj = currentPage[component];
- }
- const func = obj[prop].bind(obj);
- try {
- return func();
- } catch (e) {
- throw new Error(
- `The '${prop}' property on the '${component}' page object doesn't exist.\n${e.message}`
- );
- }
- })
- .when('I submit', function(selector) {
- return currentPage.submit();
- })
- .then('I fill in "$name" with "$value"', function(name, value) {
- return currentPage.fillIn(name, value);
- })
- .then(['I fill in with yaml\n$yaml', 'I fill in with json\n$json'], function(data) {
- return Object.keys(data).reduce(function(prev, item, i, arr) {
- return fillInElement(prev, item, data[item]);
- }, currentPage);
- })
- .then(
- ['I fill in the $form form with yaml\n$yaml', 'I fill in the $form with json\n$json'],
- function(form, data) {
- return Object.keys(data).reduce(function(prev, item, i, arr) {
- const name = `${form}[${item}]`;
- return fillInElement(prev, name, data[item]);
- }, currentPage);
- }
- )
- .then(['I type "$text" into "$selector"'], function(text, selector) {
- return fillIn(selector, text);
- })
- .then(['I type with yaml\n$yaml'], function(data) {
- const keys = Object.keys(data);
- return keys
- .reduce(function(prev, item, i, arr) {
- return prev.fillIn(item, data[item]);
- }, currentPage)
- .then(function() {
- return Promise.all(
- keys.map(function(item) {
- return triggerKeyEvent(`[name="${item}"]`, 'keyup', 83); // TODO: This is 's', be more generic
- })
- );
- });
- })
- // debugging helpers
- .then('print the current url', function(url) {
- console.log(currentURL());
- return Promise.resolve();
- })
- .then('log the "$text"', function(text) {
- console.log(text);
- return Promise.resolve();
- })
- .then('pause for $milliseconds', function(milliseconds) {
- return new Promise(function(resolve) {
- setTimeout(resolve, milliseconds);
- });
- })
- // assertions
- .then('a $method request is made to "$url" with the body from yaml\n$yaml', function(
- method,
- url,
- data
- ) {
- const request = api.server.history[api.server.history.length - 2];
- assert.equal(
- request.method,
- method,
- `Expected the request method to be ${method}, was ${request.method}`
- );
- assert.equal(request.url, url, `Expected the request url to be ${url}, was ${request.url}`);
- const body = JSON.parse(request.requestBody);
- Object.keys(data).forEach(function(key, i, arr) {
- assert.deepEqual(
- body[key],
- data[key],
- `Expected the payload to contain ${key} equaling ${data[key]}, ${key} was ${body[key]}`
- );
- });
- })
- // TODO: This one can replace the above one, it covers more use cases
- // also DRY it out a bit
- .then('a $method request is made to "$url" from yaml\n$yaml', function(method, url, yaml) {
- const request = api.server.history[api.server.history.length - 2];
- assert.equal(
- request.method,
- method,
- `Expected the request method to be ${method}, was ${request.method}`
- );
- assert.equal(request.url, url, `Expected the request url to be ${url}, was ${request.url}`);
- let data = yaml.body || {};
- const body = JSON.parse(request.requestBody);
- Object.keys(data).forEach(function(key, i, arr) {
- assert.equal(
- body[key],
- data[key],
- `Expected the payload to contain ${key} to equal ${body[key]}, ${key} was ${data[key]}`
- );
- });
- data = yaml.headers || {};
- const headers = request.requestHeaders;
- Object.keys(data).forEach(function(key, i, arr) {
- assert.equal(
- headers[key],
- data[key],
- `Expected the payload to contain ${key} to equal ${headers[key]}, ${key} was ${
- data[key]
- }`
- );
- });
- })
- .then('a $method request is made to "$url" with the body "$body"', function(
- method,
- url,
- data
- ) {
- const request = api.server.history[api.server.history.length - 2];
- assert.equal(
- request.method,
- method,
- `Expected the request method to be ${method}, was ${request.method}`
- );
- assert.equal(request.url, url, `Expected the request url to be ${url}, was ${request.url}`);
- const body = request.requestBody;
- assert.equal(body, data, `Expected the request body to be ${data}, was ${body}`);
- })
- .then('a $method request is made to "$url" with no body', function(method, url) {
- const request = api.server.history[api.server.history.length - 2];
- assert.equal(
- request.method,
- method,
- `Expected the request method to be ${method}, was ${request.method}`
- );
- assert.equal(request.url, url, `Expected the request url to be ${url}, was ${request.url}`);
- const body = request.requestBody;
- assert.equal(body, null, `Expected the request body to be null, was ${body}`);
- })
+ };
- .then('a $method request is made to "$url"', function(method, url) {
- const request = api.server.history[api.server.history.length - 2];
- assert.equal(
- request.method,
- method,
- `Expected the request method to be ${method}, was ${request.method}`
- );
- assert.equal(request.url, url, `Expected the request url to be ${url}, was ${request.url}`);
- })
- .then('the last $method request was made to "$url"', function(method, url) {
- const request = lastRequest(method);
- assert.equal(
- request.method,
- method,
- `Expected the request method to be ${method}, was ${request.method}`
- );
- assert.equal(request.url, url, `Expected the request url to be ${url}, was ${request.url}`);
- })
- .then('the last $method request was made to "$url" with the body from yaml\n$yaml', function(
- method,
- url,
- data
- ) {
- const request = lastRequest(method);
- assert.ok(request, `Expected a ${method} request`);
- assert.equal(
- request.method,
- method,
- `Expected the request method to be ${method}, was ${request.method}`
- );
- assert.equal(request.url, url, `Expected the request url to be ${url}, was ${request.url}`);
- const body = JSON.parse(request.requestBody);
- Object.keys(data).forEach(function(key, i, arr) {
- assert.deepEqual(
- body[key],
- data[key],
- `Expected the payload to contain ${key} equaling ${data[key]}, ${key} was ${body[key]}`
- );
- });
- })
- .then('the last $method requests were like yaml\n$yaml', function(method, data) {
- const requests = api.server.history.reverse().filter(function(item) {
- return item.method === method;
- });
- data.reverse().forEach(function(item, i, arr) {
- assert.equal(
- requests[i].url,
- item,
- `Expected the request url to be ${item}, was ${requests[i].url}`
- );
- });
- })
- .then('the url should be $url', function(url) {
- // TODO: nice! $url should be wrapped in ""
- if (url === "''") {
- url = '';
+ const pauseUntil = function(cb) {
+ return new Promise(function(resolve, reject) {
+ let count = 0;
+ const interval = setInterval(function() {
+ if (++count >= 50) {
+ clearInterval(interval);
+ assert.ok(false);
+ reject();
}
- const current = currentURL() || '';
- assert.equal(current, url, `Expected the url to be ${url} was ${current}`);
- })
- .then(['I see $num $model', 'I see $num $model model', 'I see $num $model models'], function(
- num,
- model
- ) {
- const len = currentPage[pluralize(model)].filter(function(item) {
- return item.isVisible;
- }).length;
-
- assert.equal(len, num, `Expected ${num} ${pluralize(model)}, saw ${len}`);
- })
- // TODO: I${ dont } see
- .then([`I see $num $model model[s]? with the $property "$value"`], function(
- // negate,
- num,
- model,
- property,
- value
- ) {
- const len = currentPage[pluralize(model)].filter(function(item) {
- return item.isVisible && item[property] == value;
- }).length;
- assert.equal(
- len,
- num,
- `Expected ${num} ${pluralize(model)} with ${property} set to "${value}", saw ${len}`
- );
- })
- // TODO: Make this accept a 'contains' word so you can search for text containing also
- .then('I have settings like yaml\n$yaml', function(data) {
- // TODO: Inject this
- const settings = window.localStorage;
- Object.keys(data).forEach(function(prop) {
- const actual = settings.getItem(prop);
- const expected = data[prop];
- assert.strictEqual(actual, expected, `Expected settings to be ${expected} was ${actual}`);
+ cb(function() {
+ clearInterval(interval);
+ resolve();
});
- })
- .then('I see $property on the $component like yaml\n$yaml', function(
- property,
- component,
- yaml
- ) {
- const _component = currentPage[component];
- const iterator = new Array(_component.length).fill(true);
- // this will catch if we get aren't managing to select a component
- assert.ok(iterator.length > 0);
- iterator.forEach(function(item, i, arr) {
- const actual =
- typeof _component.objectAt(i)[property] === 'undefined'
- ? null
- : _component.objectAt(i)[property];
-
- // anything coming from the DOM is going to be text/strings
- // if the yaml has numbers, cast them to strings
- // TODO: This would get problematic for deeper objects
- // will have to look to do this recursively
- const expected = typeof yaml[i] === 'number' ? yaml[i].toString() : yaml[i];
-
- assert.deepEqual(
- actual,
- expected,
- `Expected to see ${property} on ${component}[${i}] as ${JSON.stringify(
- expected
- )}, was ${JSON.stringify(actual)}`
- );
- });
- })
- .then(['I see $property on the $component'], function(property, component) {
- // TODO: Time to work on repetition
- // Collection
- var obj;
- if (typeof currentPage[component].objectAt === 'function') {
- obj = currentPage[component].objectAt(0);
- } else {
- obj = currentPage[component];
- }
- let _component;
- if (typeof obj === 'function') {
- const func = obj[property].bind(obj);
- try {
- _component = func();
- } catch (e) {
- console.error(e);
- throw new Error(
- `The '${property}' property on the '${component}' page object doesn't exist`
- );
+ }, 100);
+ });
+ };
+ const mb = function(path) {
+ return function(obj) {
+ return (
+ path.map(function(prop) {
+ obj = obj || {};
+ if (isNaN(parseInt(prop))) {
+ return (obj = obj[prop]);
+ } else {
+ return (obj = obj.objectAt(prop));
}
- } else {
- _component = obj;
- }
- assert.ok(_component[property], `Expected to see ${property} on ${component}`);
- })
- .then(["I don't see $property on the $component"], function(property, component) {
- // Collection
- var obj;
- if (typeof currentPage[component].objectAt === 'function') {
- obj = currentPage[component].objectAt(0);
- } else {
- obj = currentPage[component];
- }
- const func = obj[property].bind(obj);
- assert.throws(
- function() {
- func();
- },
- function(e) {
- return e.toString().indexOf('Element not found') !== -1;
- },
- `Expected to not see ${property} on ${component}`
- );
- })
- .then(["I don't see $property"], function(property) {
- assert.throws(
- function() {
- currentPage[property]();
- },
- function(e) {
- return e.toString().indexOf('Element not found') !== -1;
- },
- `Expected to not see ${property}`
- );
- })
- .then(['I see $property'], function(property) {
- assert.ok(currentPage[property], `Expected to see ${property}`);
- })
- .then(['I see $property like "$value"'], function(property, value) {
- assert.equal(
- currentPage[property],
- value,
- `Expected to see ${property}, was ${currentPage[property]}`
- );
- })
- .then(['I see the text "$text" in "$selector"'], function(text, selector) {
- assert.ok(
- find(selector).textContent.indexOf(text) !== -1,
- `Expected to see "${text}" in "${selector}"`
- );
- })
- // TODO: Think of better language
- // TODO: These should be mergeable
- .then(['"$selector" has the "$class" class'], function(selector, cls) {
- // because `find` doesn't work, guessing its sandboxed to ember's container
- assert.ok(
- document.querySelector(selector).classList.contains(cls),
- `Expected [class] to contain ${cls} on ${selector}`
- );
- })
- .then(['"$selector" doesn\'t have the "$class" class'], function(selector, cls) {
- assert.ok(
- !document.querySelector(selector).classList.contains(cls),
- `Expected [class] not to contain ${cls} on ${selector}`
- );
- })
- .then('ok', function() {
- assert.ok(true);
- })
- );
+ }) && obj
+ );
+ };
+ };
+ const find = function(path) {
+ const page = getCurrentPage();
+ const parts = path.split('.');
+ const last = parts.pop();
+ let obj;
+ let parent = mb(parts)(page) || page;
+ if (typeof parent.objectAt === 'function') {
+ parent = parent.objectAt(0);
+ }
+ obj = parent[last];
+ if (typeof obj === 'undefined') {
+ throw new Error(`The '${path}' object doesn't exist`);
+ }
+ if (typeof obj === 'function') {
+ obj = obj.bind(parent);
+ }
+ return obj;
+ };
+ models(library, utils.create);
+ http(library, utils.respondWith, utils.set);
+ visit(library, pages, setCurrentPage);
+ click(library, find, utils.click);
+ form(library, find, utils.fillIn, utils.triggerKeyEvent, getCurrentPage);
+ debug(library, assert, utils.currentURL);
+ assertHttp(library, assert, utils.lastNthRequest);
+ assertModel(library, assert, find, getCurrentPage, pauseUntil, utils.pluralize);
+ assertPage(library, assert, find, getCurrentPage);
+ assertDom(library, assert, pauseUntil, utils.find, utils.currentURL);
+
+ return library.given(["I'm using a legacy token"], function(number, model, data) {
+ window.localStorage['consul:token'] = JSON.stringify({ AccessorID: null, SecretID: 'id' });
+ });
}
diff --git a/ui-v2/tests/steps/assertions/dom.js b/ui-v2/tests/steps/assertions/dom.js
new file mode 100644
index 0000000000..12699d04bf
--- /dev/null
+++ b/ui-v2/tests/steps/assertions/dom.js
@@ -0,0 +1,63 @@
+export default function(scenario, assert, pauseUntil, find, currentURL) {
+ scenario
+ .then('pause until I see the text "$text" in "$selector"', function(text, selector) {
+ return pauseUntil(function(resolve) {
+ const $el = find(selector);
+ if ($el) {
+ const hasText = $el.textContent.indexOf(text) !== -1;
+ if (hasText) {
+ assert.ok(hasText, `Expected to see "${text}" in "${selector}"`);
+ resolve();
+ }
+ }
+ });
+ })
+ .then(['I see the text "$text" in "$selector"'], function(text, selector) {
+ assert.ok(
+ find(selector).textContent.indexOf(text) !== -1,
+ `Expected to see "${text}" in "${selector}"`
+ );
+ })
+ .then(['I see the exact text "$text" in "$selector"'], function(text, selector) {
+ assert.ok(
+ find(selector).textContent.trim() === text,
+ `Expected to see the exact "${text}" in "${selector}"`
+ );
+ })
+ // TODO: Think of better language
+ // TODO: These should be mergeable
+ .then(['"$selector" has the "$class" class'], function(selector, cls) {
+ // because `find` doesn't work, guessing its sandboxed to ember's container
+ assert.ok(
+ document.querySelector(selector).classList.contains(cls),
+ `Expected [class] to contain ${cls} on ${selector}`
+ );
+ })
+ .then(['"$selector" doesn\'t have the "$class" class'], function(selector, cls) {
+ assert.ok(
+ !document.querySelector(selector).classList.contains(cls),
+ `Expected [class] not to contain ${cls} on ${selector}`
+ );
+ })
+ // TODO: Make this accept a 'contains' word so you can search for text containing also
+ .then('I have settings like yaml\n$yaml', function(data) {
+ // TODO: Inject this
+ const settings = window.localStorage;
+ // TODO: this and the setup should probably use consul:
+ // as we are talking about 'settings' here not localStorage
+ // so the prefix should be hidden
+ Object.keys(data).forEach(function(prop) {
+ const actual = settings.getItem(prop);
+ const expected = data[prop];
+ assert.strictEqual(actual, expected, `Expected settings to be ${expected} was ${actual}`);
+ });
+ })
+ .then('the url should be $url', function(url) {
+ // TODO: nice! $url should be wrapped in ""
+ if (url === "''") {
+ url = '';
+ }
+ const current = currentURL() || '';
+ assert.equal(current, url, `Expected the url to be ${url} was ${current}`);
+ });
+}
diff --git a/ui-v2/tests/steps/assertions/http.js b/ui-v2/tests/steps/assertions/http.js
new file mode 100644
index 0000000000..b6dc7042f5
--- /dev/null
+++ b/ui-v2/tests/steps/assertions/http.js
@@ -0,0 +1,111 @@
+export default function(scenario, assert, lastNthRequest) {
+ // lastNthRequest should return a
+ // {
+ // method: '',
+ // requestBody: '',
+ // requestHeaders: ''
+ // }
+ const assertRequest = function(request, method, url) {
+ assert.equal(
+ request.method,
+ method,
+ `Expected the request method to be ${method}, was ${request.method}`
+ );
+ assert.equal(request.url, url, `Expected the request url to be ${url}, was ${request.url}`);
+ };
+ scenario
+ .then('a $method request is made to "$url" with the body from yaml\n$yaml', function(
+ method,
+ url,
+ data
+ ) {
+ const request = lastNthRequest(1);
+ assertRequest(request, method, url);
+ const body = JSON.parse(request.requestBody);
+ Object.keys(data).forEach(function(key, i, arr) {
+ assert.deepEqual(
+ body[key],
+ data[key],
+ `Expected the payload to contain ${key} equaling ${data[key]}, ${key} was ${body[key]}`
+ );
+ });
+ })
+ // TODO: This one can replace the above one, it covers more use cases
+ // also DRY it out a bit
+ .then('a $method request is made to "$url" from yaml\n$yaml', function(method, url, yaml) {
+ const request = lastNthRequest(1);
+ assertRequest(request, method, url);
+ let data = yaml.body || {};
+ const body = JSON.parse(request.requestBody);
+ Object.keys(data).forEach(function(key, i, arr) {
+ assert.equal(
+ body[key],
+ data[key],
+ `Expected the payload to contain ${key} to equal ${body[key]}, ${key} was ${data[key]}`
+ );
+ });
+ data = yaml.headers || {};
+ const headers = request.requestHeaders;
+ Object.keys(data).forEach(function(key, i, arr) {
+ assert.equal(
+ headers[key],
+ data[key],
+ `Expected the payload to contain ${key} to equal ${headers[key]}, ${key} was ${data[key]}`
+ );
+ });
+ })
+ .then('a $method request is made to "$url" with the body "$body"', function(method, url, data) {
+ const request = lastNthRequest(1);
+ assertRequest(request, method, url);
+ assert.equal(
+ request.requestBody,
+ data,
+ `Expected the request body to be ${data}, was ${request.requestBody}`
+ );
+ })
+ .then('a $method request is made to "$url" with no body', function(method, url) {
+ const request = lastNthRequest(1);
+ assertRequest(request, method, url);
+ assert.equal(
+ request.requestBody,
+ null,
+ `Expected the request body to be null, was ${request.requestBody}`
+ );
+ })
+
+ .then('a $method request is made to "$url"', function(method, url) {
+ const request = lastNthRequest(1);
+ assertRequest(request, method, url);
+ })
+ .then('the last $method request was made to "$url"', function(method, url) {
+ const request = lastNthRequest(0, method);
+ assertRequest(request, method, url);
+ })
+ .then('the last $method request was made to "$url" with the body from yaml\n$yaml', function(
+ method,
+ url,
+ data
+ ) {
+ const request = lastNthRequest(0, method);
+ const body = JSON.parse(request.requestBody);
+ assert.ok(request, `Expected a ${method} request`);
+ assertRequest(request, method, url);
+ Object.keys(data).forEach(function(key, i, arr) {
+ assert.deepEqual(
+ body[key],
+ data[key],
+ `Expected the payload to contain ${key} equaling ${data[key]}, ${key} was ${body[key]}`
+ );
+ });
+ })
+ .then('the last $method requests were like yaml\n$yaml', function(method, data) {
+ const requests = lastNthRequest(null, method);
+ data.reverse().forEach(function(item, i, arr) {
+ assert.equal(
+ requests[i].url,
+ item,
+ `Expected the request url to be ${item}, was ${requests[i].url}`
+ );
+ });
+ });
+}
diff --git a/ui-v2/tests/steps/assertions/model.js b/ui-v2/tests/steps/assertions/model.js
new file mode 100644
index 0000000000..54b30279b3
--- /dev/null
+++ b/ui-v2/tests/steps/assertions/model.js
@@ -0,0 +1,50 @@
+export default function(scenario, assert, find, currentPage, pauseUntil, pluralize) {
+ scenario
+ .then('pause until I see $number $model model[s]?', function(num, model) {
+ return pauseUntil(function(resolve) {
+ const len = currentPage()[pluralize(model)].filter(function(item) {
+ return item.isVisible;
+ }).length;
+ if (len === num) {
+ assert.equal(len, num, `Expected ${num} ${model}s, saw ${len}`);
+ resolve();
+ }
+ });
+ })
+ .then(['I see $num $model model[s]?'], function(num, model) {
+ const len = currentPage()[pluralize(model)].filter(function(item) {
+ return item.isVisible;
+ }).length;
+
+ assert.equal(len, num, `Expected ${num} ${pluralize(model)}, saw ${len}`);
+ })
+ .then(['I see $num $model model[s]? on the $component component'], function(
+ num,
+ model,
+ component
+ ) {
+ const obj = find(component);
+ const len = obj[pluralize(model)].filter(function(item) {
+ return item.isVisible;
+ }).length;
+
+ assert.equal(len, num, `Expected ${num} ${pluralize(model)}, saw ${len}`);
+ })
+ // TODO: I${ dont } see
+ .then([`I see $num $model model[s]? with the $property "$value"`], function(
+ // negate,
+ num,
+ model,
+ property,
+ value
+ ) {
+ const len = currentPage()[pluralize(model)].filter(function(item) {
+ return item.isVisible && item[property] == value;
+ }).length;
+ assert.equal(
+ len,
+ num,
+ `Expected ${num} ${pluralize(model)} with ${property} set to "${value}", saw ${len}`
+ );
+ });
+}
diff --git a/ui-v2/tests/steps/assertions/page.js b/ui-v2/tests/steps/assertions/page.js
new file mode 100644
index 0000000000..8e93cf85f7
--- /dev/null
+++ b/ui-v2/tests/steps/assertions/page.js
@@ -0,0 +1,115 @@
+/* eslint no-console: "off" */
+export default function(scenario, assert, find, currentPage) {
+ scenario
+ .then('I see $property on the $component like yaml\n$yaml', function(
+ property,
+ component,
+ yaml
+ ) {
+ const _component = currentPage()[component];
+ const iterator = new Array(_component.length).fill(true);
+ // this will catch if we get aren't managing to select a component
+ assert.ok(iterator.length > 0);
+ iterator.forEach(function(item, i, arr) {
+ const actual =
+ typeof _component.objectAt(i)[property] === 'undefined'
+ ? null
+ : _component.objectAt(i)[property];
+
+ // anything coming from the DOM is going to be text/strings
+ // if the yaml has numbers, cast them to strings
+ // TODO: This would get problematic for deeper objects
+ // will have to look to do this recursively
+ const expected = typeof yaml[i] === 'number' ? yaml[i].toString() : yaml[i];
+
+ assert.deepEqual(
+ actual,
+ expected,
+ `Expected to see ${property} on ${component}[${i}] as ${JSON.stringify(
+ expected
+ )}, was ${JSON.stringify(actual)}`
+ );
+ });
+ })
+ .then(['I see $property on the $component'], function(property, component) {
+ // TODO: Time to work on repetition
+ // Collection
+ var obj;
+ if (typeof currentPage()[component].objectAt === 'function') {
+ obj = currentPage()[component].objectAt(0);
+ } else {
+ obj = currentPage()[component];
+ }
+ let _component;
+ if (typeof obj === 'function') {
+ const func = obj[property].bind(obj);
+ try {
+ _component = func();
+ } catch (e) {
+ console.error(e);
+ throw new Error(
+ `The '${property}' property on the '${component}' page object doesn't exist`
+ );
+ }
+ } else {
+ _component = obj;
+ }
+ assert.ok(_component[property], `Expected to see ${property} on ${component}`);
+ })
+ .then(['I see $num of the $component object'], function(num, component) {
+ assert.equal(
+ currentPage()[component].length,
+ num,
+ `Expected to see ${num} items in the ${component} object`
+ );
+ })
+ .then(["I don't see $property on the $component"], function(property, component) {
+ // Collection
+ var obj;
+ if (typeof currentPage()[component].objectAt === 'function') {
+ obj = currentPage()[component].objectAt(0);
+ } else {
+ obj = currentPage()[component];
+ }
+ assert.throws(
+ function() {
+ const func = obj[property].bind(obj);
+ func();
+ },
+ function(e) {
+ return e.message.startsWith('Element not found');
+ },
+ `Expected to not see ${property} on ${component}`
+ );
+ })
+ .then(["I don't see $property"], function(property) {
+ assert.throws(
+ function() {
+ return currentPage()[property]();
+ },
+ function(e) {
+ return e.message.startsWith('Element not found');
+ },
+ `Expected to not see ${property}`
+ );
+ })
+ .then(['I see $property'], function(property) {
+ assert.ok(currentPage()[property], `Expected to see ${property}`);
+ })
+ .then(['I see $property on the $component like "$value"'], function(
+ property,
+ component,
+ value
+ ) {
+ const target = currentPage()[component][property];
+ assert.equal(
+ target,
+ value,
+ `Expected to see ${property} on ${component} as ${value}, was ${target}`
+ );
+ })
+ .then(['I see $property like "$value"'], function(property, value) {
+ const target = currentPage()[property];
+ assert.equal(target, value, `Expected to see ${property} as ${value}, was ${target}`);
+ });
+}
diff --git a/ui-v2/tests/steps/debug/index.js b/ui-v2/tests/steps/debug/index.js
new file mode 100644
index 0000000000..79622ad922
--- /dev/null
+++ b/ui-v2/tests/steps/debug/index.js
@@ -0,0 +1,20 @@
+/* eslint no-console: "off" */
+export default function(scenario, assert, currentURL) {
+ scenario
+ .then('print the current url', function(url) {
+ console.log(currentURL());
+ return Promise.resolve();
+ })
+ .then('log the "$text"', function(text) {
+ console.log(text);
+ return Promise.resolve();
+ })
+ .then('pause for $milliseconds', function(milliseconds) {
+ return new Promise(function(resolve) {
+ setTimeout(resolve, milliseconds);
+ });
+ })
+ .then('ok', function() {
+ assert.ok(true);
+ });
+}
diff --git a/ui-v2/tests/steps/doubles/http.js b/ui-v2/tests/steps/doubles/http.js
new file mode 100644
index 0000000000..67a725b535
--- /dev/null
+++ b/ui-v2/tests/steps/doubles/http.js
@@ -0,0 +1,15 @@
+export default function(scenario, respondWith, set) {
+ // respondWith should set the url to return a certain response shape
+ scenario
+ .given(['the url "$url" responds with a $status status'], function(url, status) {
+ respondWith(url, {
+ status: parseInt(status),
+ });
+ })
+ .given(['the url "$url" responds with from yaml\n$yaml'], function(url, data) {
+ respondWith(url, data);
+ })
+ .given('a network latency of $number', function(number) {
+ set('CONSUL_LATENCY', number);
+ });
+}
diff --git a/ui-v2/tests/steps/doubles/model.js b/ui-v2/tests/steps/doubles/model.js
new file mode 100644
index 0000000000..f02bdbf50a
--- /dev/null
+++ b/ui-v2/tests/steps/doubles/model.js
@@ -0,0 +1,22 @@
+export default function(scenario, create) {
+ scenario
+ .given(['an external edit results in $number $model model[s]?'], function(number, model) {
+ return create(number, model);
+ })
+ .given(['$number $model model[s]?'], function(number, model) {
+ return create(number, model);
+ })
+ .given(['$number $model model[s]? with the value "$value"'], function(number, model, value) {
+ return create(number, model, value);
+ })
+ .given(
+ ['$number $model model[s]? from yaml\n$yaml', '$number $model model[s]? from json\n$json'],
+ function(number, model, data) {
+ return create(number, model, data);
+ }
+ ).given(['settings from yaml\n$yaml'], function(data) {
+ return Object.keys(data).forEach(function(key) {
+ window.localStorage[key] = JSON.stringify(data[key]);
+ });
+ });
+}
diff --git a/ui-v2/tests/steps/interactions/click.js b/ui-v2/tests/steps/interactions/click.js
new file mode 100644
index 0000000000..f1a2877256
--- /dev/null
+++ b/ui-v2/tests/steps/interactions/click.js
@@ -0,0 +1,21 @@
+export default function(scenario, find, click) {
+ scenario
+ .when('I click "$selector"', function(selector) {
+ return click(selector);
+ })
+ // TODO: Probably nicer to think of better vocab than having the 'without " rule'
+ .when(['I click (?!")$property(?!")', 'I click $property on the $component'], function(
+ property,
+ component,
+ next
+ ) {
+ try {
+ if (typeof component === 'string') {
+ property = `${component}.${property}`;
+ }
+ return find(property)();
+ } catch (e) {
+ throw e;
+ }
+ });
+}
diff --git a/ui-v2/tests/steps/interactions/form.js b/ui-v2/tests/steps/interactions/form.js
new file mode 100644
index 0000000000..60d91b8fa3
--- /dev/null
+++ b/ui-v2/tests/steps/interactions/form.js
@@ -0,0 +1,78 @@
+export default function(scenario, find, fillIn, triggerKeyEvent, currentPage) {
+ const fillInElement = function(page, name, value) {
+ const cm = document.querySelector(`textarea[name="${name}"] + .CodeMirror`);
+ if (cm) {
+ cm.CodeMirror.setValue(value);
+ return page;
+ } else {
+ return page.fillIn(name, value);
+ }
+ };
+ scenario
+ .when('I submit', function(selector) {
+ return currentPage().submit();
+ })
+ .then('I fill in "$name" with "$value"', function(name, value) {
+ return currentPage().fillIn(name, value);
+ })
+ .then(['I fill in with yaml\n$yaml', 'I fill in with json\n$json'], function(data) {
+ return Object.keys(data).reduce(function(prev, item, i, arr) {
+ return fillInElement(prev, item, data[item]);
+ }, currentPage());
+ })
+ .then(
+ [
+ 'I fill in the $property form with yaml\n$yaml',
+ 'I fill in $property with yaml\n$yaml',
+ 'I fill in the $property with yaml\n$yaml',
+ 'I fill in the property form with json\n$json',
+
+ 'I fill in the $property form on the $component component with yaml\n$yaml',
+ 'I fill in the $property form on the $component component with json\n$json',
+ 'I fill in the $property on the $component component with yaml\n$yaml',
+ ],
+ function(property, component, data, next) {
+ try {
+ switch (true) {
+ case typeof component === 'string':
+ property = `${component}.${property}`;
+ // fallthrough
+ case typeof data === 'undefined':
+ data = component;
+ // // fallthrough
+ // case typeof property !== 'string':
+ // data = property;
+ }
+ let obj;
+ try {
+ obj = find(property);
+ } catch (e) {
+ obj = currentPage();
+ }
+ return Object.keys(data).reduce(function(prev, item, i, arr) {
+ const name = `${obj.prefix || property}[${item}]`;
+ return fillInElement(prev, name, data[item]);
+ }, obj);
+ } catch (e) {
+ throw e;
+ }
+ }
+ )
+ .then(['I type "$text" into "$selector"'], function(text, selector) {
+ return fillIn(selector, text);
+ })
+ .then(['I type with yaml\n$yaml'], function(data) {
+ const keys = Object.keys(data);
+ return keys
+ .reduce(function(prev, item, i, arr) {
+ return prev.fillIn(item, data[item]);
+ }, currentPage())
+ .then(function() {
+ return Promise.all(
+ keys.map(function(item) {
+ return triggerKeyEvent(`[name="${item}"]`, 'keyup', 83); // TODO: This is 's', be more generic
+ })
+ );
+ });
+ });
+}
diff --git a/ui-v2/tests/steps/interactions/visit.js b/ui-v2/tests/steps/interactions/visit.js
new file mode 100644
index 0000000000..6a2ff76dc1
--- /dev/null
+++ b/ui-v2/tests/steps/interactions/visit.js
@@ -0,0 +1,19 @@
+export default function(scenario, pages, set) {
+ scenario
+ .when('I visit the $name page', function(name) {
+ return set(pages[name]).visit();
+ })
+ .when('I visit the $name page for the "$id" $model', function(name, id, model) {
+ return set(pages[name]).visit({
+ [model]: id,
+ });
+ })
+ .when(
+ ['I visit the $name page for yaml\n$yaml', 'I visit the $name page for json\n$json'],
+ function(name, data) {
+ // TODO: Consider putting an assertion here for testing the current url
+ // do I absolutely definitely need that all the time?
+ return set(pages[name]).visit(data);
+ }
+ );
+}
diff --git a/ui-v2/tests/unit/adapters/kv-test.js b/ui-v2/tests/unit/adapters/kv-test.js
index 82efbe1b37..07d7e8868f 100644
--- a/ui-v2/tests/unit/adapters/kv-test.js
+++ b/ui-v2/tests/unit/adapters/kv-test.js
@@ -85,11 +85,11 @@ module('Unit | Adapter | kv', function(hooks) {
});
});
// not included in the above forEach as it's a slightly different concept
- it('returns string KV object when calling queryRecord (or anything else) record', function() {
+ it('returns string KV object when calling queryRecord (or anything else) record', function(message) {
const actual = adapter.dataForRequest({
requestType: 'queryRecord',
});
- assert.equal(actual, null);
+ assert.deepEqual(actual, deep);
});
});
test('methodForRequest returns the correct method', function(assert) {
diff --git a/ui-v2/tests/unit/adapters/proxy-test.js b/ui-v2/tests/unit/adapters/proxy-test.js
new file mode 100644
index 0000000000..13859457ed
--- /dev/null
+++ b/ui-v2/tests/unit/adapters/proxy-test.js
@@ -0,0 +1,12 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Adapter | proxy', function(hooks) {
+ setupTest(hooks);
+
+ // Replace this with your real tests.
+ test('it exists', function(assert) {
+ let adapter = this.owner.lookup('adapter:proxy');
+ assert.ok(adapter);
+ });
+});
diff --git a/ui-v2/tests/unit/adapters/role-test.js b/ui-v2/tests/unit/adapters/role-test.js
new file mode 100644
index 0000000000..3d847ae204
--- /dev/null
+++ b/ui-v2/tests/unit/adapters/role-test.js
@@ -0,0 +1,12 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Adapter | role', function(hooks) {
+ setupTest(hooks);
+
+ // Replace this with your real tests.
+ test('it exists', function(assert) {
+ let adapter = this.owner.lookup('adapter:role');
+ assert.ok(adapter);
+ });
+});
diff --git a/ui-v2/tests/unit/controllers/dc/acls/create-test.js b/ui-v2/tests/unit/controllers/dc/acls/create-test.js
index 88fab0190b..e8d3df59c7 100644
--- a/ui-v2/tests/unit/controllers/dc/acls/create-test.js
+++ b/ui-v2/tests/unit/controllers/dc/acls/create-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/acls/create', 'Unit | Controller | dc/acls/create', {
// Specify the other units that are required for this test.
- // needs: ['controller:foo']
+ needs: ['service:dom', 'service:form', 'service:repository/role', 'service:repository/policy'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/acls/edit-test.js b/ui-v2/tests/unit/controllers/dc/acls/edit-test.js
index 6a6fb1edcf..1fda00e728 100644
--- a/ui-v2/tests/unit/controllers/dc/acls/edit-test.js
+++ b/ui-v2/tests/unit/controllers/dc/acls/edit-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/acls/edit', 'Unit | Controller | dc/acls/edit', {
// Specify the other units that are required for this test.
- // needs: ['controller:foo']
+ needs: ['service:dom', 'service:form', 'service:repository/role', 'service:repository/policy'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/acls/index-test.js b/ui-v2/tests/unit/controllers/dc/acls/index-test.js
index 3772df42f2..872cd98caa 100644
--- a/ui-v2/tests/unit/controllers/dc/acls/index-test.js
+++ b/ui-v2/tests/unit/controllers/dc/acls/index-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/acls/index', 'Unit | Controller | dc/acls/index', {
// Specify the other units that are required for this test.
- // needs: ['controller:foo']
+ needs: ['service:search', 'service:dom', 'service:repository/role', 'service:repository/policy'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/acls/policies/create-test.js b/ui-v2/tests/unit/controllers/dc/acls/policies/create-test.js
index 12f561f5e9..2e10ac941b 100644
--- a/ui-v2/tests/unit/controllers/dc/acls/policies/create-test.js
+++ b/ui-v2/tests/unit/controllers/dc/acls/policies/create-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/acls/policies/create', 'Unit | Controller | dc/acls/policies/create', {
// Specify the other units that are required for this test.
- needs: ['service:dom', 'service:form'],
+ needs: ['service:form', 'service:dom', 'service:repository/role', 'service:repository/policy'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/acls/policies/edit-test.js b/ui-v2/tests/unit/controllers/dc/acls/policies/edit-test.js
index 1b94ab8a74..b109e75c6d 100644
--- a/ui-v2/tests/unit/controllers/dc/acls/policies/edit-test.js
+++ b/ui-v2/tests/unit/controllers/dc/acls/policies/edit-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/acls/policies/edit', 'Unit | Controller | dc/acls/policies/edit', {
// Specify the other units that are required for this test.
- needs: ['service:dom', 'service:form'],
+ needs: ['service:dom', 'service:form', 'service:repository/role', 'service:repository/policy'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/acls/policies/index-test.js b/ui-v2/tests/unit/controllers/dc/acls/policies/index-test.js
index fc5e3a97b3..3e9dc37537 100644
--- a/ui-v2/tests/unit/controllers/dc/acls/policies/index-test.js
+++ b/ui-v2/tests/unit/controllers/dc/acls/policies/index-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/acls/policies/index', 'Unit | Controller | dc/acls/policies/index', {
// Specify the other units that are required for this test.
- // needs: ['controller:foo']
+ needs: ['service:search', 'service:dom', 'service:repository/role', 'service:repository/policy'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/acls/roles/create-test.js b/ui-v2/tests/unit/controllers/dc/acls/roles/create-test.js
new file mode 100644
index 0000000000..8f5a936868
--- /dev/null
+++ b/ui-v2/tests/unit/controllers/dc/acls/roles/create-test.js
@@ -0,0 +1,12 @@
+import { moduleFor, test } from 'ember-qunit';
+
+moduleFor('controller:dc/acls/roles/create', 'Unit | Controller | dc/acls/roles/create', {
+ // Specify the other units that are required for this test.
+ needs: ['service:dom', 'service:form', 'service:repository/role', 'service:repository/policy'],
+});
+
+// Replace this with your real tests.
+test('it exists', function(assert) {
+ let controller = this.subject();
+ assert.ok(controller);
+});
diff --git a/ui-v2/tests/unit/controllers/dc/acls/roles/edit-test.js b/ui-v2/tests/unit/controllers/dc/acls/roles/edit-test.js
new file mode 100644
index 0000000000..4ad13d69c6
--- /dev/null
+++ b/ui-v2/tests/unit/controllers/dc/acls/roles/edit-test.js
@@ -0,0 +1,12 @@
+import { moduleFor, test } from 'ember-qunit';
+
+moduleFor('controller:dc/acls/roles/edit', 'Unit | Controller | dc/acls/roles/edit', {
+ // Specify the other units that are required for this test.
+ needs: ['service:dom', 'service:form', 'service:repository/role', 'service:repository/policy'],
+});
+
+// Replace this with your real tests.
+test('it exists', function(assert) {
+ let controller = this.subject();
+ assert.ok(controller);
+});
diff --git a/ui-v2/tests/unit/controllers/dc/acls/roles/index-test.js b/ui-v2/tests/unit/controllers/dc/acls/roles/index-test.js
new file mode 100644
index 0000000000..7e41bf468e
--- /dev/null
+++ b/ui-v2/tests/unit/controllers/dc/acls/roles/index-test.js
@@ -0,0 +1,12 @@
+import { moduleFor, test } from 'ember-qunit';
+
+moduleFor('controller:dc/acls/roles/index', 'Unit | Controller | dc/acls/roles/index', {
+ // Specify the other units that are required for this test.
+ needs: ['service:search', 'service:dom', 'service:repository/role', 'service:repository/policy'],
+});
+
+// Replace this with your real tests.
+test('it exists', function(assert) {
+ let controller = this.subject();
+ assert.ok(controller);
+});
diff --git a/ui-v2/tests/unit/controllers/dc/acls/tokens/create-test.js b/ui-v2/tests/unit/controllers/dc/acls/tokens/create-test.js
index ea9cc3ab6c..683bcf7911 100644
--- a/ui-v2/tests/unit/controllers/dc/acls/tokens/create-test.js
+++ b/ui-v2/tests/unit/controllers/dc/acls/tokens/create-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/acls/tokens/create', 'Unit | Controller | dc/acls/tokens/create', {
// Specify the other units that are required for this test.
- needs: ['service:dom', 'service:form'],
+ needs: ['service:dom', 'service:form', 'service:repository/role', 'service:repository/policy'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/acls/tokens/edit-test.js b/ui-v2/tests/unit/controllers/dc/acls/tokens/edit-test.js
index c331988ad4..6a96f62b08 100644
--- a/ui-v2/tests/unit/controllers/dc/acls/tokens/edit-test.js
+++ b/ui-v2/tests/unit/controllers/dc/acls/tokens/edit-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/acls/tokens/edit', 'Unit | Controller | dc/acls/tokens/edit', {
// Specify the other units that are required for this test.
- needs: ['service:dom', 'service:form'],
+ needs: ['service:dom', 'service:form', 'service:repository/role', 'service:repository/policy'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/acls/tokens/index-test.js b/ui-v2/tests/unit/controllers/dc/acls/tokens/index-test.js
index 9eb384d4b1..f2a6d2ab07 100644
--- a/ui-v2/tests/unit/controllers/dc/acls/tokens/index-test.js
+++ b/ui-v2/tests/unit/controllers/dc/acls/tokens/index-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/acls/tokens/index', 'Unit | Controller | dc/acls/tokens/index', {
// Specify the other units that are required for this test.
- // needs: ['controller:foo']
+ needs: ['service:search', 'service:dom', 'service:repository/role', 'service:repository/policy'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/intentions/create-test.js b/ui-v2/tests/unit/controllers/dc/intentions/create-test.js
index 1d9330ebd0..5226e4cf23 100644
--- a/ui-v2/tests/unit/controllers/dc/intentions/create-test.js
+++ b/ui-v2/tests/unit/controllers/dc/intentions/create-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/intentions/create', 'Unit | Controller | dc/intentions/create', {
// Specify the other units that are required for this test.
- // needs: ['controller:foo']
+ needs: ['service:dom', 'service:form', 'service:repository/role', 'service:repository/policy'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/intentions/edit-test.js b/ui-v2/tests/unit/controllers/dc/intentions/edit-test.js
index a442f66951..c7e0da3765 100644
--- a/ui-v2/tests/unit/controllers/dc/intentions/edit-test.js
+++ b/ui-v2/tests/unit/controllers/dc/intentions/edit-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/intentions/edit', 'Unit | Controller | dc/intentions/edit', {
// Specify the other units that are required for this test.
- // needs: ['controller:foo']
+ needs: ['service:dom', 'service:form', 'service:repository/role', 'service:repository/policy'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/intentions/index-test.js b/ui-v2/tests/unit/controllers/dc/intentions/index-test.js
index d17e006b2d..9cfefad559 100644
--- a/ui-v2/tests/unit/controllers/dc/intentions/index-test.js
+++ b/ui-v2/tests/unit/controllers/dc/intentions/index-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/intentions/index', 'Unit | Controller | dc/intentions/index', {
// Specify the other units that are required for this test.
- // needs: ['controller:foo']
+ needs: ['service:search', 'service:dom', 'service:repository/role', 'service:repository/policy'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/kv/create-test.js b/ui-v2/tests/unit/controllers/dc/kv/create-test.js
index 723ab126a0..e7b7c506d2 100644
--- a/ui-v2/tests/unit/controllers/dc/kv/create-test.js
+++ b/ui-v2/tests/unit/controllers/dc/kv/create-test.js
@@ -2,7 +2,13 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/kv/create', 'Unit | Controller | dc/kv/create', {
// Specify the other units that are required for this test.
- needs: ['service:btoa'],
+ needs: [
+ 'service:btoa',
+ 'service:dom',
+ 'service:form',
+ 'service:repository/role',
+ 'service:repository/policy',
+ ],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/kv/edit-test.js b/ui-v2/tests/unit/controllers/dc/kv/edit-test.js
index 02c0b816f0..893fbf518c 100644
--- a/ui-v2/tests/unit/controllers/dc/kv/edit-test.js
+++ b/ui-v2/tests/unit/controllers/dc/kv/edit-test.js
@@ -2,7 +2,13 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/kv/edit', 'Unit | Controller | dc/kv/edit', {
// Specify the other units that are required for this test.
- needs: ['service:btoa'],
+ needs: [
+ 'service:btoa',
+ 'service:dom',
+ 'service:form',
+ 'service:repository/role',
+ 'service:repository/policy',
+ ],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/kv/folder-test.js b/ui-v2/tests/unit/controllers/dc/kv/folder-test.js
index c9edb0e623..34f4ed9a19 100644
--- a/ui-v2/tests/unit/controllers/dc/kv/folder-test.js
+++ b/ui-v2/tests/unit/controllers/dc/kv/folder-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/kv/folder', 'Unit | Controller | dc/kv/folder', {
// Specify the other units that are required for this test.
- // needs: ['controller:foo']
+ needs: ['service:search', 'service:dom'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/kv/index-test.js b/ui-v2/tests/unit/controllers/dc/kv/index-test.js
index af2dfbad18..fb811912b6 100644
--- a/ui-v2/tests/unit/controllers/dc/kv/index-test.js
+++ b/ui-v2/tests/unit/controllers/dc/kv/index-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/kv/index', 'Unit | Controller | dc/kv/index', {
// Specify the other units that are required for this test.
- // needs: ['controller:foo']
+ needs: ['service:search', 'service:dom'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/kv/root-create-test.js b/ui-v2/tests/unit/controllers/dc/kv/root-create-test.js
index 845c1ed475..732c66ef78 100644
--- a/ui-v2/tests/unit/controllers/dc/kv/root-create-test.js
+++ b/ui-v2/tests/unit/controllers/dc/kv/root-create-test.js
@@ -2,7 +2,13 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/kv/root-create', 'Unit | Controller | dc/kv/root-create', {
// Specify the other units that are required for this test.
- needs: ['service:btoa'],
+ needs: [
+ 'service:btoa',
+ 'service:dom',
+ 'service:form',
+ 'service:repository/role',
+ 'service:repository/policy',
+ ],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/nodes/index-test.js b/ui-v2/tests/unit/controllers/dc/nodes/index-test.js
index a24ec182f2..c5a1251af6 100644
--- a/ui-v2/tests/unit/controllers/dc/nodes/index-test.js
+++ b/ui-v2/tests/unit/controllers/dc/nodes/index-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/nodes/index', 'Unit | Controller | dc/nodes/index', {
// Specify the other units that are required for this test.
- // needs: ['controller:foo']
+ needs: ['service:search', 'service:dom'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/nodes/show-test.js b/ui-v2/tests/unit/controllers/dc/nodes/show-test.js
index 12974449f7..d6f93489b6 100644
--- a/ui-v2/tests/unit/controllers/dc/nodes/show-test.js
+++ b/ui-v2/tests/unit/controllers/dc/nodes/show-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/nodes/show', 'Unit | Controller | dc/nodes/show', {
// Specify the other units that are required for this test.
- // needs: ['controller:foo']
+ needs: ['service:search', 'service:dom', 'service:flashMessages'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/services/index-test.js b/ui-v2/tests/unit/controllers/dc/services/index-test.js
index c9e423e1d1..a949d17085 100644
--- a/ui-v2/tests/unit/controllers/dc/services/index-test.js
+++ b/ui-v2/tests/unit/controllers/dc/services/index-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/services/index', 'Unit | Controller | dc/services/index', {
// Specify the other units that are required for this test.
- // needs: ['controller:foo']
+ needs: ['service:search', 'service:dom'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/dc/services/instance-test.js b/ui-v2/tests/unit/controllers/dc/services/instance-test.js
new file mode 100644
index 0000000000..7e4fc2ca75
--- /dev/null
+++ b/ui-v2/tests/unit/controllers/dc/services/instance-test.js
@@ -0,0 +1,12 @@
+import { moduleFor, test } from 'ember-qunit';
+
+moduleFor('controller:dc/services/instance', 'Unit | Controller | dc/services/instance', {
+ // Specify the other units that are required for this test.
+ needs: ['service:dom', 'service:flashMessages'],
+});
+
+// Replace this with your real tests.
+test('it exists', function(assert) {
+ let controller = this.subject();
+ assert.ok(controller);
+});
diff --git a/ui-v2/tests/unit/controllers/dc/services/show-test.js b/ui-v2/tests/unit/controllers/dc/services/show-test.js
index 6e90df4d1c..373d5bec9a 100644
--- a/ui-v2/tests/unit/controllers/dc/services/show-test.js
+++ b/ui-v2/tests/unit/controllers/dc/services/show-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('controller:dc/services/show', 'Unit | Controller | dc/services/show', {
// Specify the other units that are required for this test.
- // needs: ['controller:foo']
+ needs: ['service:search', 'service:dom', 'service:flashMessages'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/controllers/settings-test.js b/ui-v2/tests/unit/controllers/settings-test.js
new file mode 100644
index 0000000000..236dfcdf2d
--- /dev/null
+++ b/ui-v2/tests/unit/controllers/settings-test.js
@@ -0,0 +1,12 @@
+import { moduleFor, test } from 'ember-qunit';
+
+moduleFor('controller:settings', 'Unit | Controller | settings', {
+ // Specify the other units that are required for this test.
+ needs: ['service:settings', 'service:dom'],
+});
+
+// Replace this with your real tests.
+test('it exists', function(assert) {
+ let controller = this.subject();
+ assert.ok(controller);
+});
diff --git a/ui-v2/tests/unit/helpers/policy/is-management-test.js b/ui-v2/tests/unit/helpers/policy/is-management-test.js
deleted file mode 100644
index e77a427650..0000000000
--- a/ui-v2/tests/unit/helpers/policy/is-management-test.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import { isManagement } from 'consul-ui/helpers/policy/is-management';
-import { module, test } from 'qunit';
-
-module('Unit | Helper | policy/is-management');
-
-test('it returns true if the policy is the management policy', function(assert) {
- const actual = isManagement([{ ID: '00000000-0000-0000-0000-000000000001' }]);
- assert.ok(actual);
-});
-test("it returns false if the policy isn't the management policy", function(assert) {
- const actual = isManagement([{ ID: '00000000-0000-0000-0000-000000000000' }]);
- assert.ok(!actual);
-});
diff --git a/ui-v2/tests/unit/helpers/token/is-anonymous-test.js b/ui-v2/tests/unit/helpers/token/is-anonymous-test.js
index b3f077f165..628bfd7bcd 100644
--- a/ui-v2/tests/unit/helpers/token/is-anonymous-test.js
+++ b/ui-v2/tests/unit/helpers/token/is-anonymous-test.js
@@ -9,5 +9,5 @@ test('it returns true if the token is the anonymous token', function(assert) {
});
test("it returns false if the token isn't the anonymous token", function(assert) {
const actual = isAnonymous([{ AccessorID: '00000000-0000-0000-0000-000000000000' }]);
- assert.ok(!actual);
+ assert.notOk(actual);
});
diff --git a/ui-v2/tests/unit/mixins/policy/as-many-test.js b/ui-v2/tests/unit/mixins/policy/as-many-test.js
new file mode 100644
index 0000000000..944edbc5a4
--- /dev/null
+++ b/ui-v2/tests/unit/mixins/policy/as-many-test.js
@@ -0,0 +1,12 @@
+import EmberObject from '@ember/object';
+import PolicyAsManyMixin from 'consul-ui/mixins/policy/as-many';
+import { module, test } from 'qunit';
+
+module('Unit | Mixin | policy/as many');
+
+// Replace this with your real tests.
+test('it works', function(assert) {
+ let PolicyAsManyObject = EmberObject.extend(PolicyAsManyMixin);
+ let subject = PolicyAsManyObject.create();
+ assert.ok(subject);
+});
diff --git a/ui-v2/tests/unit/mixins/role/as-many-test.js b/ui-v2/tests/unit/mixins/role/as-many-test.js
new file mode 100644
index 0000000000..76bcb80c50
--- /dev/null
+++ b/ui-v2/tests/unit/mixins/role/as-many-test.js
@@ -0,0 +1,12 @@
+import EmberObject from '@ember/object';
+import RoleAsManyMixin from 'consul-ui/mixins/role/as-many';
+import { module, test } from 'qunit';
+
+module('Unit | Mixin | role/as many');
+
+// Replace this with your real tests.
+test('it works', function(assert) {
+ let RoleAsManyObject = EmberObject.extend(RoleAsManyMixin);
+ let subject = RoleAsManyObject.create();
+ assert.ok(subject);
+});
diff --git a/ui-v2/tests/unit/mixins/role/with-actions-test.js b/ui-v2/tests/unit/mixins/role/with-actions-test.js
new file mode 100644
index 0000000000..c890bc792e
--- /dev/null
+++ b/ui-v2/tests/unit/mixins/role/with-actions-test.js
@@ -0,0 +1,29 @@
+import { moduleFor } from 'ember-qunit';
+import test from 'ember-sinon-qunit/test-support/test';
+import { getOwner } from '@ember/application';
+import Route from 'consul-ui/routes/dc/acls/roles/index';
+
+import Mixin from 'consul-ui/mixins/role/with-actions';
+
+moduleFor('mixin:policy/with-actions', 'Unit | Mixin | role/with actions', {
+ // Specify the other units that are required for this test.
+ needs: [
+ 'mixin:with-blocking-actions',
+ 'service:feedback',
+ 'service:flashMessages',
+ 'service:logger',
+ 'service:settings',
+ 'service:repository/role',
+ ],
+ subject: function() {
+ const MixedIn = Route.extend(Mixin);
+ this.register('test-container:role/with-actions-object', MixedIn);
+ return getOwner(this).lookup('test-container:role/with-actions-object');
+ },
+});
+
+// Replace this with your real tests.
+test('it works', function(assert) {
+ const subject = this.subject();
+ assert.ok(subject);
+});
diff --git a/ui-v2/tests/unit/mixins/with-listeners-test.js b/ui-v2/tests/unit/mixins/with-listeners-test.js
new file mode 100644
index 0000000000..830437ba44
--- /dev/null
+++ b/ui-v2/tests/unit/mixins/with-listeners-test.js
@@ -0,0 +1,21 @@
+import { moduleFor } from 'ember-qunit';
+import test from 'ember-sinon-qunit/test-support/test';
+import { getOwner } from '@ember/application';
+import Controller from '@ember/controller';
+import Mixin from 'consul-ui/mixins/with-listeners';
+
+moduleFor('mixin:with-listeners', 'Unit | Mixin | with listeners', {
+ // Specify the other units that are required for this test.
+ needs: ['service:dom'],
+ subject: function() {
+ const MixedIn = Controller.extend(Mixin);
+ this.register('test-container:with-listeners-object', MixedIn);
+ return getOwner(this).lookup('test-container:with-listeners-object');
+ },
+});
+
+// Replace this with your real tests.
+test('it works', function(assert) {
+ const subject = this.subject();
+ assert.ok(subject);
+});
diff --git a/ui-v2/tests/unit/mixins/with-searching-test.js b/ui-v2/tests/unit/mixins/with-searching-test.js
new file mode 100644
index 0000000000..e3c550b219
--- /dev/null
+++ b/ui-v2/tests/unit/mixins/with-searching-test.js
@@ -0,0 +1,21 @@
+import { moduleFor } from 'ember-qunit';
+import test from 'ember-sinon-qunit/test-support/test';
+import { getOwner } from '@ember/application';
+import Controller from '@ember/controller';
+import Mixin from 'consul-ui/mixins/with-searching';
+
+moduleFor('mixin:with-searching', 'Unit | Mixin | with searching', {
+ // Specify the other units that are required for this test.
+ needs: ['service:search', 'service:dom'],
+ subject: function() {
+ const MixedIn = Controller.extend(Mixin);
+ this.register('test-container:with-searching-object', MixedIn);
+ return getOwner(this).lookup('test-container:with-searching-object');
+ },
+});
+
+// Replace this with your real tests.
+test('it works', function(assert) {
+ const subject = this.subject();
+ assert.ok(subject);
+});
diff --git a/ui-v2/tests/unit/models/proxy-test.js b/ui-v2/tests/unit/models/proxy-test.js
new file mode 100644
index 0000000000..b37e80f56d
--- /dev/null
+++ b/ui-v2/tests/unit/models/proxy-test.js
@@ -0,0 +1,14 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+import { run } from '@ember/runloop';
+
+module('Unit | Model | proxy', function(hooks) {
+ setupTest(hooks);
+
+ // Replace this with your real tests.
+ test('it exists', function(assert) {
+ let store = this.owner.lookup('service:store');
+ let model = run(() => store.createRecord('proxy', {}));
+ assert.ok(model);
+ });
+});
diff --git a/ui-v2/tests/unit/models/role-test.js b/ui-v2/tests/unit/models/role-test.js
new file mode 100644
index 0000000000..bf2204528c
--- /dev/null
+++ b/ui-v2/tests/unit/models/role-test.js
@@ -0,0 +1,14 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+import { run } from '@ember/runloop';
+
+module('Unit | Model | role', function(hooks) {
+ setupTest(hooks);
+
+ // Replace this with your real tests.
+ test('it exists', function(assert) {
+ let store = this.owner.lookup('service:store');
+ let model = run(() => store.createRecord('role', {}));
+ assert.ok(model);
+ });
+});
diff --git a/ui-v2/tests/unit/routes/application-test.js b/ui-v2/tests/unit/routes/application-test.js
index 5474f3d76c..185737a913 100644
--- a/ui-v2/tests/unit/routes/application-test.js
+++ b/ui-v2/tests/unit/routes/application-test.js
@@ -8,6 +8,7 @@ moduleFor('route:application', 'Unit | Route | application', {
'service:feedback',
'service:flashMessages',
'service:logger',
+ 'service:dom',
],
});
diff --git a/ui-v2/tests/unit/routes/dc/acls/roles/create-test.js b/ui-v2/tests/unit/routes/dc/acls/roles/create-test.js
new file mode 100644
index 0000000000..a456cd449f
--- /dev/null
+++ b/ui-v2/tests/unit/routes/dc/acls/roles/create-test.js
@@ -0,0 +1,20 @@
+import { moduleFor, test } from 'ember-qunit';
+
+moduleFor('route:dc/acls/roles/create', 'Unit | Route | dc/acls/roles/create', {
+ // Specify the other units that are required for this test.
+ needs: [
+ 'service:repository/role',
+ 'service:repository/policy',
+ 'service:repository/token',
+ 'service:repository/dc',
+ 'service:feedback',
+ 'service:logger',
+ 'service:settings',
+ 'service:flashMessages',
+ ],
+});
+
+test('it exists', function(assert) {
+ let route = this.subject();
+ assert.ok(route);
+});
diff --git a/ui-v2/tests/unit/routes/dc/acls/roles/edit-test.js b/ui-v2/tests/unit/routes/dc/acls/roles/edit-test.js
new file mode 100644
index 0000000000..c83d85e77c
--- /dev/null
+++ b/ui-v2/tests/unit/routes/dc/acls/roles/edit-test.js
@@ -0,0 +1,20 @@
+import { moduleFor, test } from 'ember-qunit';
+
+moduleFor('route:dc/acls/roles/edit', 'Unit | Route | dc/acls/roles/edit', {
+ // Specify the other units that are required for this test.
+ needs: [
+ 'service:repository/role',
+ 'service:repository/policy',
+ 'service:repository/token',
+ 'service:repository/dc',
+ 'service:feedback',
+ 'service:logger',
+ 'service:settings',
+ 'service:flashMessages',
+ ],
+});
+
+test('it exists', function(assert) {
+ let route = this.subject();
+ assert.ok(route);
+});
diff --git a/ui-v2/tests/unit/routes/dc/acls/roles/index-test.js b/ui-v2/tests/unit/routes/dc/acls/roles/index-test.js
new file mode 100644
index 0000000000..b230b62b3f
--- /dev/null
+++ b/ui-v2/tests/unit/routes/dc/acls/roles/index-test.js
@@ -0,0 +1,17 @@
+import { moduleFor, test } from 'ember-qunit';
+
+moduleFor('route:dc/acls/roles/index', 'Unit | Route | dc/acls/roles/index', {
+ // Specify the other units that are required for this test.
+ needs: [
+ 'service:repository/role',
+ 'service:feedback',
+ 'service:logger',
+ 'service:settings',
+ 'service:flashMessages',
+ ],
+});
+
+test('it exists', function(assert) {
+ let route = this.subject();
+ assert.ok(route);
+});
diff --git a/ui-v2/tests/unit/routes/dc/acls/tokens/create-test.js b/ui-v2/tests/unit/routes/dc/acls/tokens/create-test.js
index aae140c7de..763ff102ef 100644
--- a/ui-v2/tests/unit/routes/dc/acls/tokens/create-test.js
+++ b/ui-v2/tests/unit/routes/dc/acls/tokens/create-test.js
@@ -5,6 +5,7 @@ moduleFor('route:dc/acls/tokens/create', 'Unit | Route | dc/acls/tokens/create',
needs: [
'service:repository/token',
'service:repository/policy',
+ 'service:repository/role',
'service:repository/dc',
'service:feedback',
'service:logger',
diff --git a/ui-v2/tests/unit/routes/dc/acls/tokens/edit-test.js b/ui-v2/tests/unit/routes/dc/acls/tokens/edit-test.js
index 8d7c8c0c38..892a4db5b4 100644
--- a/ui-v2/tests/unit/routes/dc/acls/tokens/edit-test.js
+++ b/ui-v2/tests/unit/routes/dc/acls/tokens/edit-test.js
@@ -5,6 +5,7 @@ moduleFor('route:dc/acls/tokens/edit', 'Unit | Route | dc/acls/tokens/edit', {
needs: [
'service:repository/token',
'service:repository/policy',
+ 'service:repository/role',
'service:repository/dc',
'service:feedback',
'service:logger',
diff --git a/ui-v2/tests/unit/routes/dc/nodes/show-test.js b/ui-v2/tests/unit/routes/dc/nodes/show-test.js
index e6f486e6bc..4f512ba9aa 100644
--- a/ui-v2/tests/unit/routes/dc/nodes/show-test.js
+++ b/ui-v2/tests/unit/routes/dc/nodes/show-test.js
@@ -4,6 +4,7 @@ moduleFor('route:dc/nodes/show', 'Unit | Route | dc/nodes/show', {
// Specify the other units that are required for this test.
needs: [
'service:repository/node',
+ 'service:repository/coordinate',
'service:repository/session',
'service:feedback',
'service:logger',
diff --git a/ui-v2/tests/unit/routes/dc/services/instance-test.js b/ui-v2/tests/unit/routes/dc/services/instance-test.js
new file mode 100644
index 0000000000..122dc9ee16
--- /dev/null
+++ b/ui-v2/tests/unit/routes/dc/services/instance-test.js
@@ -0,0 +1,11 @@
+import { moduleFor, test } from 'ember-qunit';
+
+moduleFor('route:dc/services/instance', 'Unit | Route | dc/services/instance', {
+ // Specify the other units that are required for this test.
+ needs: ['service:repository/service', 'service:repository/proxy'],
+});
+
+test('it exists', function(assert) {
+ let route = this.subject();
+ assert.ok(route);
+});
diff --git a/ui-v2/tests/unit/routes/dc/services/show-test.js b/ui-v2/tests/unit/routes/dc/services/show-test.js
index 773bf7326d..349c9cace2 100644
--- a/ui-v2/tests/unit/routes/dc/services/show-test.js
+++ b/ui-v2/tests/unit/routes/dc/services/show-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('route:dc/services/show', 'Unit | Route | dc/services/show', {
// Specify the other units that are required for this test.
- needs: ['service:repository/service'],
+ needs: ['service:repository/service', 'service:settings'],
});
test('it exists', function(assert) {
diff --git a/ui-v2/tests/unit/routes/settings-test.js b/ui-v2/tests/unit/routes/settings-test.js
index 52f71ebcec..53583e8088 100644
--- a/ui-v2/tests/unit/routes/settings-test.js
+++ b/ui-v2/tests/unit/routes/settings-test.js
@@ -3,6 +3,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('route:settings', 'Unit | Route | settings', {
// Specify the other units that are required for this test.
needs: [
+ 'service:client/http',
'service:repository/dc',
'service:settings',
'service:logger',
diff --git a/ui-v2/tests/unit/search/filters/acl-test.js b/ui-v2/tests/unit/search/filters/acl-test.js
new file mode 100644
index 0000000000..7531f6e691
--- /dev/null
+++ b/ui-v2/tests/unit/search/filters/acl-test.js
@@ -0,0 +1,36 @@
+import getFilter from 'consul-ui/search/filters/acl';
+import { module, test } from 'qunit';
+
+module('Unit | Search | Filter | acl');
+
+const filter = getFilter(cb => cb);
+test('items are found by properties', function(assert) {
+ [
+ {
+ ID: 'HIT-id',
+ Name: 'name',
+ },
+ {
+ ID: 'id',
+ Name: 'name-HIT',
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.ok(actual);
+ });
+});
+test('items are not found', function(assert) {
+ [
+ {
+ ID: 'id',
+ Name: 'name',
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.notOk(actual);
+ });
+});
diff --git a/ui-v2/tests/unit/search/filters/intention-test.js b/ui-v2/tests/unit/search/filters/intention-test.js
new file mode 100644
index 0000000000..0d0846182a
--- /dev/null
+++ b/ui-v2/tests/unit/search/filters/intention-test.js
@@ -0,0 +1,72 @@
+import getFilter from 'consul-ui/search/filters/intention';
+import { module, test } from 'qunit';
+
+module('Unit | Search | Filter | intention');
+
+const filter = getFilter(cb => cb);
+test('items are found by properties', function(assert) {
+ [
+ {
+ SourceName: 'Hit',
+ DestinationName: 'destination',
+ },
+ {
+ SourceName: 'source',
+ DestinationName: 'hiT',
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.ok(actual);
+ });
+});
+test('items are not found', function(assert) {
+ [
+ {
+ SourceName: 'source',
+ DestinationName: 'destination',
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: '*',
+ });
+ assert.notOk(actual);
+ });
+});
+test('items are found by *', function(assert) {
+ [
+ {
+ SourceName: '*',
+ DestinationName: 'destination',
+ },
+ {
+ SourceName: 'source',
+ DestinationName: '*',
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: '*',
+ });
+ assert.ok(actual);
+ });
+});
+test("* items are found by searching anything in 'All Services (*)'", function(assert) {
+ [
+ {
+ SourceName: '*',
+ DestinationName: 'destination',
+ },
+ {
+ SourceName: 'source',
+ DestinationName: '*',
+ },
+ ].forEach(function(item) {
+ ['All Services (*)', 'SerVices', '(*)', '*', 'vIces', 'lL Ser'].forEach(function(term) {
+ const actual = filter(item, {
+ s: term,
+ });
+ assert.ok(actual);
+ });
+ });
+});
diff --git a/ui-v2/tests/unit/search/filters/kv-test.js b/ui-v2/tests/unit/search/filters/kv-test.js
new file mode 100644
index 0000000000..c6ca5ccd46
--- /dev/null
+++ b/ui-v2/tests/unit/search/filters/kv-test.js
@@ -0,0 +1,36 @@
+import getFilter from 'consul-ui/search/filters/kv';
+import { module, test } from 'qunit';
+
+module('Unit | Search | Filter | kv');
+
+const filter = getFilter(cb => cb);
+test('items are found by properties', function(assert) {
+ [
+ {
+ Key: 'HIT-here',
+ },
+ {
+ Key: 'folder-HIT/',
+ },
+ {
+ Key: 'really/long/path/HIT-here',
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.ok(actual);
+ });
+});
+test('items are not found', function(assert) {
+ [
+ {
+ Key: 'key',
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.notOk(actual);
+ });
+});
diff --git a/ui-v2/tests/unit/search/filters/node-test.js b/ui-v2/tests/unit/search/filters/node-test.js
new file mode 100644
index 0000000000..0e263d6fe9
--- /dev/null
+++ b/ui-v2/tests/unit/search/filters/node-test.js
@@ -0,0 +1,30 @@
+import getFilter from 'consul-ui/search/filters/node';
+import { module, test } from 'qunit';
+
+module('Unit | Search | Filter | node');
+
+const filter = getFilter(cb => cb);
+test('items are found by properties', function(assert) {
+ [
+ {
+ Node: 'node-HIT',
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.ok(actual);
+ });
+});
+test('items are not found', function(assert) {
+ [
+ {
+ Node: 'name',
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.notOk(actual);
+ });
+});
diff --git a/ui-v2/tests/unit/search/filters/node/service-test.js b/ui-v2/tests/unit/search/filters/node/service-test.js
new file mode 100644
index 0000000000..8b14ec21be
--- /dev/null
+++ b/ui-v2/tests/unit/search/filters/node/service-test.js
@@ -0,0 +1,94 @@
+import getFilter from 'consul-ui/search/filters/node/service';
+import { module, test } from 'qunit';
+
+module('Unit | Search | Filter | node/service');
+
+const filter = getFilter(cb => cb);
+test('items are found by properties', function(assert) {
+ [
+ {
+ Service: 'service-HIT',
+ ID: 'id',
+ Port: 8500,
+ Tags: [],
+ },
+ {
+ Service: 'service',
+ ID: 'id-HiT',
+ Port: 8500,
+ Tags: [],
+ },
+ {
+ Service: 'service',
+ ID: 'id',
+ Port: 8500,
+ Tags: ['tag', 'tag-withHiT'],
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.ok(actual);
+ });
+});
+test('items are found by port (non-string)', function(assert) {
+ [
+ {
+ Service: 'service',
+ ID: 'id',
+ Port: 8500,
+ Tags: ['tag', 'tag'],
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: '8500',
+ });
+ assert.ok(actual);
+ });
+});
+test('items are not found', function(assert) {
+ [
+ {
+ Service: 'service',
+ ID: 'id',
+ Port: 8500,
+ },
+ {
+ Service: 'service',
+ ID: 'id',
+ Port: 8500,
+ Tags: ['one', 'two'],
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.notOk(actual);
+ });
+});
+test('tags can be empty', function(assert) {
+ [
+ {
+ Service: 'service',
+ ID: 'id',
+ Port: 8500,
+ },
+ {
+ Service: 'service',
+ ID: 'id',
+ Port: 8500,
+ Tags: null,
+ },
+ {
+ Service: 'service',
+ ID: 'id',
+ Port: 8500,
+ Tags: [],
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.notOk(actual);
+ });
+});
diff --git a/ui-v2/tests/unit/search/filters/policy-test.js b/ui-v2/tests/unit/search/filters/policy-test.js
new file mode 100644
index 0000000000..ee9ba79ecf
--- /dev/null
+++ b/ui-v2/tests/unit/search/filters/policy-test.js
@@ -0,0 +1,36 @@
+import getFilter from 'consul-ui/search/filters/policy';
+import { module, test } from 'qunit';
+
+module('Unit | Search | Filter | policy');
+
+const filter = getFilter(cb => cb);
+test('items are found by properties', function(assert) {
+ [
+ {
+ Name: 'name-HIT',
+ Description: 'description',
+ },
+ {
+ Name: 'name',
+ Description: 'desc-HIT-ription',
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.ok(actual);
+ });
+});
+test('items are not found', function(assert) {
+ [
+ {
+ Name: 'name',
+ Description: 'description',
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.notOk(actual);
+ });
+});
diff --git a/ui-v2/tests/unit/search/filters/service-test.js b/ui-v2/tests/unit/search/filters/service-test.js
new file mode 100644
index 0000000000..1b1c7c58dd
--- /dev/null
+++ b/ui-v2/tests/unit/search/filters/service-test.js
@@ -0,0 +1,59 @@
+import getFilter from 'consul-ui/search/filters/service';
+import { module, test } from 'qunit';
+
+module('Unit | Search | Filter | service');
+
+const filter = getFilter(cb => cb);
+test('items are found by properties', function(assert) {
+ [
+ {
+ Name: 'name-HIT',
+ Tags: [],
+ },
+ {
+ Name: 'name',
+ Tags: ['tag', 'tag-withHiT'],
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.ok(actual);
+ });
+});
+test('items are not found', function(assert) {
+ [
+ {
+ Name: 'name',
+ },
+ {
+ Name: 'name',
+ Tags: ['one', 'two'],
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.notOk(actual);
+ });
+});
+test('tags can be empty', function(assert) {
+ [
+ {
+ Name: 'name',
+ },
+ {
+ Name: 'name',
+ Tags: null,
+ },
+ {
+ Name: 'name',
+ Tags: [],
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.notOk(actual);
+ });
+});
diff --git a/ui-v2/tests/unit/search/filters/service/node-test.js b/ui-v2/tests/unit/search/filters/service/node-test.js
new file mode 100644
index 0000000000..ca6f6ae326
--- /dev/null
+++ b/ui-v2/tests/unit/search/filters/service/node-test.js
@@ -0,0 +1,56 @@
+import getFilter from 'consul-ui/search/filters/service/node';
+import { module, test } from 'qunit';
+
+module('Unit | Search | Filter | service/node');
+
+const filter = getFilter(cb => cb);
+test('items are found by properties', function(assert) {
+ [
+ {
+ Service: {
+ ID: 'hit',
+ },
+ Node: {
+ Node: 'node',
+ },
+ },
+ {
+ Service: {
+ ID: 'id',
+ },
+ Node: {
+ Node: 'nodeHiT',
+ },
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.ok(actual);
+ });
+});
+test('items are not found', function(assert) {
+ [
+ {
+ Service: {
+ ID: 'ID',
+ },
+ Node: {
+ Node: 'node',
+ },
+ },
+ {
+ Service: {
+ ID: 'id',
+ },
+ Node: {
+ Node: 'node',
+ },
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.notOk(actual);
+ });
+});
diff --git a/ui-v2/tests/unit/search/filters/token-test.js b/ui-v2/tests/unit/search/filters/token-test.js
new file mode 100644
index 0000000000..fabe1f9e4d
--- /dev/null
+++ b/ui-v2/tests/unit/search/filters/token-test.js
@@ -0,0 +1,86 @@
+import getFilter from 'consul-ui/search/filters/token';
+import { module, test } from 'qunit';
+
+module('Unit | Search | Filter | token');
+
+const filter = getFilter(cb => cb);
+test('items are found by properties', function(assert) {
+ [
+ {
+ AccessorID: 'HIT-id',
+ Name: 'name',
+ Description: 'description',
+ Policies: [],
+ },
+ {
+ AccessorID: 'id',
+ Name: 'name-HIT',
+ Description: 'description',
+ Policies: [],
+ },
+ {
+ AccessorID: 'id',
+ Name: 'name',
+ Description: 'desc-HIT-ription',
+ Policies: [],
+ },
+ {
+ AccessorID: 'id',
+ Name: 'name',
+ Description: 'description',
+ Policies: [{ Name: 'policy' }, { Name: 'policy-HIT' }],
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.ok(actual);
+ });
+});
+test('items are not found', function(assert) {
+ [
+ {
+ AccessorID: 'id',
+ Name: 'name',
+ Description: 'description',
+ Policies: [],
+ },
+ {
+ AccessorID: 'id',
+ Name: 'name',
+ Description: 'description',
+ Policies: [{ Name: 'policy' }, { Name: 'policy-second' }],
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.notOk(actual);
+ });
+});
+test('policies can be empty', function(assert) {
+ [
+ {
+ AccessorID: 'id',
+ Name: 'name',
+ Description: 'description',
+ },
+ {
+ AccessorID: 'id',
+ Name: 'name',
+ Description: 'description',
+ Policies: null,
+ },
+ {
+ AccessorID: 'id',
+ Name: 'name',
+ Description: 'description',
+ Policies: [],
+ },
+ ].forEach(function(item) {
+ const actual = filter(item, {
+ s: 'hit',
+ });
+ assert.notOk(actual);
+ });
+});
diff --git a/ui-v2/tests/unit/serializers/proxy-test.js b/ui-v2/tests/unit/serializers/proxy-test.js
new file mode 100644
index 0000000000..44090cfe02
--- /dev/null
+++ b/ui-v2/tests/unit/serializers/proxy-test.js
@@ -0,0 +1,24 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+import { run } from '@ember/runloop';
+
+module('Unit | Serializer | proxy', function(hooks) {
+ setupTest(hooks);
+
+ // Replace this with your real tests.
+ test('it exists', function(assert) {
+ let store = this.owner.lookup('service:store');
+ let serializer = store.serializerFor('proxy');
+
+ assert.ok(serializer);
+ });
+
+ test('it serializes records', function(assert) {
+ let store = this.owner.lookup('service:store');
+ let record = run(() => store.createRecord('proxy', {}));
+
+ let serializedRecord = record.serialize();
+
+ assert.ok(serializedRecord);
+ });
+});
diff --git a/ui-v2/tests/unit/serializers/role-test.js b/ui-v2/tests/unit/serializers/role-test.js
new file mode 100644
index 0000000000..69dc162d9e
--- /dev/null
+++ b/ui-v2/tests/unit/serializers/role-test.js
@@ -0,0 +1,24 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+import { run } from '@ember/runloop';
+
+module('Unit | Serializer | role', function(hooks) {
+ setupTest(hooks);
+
+ // Replace this with your real tests.
+ test('it exists', function(assert) {
+ let store = this.owner.lookup('service:store');
+ let serializer = store.serializerFor('role');
+
+ assert.ok(serializer);
+ });
+
+ test('it serializes records', function(assert) {
+ let store = this.owner.lookup('service:store');
+ let record = run(() => store.createRecord('role', {}));
+
+ let serializedRecord = record.serialize();
+
+ assert.ok(serializedRecord);
+ });
+});
diff --git a/ui-v2/tests/unit/services/client/http-test.js b/ui-v2/tests/unit/services/client/http-test.js
new file mode 100644
index 0000000000..98ff23ff4d
--- /dev/null
+++ b/ui-v2/tests/unit/services/client/http-test.js
@@ -0,0 +1,12 @@
+import { moduleFor, test } from 'ember-qunit';
+
+moduleFor('service:client/http', 'Unit | Service | client/http', {
+ // Specify the other units that are required for this test.
+ needs: ['service:dom'],
+});
+
+// Replace this with your real tests.
+test('it exists', function(assert) {
+ let service = this.subject();
+ assert.ok(service);
+});
diff --git a/ui-v2/tests/unit/services/code-mirror/linter-test.js b/ui-v2/tests/unit/services/code-mirror/linter-test.js
new file mode 100644
index 0000000000..6283eb92e2
--- /dev/null
+++ b/ui-v2/tests/unit/services/code-mirror/linter-test.js
@@ -0,0 +1,12 @@
+import { moduleFor, test } from 'ember-qunit';
+
+moduleFor('service:code-mirror/linter', 'Unit | Service | code mirror/linter', {
+ // Specify the other units that are required for this test.
+ needs: ['service:dom'],
+});
+
+// Replace this with your real tests.
+test('it exists', function(assert) {
+ let service = this.subject();
+ assert.ok(service);
+});
diff --git a/ui-v2/tests/unit/services/form-test.js b/ui-v2/tests/unit/services/form-test.js
index d256f3e452..2e65374337 100644
--- a/ui-v2/tests/unit/services/form-test.js
+++ b/ui-v2/tests/unit/services/form-test.js
@@ -2,7 +2,7 @@ import { moduleFor, test } from 'ember-qunit';
moduleFor('service:form', 'Unit | Service | form', {
// Specify the other units that are required for this test.
- // needs: ['service:foo']
+ needs: ['service:repository/role', 'service:repository/policy'],
});
// Replace this with your real tests.
diff --git a/ui-v2/tests/unit/services/repository/role-test.js b/ui-v2/tests/unit/services/repository/role-test.js
new file mode 100644
index 0000000000..99168bdf16
--- /dev/null
+++ b/ui-v2/tests/unit/services/repository/role-test.js
@@ -0,0 +1,12 @@
+import { moduleFor, test } from 'ember-qunit';
+
+moduleFor('service:repository/role', 'Unit | Service | repository/role', {
+ // Specify the other units that are required for this test.
+ // needs: ['service:foo']
+});
+
+// Replace this with your real tests.
+test('it exists', function(assert) {
+ let service = this.subject();
+ assert.ok(service);
+});
diff --git a/ui-v2/tests/unit/services/search-test.js b/ui-v2/tests/unit/services/search-test.js
new file mode 100644
index 0000000000..d499f7a472
--- /dev/null
+++ b/ui-v2/tests/unit/services/search-test.js
@@ -0,0 +1,12 @@
+import { moduleFor, test } from 'ember-qunit';
+
+moduleFor('service:search', 'Unit | Service | search', {
+ // Specify the other units that are required for this test.
+ // needs: ['service:foo']
+});
+
+// Replace this with your real tests.
+test('it exists', function(assert) {
+ let service = this.subject();
+ assert.ok(service);
+});
diff --git a/ui-v2/tests/unit/utils/acls-status-test.js b/ui-v2/tests/unit/utils/acls-status-test.js
index 19766d2a44..4504dfac8b 100644
--- a/ui-v2/tests/unit/utils/acls-status-test.js
+++ b/ui-v2/tests/unit/utils/acls-status-test.js
@@ -1,10 +1,89 @@
+import { module } from 'ember-qunit';
+import test from 'ember-sinon-qunit/test-support/test';
import aclsStatus from 'consul-ui/utils/acls-status';
-import { module, test } from 'qunit';
module('Unit | Utility | acls status');
-// Replace this with your real tests.
-test('it works', function(assert) {
- let result = aclsStatus();
- assert.ok(result);
+test('it rejects and nothing is enabled or authorized', function(assert) {
+ const isValidServerError = this.stub().returns(false);
+ const status = aclsStatus(isValidServerError);
+ [
+ this.stub().rejects(),
+ this.stub().rejects({ errors: [] }),
+ this.stub().rejects({ errors: [{ status: '404' }] }),
+ ].forEach(function(reject) {
+ const actual = status({
+ response: reject(),
+ });
+ assert.rejects(actual.response);
+ ['isAuthorized', 'isEnabled'].forEach(function(prop) {
+ actual[prop].then(function(actual) {
+ assert.notOk(actual);
+ });
+ });
+ });
+});
+test('with a 401 it resolves with an empty array and nothing is enabled or authorized', function(assert) {
+ assert.expect(3);
+ const isValidServerError = this.stub().returns(false);
+ const status = aclsStatus(isValidServerError);
+ const actual = status({
+ response: this.stub().rejects({ errors: [{ status: '401' }] })(),
+ });
+ actual.response.then(function(actual) {
+ assert.deepEqual(actual, []);
+ });
+ ['isAuthorized', 'isEnabled'].forEach(function(prop) {
+ actual[prop].then(function(actual) {
+ assert.notOk(actual);
+ });
+ });
+});
+test("with a 403 it resolves with an empty array and it's enabled but not authorized", function(assert) {
+ assert.expect(3);
+ const isValidServerError = this.stub().returns(false);
+ const status = aclsStatus(isValidServerError);
+ const actual = status({
+ response: this.stub().rejects({ errors: [{ status: '403' }] })(),
+ });
+ actual.response.then(function(actual) {
+ assert.deepEqual(actual, []);
+ });
+ actual.isEnabled.then(function(actual) {
+ assert.ok(actual);
+ });
+ actual.isAuthorized.then(function(actual) {
+ assert.notOk(actual);
+ });
+});
+test("with a 500 (but not a 'valid' error) it rejects and nothing is enabled or authorized", function(assert) {
+ assert.expect(3);
+ const isValidServerError = this.stub().returns(false);
+ const status = aclsStatus(isValidServerError);
+ const actual = status({
+ response: this.stub().rejects({ errors: [{ status: '500' }] })(),
+ });
+ assert.rejects(actual.response);
+ ['isAuthorized', 'isEnabled'].forEach(function(prop) {
+ actual[prop].then(function(actual) {
+ assert.notOk(actual);
+ });
+ });
+});
+test("with a 500 and a 'valid' error, it resolves with an empty array and it's enabled but not authorized", function(assert) {
+ assert.expect(3);
+ const isValidServerError = this.stub().returns(true);
+ const status = aclsStatus(isValidServerError);
+ const actual = status({
+ response: this.stub().rejects({ errors: [{ status: '500' }] })(),
+ });
+ actual.response.then(function(actual) {
+ assert.deepEqual(actual, []);
+ });
+ actual.isEnabled.then(function(actual) {
+ assert.ok(actual);
+ });
+ actual.isAuthorized.then(function(actual) {
+ assert.notOk(actual);
+ });
});
diff --git a/ui-v2/tests/unit/utils/dom/create-listeners-test.js b/ui-v2/tests/unit/utils/dom/create-listeners-test.js
new file mode 100644
index 0000000000..ae735467ba
--- /dev/null
+++ b/ui-v2/tests/unit/utils/dom/create-listeners-test.js
@@ -0,0 +1,79 @@
+import createListeners from 'consul-ui/utils/dom/create-listeners';
+import { module } from 'ember-qunit';
+import test from 'ember-sinon-qunit/test-support/test';
+
+module('Unit | Utility | dom/create listeners');
+
+test('it has add and remove methods', function(assert) {
+ const listeners = createListeners();
+ assert.ok(typeof listeners.add === 'function');
+ assert.ok(typeof listeners.remove === 'function');
+});
+test('add returns an remove function', function(assert) {
+ const listeners = createListeners();
+ const remove = listeners.add({
+ addEventListener: function() {},
+ });
+ assert.ok(typeof remove === 'function');
+});
+test('remove returns an array of removed handlers (the return of a saved remove)', function(assert) {
+ // just use true here to prove that it's what gets returned
+ const expected = true;
+ const handlers = [
+ function() {
+ return expected;
+ },
+ ];
+ const listeners = createListeners(handlers);
+ const actual = listeners.remove();
+ assert.deepEqual(actual, [expected]);
+ // handlers should now be empty
+ assert.equal(handlers.length, 0);
+});
+test('remove calls the remove functions', function(assert) {
+ const expected = this.stub();
+ const arr = [expected];
+ const listeners = createListeners(arr);
+ listeners.remove();
+ assert.ok(expected.calledOnce);
+ assert.equal(arr.length, 0);
+});
+test('listeners are added on add', function(assert) {
+ const listeners = createListeners();
+ const stub = this.stub();
+ const target = {
+ addEventListener: stub,
+ };
+ const name = 'test';
+ const handler = function(e) {};
+ listeners.add(target, name, handler);
+ assert.ok(stub.calledOnce);
+ assert.ok(stub.calledWith(name, handler));
+});
+test('listeners are removed on remove', function(assert) {
+ const listeners = createListeners();
+ const stub = this.stub();
+ const target = {
+ addEventListener: function() {},
+ removeEventListener: stub,
+ };
+ const name = 'test';
+ const handler = function(e) {};
+ const remove = listeners.add(target, name, handler);
+ remove();
+ assert.ok(stub.calledOnce);
+ assert.ok(stub.calledWith(name, handler));
+});
+test('remove returns the original handler', function(assert) {
+ const listeners = createListeners();
+ const target = {
+ addEventListener: function() {},
+ removeEventListener: function() {},
+ };
+ const name = 'test';
+ const expected = this.stub();
+ const remove = listeners.add(target, name, expected);
+ const actual = remove();
+ actual();
+ assert.ok(expected.calledOnce);
+});
diff --git a/ui-v2/tests/unit/utils/dom/event-source/blocking-test.js b/ui-v2/tests/unit/utils/dom/event-source/blocking-test.js
new file mode 100644
index 0000000000..ee506165b5
--- /dev/null
+++ b/ui-v2/tests/unit/utils/dom/event-source/blocking-test.js
@@ -0,0 +1,148 @@
+import domEventSourceBlocking, {
+ validateCursor,
+ create5xxBackoff,
+} from 'consul-ui/utils/dom/event-source/blocking';
+import { module } from 'qunit';
+import test from 'ember-sinon-qunit/test-support/test';
+
+module('Unit | Utility | dom/event-source/blocking');
+
+const createEventSource = function() {
+ return class {
+ constructor(cb) {
+ this.readyState = 1;
+ this.source = cb;
+ this.source.apply(this, arguments);
+ }
+ addEventListener() {}
+ removeEventListener() {}
+ dispatchEvent() {}
+ close() {}
+ };
+};
+const createPromise = function(resolve = function() {}) {
+ class PromiseMock {
+ constructor(cb = function() {}) {
+ cb(resolve);
+ }
+ then(cb) {
+ setTimeout(() => cb.bind(this)(), 0);
+ return this;
+ }
+ catch(cb) {
+ cb({ message: 'error' });
+ return this;
+ }
+ }
+ PromiseMock.resolve = function() {
+ return new PromiseMock();
+ };
+ return PromiseMock;
+};
+test('it creates an BlockingEventSource class implementing EventSource', function(assert) {
+ const EventSource = createEventSource();
+ const BlockingEventSource = domEventSourceBlocking(EventSource, function() {});
+ assert.ok(BlockingEventSource instanceof Function);
+ const source = new BlockingEventSource(function() {
+ return createPromise().resolve();
+ });
+ assert.ok(source instanceof EventSource);
+});
+test("the 5xx backoff continues to throw when it's not a 5xx", function(assert) {
+ const backoff = create5xxBackoff();
+ [
+ undefined,
+ null,
+ new Error(),
+ { errors: [] },
+ { errors: [{ status: '0' }] },
+ { errors: [{ status: 501 }] },
+ { errors: [{ status: '401' }] },
+ { errors: [{ status: '500' }] },
+ { errors: [{ status: '5' }] },
+ { errors: [{ status: '50' }] },
+ { errors: [{ status: '5000' }] },
+ { errors: [{ status: '5050' }] },
+ ].forEach(function(item) {
+ assert.throws(function() {
+ backoff(item);
+ });
+ });
+});
+test('the 5xx backoff returns a resolve promise on a 5xx (apart from 500)', function(assert) {
+ [
+ { errors: [{ status: '501' }] },
+ { errors: [{ status: '503' }] },
+ { errors: [{ status: '504' }] },
+ { errors: [{ status: '524' }] },
+ ].forEach(item => {
+ const timeout = this.stub().callsArg(0);
+ const resolve = this.stub().withArgs(item);
+ const Promise = createPromise(resolve);
+ const backoff = create5xxBackoff(undefined, Promise, timeout);
+ const promise = backoff(item);
+ assert.ok(promise instanceof Promise, 'a promise was returned');
+ assert.ok(resolve.calledOnce, 'the promise was resolved with the correct arguments');
+ assert.ok(timeout.calledOnce, 'timeout was called once');
+ });
+});
+test("the cursor validation always returns undefined if the cursor can't be parsed to an integer", function(assert) {
+ ['null', null, '', undefined].forEach(item => {
+ const actual = validateCursor(item);
+ assert.equal(actual, undefined);
+ });
+});
+test('the cursor validation always returns a cursor greater than zero', function(assert) {
+ [
+ {
+ cursor: 0,
+ expected: 1,
+ },
+ {
+ cursor: -10,
+ expected: 1,
+ },
+ {
+ cursor: -1,
+ expected: 1,
+ },
+ {
+ cursor: -1000,
+ expected: 1,
+ },
+ {
+ cursor: 10,
+ expected: 10,
+ },
+ ].forEach(item => {
+ const actual = validateCursor(item.cursor);
+ assert.equal(actual, item.expected, 'cursor is greater than zero');
+ });
+});
+test('the cursor validation resets to 1 if its less than the previous cursor', function(assert) {
+ [
+ {
+ previous: 100,
+ cursor: 99,
+ expected: 1,
+ },
+ {
+ previous: 100,
+ cursor: -10,
+ expected: 1,
+ },
+ {
+ previous: 100,
+ cursor: 0,
+ expected: 1,
+ },
+ {
+ previous: 100,
+ cursor: 101,
+ expected: 101,
+ },
+ ].forEach(item => {
+ const actual = validateCursor(item.cursor, item.previous);
+ assert.equal(actual, item.expected);
+ });
+});
diff --git a/ui-v2/tests/unit/utils/dom/event-source/cache-test.js b/ui-v2/tests/unit/utils/dom/event-source/cache-test.js
new file mode 100644
index 0000000000..1d9a4df49a
--- /dev/null
+++ b/ui-v2/tests/unit/utils/dom/event-source/cache-test.js
@@ -0,0 +1,145 @@
+import domEventSourceCache from 'consul-ui/utils/dom/event-source/cache';
+import { module } from 'qunit';
+import test from 'ember-sinon-qunit/test-support/test';
+
+module('Unit | Utility | dom/event-source/cache');
+
+const createEventSource = function() {
+ return class {
+ constructor(cb) {
+ this.source = cb;
+ this.source.apply(this, arguments);
+ }
+ addEventListener() {}
+ removeEventListener() {}
+ dispatchEvent() {}
+ close() {}
+ };
+};
+const createPromise = function(
+ resolve = result => result,
+ reject = (result = { message: 'error' }) => result
+) {
+ class PromiseMock {
+ constructor(cb = function() {}) {
+ cb(resolve);
+ }
+ then(cb) {
+ setTimeout(() => cb.bind(this)(resolve()), 0);
+ return this;
+ }
+ catch(cb) {
+ setTimeout(() => cb.bind(this)(reject()), 0);
+ return this;
+ }
+ }
+ PromiseMock.resolve = function(result) {
+ return new PromiseMock(function(resolve) {
+ resolve(result);
+ });
+ };
+ PromiseMock.reject = function() {
+ return new PromiseMock();
+ };
+ return PromiseMock;
+};
+test('it returns a function', function(assert) {
+ const EventSource = createEventSource();
+ const Promise = createPromise();
+
+ const getCache = domEventSourceCache(function() {}, EventSource, Promise);
+ assert.ok(typeof getCache === 'function');
+});
+test('getCache returns a function', function(assert) {
+ const EventSource = createEventSource();
+ const Promise = createPromise();
+
+ const getCache = domEventSourceCache(function() {}, EventSource, Promise);
+ const obj = {};
+ const cache = getCache(obj);
+ assert.ok(typeof cache === 'function');
+});
+test('cache creates the default EventSource and keeps it open when there is a cursor', function(assert) {
+ const EventSource = createEventSource();
+ const stub = {
+ configuration: { cursor: 1 },
+ };
+ const Promise = createPromise(function() {
+ return stub;
+ });
+ const source = this.stub().returns(Promise.resolve());
+ const cb = this.stub();
+ const getCache = domEventSourceCache(source, EventSource, Promise);
+ const obj = {};
+ const cache = getCache(obj);
+ const promisedEventSource = cache(cb, {
+ key: 'key',
+ settings: {
+ enabled: true,
+ },
+ });
+ assert.ok(source.calledOnce, 'promisifying source called once');
+ assert.ok(promisedEventSource instanceof Promise, 'source returns a Promise');
+ const retrievedEventSource = cache(cb, {
+ key: 'key',
+ settings: {
+ enabled: true,
+ },
+ });
+ assert.deepEqual(promisedEventSource, retrievedEventSource);
+ assert.ok(source.calledTwice, 'promisifying source called once');
+ assert.ok(retrievedEventSource instanceof Promise, 'source returns a Promise');
+});
+test('cache creates the default EventSource and keeps it open when there is a cursor', function(assert) {
+ const EventSource = createEventSource();
+ const stub = {
+ close: this.stub(),
+ configuration: { cursor: 1 },
+ };
+ const Promise = createPromise(function() {
+ return stub;
+ });
+ const source = this.stub().returns(Promise.resolve());
+ const cb = this.stub();
+ const getCache = domEventSourceCache(source, EventSource, Promise);
+ const obj = {};
+ const cache = getCache(obj);
+ const promisedEventSource = cache(cb, {
+ key: 0,
+ settings: {
+ enabled: true,
+ },
+ });
+ assert.ok(source.calledOnce, 'promisifying source called once');
+ assert.ok(cb.calledOnce, 'callable event source callable called once');
+ assert.ok(promisedEventSource instanceof Promise, 'source returns a Promise');
+ // >>
+ return promisedEventSource.then(function() {
+ assert.notOk(stub.close.called, "close wasn't called");
+ });
+});
+test("cache creates the default EventSource and closes it when there isn't a cursor", function(assert) {
+ const EventSource = createEventSource();
+ const stub = {
+ close: this.stub(),
+ configuration: {},
+ };
+ const Promise = createPromise(function() {
+ return stub;
+ });
+ const source = this.stub().returns(Promise.resolve());
+ const cb = this.stub();
+ const getCache = domEventSourceCache(source, EventSource, Promise);
+ const obj = {};
+ const cache = getCache(obj);
+ const promisedEventSource = cache(cb, {
+ key: 0,
+ });
+ assert.ok(source.calledOnce, 'promisifying source called once');
+ assert.ok(cb.calledOnce, 'callable event source callable called once');
+ assert.ok(promisedEventSource instanceof Promise, 'source returns a Promise');
+ // >>
+ return promisedEventSource.then(function() {
+ assert.ok(stub.close.calledOnce, 'close was called');
+ });
+});
diff --git a/ui-v2/tests/unit/utils/dom/event-source/callable-test.js b/ui-v2/tests/unit/utils/dom/event-source/callable-test.js
new file mode 100644
index 0000000000..fe5c7ba708
--- /dev/null
+++ b/ui-v2/tests/unit/utils/dom/event-source/callable-test.js
@@ -0,0 +1,67 @@
+import domEventSourceCallable, { defaultRunner } from 'consul-ui/utils/dom/event-source/callable';
+import { module } from 'qunit';
+import test from 'ember-sinon-qunit/test-support/test';
+
+module('Unit | Utility | dom/event-source/callable');
+
+const createEventTarget = function() {
+ return class {
+ addEventListener() {}
+ removeEventListener() {}
+ dispatchEvent() {}
+ };
+};
+const createPromise = function() {
+ class PromiseMock {
+ then(cb) {
+ cb();
+ return this;
+ }
+ catch(cb) {
+ cb({ message: 'error' });
+ return this;
+ }
+ }
+ PromiseMock.resolve = function() {
+ return new PromiseMock();
+ };
+ return PromiseMock;
+};
+test('it creates an EventSource class implementing EventTarget', function(assert) {
+ const EventTarget = createEventTarget();
+ const EventSource = domEventSourceCallable(EventTarget, createPromise());
+ assert.ok(EventSource instanceof Function);
+ const source = new EventSource();
+ assert.ok(source instanceof EventTarget);
+});
+test('the default runner loops and can be closed', function(assert) {
+ assert.expect(13); // 10 not closed, 1 to close, the final call count, plus the close event
+ let count = 0;
+ const isClosed = function() {
+ count++;
+ assert.ok(true);
+ return count === 11;
+ };
+ const configuration = {};
+ const then = this.stub().callsArg(0);
+ const target = {
+ source: function(configuration) {
+ return {
+ then: then,
+ };
+ },
+ dispatchEvent: this.stub(),
+ };
+ defaultRunner(target, configuration, isClosed);
+ assert.ok(then.callCount == 10);
+ assert.ok(target.dispatchEvent.calledOnce);
+});
+test('it calls the defaultRunner', function(assert) {
+ const Promise = createPromise();
+ const EventTarget = createEventTarget();
+ const run = this.stub();
+ const EventSource = domEventSourceCallable(EventTarget, Promise, run);
+ const source = new EventSource();
+ assert.ok(run.calledOnce);
+ assert.equal(source.readyState, 2);
+});
diff --git a/ui-v2/tests/unit/utils/dom/event-source/index-test.js b/ui-v2/tests/unit/utils/dom/event-source/index-test.js
new file mode 100644
index 0000000000..0e99418ebe
--- /dev/null
+++ b/ui-v2/tests/unit/utils/dom/event-source/index-test.js
@@ -0,0 +1,28 @@
+import {
+ source,
+ proxy,
+ cache,
+ resolve,
+ CallableEventSource,
+ ReopenableEventSource,
+ BlockingEventSource,
+ StorageEventSource,
+} from 'consul-ui/utils/dom/event-source/index';
+import { module, test } from 'qunit';
+
+module('Unit | Utility | dom/event source/index');
+
+// Replace this with your real tests.
+test('it works', function(assert) {
+ // All The EventSource
+ assert.ok(typeof CallableEventSource === 'function');
+ assert.ok(typeof ReopenableEventSource === 'function');
+ assert.ok(typeof BlockingEventSource === 'function');
+ assert.ok(typeof StorageEventSource === 'function');
+
+ // Utils
+ assert.ok(typeof source === 'function');
+ assert.ok(typeof proxy === 'function');
+ assert.ok(typeof cache === 'function');
+ assert.ok(typeof resolve === 'function');
+});
diff --git a/ui-v2/tests/unit/utils/dom/event-source/proxy-test.js b/ui-v2/tests/unit/utils/dom/event-source/proxy-test.js
new file mode 100644
index 0000000000..75f136efaa
--- /dev/null
+++ b/ui-v2/tests/unit/utils/dom/event-source/proxy-test.js
@@ -0,0 +1,10 @@
+import domEventSourceProxy from 'consul-ui/utils/dom/event-source/proxy';
+import { module, test } from 'qunit';
+
+module('Unit | Utility | dom/event source/proxy');
+
+// Replace this with your real tests.
+test('it works', function(assert) {
+ let result = domEventSourceProxy();
+ assert.ok(result);
+});
diff --git a/ui-v2/tests/unit/utils/dom/event-source/reopenable-test.js b/ui-v2/tests/unit/utils/dom/event-source/reopenable-test.js
new file mode 100644
index 0000000000..4936690140
--- /dev/null
+++ b/ui-v2/tests/unit/utils/dom/event-source/reopenable-test.js
@@ -0,0 +1,46 @@
+import domEventSourceReopenable from 'consul-ui/utils/dom/event-source/reopenable';
+import { module } from 'qunit';
+import test from 'ember-sinon-qunit/test-support/test';
+
+module('Unit | Utility | dom/event-source/reopenable');
+
+const createEventSource = function() {
+ return class {
+ constructor(cb) {
+ this.readyState = 1;
+ this.source = cb;
+ this.source.apply(this, arguments);
+ }
+ addEventListener() {}
+ removeEventListener() {}
+ dispatchEvent() {}
+ close() {}
+ };
+};
+test('it creates an Reopenable class implementing EventSource', function(assert) {
+ const EventSource = createEventSource();
+ const ReopenableEventSource = domEventSourceReopenable(EventSource);
+ assert.ok(ReopenableEventSource instanceof Function);
+ const source = new ReopenableEventSource(function() {});
+ assert.ok(source instanceof EventSource);
+});
+test('it reopens the event source when reopen is called', function(assert) {
+ const callable = this.stub();
+ const EventSource = createEventSource();
+ const ReopenableEventSource = domEventSourceReopenable(EventSource);
+ const source = new ReopenableEventSource(callable);
+ assert.equal(source.readyState, 1);
+ // first automatic EventSource `open`
+ assert.ok(callable.calledOnce);
+ source.readyState = 3;
+ source.reopen();
+ // still only called once as it hasn't completely closed yet
+ // therefore is just opened by resetting the readyState
+ assert.ok(callable.calledOnce);
+ assert.equal(source.readyState, 1);
+ // properly close the source
+ source.readyState = 2;
+ source.reopen();
+ // this time it is reopened via a recall of the callable
+ assert.ok(callable.calledTwice);
+});
diff --git a/ui-v2/tests/unit/utils/dom/event-source/resolver-test.js b/ui-v2/tests/unit/utils/dom/event-source/resolver-test.js
new file mode 100644
index 0000000000..e123dbed9f
--- /dev/null
+++ b/ui-v2/tests/unit/utils/dom/event-source/resolver-test.js
@@ -0,0 +1,10 @@
+import domEventSourceResolver from 'consul-ui/utils/dom/event-source/resolver';
+import { module, test } from 'qunit';
+
+module('Unit | Utility | dom/event source/resolver');
+
+// Replace this with your real tests.
+test('it works', function(assert) {
+ let result = domEventSourceResolver();
+ assert.ok(result);
+});
diff --git a/ui-v2/tests/unit/utils/dom/event-source/storage-test.js b/ui-v2/tests/unit/utils/dom/event-source/storage-test.js
new file mode 100644
index 0000000000..ccd83872a7
--- /dev/null
+++ b/ui-v2/tests/unit/utils/dom/event-source/storage-test.js
@@ -0,0 +1,10 @@
+import domEventSourceStorage from 'consul-ui/utils/dom/event-source/storage';
+import { module, test } from 'qunit';
+
+module('Unit | Utility | dom/event source/storage');
+
+// Replace this with your real tests.
+test('it works', function(assert) {
+ let result = domEventSourceStorage(function EventTarget() {});
+ assert.ok(result);
+});
diff --git a/ui-v2/tests/unit/utils/dom/event-target/rsvp-test.js b/ui-v2/tests/unit/utils/dom/event-target/rsvp-test.js
new file mode 100644
index 0000000000..7cea41640e
--- /dev/null
+++ b/ui-v2/tests/unit/utils/dom/event-target/rsvp-test.js
@@ -0,0 +1,13 @@
+import domEventTargetRsvp from 'consul-ui/utils/dom/event-target/rsvp';
+import { module, test } from 'qunit';
+
+module('Unit | Utility | dom/event-target/rsvp');
+
+// Replace this with your real tests.
+test('it has EventTarget methods', function(assert) {
+ const result = domEventTargetRsvp;
+ assert.equal(typeof result, 'function');
+ ['addEventListener', 'removeEventListener', 'dispatchEvent'].forEach(function(item) {
+ assert.equal(typeof result.prototype[item], 'function');
+ });
+});
diff --git a/ui-v2/tests/unit/utils/get-component-factory-test.js b/ui-v2/tests/unit/utils/dom/get-component-factory-test.js
similarity index 90%
rename from ui-v2/tests/unit/utils/get-component-factory-test.js
rename to ui-v2/tests/unit/utils/dom/get-component-factory-test.js
index 16be5c677e..bbac456045 100644
--- a/ui-v2/tests/unit/utils/get-component-factory-test.js
+++ b/ui-v2/tests/unit/utils/dom/get-component-factory-test.js
@@ -1,7 +1,7 @@
-import getComponentFactory from 'consul-ui/utils/get-component-factory';
+import getComponentFactory from 'consul-ui/utils/dom/get-component-factory';
import { module, test } from 'qunit';
-module('Unit | Utility | get component factory');
+module('Unit | Utility | dom/get component factory');
test("it uses lookup to locate the instance of the component based on the DOM element's id", function(assert) {
const expected = 'name';
diff --git a/ui-v2/tests/unit/utils/get-object-pool-test.js b/ui-v2/tests/unit/utils/get-object-pool-test.js
new file mode 100644
index 0000000000..9a82039462
--- /dev/null
+++ b/ui-v2/tests/unit/utils/get-object-pool-test.js
@@ -0,0 +1,98 @@
+import getObjectPool from 'consul-ui/utils/get-object-pool';
+import { module, skip } from 'qunit';
+import test from 'ember-sinon-qunit/test-support/test';
+
+module('Unit | Utility | get object pool');
+
+skip('Decide what to do if you add 2 objects with the same id');
+test('acquire adds objects', function(assert) {
+ const actual = [];
+ const expected = {
+ hi: 'there',
+ id: 'hi-there-123',
+ };
+ const expected2 = {
+ hi: 'there',
+ id: 'hi-there-456',
+ };
+ const pool = getObjectPool(function() {}, 10, actual);
+ pool.acquire(expected, expected.id);
+ assert.deepEqual(actual[0], expected);
+ pool.acquire(expected2, expected2.id);
+ assert.deepEqual(actual[1], expected2);
+});
+test('acquire adds objects and returns the id', function(assert) {
+ const arr = [];
+ const expected = 'hi-there-123';
+ const obj = {
+ hi: 'there',
+ id: expected,
+ };
+ const pool = getObjectPool(function() {}, 10, arr);
+ const actual = pool.acquire(obj, expected);
+ assert.equal(actual, expected);
+});
+test('acquire adds objects, and disposes when there is no room', function(assert) {
+ const actual = [];
+ const expected = {
+ hi: 'there',
+ id: 'hi-there-123',
+ };
+ const expected2 = {
+ hi: 'there',
+ id: 'hi-there-456',
+ };
+ const dispose = this.stub()
+ .withArgs(expected)
+ .returnsArg(0);
+ const pool = getObjectPool(dispose, 1, actual);
+ pool.acquire(expected, expected.id);
+ assert.deepEqual(actual[0], expected);
+ pool.acquire(expected2, expected2.id);
+ assert.deepEqual(actual[0], expected2);
+ assert.ok(dispose.calledOnce);
+});
+test('it disposes', function(assert) {
+ const arr = [];
+ const expected = {
+ hi: 'there',
+ id: 'hi-there-123',
+ };
+ const expected2 = {
+ hi: 'there',
+ id: 'hi-there-456',
+ };
+ const dispose = this.stub().returnsArg(0);
+ const pool = getObjectPool(dispose, 2, arr);
+ const id = pool.acquire(expected, expected.id);
+ assert.deepEqual(arr[0], expected);
+ pool.acquire(expected2, expected2.id);
+ assert.deepEqual(arr[1], expected2);
+ const actual = pool.dispose(id);
+ assert.ok(dispose.calledOnce);
+ assert.equal(arr.length, 1, 'object was removed from array');
+ assert.deepEqual(actual, expected, 'returned object is expected object');
+ assert.deepEqual(arr[0], expected2, 'object in the pool is expected object');
+});
+test('it purges', function(assert) {
+ const arr = [];
+ const expected = {
+ hi: 'there',
+ id: 'hi-there-123',
+ };
+ const expected2 = {
+ hi: 'there',
+ id: 'hi-there-456',
+ };
+ const dispose = this.stub().returnsArg(0);
+ const pool = getObjectPool(dispose, 2, arr);
+ pool.acquire(expected, expected.id);
+ assert.deepEqual(arr[0], expected);
+ pool.acquire(expected2, expected2.id);
+ assert.deepEqual(arr[1], expected2);
+ const actual = pool.purge();
+ assert.ok(dispose.calledTwice, 'dispose was called on everything');
+ assert.equal(arr.length, 0, 'the pool is empty');
+ assert.deepEqual(actual[0], expected, 'the first purged object is correct');
+ assert.deepEqual(actual[1], expected2, 'the second purged object is correct');
+});
diff --git a/ui-v2/tests/unit/utils/http/request-test.js b/ui-v2/tests/unit/utils/http/request-test.js
new file mode 100644
index 0000000000..c10dcbd899
--- /dev/null
+++ b/ui-v2/tests/unit/utils/http/request-test.js
@@ -0,0 +1,10 @@
+import httpRequest from 'consul-ui/utils/http/request';
+import { module, test } from 'qunit';
+
+module('Unit | Utility | http/request');
+
+// Replace this with your real tests.
+test('it works', function(assert) {
+ const actual = httpRequest;
+ assert.ok(typeof actual === 'function');
+});
diff --git a/ui-v2/tests/unit/utils/search/filterable-test.js b/ui-v2/tests/unit/utils/search/filterable-test.js
new file mode 100644
index 0000000000..1d11342015
--- /dev/null
+++ b/ui-v2/tests/unit/utils/search/filterable-test.js
@@ -0,0 +1,10 @@
+import searchFilterable from 'consul-ui/utils/search/filterable';
+import { module, test } from 'qunit';
+
+module('Unit | Utility | search/filterable');
+
+// Replace this with your real tests.
+test('it works', function(assert) {
+ let result = searchFilterable();
+ assert.ok(result);
+});
diff --git a/ui-v2/tests/unit/utils/update-array-object-test.js b/ui-v2/tests/unit/utils/update-array-object-test.js
index f066b3cc9d..874d1ce84c 100644
--- a/ui-v2/tests/unit/utils/update-array-object-test.js
+++ b/ui-v2/tests/unit/utils/update-array-object-test.js
@@ -4,7 +4,7 @@ import { module, test } from 'qunit';
module('Unit | Utility | update array object');
// Replace this with your real tests.
-test('it works', function(assert) {
+test('it updates the correct item in the array', function(assert) {
const expected = {
data: {
id: '2',
@@ -27,4 +27,5 @@ test('it works', function(assert) {
];
const actual = updateArrayObject(arr, expected, 'id');
assert.ok(actual, expected);
+ assert.equal(arr[1].name, expected.name);
});
diff --git a/ui-v2/yarn.lock b/ui-v2/yarn.lock
index ce3cef4734..6f7037acac 100644
--- a/ui-v2/yarn.lock
+++ b/ui-v2/yarn.lock
@@ -27,6 +27,46 @@
semver "^5.4.1"
source-map "^0.5.0"
+"@babel/core@^7.2.2":
+ version "7.3.3"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.3.3.tgz#d090d157b7c5060d05a05acaebc048bd2b037947"
+ integrity sha512-w445QGI2qd0E0GlSnq6huRZWPMmQGCp5gd5ZWS4hagn0EiwzxD5QMFkpchyusAyVC1n27OKXzQ0/88aVU9n4xQ==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ "@babel/generator" "^7.3.3"
+ "@babel/helpers" "^7.2.0"
+ "@babel/parser" "^7.3.3"
+ "@babel/template" "^7.2.2"
+ "@babel/traverse" "^7.2.2"
+ "@babel/types" "^7.3.3"
+ convert-source-map "^1.1.0"
+ debug "^4.1.0"
+ json5 "^2.1.0"
+ lodash "^4.17.11"
+ resolve "^1.3.2"
+ semver "^5.4.1"
+ source-map "^0.5.0"
+
+"@babel/core@^7.3.3", "@babel/core@^7.3.4":
+ version "7.4.3"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.3.tgz#198d6d3af4567be3989550d97e068de94503074f"
+ integrity sha512-oDpASqKFlbspQfzAE7yaeTmdljSH2ADIvBlb0RwbStltTuWa0+7CCI1fYVINNv9saHPa1W7oaKeuNuKj+RQCvA==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ "@babel/generator" "^7.4.0"
+ "@babel/helpers" "^7.4.3"
+ "@babel/parser" "^7.4.3"
+ "@babel/template" "^7.4.0"
+ "@babel/traverse" "^7.4.3"
+ "@babel/types" "^7.4.0"
+ convert-source-map "^1.1.0"
+ debug "^4.1.0"
+ json5 "^2.1.0"
+ lodash "^4.17.11"
+ resolve "^1.3.2"
+ semver "^5.4.1"
+ source-map "^0.5.0"
+
"@babel/generator@^7.0.0", "@babel/generator@^7.1.2":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.1.2.tgz#fde75c072575ce7abbd97322e8fef5bae67e4630"
@@ -37,6 +77,28 @@
source-map "^0.5.0"
trim-right "^1.0.1"
+"@babel/generator@^7.2.2", "@babel/generator@^7.3.3":
+ version "7.3.3"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.3.tgz#185962ade59a52e00ca2bdfcfd1d58e528d4e39e"
+ integrity sha512-aEADYwRRZjJyMnKN7llGIlircxTCofm3dtV5pmY6ob18MSIuipHpA2yZWkPlycwu5HJcx/pADS3zssd8eY7/6A==
+ dependencies:
+ "@babel/types" "^7.3.3"
+ jsesc "^2.5.1"
+ lodash "^4.17.11"
+ source-map "^0.5.0"
+ trim-right "^1.0.1"
+
+"@babel/generator@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.0.tgz#c230e79589ae7a729fd4631b9ded4dc220418196"
+ integrity sha512-/v5I+a1jhGSKLgZDcmAUZ4K/VePi43eRkUs3yePW1HB1iANOD5tqJXwGSG4BZhSksP8J9ejSlwGeTiiOFZOrXQ==
+ dependencies:
+ "@babel/types" "^7.4.0"
+ jsesc "^2.5.1"
+ lodash "^4.17.11"
+ source-map "^0.5.0"
+ trim-right "^1.0.1"
+
"@babel/helper-annotate-as-pure@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32"
@@ -58,6 +120,18 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.0.0"
+"@babel/helper-create-class-features-plugin@^7.4.0":
+ version "7.4.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.3.tgz#5bbd279c6c3ac6a60266b89bbfe7f8021080a1ef"
+ integrity sha512-UMl3TSpX11PuODYdWGrUeW6zFkdYhDn7wRLrOuNVM6f9L+S9CzmDXYyrp3MTHcwWjnzur1f/Op8A7iYZWya2Yg==
+ dependencies:
+ "@babel/helper-function-name" "^7.1.0"
+ "@babel/helper-member-expression-to-functions" "^7.0.0"
+ "@babel/helper-optimise-call-expression" "^7.0.0"
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/helper-replace-supers" "^7.4.0"
+ "@babel/helper-split-export-declaration" "^7.4.0"
+
"@babel/helper-define-map@^7.1.0":
version "7.1.0"
resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz#3b74caec329b3c80c116290887c0dd9ae468c20c"
@@ -151,6 +225,16 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.0.0"
+"@babel/helper-replace-supers@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.4.0.tgz#4f56adb6aedcd449d2da9399c2dcf0545463b64c"
+ integrity sha512-PVwCVnWWAgnal+kJ+ZSAphzyl58XrFeSKSAJRiqg5QToTsjL+Xu1f9+RJ+d+Q0aPhPfBGaYfkox66k86thxNSg==
+ dependencies:
+ "@babel/helper-member-expression-to-functions" "^7.0.0"
+ "@babel/helper-optimise-call-expression" "^7.0.0"
+ "@babel/traverse" "^7.4.0"
+ "@babel/types" "^7.4.0"
+
"@babel/helper-simple-access@^7.1.0":
version "7.1.0"
resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz#65eeb954c8c245beaa4e859da6188f39d71e585c"
@@ -164,6 +248,13 @@
dependencies:
"@babel/types" "^7.0.0"
+"@babel/helper-split-export-declaration@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.0.tgz#571bfd52701f492920d63b7f735030e9a3e10b55"
+ integrity sha512-7Cuc6JZiYShaZnybDmfwhY4UYHzI6rlqhWjaIqbsJGsIqPimEYy5uh3akSRLMg65LSdSEnJ8a8/bWQN6u2oMGw==
+ dependencies:
+ "@babel/types" "^7.4.0"
+
"@babel/helper-wrap-function@^7.1.0":
version "7.1.0"
resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.1.0.tgz#8cf54e9190706067f016af8f75cb3df829cc8c66"
@@ -181,6 +272,24 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.1.2"
+"@babel/helpers@^7.2.0":
+ version "7.3.1"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.3.1.tgz#949eec9ea4b45d3210feb7dc1c22db664c9e44b9"
+ integrity sha512-Q82R3jKsVpUV99mgX50gOPCWwco9Ec5Iln/8Vyu4osNIOQgSrd9RFrQeUvmvddFNoLwMyOUWU+5ckioEKpDoGA==
+ dependencies:
+ "@babel/template" "^7.1.2"
+ "@babel/traverse" "^7.1.5"
+ "@babel/types" "^7.3.0"
+
+"@babel/helpers@^7.4.3":
+ version "7.4.3"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.3.tgz#7b1d354363494b31cb9a2417ae86af32b7853a3b"
+ integrity sha512-BMh7X0oZqb36CfyhvtbSmcWc3GXocfxv3yNsAEuM0l+fAqSO22rQrUpijr3oE/10jCTrB6/0b9kzmG4VetCj8Q==
+ dependencies:
+ "@babel/template" "^7.4.0"
+ "@babel/traverse" "^7.4.3"
+ "@babel/types" "^7.4.0"
+
"@babel/highlight@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4"
@@ -193,6 +302,16 @@
version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.2.tgz#85c5c47af6d244fab77bce6b9bd830e38c978409"
+"@babel/parser@^7.2.2", "@babel/parser@^7.2.3", "@babel/parser@^7.3.3":
+ version "7.3.3"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.3.tgz#092d450db02bdb6ccb1ca8ffd47d8774a91aef87"
+ integrity sha512-xsH1CJoln2r74hR+y7cg2B5JCPaTh+Hd+EbBRk9nWGSNspuo6krjhX0Om6RnRQuIvFq8wVXCLKH3kwKDYhanSg==
+
+"@babel/parser@^7.4.0", "@babel/parser@^7.4.3":
+ version "7.4.3"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.3.tgz#eb3ac80f64aa101c907d4ce5406360fe75b7895b"
+ integrity sha512-gxpEUhTS1sGA63EGQGuA+WESPR/6tz6ng7tSHFCmaTJK/cGK8y37cBTspX+U2xCAue2IQVvF6Z0oigmjwD8YGQ==
+
"@babel/plugin-proposal-async-generator-functions@^7.1.0":
version "7.1.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.1.0.tgz#41c1a702e10081456e23a7b74d891922dd1bb6ce"
@@ -201,6 +320,23 @@
"@babel/helper-remap-async-to-generator" "^7.1.0"
"@babel/plugin-syntax-async-generators" "^7.0.0"
+"@babel/plugin-proposal-class-properties@^7.3.4":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.0.tgz#d70db61a2f1fd79de927eea91f6411c964e084b8"
+ integrity sha512-t2ECPNOXsIeK1JxJNKmgbzQtoG27KIlVE61vTqX0DKR9E9sZlVVxWUtEW9D5FlZ8b8j7SBNCHY47GgPKCKlpPg==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.4.0"
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-proposal-decorators@^7.3.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.4.0.tgz#8e1bfd83efa54a5f662033afcc2b8e701f4bb3a9"
+ integrity sha512-d08TLmXeK/XbgCo7ZeZ+JaeZDtDai/2ctapTRsWWkkmy7G/cqz8DQN/HlWG7RR4YmfXxmExsbU3SuCjlM7AtUg==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.4.0"
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-decorators" "^7.2.0"
+
"@babel/plugin-proposal-json-strings@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.0.0.tgz#3b4d7b5cf51e1f2e70f52351d28d44fc2970d01e"
@@ -236,6 +372,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
+"@babel/plugin-syntax-decorators@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz#c50b1b957dcc69e4b1127b65e1c33eef61570c1b"
+ integrity sha512-38QdqVoXdHUQfTpZo3rQwqQdWtCn5tMv4uV6r2RMfTqNBuv4ZBhz79SfaQWKTVmxHjeFv/DnXVC/+agHCklYWA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
"@babel/plugin-syntax-json-strings@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.0.0.tgz#0d259a68090e15b383ce3710e01d5b23f3770cbd"
@@ -402,6 +545,16 @@
dependencies:
regenerator-transform "^0.13.3"
+"@babel/plugin-transform-runtime@^7.2.0":
+ version "7.4.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.4.3.tgz#4d6691690ecdc9f5cb8c3ab170a1576c1f556371"
+ integrity sha512-7Q61bU+uEI7bCUFReT1NKn7/X6sDQsZ7wL1sJ9IYMAO7cI+eg6x9re1cEw2fCRMbbTVyoeUKWSV1M6azEfKCfg==
+ dependencies:
+ "@babel/helper-module-imports" "^7.0.0"
+ "@babel/helper-plugin-utils" "^7.0.0"
+ resolve "^1.8.1"
+ semver "^5.5.1"
+
"@babel/plugin-transform-shorthand-properties@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0.tgz#85f8af592dcc07647541a0350e8c95c7bf419d15"
@@ -495,6 +648,13 @@
js-levenshtein "^1.1.3"
semver "^5.3.0"
+"@babel/runtime@^7.2.0":
+ version "7.4.3"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.3.tgz#79888e452034223ad9609187a0ad1fe0d2ad4bdc"
+ integrity sha512-9lsJwJLxDh/T3Q3SZszfWOTkk3pHbkmH+3KY+zwIDmsNlxsumuhS2TH3NIpktU4kNvfzy+k3eLT7aTJSPTo0OA==
+ dependencies:
+ regenerator-runtime "^0.13.2"
+
"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644"
@@ -503,6 +663,24 @@
"@babel/parser" "^7.1.2"
"@babel/types" "^7.1.2"
+"@babel/template@^7.2.2":
+ version "7.2.2"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907"
+ integrity sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ "@babel/parser" "^7.2.2"
+ "@babel/types" "^7.2.2"
+
+"@babel/template@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.0.tgz#12474e9c077bae585c5d835a95c0b0b790c25c8b"
+ integrity sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ "@babel/parser" "^7.4.0"
+ "@babel/types" "^7.4.0"
+
"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0":
version "7.1.0"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.1.0.tgz#503ec6669387efd182c3888c4eec07bcc45d91b2"
@@ -517,6 +695,36 @@
globals "^11.1.0"
lodash "^4.17.10"
+"@babel/traverse@^7.1.5", "@babel/traverse@^7.2.2":
+ version "7.2.3"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8"
+ integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ "@babel/generator" "^7.2.2"
+ "@babel/helper-function-name" "^7.1.0"
+ "@babel/helper-split-export-declaration" "^7.0.0"
+ "@babel/parser" "^7.2.3"
+ "@babel/types" "^7.2.2"
+ debug "^4.1.0"
+ globals "^11.1.0"
+ lodash "^4.17.10"
+
+"@babel/traverse@^7.4.0", "@babel/traverse@^7.4.3":
+ version "7.4.3"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.3.tgz#1a01f078fc575d589ff30c0f71bf3c3d9ccbad84"
+ integrity sha512-HmA01qrtaCwwJWpSKpA948cBvU5BrmviAief/b3AVw936DtcdsTexlbyzNuDnthwhOQ37xshn7hvQaEQk7ISYQ==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ "@babel/generator" "^7.4.0"
+ "@babel/helper-function-name" "^7.1.0"
+ "@babel/helper-split-export-declaration" "^7.4.0"
+ "@babel/parser" "^7.4.3"
+ "@babel/types" "^7.4.0"
+ debug "^4.1.0"
+ globals "^11.1.0"
+ lodash "^4.17.11"
+
"@babel/types@^7.0.0", "@babel/types@^7.1.2":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.2.tgz#183e7952cf6691628afdc2e2b90d03240bac80c0"
@@ -525,6 +733,24 @@
lodash "^4.17.10"
to-fast-properties "^2.0.0"
+"@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3":
+ version "7.3.3"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.3.tgz#6c44d1cdac2a7625b624216657d5bc6c107ab436"
+ integrity sha512-2tACZ80Wg09UnPg5uGAOUvvInaqLk3l/IAhQzlxLQOIXacr6bMsra5SH6AWw/hIDRCSbCdHP2KzSOD+cT7TzMQ==
+ dependencies:
+ esutils "^2.0.2"
+ lodash "^4.17.11"
+ to-fast-properties "^2.0.0"
+
+"@babel/types@^7.4.0":
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.0.tgz#670724f77d24cce6cc7d8cf64599d511d164894c"
+ integrity sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==
+ dependencies:
+ esutils "^2.0.2"
+ lodash "^4.17.11"
+ to-fast-properties "^2.0.0"
+
"@ember/ordered-set@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@ember/ordered-set/-/ordered-set-1.0.0.tgz#cf9ab5fd7510bcad370370ebcded705f6d1c542b"
@@ -543,6 +769,7 @@
"@gardenhq/component-factory@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@gardenhq/component-factory/-/component-factory-1.4.0.tgz#f5da8ddf2050fde9c69f4426d61fe55de043e78d"
+ integrity sha1-9dqN3yBQ/enGn0Qm1h/lXeBD540=
dependencies:
"@gardenhq/domino" "^1.0.0"
"@gardenhq/tick-control" "^2.0.0"
@@ -552,6 +779,7 @@
"@gardenhq/domino@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@gardenhq/domino/-/domino-1.0.0.tgz#832c493f3f05697b7df4ccce00c4cf620dc60923"
+ integrity sha1-gyxJPz8FaXt99MzOAMTPYg3GCSM=
optionalDependencies:
min-document "^2.19.0"
unfetch "^2.1.2"
@@ -560,6 +788,7 @@
"@gardenhq/o@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@gardenhq/o/-/o-8.0.1.tgz#d6772cec7e4295a951165284cf43fbd0a373b779"
+ integrity sha1-1ncs7H5ClalRFlKEz0P70KNzt3k=
dependencies:
"@gardenhq/component-factory" "^1.4.0"
"@gardenhq/tick-control" "^2.0.0"
@@ -577,10 +806,12 @@
"@gardenhq/tick-control@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@gardenhq/tick-control/-/tick-control-2.0.0.tgz#f84fe38ca7a09b7b2b52f42945c50429ba639897"
+ integrity sha1-+E/jjKegm3srUvQpRcUEKbpjmJc=
"@gardenhq/willow@^6.2.0":
version "6.2.0"
resolved "https://registry.yarnpkg.com/@gardenhq/willow/-/willow-6.2.0.tgz#3e4bc220a89099732746ead3385cc097bfb70186"
+ integrity sha1-PkvCIKiQmXMnRurTOFzAl7+3AYY=
"@glimmer/di@^0.2.0":
version "0.2.1"
@@ -593,8 +824,9 @@
"@glimmer/di" "^0.2.0"
"@hashicorp/api-double@^1.3.0":
- version "1.4.4"
- resolved "https://registry.yarnpkg.com/@hashicorp/api-double/-/api-double-1.4.4.tgz#db5521230b0031bfc3dc3cc5b775f17413a4fe91"
+ version "1.4.5"
+ resolved "https://registry.yarnpkg.com/@hashicorp/api-double/-/api-double-1.4.5.tgz#839ba882fad76eb17fd2eb3a8899bf5dd5a162a8"
+ integrity sha512-X8xRtZGXu4JAlh/deaaPW15L8gJIqwNpVEM2OKLkQu1AWHXSh3NF8Vhd5U81061+Dha8Ohl8aEE7LZ8f1tPvzg==
dependencies:
"@gardenhq/o" "^8.0.1"
"@gardenhq/tick-control" "^2.0.0"
@@ -606,12 +838,14 @@
js-yaml "^3.10.0"
"@hashicorp/consul-api-double@^2.0.1":
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-2.0.1.tgz#eaf2e3f230fbdd876c90b931fd4bb4d94aac10e2"
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-2.3.0.tgz#1163f6dacb29d43d8dac4d1473263c257321682a"
+ integrity sha512-wbaOyOoA1X5Ur7Gj4VSZkor1zuJ2+GTbavPJGtpZZXd6CtL3RXC4HaldruBIF79j3lBXVgS/Y9ETMfGLdoAYgA==
"@hashicorp/ember-cli-api-double@^1.3.0":
version "1.7.0"
resolved "https://registry.yarnpkg.com/@hashicorp/ember-cli-api-double/-/ember-cli-api-double-1.7.0.tgz#4fdab6152157dd82b999de030c593c87e0cdb8b7"
+ integrity sha512-ojPcUPyId+3hTbwAtBGYbP5TfCGVAH8Ky6kH+BzlisIO/8XKURo9BSYnFtmYWLgXQVLOIE3iuoia5kOjGS/w2A==
dependencies:
"@hashicorp/api-double" "^1.3.0"
array-range "^1.0.1"
@@ -637,6 +871,11 @@
version "0.0.39"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
+"@types/minimatch@^3.0.3":
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
+ integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
+
"@types/node@*":
version "10.11.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.11.3.tgz#c055536ac8a5e871701aa01914be5731539d01ee"
@@ -769,6 +1008,7 @@
"@xg-wang/whatwg-fetch@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@xg-wang/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#f7b222c012a238e7d6e89ed3d72a1e0edb58453d"
+ integrity sha512-ULtqA6L75RLzTNW68IiOja0XYv4Ebc3OGMzfia1xxSEMpD0mk/pMvkQX0vbCFyQmKc5xGp80Ms2WiSlXLh8hbA==
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
@@ -995,6 +1235,7 @@ are-we-there-yet@~1.1.2:
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
+ integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
dependencies:
sprintf-js "~1.0.2"
@@ -1031,6 +1272,7 @@ array-find-index@^1.0.1:
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+ integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
array-map@~0.0.0:
version "0.0.0"
@@ -1039,6 +1281,7 @@ array-map@~0.0.0:
array-range@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/array-range/-/array-range-1.0.1.tgz#f56e46591843611c6a56f77ef02eda7c50089bfc"
+ integrity sha1-9W5GWRhDYRxqVvd+8C7afFAIm/w=
array-reduce@~0.0.0:
version "0.0.0"
@@ -1219,6 +1462,7 @@ babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
babel-core@^6.14.0, babel-core@^6.26.0, babel-core@^6.26.3:
version "6.26.3"
resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207"
+ integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==
dependencies:
babel-code-frame "^6.26.0"
babel-generator "^6.26.0"
@@ -1402,6 +1646,13 @@ babel-plugin-debug-macros@^0.2.0-beta.6:
dependencies:
semver "^5.3.0"
+babel-plugin-debug-macros@^0.3.0:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-debug-macros/-/babel-plugin-debug-macros-0.3.1.tgz#cf45b497ab02cdd0c98e11f6da79b1e0773ceae1"
+ integrity sha512-1tnO63L4d9HFHguR4Xc+/Y7Og1+mDsXwiStVrsayyXIDauv6r1o9dnhRKPmmCV5digG2XgScnQJpWDsxNNLU7g==
+ dependencies:
+ semver "^5.3.0"
+
babel-plugin-ember-modules-api-polyfill@^1.4.2:
version "1.6.0"
resolved "https://registry.yarnpkg.com/babel-plugin-ember-modules-api-polyfill/-/babel-plugin-ember-modules-api-polyfill-1.6.0.tgz#abd1afa4237b3121cb51222f9bf3283cad8990aa"
@@ -1426,6 +1677,13 @@ babel-plugin-ember-modules-api-polyfill@^2.6.0:
dependencies:
ember-rfc176-data "^0.3.6"
+babel-plugin-ember-modules-api-polyfill@^2.8.0:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-ember-modules-api-polyfill/-/babel-plugin-ember-modules-api-polyfill-2.9.0.tgz#8503e7b4192aeb336b00265e6235258ff6b754aa"
+ integrity sha512-c03h50291phJ2gQxo/aIOvFQE2c6glql1A7uagE3XbPXpKVAJOUxtVDjvWG6UAB6BC5ynsJfMWvY0w4TPRKIHQ==
+ dependencies:
+ ember-rfc176-data "^0.3.9"
+
babel-plugin-feature-flags@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/babel-plugin-feature-flags/-/babel-plugin-feature-flags-0.3.1.tgz#9c827cf9a4eb9a19f725ccb239e85cab02036fc1"
@@ -1745,6 +2003,7 @@ babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
babel-standalone@^6.24.2:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-standalone/-/babel-standalone-6.26.0.tgz#15fb3d35f2c456695815ebf1ed96fe7f015b6886"
+ integrity sha1-Ffs9NfLEVmlYFevx7Zb+fwFbaIY=
babel-template@^6.24.1, babel-template@^6.26.0:
version "6.26.0"
@@ -1903,6 +2162,7 @@ body-parser@1.18.2:
body-parser@1.18.3, body-parser@^1.18.3:
version "1.18.3"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4"
+ integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=
dependencies:
bytes "3.0.0"
content-type "~1.0.4"
@@ -2063,6 +2323,23 @@ broccoli-babel-transpiler@^7.1.0:
rsvp "^4.8.3"
workerpool "^2.3.1"
+broccoli-babel-transpiler@^7.1.2:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/broccoli-babel-transpiler/-/broccoli-babel-transpiler-7.2.0.tgz#5c0d694c4055106abb385e2d3d88936d35b7cb18"
+ integrity sha512-lkP9dNFfK810CRHHWsNl9rjyYqcXH3qg0kArnA6tV9Owx3nlZm3Eyr0cGo6sMUQCNLH+2oKrRjOdUGSc6Um6Cw==
+ dependencies:
+ "@babel/core" "^7.3.3"
+ "@babel/polyfill" "^7.0.0"
+ broccoli-funnel "^2.0.2"
+ broccoli-merge-trees "^3.0.2"
+ broccoli-persistent-filter "^2.2.1"
+ clone "^2.1.2"
+ hash-for-dep "^1.4.7"
+ heimdalljs-logger "^0.1.9"
+ json-stable-stringify "^1.0.1"
+ rsvp "^4.8.4"
+ workerpool "^3.1.1"
+
broccoli-brocfile-loader@^0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/broccoli-brocfile-loader/-/broccoli-brocfile-loader-0.18.0.tgz#2e86021c805c34ffc8d29a2fb721cf273e819e4b"
@@ -2234,6 +2511,25 @@ broccoli-funnel@^2.0.0, broccoli-funnel@^2.0.1:
symlink-or-copy "^1.0.0"
walk-sync "^0.3.1"
+broccoli-funnel@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/broccoli-funnel/-/broccoli-funnel-2.0.2.tgz#0edf629569bc10bd02cc525f74b9a38e71366a75"
+ integrity sha512-/vDTqtv7ipjEZQOVqO4vGDVAOZyuYzQ/EgGoyewfOgh1M7IQAToBKZI0oAQPgMBeFPPlIbfMuAngk+ohPBuaHQ==
+ dependencies:
+ array-equal "^1.0.0"
+ blank-object "^1.0.1"
+ broccoli-plugin "^1.3.0"
+ debug "^2.2.0"
+ fast-ordered-set "^1.0.0"
+ fs-tree-diff "^0.5.3"
+ heimdalljs "^0.2.0"
+ minimatch "^3.0.0"
+ mkdirp "^0.5.0"
+ path-posix "^1.0.0"
+ rimraf "^2.4.3"
+ symlink-or-copy "^1.0.0"
+ walk-sync "^0.3.1"
+
broccoli-kitchen-sink-helpers@^0.2.5, broccoli-kitchen-sink-helpers@~0.2.0:
version "0.2.9"
resolved "https://registry.yarnpkg.com/broccoli-kitchen-sink-helpers/-/broccoli-kitchen-sink-helpers-0.2.9.tgz#a5e0986ed8d76fb5984b68c3f0450d3a96e36ecc"
@@ -2287,6 +2583,14 @@ broccoli-merge-trees@^3.0.0:
broccoli-plugin "^1.3.0"
merge-trees "^2.0.0"
+broccoli-merge-trees@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/broccoli-merge-trees/-/broccoli-merge-trees-3.0.2.tgz#f33b451994225522b5c9bcf27d59decfd8ba537d"
+ integrity sha512-ZyPAwrOdlCddduFbsMyyFzJUrvW6b04pMvDiAQZrCwghlvgowJDY+EfoXn+eR1RRA5nmGHJ+B68T63VnpRiT1A==
+ dependencies:
+ broccoli-plugin "^1.3.0"
+ merge-trees "^2.0.0"
+
broccoli-middleware@^1.0.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/broccoli-middleware/-/broccoli-middleware-1.2.1.tgz#a21f255f8bfe5a21c2f0fbf2417addd9d24c9436"
@@ -2312,6 +2616,25 @@ broccoli-persistent-filter@^1.0.3, broccoli-persistent-filter@^1.1.6, broccoli-p
symlink-or-copy "^1.0.1"
walk-sync "^0.3.1"
+broccoli-persistent-filter@^2.2.1:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/broccoli-persistent-filter/-/broccoli-persistent-filter-2.2.2.tgz#e0180e75ede5dd05d4c702f24f6c049e93fba915"
+ integrity sha512-PW12RD1yY+x5SASUADuUMJce+dVSmjBO3pV1rLNHmT1C31rp1P++TvX7AgUObFmGhL7qlwviSdhMbBkY1v3G2w==
+ dependencies:
+ async-disk-cache "^1.2.1"
+ async-promise-queue "^1.0.3"
+ broccoli-plugin "^1.0.0"
+ fs-tree-diff "^1.0.2"
+ hash-for-dep "^1.5.0"
+ heimdalljs "^0.2.1"
+ heimdalljs-logger "^0.1.7"
+ mkdirp "^0.5.1"
+ promise-map-series "^0.2.1"
+ rimraf "^2.6.1"
+ rsvp "^4.7.0"
+ symlink-or-copy "^1.0.1"
+ walk-sync "^1.0.0"
+
broccoli-plugin@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/broccoli-plugin/-/broccoli-plugin-1.1.0.tgz#73e2cfa05f8ea1e3fc1420c40c3d9e7dc724bf02"
@@ -2640,6 +2963,7 @@ bytes@1:
bytes@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
+ integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
cacache@^10.0.4:
version "10.0.4"
@@ -2806,6 +3130,15 @@ chalk@^2.4.1:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
+chalk@^2.4.2:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+ integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.3.0"
+
chalk@~0.4.0:
version "0.4.0"
resolved "http://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f"
@@ -2895,6 +3228,7 @@ class-utils@^0.3.5:
classtrophobic-es5@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/classtrophobic-es5/-/classtrophobic-es5-0.2.1.tgz#9bbfa62a9928abf26f385440032fb49da1cda88f"
+ integrity sha1-m7+mKpkoq/JvOFRAAy+0naHNqI8=
clean-base-url@^1.0.0:
version "1.0.0"
@@ -3091,6 +3425,7 @@ commander@^2.6.0:
commander@~2.13.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
+ integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==
commander@~2.17.1:
version "2.17.1"
@@ -3206,10 +3541,12 @@ constants-browserify@^1.0.0, constants-browserify@~1.0.0:
content-disposition@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
+ integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ=
content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
+ integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
continuable-cache@^0.3.1:
version "0.3.1"
@@ -3232,6 +3569,7 @@ convert-source-map@~1.1.0:
cookie-parser@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.3.tgz#0fe31fa19d000b95f4aadf1f53fdc2b8a203baa5"
+ integrity sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=
dependencies:
cookie "0.3.1"
cookie-signature "1.0.6"
@@ -3239,10 +3577,12 @@ cookie-parser@^1.4.3:
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
+ integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
cookie@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
+ integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
copy-concurrently@^1.0.0:
version "1.0.5"
@@ -3601,6 +3941,7 @@ des.js@^1.0.0:
destroy@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
+ integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
detect-file@^0.1.0:
version "0.1.0"
@@ -3657,6 +3998,7 @@ dom-serializer@0:
dom-walk@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
+ integrity sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=
domain-browser@^1.1.1:
version "1.2.0"
@@ -3716,6 +4058,7 @@ editions@^1.1.1:
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+ integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.30, electron-to-chromium@^1.3.47:
version "1.3.62"
@@ -3810,22 +4153,24 @@ ember-browserify@^1.2.2:
through2 "^2.0.0"
walk-sync "^0.2.7"
-ember-changeset-validations@^1.2.11:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/ember-changeset-validations/-/ember-changeset-validations-1.3.1.tgz#713c232321862a3c1dfcb41263dfe6f930bd0c40"
+ember-changeset-validations@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/ember-changeset-validations/-/ember-changeset-validations-2.1.0.tgz#454b5adc1e4b4bcb0bdf1896a5d50f0bee77b1f0"
+ integrity sha512-LOAA8KSjpmZkDFvUkFJwDlgiJrQGUwtYAM3ZBTEpbQ8uKo5KEkZ1KkV2hNBV3U9Dv6AEauKp/RpwB9Pn10fzdA==
dependencies:
- ember-changeset "1.5.0"
- ember-cli-babel "^6.6.0"
- ember-cli-htmlbars "^1.1.1"
+ ember-changeset "^2.0.0"
+ ember-cli-babel "^6.16.0"
+ ember-cli-htmlbars "^3.0.0"
ember-get-config "^0.2.4"
- ember-validators "^1.2.0"
+ ember-validators "^2.0.0"
-ember-changeset@1.5.0:
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/ember-changeset/-/ember-changeset-1.5.0.tgz#730d0ca3ac7a54a471322d159c77e734d0f42751"
+ember-changeset@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/ember-changeset/-/ember-changeset-2.0.1.tgz#670c8f9925ae1dbc75df05de171b4212004d420f"
+ integrity sha512-FgxWG2qaI2koCX9hc2+2qewBBwOJTm+l0cop9S5hc9PbF33OVAK9l/0l6oVtv3bJeyUVQ83opgK9aocOqao41w==
dependencies:
- ember-cli-babel "^6.8.2"
- ember-deep-set "^0.1.4"
+ ember-cli-babel "^6.16.0"
+ ember-deep-set "^0.2.0"
ember-cli-app-version@^3.0.0:
version "3.2.0"
@@ -3841,6 +4186,11 @@ ember-cli-autoprefixer@^0.8.1:
broccoli-autoprefixer "^5.0.0"
lodash "^4.0.0"
+ember-cli-babel-plugin-helpers@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/ember-cli-babel-plugin-helpers/-/ember-cli-babel-plugin-helpers-1.1.0.tgz#de3baedd093163b6c2461f95964888c1676325ac"
+ integrity sha512-Zr4my8Xn+CzO0gIuFNXji0eTRml5AxZUTDQz/wsNJ5AJAtyFWCY4QtKdoELNNbiCVGt1lq5yLiwTm4scGKu6xA==
+
ember-cli-babel@6.12.0, ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.0.0-beta.7, ember-cli-babel@^6.10.0, ember-cli-babel@^6.11.0, ember-cli-babel@^6.12.0, ember-cli-babel@^6.3.0, ember-cli-babel@^6.6.0, ember-cli-babel@^6.7.2, ember-cli-babel@^6.8.0, ember-cli-babel@^6.8.1, ember-cli-babel@^6.9.0, ember-cli-babel@^6.9.2:
version "6.12.0"
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-6.12.0.tgz#3adcdbe1278da1fcd0b9038f1360cb4ac5d4414c"
@@ -3916,6 +4266,33 @@ ember-cli-babel@^7.1.0:
ensure-posix-path "^1.0.2"
semver "^5.5.0"
+ember-cli-babel@^7.1.2:
+ version "7.7.3"
+ resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.7.3.tgz#f94709f6727583d18685ca6773a995877b87b8a0"
+ integrity sha512-/LWwyKIoSlZQ7k52P+6agC7AhcOBqPJ5C2u27qXHVVxKvCtg6ahNuRk/KmfZmV4zkuw4EjTZxfJE1PzpFyHkXg==
+ dependencies:
+ "@babel/core" "^7.0.0"
+ "@babel/plugin-proposal-class-properties" "^7.3.4"
+ "@babel/plugin-proposal-decorators" "^7.3.0"
+ "@babel/plugin-transform-modules-amd" "^7.0.0"
+ "@babel/plugin-transform-runtime" "^7.2.0"
+ "@babel/polyfill" "^7.0.0"
+ "@babel/preset-env" "^7.0.0"
+ "@babel/runtime" "^7.2.0"
+ amd-name-resolver "^1.2.1"
+ babel-plugin-debug-macros "^0.3.0"
+ babel-plugin-ember-modules-api-polyfill "^2.8.0"
+ babel-plugin-module-resolver "^3.1.1"
+ broccoli-babel-transpiler "^7.1.2"
+ broccoli-debug "^0.6.4"
+ broccoli-funnel "^2.0.1"
+ broccoli-source "^1.1.0"
+ clone "^2.1.2"
+ ember-cli-babel-plugin-helpers "^1.1.0"
+ ember-cli-version-checker "^2.1.2"
+ ensure-posix-path "^1.0.2"
+ semver "^5.5.0"
+
ember-cli-babel@^7.1.3:
version "7.2.0"
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.2.0.tgz#5c5bd877fb73f6fb198c878d3127ba9e18e9b8a0"
@@ -4036,16 +4413,6 @@ ember-cli-htmlbars-inline-precompile@^1.0.0:
heimdalljs-logger "^0.1.9"
silent-error "^1.1.0"
-ember-cli-htmlbars@^1.1.1:
- version "1.3.4"
- resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-1.3.4.tgz#461289724b34af372a6a0c4b6635819156963353"
- dependencies:
- broccoli-persistent-filter "^1.0.3"
- ember-cli-version-checker "^1.0.2"
- hash-for-dep "^1.0.2"
- json-stable-stringify "^1.0.0"
- strip-bom "^2.0.0"
-
ember-cli-htmlbars@^2.0.1, ember-cli-htmlbars@^2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-2.0.4.tgz#0bcda483f14271663c38756e1fd1cb89da6a50cf"
@@ -4228,12 +4595,6 @@ ember-cli-valid-component-name@^1.0.0:
dependencies:
silent-error "^1.0.0"
-ember-cli-version-checker@^1.0.2:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/ember-cli-version-checker/-/ember-cli-version-checker-1.3.1.tgz#0bc2d134c830142da64bf9627a0eded10b61ae72"
- dependencies:
- semver "^5.3.0"
-
ember-cli-version-checker@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ember-cli-version-checker/-/ember-cli-version-checker-2.1.0.tgz#fc79a56032f3717cf844ada7cbdec1a06fedb604"
@@ -4420,11 +4781,12 @@ ember-data@^3.0.2:
semver "^5.1.0"
silent-error "^1.0.0"
-ember-deep-set@^0.1.4:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/ember-deep-set/-/ember-deep-set-0.1.4.tgz#a9989969498d89685be155b72109b037af4d5645"
+ember-deep-set@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/ember-deep-set/-/ember-deep-set-0.2.0.tgz#93428b599f884c3da0550cbcc062b9ec5969a71e"
+ integrity sha512-3vg9Cw4CIInXzufZMQmScClg23mUw+2ybO53L51spFYP/eGaVmGduWmhrVljyl4lHKN7hW/jvG/YVWtwTPSTKA==
dependencies:
- ember-cli-babel "^6.6.0"
+ ember-cli-babel "^7.1.2"
ember-exam@^2.0.1:
version "2.0.1"
@@ -4548,9 +4910,10 @@ ember-qunit@^3.3.2:
ember-cli-test-loader "^2.2.0"
qunit "^2.5.0"
-ember-require-module@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/ember-require-module/-/ember-require-module-0.2.0.tgz#eafe436737ead4762220a9166b78364abf754274"
+ember-require-module@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/ember-require-module/-/ember-require-module-0.3.0.tgz#65aff7908b5b846467e4526594d33cfe0c23456b"
+ integrity sha512-rYN4YoWbR9VlJISSmx0ZcYZOgMcXZLGR7kdvp3zDerjIvYmHm/3p+K56fEAYmJILA6W4F+cBe41Tq2HuQAZizA==
dependencies:
ember-cli-babel "^6.9.2"
@@ -4582,6 +4945,11 @@ ember-rfc176-data@^0.3.6:
version "0.3.6"
resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.6.tgz#7138db8dfccec39c9a832adfbd4c49d670028907"
+ember-rfc176-data@^0.3.9:
+ version "0.3.9"
+ resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.9.tgz#44b6e051ead6c044ea87bd551f402e2cf89a7e3d"
+ integrity sha512-EiTo5YQS0Duy0xp9gCP8ekzv9vxirNi7MnIB4zWs+thtWp/mEKgf5mkiiLU2+oo8C5DuavVHhoPQDmyxh8Io1Q==
+
ember-router-generator@^1.0.0, ember-router-generator@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/ember-router-generator/-/ember-router-generator-1.2.3.tgz#8ed2ca86ff323363120fc14278191e9e8f1315ee"
@@ -4687,12 +5055,13 @@ ember-url@^0.6.0:
dependencies:
ember-cli-babel "^6.12.0"
-ember-validators@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/ember-validators/-/ember-validators-1.2.0.tgz#b4bc9aeaf97921d80c41b7dc21acca288d1216ae"
+ember-validators@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ember-validators/-/ember-validators-2.0.0.tgz#4100e17feb9c3a6cf4072732010697bbd674f8cb"
+ integrity sha512-OhXGN2UbFQY+lhkWOdW347NZsIWGj/fpTJbOfNxjyMQW/c3fvPEIvrhlvWf1JwHGKQTJDHpMQJgA/Luq39GDgQ==
dependencies:
ember-cli-babel "^6.9.2"
- ember-require-module "^0.2.0"
+ ember-require-module "^0.3.0"
emojis-list@^2.0.0:
version "2.1.0"
@@ -4701,6 +5070,7 @@ emojis-list@^2.0.0:
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+ integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
encoding@^0.1.11:
version "0.1.12"
@@ -4763,6 +5133,11 @@ ensure-posix-path@^1.0.0, ensure-posix-path@^1.0.1, ensure-posix-path@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/ensure-posix-path/-/ensure-posix-path-1.0.2.tgz#a65b3e42d0b71cfc585eb774f9943c8d9b91b0c2"
+ensure-posix-path@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz#3c62bdb19fa4681544289edb2b382adc029179ce"
+ integrity sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw==
+
entities@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
@@ -4866,6 +5241,7 @@ es6-weak-map@^2.0.1:
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+ integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
@@ -4958,6 +5334,7 @@ espree@^3.5.4:
esprima@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
+ integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
esprima@~3.0.0:
version "3.0.0"
@@ -4994,6 +5371,7 @@ esutils@^2.0.2:
etag@~1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
+ integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
event-emitter@~0.3.5:
version "0.3.5"
@@ -5189,6 +5567,7 @@ express@^4.10.7, express@^4.12.3:
express@^4.16.2:
version "4.16.4"
resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e"
+ integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==
dependencies:
accepts "~1.3.5"
array-flatten "1.1.1"
@@ -5292,10 +5671,12 @@ eyes@0.1.x:
fake-xml-http-request@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fake-xml-http-request/-/fake-xml-http-request-2.0.0.tgz#41a92f0ca539477700cb1dafd2df251d55dac8ff"
+ integrity sha512-UjNnynb6eLAB0lyh2PlTEkjRJORnNsVF1hbzU+PQv89/cyBV9GDRCy7JAcLQgeCLYT+3kaumWWZKEJvbaK74eQ==
faker@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f"
+ integrity sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=
fast-deep-equal@^1.0.0:
version "1.1.0"
@@ -5372,7 +5753,8 @@ file-entry-cache@^2.0.0:
file-saver@^1.3.3:
version "1.3.8"
- resolved "http://registry.npmjs.org/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8"
+ resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8"
+ integrity sha512-spKHSBQIxxS81N/O21WmuXA2F6wppUCsutpzenOeZzOCCJ5gEfcbqJP983IrpLXzYmXnMUa6J03SubcNPdKrlg==
filename-regex@^2.0.0:
version "2.0.1"
@@ -5410,7 +5792,8 @@ fill-range@^4.0.0:
finalhandler@1.1.1:
version "1.1.1"
- resolved "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105"
+ integrity sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==
dependencies:
debug "2.6.9"
encodeurl "~1.0.2"
@@ -5552,6 +5935,7 @@ formatio@1.2.0:
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
+ integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
fragment-cache@^0.2.1:
version "0.2.1"
@@ -5562,6 +5946,7 @@ fragment-cache@^0.2.1:
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+ integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
from2@^2.1.0:
version "2.3.0"
@@ -5674,6 +6059,16 @@ fs-tree-diff@^0.5.2, fs-tree-diff@^0.5.3, fs-tree-diff@^0.5.4, fs-tree-diff@^0.5
path-posix "^1.0.0"
symlink-or-copy "^1.1.8"
+fs-tree-diff@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/fs-tree-diff/-/fs-tree-diff-1.0.2.tgz#0e2931733a85b55feb3472c0b89a20b0c03ac0de"
+ integrity sha512-Zro2ACaPVDgVOx9+s5s5AfPlAD0kMJdbwGvTGF6KC1SjxjiGWxJvV4mUTDkFVSy3OUw2C/f1qpdjF81hGqSBAw==
+ dependencies:
+ heimdalljs-logger "^0.1.7"
+ object-assign "^4.1.0"
+ path-posix "^1.0.0"
+ symlink-or-copy "^1.1.8"
+
fs-tree@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-tree/-/fs-tree-1.0.0.tgz#ef64da3e6dd32cc0df27c3b3e0c299ffa575c026"
@@ -6100,6 +6495,18 @@ hash-for-dep@^1.0.2, hash-for-dep@^1.2.3:
heimdalljs-logger "^0.1.7"
resolve "^1.4.0"
+hash-for-dep@^1.4.7, hash-for-dep@^1.5.0:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/hash-for-dep/-/hash-for-dep-1.5.1.tgz#497754b39bee2f1c4ade4521bfd2af0a7c1196e3"
+ integrity sha512-/dQ/A2cl7FBPI2pO0CANkvuuVi/IFS5oTyJ0PsOb6jW6WbVW1js5qJXMJTNbWHXBIPdFTWFbabjB+mE0d+gelw==
+ dependencies:
+ broccoli-kitchen-sink-helpers "^0.3.1"
+ heimdalljs "^0.2.3"
+ heimdalljs-logger "^0.1.7"
+ path-root "^0.1.1"
+ resolve "^1.10.0"
+ resolve-package-path "^1.0.11"
+
hash.js@^1.0.0, hash.js@^1.0.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846"
@@ -6200,7 +6607,8 @@ http-errors@1.6.2:
http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3:
version "1.6.3"
- resolved "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
+ integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=
dependencies:
depd "~1.1.2"
inherits "2.0.3"
@@ -6260,6 +6668,7 @@ husky@^1.1.0:
hyperhtml@^0.15.5:
version "0.15.10"
resolved "https://registry.yarnpkg.com/hyperhtml/-/hyperhtml-0.15.10.tgz#5e5f42393d4fc30cd803063fb88a5c9d97625e1c"
+ integrity sha512-D3dkc5nac47dzGXhLfGTearEoUXLk8ijSrj+5ngEH1Od+6EZ9Cwjspj/MWWx74DWpvCH+glO7M+B7WqCYSzkTg==
iconv-lite@0.4.19:
version "0.4.19"
@@ -6268,6 +6677,7 @@ iconv-lite@0.4.19:
iconv-lite@0.4.23:
version "0.4.23"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
+ integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
@@ -6431,6 +6841,7 @@ invert-kv@^1.0.0:
ipaddr.js@1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e"
+ integrity sha1-6qM9bd16zo9/b+DJygRA5wZzix4=
is-accessor-descriptor@^0.1.6:
version "0.1.6"
@@ -6631,6 +7042,7 @@ is-path-inside@^1.0.0:
is-plain-obj@^1.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
+ integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
version "2.0.4"
@@ -6862,7 +7274,15 @@ js-yaml@0.3.x:
version "0.3.7"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-0.3.7.tgz#d739d8ee86461e54b354d6a7d7d1f2ad9a167f62"
-js-yaml@^3.10.0, js-yaml@^3.11.0, js-yaml@^3.12.0, js-yaml@^3.8.4:
+js-yaml@^3.10.0, js-yaml@^3.11.0, js-yaml@^3.8.4:
+ version "3.12.1"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600"
+ integrity sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA==
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^4.0.0"
+
+js-yaml@^3.12.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
dependencies:
@@ -6940,6 +7360,13 @@ json5@^0.5.0, json5@^0.5.1:
version "0.5.1"
resolved "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
+json5@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850"
+ integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==
+ dependencies:
+ minimist "^1.2.0"
+
jsonfile@^2.1.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
@@ -7545,9 +7972,10 @@ lodash@^4.14.0, lodash@^4.17.4:
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
-lodash@^4.17.10, lodash@^4.17.5, lodash@~4.17.10:
+lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.5, lodash@~4.17.10:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
+ integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
log-symbols@^1.0.2:
version "1.0.2"
@@ -7664,6 +8092,13 @@ matcher-collection@^1.0.0, matcher-collection@^1.0.5:
dependencies:
minimatch "^3.0.2"
+matcher-collection@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/matcher-collection/-/matcher-collection-1.1.2.tgz#1076f506f10ca85897b53d14ef54f90a5c426838"
+ integrity sha512-YQ/teqaOIIfUHedRam08PB3NK7Mjct6BvzRnJmpGDm8uFXpNr1sbY4yuflI5JcEs6COpYA0FpRQhSDBf1tT95g==
+ dependencies:
+ minimatch "^3.0.2"
+
math-random@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac"
@@ -7701,7 +8136,8 @@ mdurl@^1.0.1:
media-typer@0.3.0:
version "0.3.0"
- resolved "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+ resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+ integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
mem@^1.1.0:
version "1.1.0"
@@ -7740,10 +8176,12 @@ meow@^3.4.0, meow@^3.7.0:
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+ integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
merge-options@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-1.0.1.tgz#2a64b24457becd4e4dc608283247e94ce589aa32"
+ integrity sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==
dependencies:
is-plain-obj "^1.1"
@@ -7772,6 +8210,7 @@ merge@^1.1.3:
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+ integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
micromatch@^2.1.5, micromatch@^2.3.11, micromatch@^2.3.7:
version "2.3.11"
@@ -7827,6 +8266,7 @@ mime-db@~1.36.0:
mime-db@~1.37.0:
version "1.37.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8"
+ integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==
mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.7:
version "2.1.20"
@@ -7843,12 +8283,14 @@ mime-types@^2.1.18:
mime-types@~2.1.18:
version "2.1.21"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96"
+ integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==
dependencies:
mime-db "~1.37.0"
mime@1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
+ integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==
mimic-fn@^1.0.0:
version "1.2.0"
@@ -7857,6 +8299,7 @@ mimic-fn@^1.0.0:
min-document@^2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
+ integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=
dependencies:
dom-walk "^0.1.0"
@@ -7972,6 +8415,7 @@ morgan@^1.8.1:
mousetrap@^1.6.1:
version "1.6.2"
resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.2.tgz#caadd9cf886db0986fb2fee59a82f6bd37527587"
+ integrity sha512-jDjhi7wlHwdO6q6DS7YRmSHcuI+RVxadBkLt3KHrhd3C2b+w5pKefg3oj5beTcHZyVFA9Aksf+yEE1y5jxUjVA==
mout@^1.0.0:
version "1.1.0"
@@ -7991,6 +8435,7 @@ move-concurrently@^1.0.1:
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+ integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@^2.1.1:
version "2.1.1"
@@ -8051,7 +8496,8 @@ natural-compare@^1.4.0:
ncp@^2.0.0:
version "2.0.0"
- resolved "http://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
+ resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
+ integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=
needle@^2.2.1:
version "2.2.4"
@@ -8064,6 +8510,7 @@ needle@^2.2.1:
negotiator@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
+ integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=
neo-async@^2.5.0:
version "2.5.2"
@@ -8409,6 +8856,7 @@ object.values@^1.0.4:
on-finished@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
+ integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
dependencies:
ee-first "1.1.1"
@@ -8607,6 +9055,7 @@ parseuri@0.0.5:
parseurl@~1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3"
+ integrity sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=
pascalcase@^0.1.1:
version "0.1.1"
@@ -8648,7 +9097,7 @@ path-key@^2.0.0, path-key@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
-path-parse@^1.0.5:
+path-parse@^1.0.5, path-parse@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
@@ -8660,9 +9109,22 @@ path-posix@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/path-posix/-/path-posix-1.0.0.tgz#06b26113f56beab042545a23bfa88003ccac260f"
+path-root-regex@^0.1.0:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d"
+ integrity sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=
+
+path-root@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7"
+ integrity sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=
+ dependencies:
+ path-root-regex "^0.1.0"
+
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
+ integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
path-to-regexp@^1.7.0:
version "1.7.0"
@@ -8783,6 +9245,7 @@ preserve@^0.2.0:
pretender@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/pretender/-/pretender-2.1.1.tgz#5085f0a1272c31d5b57c488386f69e6ca207cb35"
+ integrity sha512-IkidsJzaroAanw3I43tKCFm2xCpurkQr9aPXv5/jpN+LfCwDaeI8rngVWtQZTx4qqbhc5zJspnLHJ4N/25KvDQ==
dependencies:
"@xg-wang/whatwg-fetch" "^3.0.0"
fake-xml-http-request "^2.0.0"
@@ -8974,6 +9437,7 @@ randomfill@^1.0.3:
range-parser@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
+ integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=
raw-body@2.3.2:
version "2.3.2"
@@ -8987,6 +9451,7 @@ raw-body@2.3.2:
raw-body@2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3"
+ integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==
dependencies:
bytes "3.0.0"
http-errors "1.6.3"
@@ -9115,6 +9580,7 @@ recast@^0.11.3:
recursive-readdir-sync@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/recursive-readdir-sync/-/recursive-readdir-sync-1.0.6.tgz#1dbf6d32f3c5bb8d3cde97a6c588d547a9e13d56"
+ integrity sha1-Hb9tMvPFu4083pemxYjVR6nhPVY=
redent@^1.0.0:
version "1.0.0"
@@ -9147,6 +9613,11 @@ regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1:
version "0.11.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
+regenerator-runtime@^0.13.2:
+ version "0.13.2"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447"
+ integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==
+
regenerator-runtime@^0.9.5:
version "0.9.6"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.9.6.tgz#d33eb95d0d2001a4be39659707c51b0cb71ce029"
@@ -9357,6 +9828,14 @@ resolve-from@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
+resolve-package-path@^1.0.11:
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/resolve-package-path/-/resolve-package-path-1.2.7.tgz#2a7bc37ad96865e239330e3102c31322847e652e"
+ integrity sha512-fVEKHGeK85bGbVFuwO9o1aU0n3vqQGrezPc51JGu9UTXpFQfWq5qCeKxyaRUSvephs+06c5j5rPq/dzHGEo8+Q==
+ dependencies:
+ path-root "^0.1.1"
+ resolve "^1.10.0"
+
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@@ -9377,6 +9856,13 @@ resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.0,
dependencies:
path-parse "^1.0.5"
+resolve@^1.10.0, resolve@^1.8.1:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba"
+ integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==
+ dependencies:
+ path-parse "^1.0.6"
+
resolve@^1.3.2, resolve@^1.3.3, resolve@^1.4.0, resolve@^1.7.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
@@ -9436,6 +9922,7 @@ rollup-plugin-commonjs@^9.1.0:
rollup-plugin-memory@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-memory/-/rollup-plugin-memory-2.0.0.tgz#0a8ac6b57fa0e714f89a15c3ac82bc93f89c47c5"
+ integrity sha1-CorGtX+g5xT4mhXDrIK8k/icR8U=
rollup-plugin-node-resolve@^3.3.0:
version "3.4.0"
@@ -9468,6 +9955,7 @@ rollup@^0.59.0:
route-recognizer@^0.3.3:
version "0.3.4"
resolved "https://registry.yarnpkg.com/route-recognizer/-/route-recognizer-0.3.4.tgz#39ab1ffbce1c59e6d2bdca416f0932611e4f3ca3"
+ integrity sha512-2+MhsfPhvauN1O8KaXpXAOfR/fwe8dnUXVM+xw7yt40lJRfPVQxV6yryZm0cgRvAj5fMF/mdRZbL2ptwbs5i2g==
rsvp@^3.0.14, rsvp@^3.0.16, rsvp@^3.0.17, rsvp@^3.0.18, rsvp@^3.0.21, rsvp@^3.0.6, rsvp@^3.1.0, rsvp@^3.2.1, rsvp@^3.3.3, rsvp@^3.5.0:
version "3.6.2"
@@ -9481,9 +9969,10 @@ rsvp@^4.8.2:
version "4.8.3"
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.3.tgz#25d4b9fdd0f95e216eb5884d9b3767d3fbfbe2cd"
-rsvp@^4.8.3:
+rsvp@^4.8.3, rsvp@^4.8.4:
version "4.8.4"
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.4.tgz#b50e6b34583f3dd89329a2f23a8a2be072845911"
+ integrity sha512-6FomvYPfs+Jy9TfXmBpBuMWNH94SgCsZmJKcanySzgNNP6LjWxBvyLTa9KaMfDDM5oxRfrKDB0r/qeRsLwnBfA==
rsvp@~3.0.6:
version "3.0.21"
@@ -9550,6 +10039,7 @@ safe-regex@^1.1.0:
"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+ integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
samsam@1.3.0, samsam@1.x, samsam@^1.1.3:
version "1.3.0"
@@ -9612,6 +10102,11 @@ semver@^5.3.0, semver@^5.4.1:
version "5.5.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477"
+semver@^5.5.1:
+ version "5.7.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
+ integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
+
semver@~5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
@@ -9619,6 +10114,7 @@ semver@~5.3.0:
send@0.16.2:
version "0.16.2"
resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1"
+ integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==
dependencies:
debug "2.6.9"
depd "~1.1.2"
@@ -9641,6 +10137,7 @@ serialize-javascript@^1.4.0:
serve-static@1.13.2:
version "1.13.2"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1"
+ integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"
@@ -9684,6 +10181,7 @@ setprototypeof@1.0.3:
setprototypeof@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
+ integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
sha.js@^2.4.0, sha.js@^2.4.8, sha.js@~2.4.4:
version "2.4.11"
@@ -9894,6 +10392,7 @@ source-map@0.4.x, source-map@^0.4.2, source-map@^0.4.4:
source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.3:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
+ integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
source-map@^0.6.1, source-map@~0.6.1:
version "0.6.1"
@@ -9960,6 +10459,7 @@ sprintf-js@^1.0.3:
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
+ integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
sri-toolbox@^0.2.0:
version "0.2.0"
@@ -10012,6 +10512,7 @@ static-extend@^0.1.1:
statuses@~1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
+ integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==
stdout-stream@^1.4.0:
version "1.4.1"
@@ -10608,6 +11109,7 @@ underscore@~1.6.0:
unfetch@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-2.1.2.tgz#684fee4d8acdb135bdb26c0364c642fc326ca95b"
+ integrity sha1-aE/uTYrNsTW9smwDZMZC/DJsqVs=
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
@@ -10662,6 +11164,7 @@ universalify@^0.1.0:
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+ integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
unquote@~1.1.1:
version "1.1.1"
@@ -10758,6 +11261,7 @@ util@^0.10.3:
utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
+ integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
uuid@^3.0.0, uuid@^3.1.0, uuid@^3.3.2:
version "3.3.2"
@@ -10779,6 +11283,7 @@ validate-npm-package-name@^3.0.0:
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
+ integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
verror@1.10.0:
version "1.10.0"
@@ -10819,6 +11324,15 @@ walk-sync@^0.3.1, walk-sync@^0.3.3:
ensure-posix-path "^1.0.0"
matcher-collection "^1.0.0"
+walk-sync@^1.0.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-1.1.3.tgz#3b7b6468f068b5eba2278c931c57db3d39092969"
+ integrity sha512-23ivbET0Q/389y3EHpiIgxx881AS2mwdXA7iBqUDNSymoTPYb2jWlF3gkuuAP1iLgdNXmiHw/kZ/wZwrELU6Ag==
+ dependencies:
+ "@types/minimatch" "^3.0.3"
+ ensure-posix-path "^1.1.0"
+ matcher-collection "^1.1.1"
+
walker@1.x, walker@~1.0.5:
version "1.0.7"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"
@@ -10960,6 +11474,15 @@ workerpool@^2.3.1:
dependencies:
object-assign "4.1.1"
+workerpool@^3.1.1:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-3.1.2.tgz#b34e79243647decb174b7481ab5b351dc565c426"
+ integrity sha512-WJFA0dGqIK7qj7xPTqciWBH5DlJQzoPjsANvc3Y4hNB0SScT+Emjvt0jPPkDBUjBNngX1q9hHgt1Gfwytu6pug==
+ dependencies:
+ "@babel/core" "^7.3.4"
+ object-assign "4.1.1"
+ rsvp "^4.8.4"
+
wrap-ansi@^2.0.0:
version "2.1.0"
resolved "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
@@ -11004,6 +11527,7 @@ xdg-basedir@^3.0.0:
xhr2@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.4.tgz#7f87658847716db5026323812f818cadab387a5f"
+ integrity sha1-f4dliEdxbbUCYyOBL4GMras4el8=
xmldom@^0.1.19:
version "0.1.27"