diff --git a/ui-v2/app/adapters/intention.js b/ui-v2/app/adapters/intention.js new file mode 100644 index 0000000000..486e891429 --- /dev/null +++ b/ui-v2/app/adapters/intention.js @@ -0,0 +1,64 @@ +import Adapter, { DATACENTER_KEY as API_DATACENTER_KEY } from './application'; +import { FOREIGN_KEY as DATACENTER_KEY } from 'consul-ui/models/dc'; +import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/intention'; +import { OK as HTTP_OK } from 'consul-ui/utils/http/status'; +import makeAttrable from 'consul-ui/utils/makeAttrable'; +export default Adapter.extend({ + urlForQuery: function(query, modelName) { + return this.appendURL('connect/intentions', [], this.cleanQuery(query)); + }, + urlForQueryRecord: function(query, modelName) { + return this.appendURL('connect/intentions', [query.id], this.cleanQuery(query)); + }, + urlForCreateRecord: function(modelName, snapshot) { + return this.appendURL('connect/intentions', [], { + [API_DATACENTER_KEY]: snapshot.attr(DATACENTER_KEY), + }); + }, + urlForUpdateRecord: function(id, modelName, snapshot) { + return this.appendURL('connect/intentions', [snapshot.attr(SLUG_KEY)], { + [API_DATACENTER_KEY]: snapshot.attr(DATACENTER_KEY), + }); + }, + urlForDeleteRecord: function(id, modelName, snapshot) { + return this.appendURL('connect/intentions', [snapshot.attr(SLUG_KEY)], { + [API_DATACENTER_KEY]: snapshot.attr(DATACENTER_KEY), + }); + }, + isUpdateRecord: function(url) { + return ( + url.pathname === + this.parseURL( + this.urlForUpdateRecord(null, 'intention', makeAttrable({ [DATACENTER_KEY]: '' })) + ).pathname + ); + }, + handleResponse: function(status, headers, payload, requestData) { + let response = payload; + if (status === HTTP_OK) { + const url = this.parseURL(requestData.url); + switch (true) { + case this.isQueryRecord(url): + case this.isUpdateRecord(url): + // case this.isCreateRecord(url): + response = { + ...response, + ...{ + [PRIMARY_KEY]: this.uidForURL(url), + }, + }; + break; + default: + response = response.map((item, i, arr) => { + return { + ...item, + ...{ + [PRIMARY_KEY]: this.uidForURL(url, item[SLUG_KEY]), + }, + }; + }); + } + } + return this._super(status, headers, response, requestData); + }, +}); diff --git a/ui-v2/app/components/intention-filter.js b/ui-v2/app/components/intention-filter.js new file mode 100644 index 0000000000..571a11271c --- /dev/null +++ b/ui-v2/app/components/intention-filter.js @@ -0,0 +1,8 @@ +import Component from '@ember/component'; + +export default Component.extend({ + tagName: 'form', + classNames: ['filter-bar'], + 'data-test-intention-filter': true, + onchange: function() {}, +}); diff --git a/ui-v2/app/controllers/dc/intentions/create.js b/ui-v2/app/controllers/dc/intentions/create.js new file mode 100644 index 0000000000..4723e0ce43 --- /dev/null +++ b/ui-v2/app/controllers/dc/intentions/create.js @@ -0,0 +1,2 @@ +import Controller from './edit'; +export default Controller.extend(); diff --git a/ui-v2/app/controllers/dc/intentions/edit.js b/ui-v2/app/controllers/dc/intentions/edit.js new file mode 100644 index 0000000000..b7693bff68 --- /dev/null +++ b/ui-v2/app/controllers/dc/intentions/edit.js @@ -0,0 +1,30 @@ +import Controller from '@ember/controller'; +import { set } from '@ember/object'; +// import Changeset from 'ember-changeset'; +// import validations from 'consul-ui/validations/acl'; +// import lookupValidator from 'ember-changeset-validations'; + +export default Controller.extend({ + setProperties: function(model) { + this.changeset = model.item; //new Changeset(model.item, lookupValidator(validations), validations); + this._super({ + ...model, + ...{ + item: this.changeset, + }, + }); + }, + actions: { + change: function(e) { + const target = e.target; + switch (target.name) { + case 'SourceType': + set(this.changeset, target.name, target.value); + break; + case 'Action': + set(this.changeset, target.name, target.value); + break; + } + }, + }, +}); diff --git a/ui-v2/app/controllers/dc/intentions/index.js b/ui-v2/app/controllers/dc/intentions/index.js new file mode 100644 index 0000000000..6e6291f41b --- /dev/null +++ b/ui-v2/app/controllers/dc/intentions/index.js @@ -0,0 +1,45 @@ +import Controller from '@ember/controller'; +import { computed, get } from '@ember/object'; +import WithFiltering from 'consul-ui/mixins/with-filtering'; +import ucfirst from 'consul-ui/utils/ucfirst'; +import numeral from 'numeral'; +// TODO: DRY out in acls at least +const createCounter = function(prop) { + return function(items, val) { + return val === '' ? get(items, 'length') : items.filterBy(prop, val).length; + }; +}; +const countAction = createCounter('Action'); +export default Controller.extend(WithFiltering, { + queryParams: { + action: { + as: 'action', + }, + s: { + as: 'filter', + replace: true, + }, + }, + actionFilters: computed('items', function() { + const items = get(this, 'items'); + return ['', 'allow', 'deny'].map(function(item) { + return { + label: `${item === '' ? 'All' : ucfirst(item)} (${numeral( + countAction(items, item) + ).format()})`, + value: item, + }; + }); + }), + filter: function(item, { s = '', action = '' }) { + return ( + (get(item, 'SourceName') + .toLowerCase() + .indexOf(s.toLowerCase()) !== -1 || + get(item, 'DestinationName') + .toLowerCase() + .indexOf(s.toLowerCase()) !== -1) && + (action === '' || get(item, 'Action') === action) + ); + }, +}); diff --git a/ui-v2/app/mixins/intention/with-actions.js b/ui-v2/app/mixins/intention/with-actions.js new file mode 100644 index 0000000000..f5276ab1d3 --- /dev/null +++ b/ui-v2/app/mixins/intention/with-actions.js @@ -0,0 +1,59 @@ +import Mixin from '@ember/object/mixin'; +import { get } from '@ember/object'; +import WithFeedback from 'consul-ui/mixins/with-feedback'; + +export default Mixin.create(WithFeedback, { + actions: { + create: function(item) { + get(this, 'feedback').execute( + () => { + return get(this, 'repo') + .persist(item) + .then(item => { + return this.transitionTo('dc.intentions'); + }); + }, + `Your intention has been added.`, + `There was an error adding your intention.` + ); + }, + update: function(item) { + get(this, 'feedback').execute( + () => { + return get(this, 'repo') + .persist(item) + .then(() => { + return this.transitionTo('dc.intentions'); + }); + }, + `Your intention was saved.`, + `There was an error saving your intention.` + ); + }, + delete: function(item) { + get(this, 'feedback').execute( + () => { + return ( + get(this, 'repo') + // ember-changeset doesn't support `get` + // and `data` returns an object not a model + .remove(item) + .then(() => { + switch (this.routeName) { + case 'dc.intentions.index': + return this.refresh(); + default: + return this.transitionTo('dc.intentions'); + } + }) + ); + }, + `Your intention was deleted.`, + `There was an error deleting your intention.` + ); + }, + cancel: function(item) { + this.transitionTo('dc.intentions'); + }, + }, +}); diff --git a/ui-v2/app/models/intention.js b/ui-v2/app/models/intention.js new file mode 100644 index 0000000000..43a9bb19eb --- /dev/null +++ b/ui-v2/app/models/intention.js @@ -0,0 +1,25 @@ +import Model from 'ember-data/model'; +import attr from 'ember-data/attr'; + +export const PRIMARY_KEY = 'uid'; +export const SLUG_KEY = 'ID'; + +export default Model.extend({ + [PRIMARY_KEY]: attr('string'), + [SLUG_KEY]: attr('string'), + Description: attr('string'), + SourceNS: attr('string'), + SourceName: attr('string'), + DestinationName: attr('string'), + Precedence: attr('number'), + SourceType: attr('string'), + Action: attr('string'), + DefaultAddr: attr('string'), + DefaultPort: attr('number'), + Meta: attr(), + Datacenter: attr('string'), + CreatedAt: attr('date'), + UpdatedAt: attr('date'), + CreateIndex: attr('number'), + ModifyIndex: attr('number'), +}); diff --git a/ui-v2/app/router.js b/ui-v2/app/router.js index 0d99ec539c..01f8bf0dfc 100644 --- a/ui-v2/app/router.js +++ b/ui-v2/app/router.js @@ -19,6 +19,11 @@ Router.map(function() { // Show an individual node this.route('show', { path: '/:name' }); }); + // Intentions represent a consul intention + this.route('intentions', { path: '/intentions' }, function() { + this.route('edit', { path: '/:id' }); + this.route('create', { path: '/create' }); + }); // Key/Value this.route('kv', { path: '/kv' }, function() { this.route('folder', { path: '/*key' }); diff --git a/ui-v2/app/routes/dc/intentions/create.js b/ui-v2/app/routes/dc/intentions/create.js new file mode 100644 index 0000000000..755eb0f0a7 --- /dev/null +++ b/ui-v2/app/routes/dc/intentions/create.js @@ -0,0 +1,33 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { hash } from 'rsvp'; +import { get, set } from '@ember/object'; +import WithIntentionActions from 'consul-ui/mixins/intention/with-actions'; + +export default Route.extend(WithIntentionActions, { + templateName: 'dc/intentions/edit', + repo: service('intentions'), + beforeModel: function() { + get(this, 'repo').invalidate(); + }, + model: function(params) { + this.item = get(this, 'repo').create(); + set(this.item, 'Datacenter', this.modelFor('dc').dc.Name); + return hash({ + create: true, + isLoading: false, + item: this.item, + types: ['consul', 'externaluri'], + intents: ['allow', 'deny'], + }); + }, + setupController: function(controller, model) { + this._super(...arguments); + controller.setProperties(model); + }, + deactivate: function() { + if (get(this.item, 'isNew')) { + this.item.destroyRecord(); + } + }, +}); diff --git a/ui-v2/app/routes/dc/intentions/edit.js b/ui-v2/app/routes/dc/intentions/edit.js new file mode 100644 index 0000000000..70acfae4d7 --- /dev/null +++ b/ui-v2/app/routes/dc/intentions/edit.js @@ -0,0 +1,22 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { hash } from 'rsvp'; +import { get } from '@ember/object'; + +import WithAclActions from 'consul-ui/mixins/intention/with-actions'; + +export default Route.extend(WithAclActions, { + repo: service('intentions'), + model: function(params) { + return hash({ + isLoading: false, + item: get(this, 'repo').findBySlug(params.id, this.modelFor('dc').dc.Name), + types: ['consul', 'externaluri'], + intents: ['allow', 'deny'], + }); + }, + setupController: function(controller, model) { + this._super(...arguments); + controller.setProperties(model); + }, +}); diff --git a/ui-v2/app/routes/dc/intentions/index.js b/ui-v2/app/routes/dc/intentions/index.js new file mode 100644 index 0000000000..6e8e10da29 --- /dev/null +++ b/ui-v2/app/routes/dc/intentions/index.js @@ -0,0 +1,23 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { hash } from 'rsvp'; +import { get } from '@ember/object'; + +export default Route.extend({ + repo: service('intentions'), + queryParams: { + s: { + as: 'filter', + replace: true, + }, + }, + model: function(params) { + return hash({ + items: get(this, 'repo').findAllByDatacenter(this.modelFor('dc').dc.Name), + }); + }, + setupController: function(controller, model) { + this._super(...arguments); + controller.setProperties(model); + }, +}); diff --git a/ui-v2/app/serializers/intention.js b/ui-v2/app/serializers/intention.js new file mode 100644 index 0000000000..d70f61c3f6 --- /dev/null +++ b/ui-v2/app/serializers/intention.js @@ -0,0 +1,6 @@ +import Serializer from './application'; +import { PRIMARY_KEY } from 'consul-ui/models/intention'; + +export default Serializer.extend({ + primaryKey: PRIMARY_KEY, +}); diff --git a/ui-v2/app/services/intentions.js b/ui-v2/app/services/intentions.js new file mode 100644 index 0000000000..e68ef5ee06 --- /dev/null +++ b/ui-v2/app/services/intentions.js @@ -0,0 +1,48 @@ +import Service, { inject as service } from '@ember/service'; +import { get, set } from '@ember/object'; +import { typeOf } from '@ember/utils'; +import { PRIMARY_KEY } from 'consul-ui/models/intention'; +export default Service.extend({ + store: service('store'), + findAllByDatacenter: function(dc) { + return get(this, 'store') + .query('intention', { dc: dc }) + .then(function(items) { + return items.forEach(function(item, i, arr) { + set(item, 'Datacenter', dc); + }); + }); + }, + findBySlug: function(slug, dc) { + return get(this, 'store') + .queryRecord('intention', { + id: slug, + dc: dc, + }) + .then(function(item) { + set(item, 'Datacenter', dc); + return item; + }); + }, + create: function() { + return get(this, 'store').createRecord('intention'); + }, + persist: function(item) { + return item.save(); + }, + remove: function(obj) { + let item = obj; + if (typeof obj.destroyRecord === 'undefined') { + item = obj.get('data'); + } + if (typeOf(item) === 'object') { + item = get(this, 'store').peekRecord('intention', item[PRIMARY_KEY]); + } + return item.destroyRecord().then(item => { + return get(this, 'store').unloadRecord(item); + }); + }, + invalidate: function() { + return get(this, 'store').unloadAll('intention'); + }, +}); diff --git a/ui-v2/app/styles/app.scss b/ui-v2/app/styles/app.scss index cebc321f17..03048d259d 100644 --- a/ui-v2/app/styles/app.scss +++ b/ui-v2/app/styles/app.scss @@ -45,6 +45,7 @@ @import 'components/notice'; @import 'routes/dc/service/index'; +@import 'routes/dc/intention/index'; @import 'routes/dc/kv/index'; main a { diff --git a/ui-v2/app/styles/components/icons.scss b/ui-v2/app/styles/components/icons.scss index 06dd4fac68..43033a0ee1 100644 --- a/ui-v2/app/styles/components/icons.scss +++ b/ui-v2/app/styles/components/icons.scss @@ -25,12 +25,13 @@ pointer-events: none; } %with-folder { - position: relative; text-indent: 30px; } %with-hashicorp, +%with-folder, %with-chevron, -%with-clipboard { +%with-clipboard, +%with-right-arrow { position: relative; } %with-chevron { @@ -142,6 +143,26 @@ @extend %pseudo-icon; background-image: url('data:image/svg+xml;charset=UTF-8,'); } +%with-right-arrow-green { + @extend %pseudo-icon; + background-image: url('data:image/svg+xml;charset=UTF-8,'); + width: 16px; + height: 14px; + background-color: transparent; +} +%with-deny-icon { + @extend %pseudo-icon; + background-image: url('data:image/svg+xml;charset=UTF-8,'); + width: 16px; + height: 16px; + background-color: transparent; +} +%with-deny::before { + @extend %with-deny-icon; +} +%with-allow::before { + @extend %with-right-arrow-green; +} %with-passing::before { @extend %with-tick; border-radius: 100%; diff --git a/ui-v2/app/styles/components/tabular-collection.scss b/ui-v2/app/styles/components/tabular-collection.scss index 3f1d9ca71f..e7ee2c6b71 100644 --- a/ui-v2/app/styles/components/tabular-collection.scss +++ b/ui-v2/app/styles/components/tabular-collection.scss @@ -19,6 +19,9 @@ table tr > * { html.template-service.template-list main table tr { @extend %services-row; } +html.template-intention.template-list main table tr { + @extend %intentions-row; +} html.template-kv.template-list main table tr { @extend %kvs-row; } @@ -65,6 +68,12 @@ html.template-node.template-show main table.sessions tr { tr > * dl { float: left; } +%intentions-row > * { + width: calc(25% - 60px); +} +%intentions-row > *:last-child { + width: 60px; +} %kvs-row > *:first-child { width: calc(100% - 60px); } diff --git a/ui-v2/app/styles/routes/dc/intention/index.scss b/ui-v2/app/styles/routes/dc/intention/index.scss new file mode 100644 index 0000000000..3776270e1e --- /dev/null +++ b/ui-v2/app/styles/routes/dc/intention/index.scss @@ -0,0 +1,8 @@ +td.intent-allow strong { + @extend %with-allow; + visibility: hidden; +} +td.intent-deny strong { + @extend %with-deny; + visibility: hidden; +} diff --git a/ui-v2/app/templates/components/hashicorp-consul.hbs b/ui-v2/app/templates/components/hashicorp-consul.hbs index f3396a4557..5705789f5b 100644 --- a/ui-v2/app/templates/components/hashicorp-consul.hbs +++ b/ui-v2/app/templates/components/hashicorp-consul.hbs @@ -27,6 +27,9 @@
  • Nodes
  • +
  • + Intentions +
  • Key/Value
  • diff --git a/ui-v2/app/templates/components/intention-filter.hbs b/ui-v2/app/templates/components/intention-filter.hbs new file mode 100644 index 0000000000..39b815e44e --- /dev/null +++ b/ui-v2/app/templates/components/intention-filter.hbs @@ -0,0 +1,4 @@ +{{!
    }} + {{freetext-filter onchange=(action onchange) value=search placeholder="Search by Source or Destination"}} + {{radio-group name="action" value=action items=filters onchange=(action onchange)}} +{{!
    }} \ No newline at end of file diff --git a/ui-v2/app/templates/dc/intentions/-form.hbs b/ui-v2/app/templates/dc/intentions/-form.hbs new file mode 100644 index 0000000000..ac5b0663f7 --- /dev/null +++ b/ui-v2/app/templates/dc/intentions/-form.hbs @@ -0,0 +1,58 @@ +
    +
    + +
    + {{#each types as |type|}} + + {{/each}} +
    + +
    + {{#each itents as |intent|}} + + {{/each}} +
    + +
    +
    +{{#if create }} + +{{ else }} + +{{/if}} + +{{# if (and item.ID (not-eq item.ID 'anonymous')) }} + {{#confirmation-dialog message='Are you sure you want to delete this Intention?'}} + {{#block-slot 'action' as |confirm|}} + + {{/block-slot}} + {{#block-slot 'dialog' as |execute cancel message|}} +

    + {{message}} +

    + + + {{/block-slot}} + {{/confirmation-dialog}} +{{/if}} +
    +
    + diff --git a/ui-v2/app/templates/dc/intentions/edit.hbs b/ui-v2/app/templates/dc/intentions/edit.hbs new file mode 100644 index 0000000000..fa90174940 --- /dev/null +++ b/ui-v2/app/templates/dc/intentions/edit.hbs @@ -0,0 +1,40 @@ +{{#app-view class="acl edit" loading=isLoading}} + {{#block-slot 'breadcrumbs'}} +
      +
    1. All Intentions
    2. +
    + {{/block-slot}} + {{#block-slot 'header'}} +

    +{{#if item.ID }} + Edit Intention +{{else}} + New Intention +{{/if}} +

    + {{/block-slot}} + {{#block-slot 'actions'}} +{{#if (not create) }} + {{#feedback-dialog type='inline'}} + {{#block-slot 'action' as |success error|}} + {{#copy-button success=(action success) error=(action error) clipboardText=item.ID title='copy UUID to clipboard'}} + Copy UUID + {{/copy-button}} + {{/block-slot}} + {{#block-slot 'success'}} +

    + Copied UUID! +

    + {{/block-slot}} + {{#block-slot 'error'}} +

    + Sorry, something went wrong! +

    + {{/block-slot}} + {{/feedback-dialog}} +{{/if}} + {{/block-slot}} + {{#block-slot 'content'}} + {{ partial 'dc/intentions/form'}} + {{/block-slot}} +{{/app-view}} \ No newline at end of file diff --git a/ui-v2/app/templates/dc/intentions/index.hbs b/ui-v2/app/templates/dc/intentions/index.hbs new file mode 100644 index 0000000000..8334b5a5c6 --- /dev/null +++ b/ui-v2/app/templates/dc/intentions/index.hbs @@ -0,0 +1,72 @@ +{{#app-view class="intention list"}} + {{#block-slot 'header'}} +

    + Intentions +

    + {{/block-slot}} + {{#block-slot 'actions'}} + Create + {{/block-slot}} + {{#block-slot 'toolbar'}} +{{#if (gt items.length 0) }} + {{intention-filter 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'}} + + {{item.SourceName}} + + + {{item.Action}} + + + {{item.DestinationName}} + + + {{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|}} +

    + {{message}} +

    + + + {{/block-slot}} + {{/confirmation-dialog}} + {{/block-slot}} + {{/tabular-collection}} +{{else}} +

    + There are no intentions. +

    +{{/if}} + {{/block-slot}} +{{/app-view}} \ No newline at end of file diff --git a/ui-v2/tests/integration/components/intention-filter-test.js b/ui-v2/tests/integration/components/intention-filter-test.js new file mode 100644 index 0000000000..7426285423 --- /dev/null +++ b/ui-v2/tests/integration/components/intention-filter-test.js @@ -0,0 +1,29 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('intention-filter', 'Integration | Component | intention filter', { + 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`{{intention-filter}}`); + + assert.equal( + this.$() + .text() + .trim(), + 'Search' + ); + + // // Template block usage: + // this.render(hbs` + // {{#intention-filter}} + // template block text + // {{/intention-filter}} + // `); + + // assert.equal(this.$().text().trim(), 'template block text'); +}); diff --git a/ui-v2/tests/unit/adapters/intention-test.js b/ui-v2/tests/unit/adapters/intention-test.js new file mode 100644 index 0000000000..7c84fcdf78 --- /dev/null +++ b/ui-v2/tests/unit/adapters/intention-test.js @@ -0,0 +1,12 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Adapter | intention', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let adapter = this.owner.lookup('adapter:intention'); + assert.ok(adapter); + }); +}); diff --git a/ui-v2/tests/unit/controllers/dc/intentions/create-test.js b/ui-v2/tests/unit/controllers/dc/intentions/create-test.js new file mode 100644 index 0000000000..1d9330ebd0 --- /dev/null +++ b/ui-v2/tests/unit/controllers/dc/intentions/create-test.js @@ -0,0 +1,12 @@ +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'] +}); + +// 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/intentions/edit-test.js b/ui-v2/tests/unit/controllers/dc/intentions/edit-test.js new file mode 100644 index 0000000000..a442f66951 --- /dev/null +++ b/ui-v2/tests/unit/controllers/dc/intentions/edit-test.js @@ -0,0 +1,12 @@ +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'] +}); + +// 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/intentions/index-test.js b/ui-v2/tests/unit/controllers/dc/intentions/index-test.js new file mode 100644 index 0000000000..d17e006b2d --- /dev/null +++ b/ui-v2/tests/unit/controllers/dc/intentions/index-test.js @@ -0,0 +1,12 @@ +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'] +}); + +// 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/mixins/intention/with-actions-test.js b/ui-v2/tests/unit/mixins/intention/with-actions-test.js new file mode 100644 index 0000000000..30f86de970 --- /dev/null +++ b/ui-v2/tests/unit/mixins/intention/with-actions-test.js @@ -0,0 +1,20 @@ +import EmberObject from '@ember/object'; +import IntentionWithActionsMixin from 'consul-ui/mixins/intention/with-actions'; +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('mixin:intention/with-actions', 'Unit | Mixin | intention/with actions', { + // Specify the other units that are required for this test. + needs: ['service:feedback'], + subject: function() { + const IntentionWithActionsObject = EmberObject.extend(IntentionWithActionsMixin); + this.register('test-container:intention/with-actions-object', IntentionWithActionsObject); + // TODO: May need to actually get this from the container + return IntentionWithActionsObject; + }, +}); + +// 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/intention-test.js b/ui-v2/tests/unit/models/intention-test.js new file mode 100644 index 0000000000..8707cffdd4 --- /dev/null +++ b/ui-v2/tests/unit/models/intention-test.js @@ -0,0 +1,14 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import { run } from '@ember/runloop'; + +module('Unit | Model | intention', 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('intention', {})); + assert.ok(model); + }); +}); diff --git a/ui-v2/tests/unit/routes/dc/intentions/create-test.js b/ui-v2/tests/unit/routes/dc/intentions/create-test.js new file mode 100644 index 0000000000..61b375b6f2 --- /dev/null +++ b/ui-v2/tests/unit/routes/dc/intentions/create-test.js @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('route:dc/intentions/create', 'Unit | Route | dc/intentions/create', { + // Specify the other units that are required for this test. + needs: ['service:intentions', 'service:feedback', 'service:flashMessages'], +}); + +test('it exists', function(assert) { + let route = this.subject(); + assert.ok(route); +}); diff --git a/ui-v2/tests/unit/routes/dc/intentions/edit-test.js b/ui-v2/tests/unit/routes/dc/intentions/edit-test.js new file mode 100644 index 0000000000..0d7b449f06 --- /dev/null +++ b/ui-v2/tests/unit/routes/dc/intentions/edit-test.js @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('route:dc/intentions/edit', 'Unit | Route | dc/intentions/edit', { + // Specify the other units that are required for this test. + needs: ['service:intentions', 'service:feedback', 'service:flashMessages'], +}); + +test('it exists', function(assert) { + let route = this.subject(); + assert.ok(route); +}); diff --git a/ui-v2/tests/unit/routes/dc/intentions/index-test.js b/ui-v2/tests/unit/routes/dc/intentions/index-test.js new file mode 100644 index 0000000000..0fb1bacb33 --- /dev/null +++ b/ui-v2/tests/unit/routes/dc/intentions/index-test.js @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('route:dc/intentions/index', 'Unit | Route | dc/intentions/index', { + // Specify the other units that are required for this test. + needs: ['service:intentions', 'service:feedback', 'service:flashMessages'], +}); + +test('it exists', function(assert) { + let route = this.subject(); + assert.ok(route); +}); diff --git a/ui-v2/tests/unit/serializers/intention-test.js b/ui-v2/tests/unit/serializers/intention-test.js new file mode 100644 index 0000000000..007dbae5e5 --- /dev/null +++ b/ui-v2/tests/unit/serializers/intention-test.js @@ -0,0 +1,24 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import { run } from '@ember/runloop'; + +module('Unit | Serializer | intention', 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('intention'); + + assert.ok(serializer); + }); + + test('it serializes records', function(assert) { + let store = this.owner.lookup('service:store'); + let record = run(() => store.createRecord('intention', {})); + + let serializedRecord = record.serialize(); + + assert.ok(serializedRecord); + }); +}); diff --git a/ui-v2/tests/unit/services/intentions-test.js b/ui-v2/tests/unit/services/intentions-test.js new file mode 100644 index 0000000000..fc2050c9c0 --- /dev/null +++ b/ui-v2/tests/unit/services/intentions-test.js @@ -0,0 +1,12 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('service:intentions', 'Unit | Service | intentions', { + // 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); +});