Merge "JSCS Cleanup - style cleanup for Launch Instance Source Step"

This commit is contained in:
Jenkins 2015-06-29 20:44:59 +00:00 committed by Gerrit Code Review
commit 10dbc487f3
9 changed files with 442 additions and 419 deletions

@ -33,7 +33,8 @@ ADD_JS_FILES = [
'dashboard/cloud-services/cloud-services.js',
LAUNCH_INST + 'launch-instance.js',
LAUNCH_INST + 'launch-instance.model.js',
LAUNCH_INST + 'source/source.js',
LAUNCH_INST + 'source/source.controller.js',
LAUNCH_INST + 'source/source-help.controller.js',
LAUNCH_INST + 'flavor/flavor.controller.js',
LAUNCH_INST + 'flavor/select-flavor-table.directive.js',
LAUNCH_INST + 'flavor/flavor-help.controller.js',

@ -71,6 +71,36 @@
windowClass: 'modal-dialog-wizard'
});
/**
* @name bootSourceTypes
* @description Boot source types
*/
module.constant('bootSourceTypes', {
IMAGE: 'image',
INSTANCE_SNAPSHOT: 'snapshot',
VOLUME: 'volume',
VOLUME_SNAPSHOT: 'volume_snapshot'
});
/**
* @ngdoc filter
* @name diskFormat
* @description
* Expects object and returns disk_format property value.
* Returns empty string if input is null or not an object.
* Uniquely required for the source step implementation of transfer tables
*/
module.filter('diskFormat', function() {
return function(input) {
if (input === null || !angular.isObject(input) ||
!angular.isDefined(input.disk_format) || input.disk_format === null) {
return '';
} else {
return input.disk_format.toUpperCase();
}
};
});
module.controller('LaunchInstanceWizardCtrl', [
'$scope',
'launchInstanceModel',

@ -1,16 +1,16 @@
<td></td>
<td colspan="{$ ::tableHeadCells.length $}" class="detail">
<td colspan="{$ ::ctrl.tableHeadCells.length $}" class="detail">
<div class="row">
<dl class="col-sm-2">
<dt>{$ ::label.min_disk $}</dt>
<dt>{$ ::ctrl.label.min_disk $}</dt>
<dd>
{$ (row.properties ? row.min_disk : row.volume_image_metadata.min_disk) || '--' $}
</dd>
</dl>
<dl class="col-sm-2">
<dt>{$ ::label.min_ram $}</dt>
<dt>{$ ::ctrl.label.min_ram $}</dt>
<dd>
{$ (row.properties ? row.min_ram : row.volume_image_metadata.min_ram) || '--' $}
</dd>

@ -0,0 +1,55 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function () {
'use strict';
/**
* @ngdoc controller
* @name LaunchInstanceSourceHelpController
* @description
* The `LaunchInstanceSourceHelpController` controller provides functions for
* configuring the help text used within the source step of the
* Launch Instance Wizard.
*/
angular
.module('hz.dashboard.launch-instance')
.controller('LaunchInstanceSourceHelpController', LaunchInstanceSourceHelpController);
function LaunchInstanceSourceHelpController() {
var ctrl = this;
ctrl.title = gettext('Select Source Help');
ctrl.instanceDetailsTitle = gettext('Instance Details');
ctrl.instanceDetailsParagraphs = [
// jscs:disable maximumLineLength
gettext('An instance name is required and used to help you uniquely identify your instance in the dashboard.'),
gettext('If you select an availability zone and plan to use the boot from volume option, make sure that the availability zone you select for the instance is the same availability zone where your bootable volume resides.')
// jscs:enable maximumLineLength
];
ctrl.instanceSourceTitle = gettext('Instance Source');
ctrl.instanceSourceParagraphs = [
// jscs:disable maximumLineLength
gettext('If you want to create an instance that uses ephemeral storage, meaning the instance data is lost when the instance is deleted, then choose one of the following boot sources:'),
gettext('<li><b>Image</b>: This option uses an image to boot the instance.</li>'),
gettext('<li><b>Instance Snapshot</b>: This option uses an instance snapshot to boot the instance.</li>'),
gettext('If you want to create an instance that uses persistent storage, meaning the instance data is saved when the instance is deleted, then select one of the following boot options:'),
gettext('<li><b>Image (with Create New Volume checked)</b>: This options uses an image to boot the instance, and creates a new volume to persist instance data. You can specify volume size and whether to delete the volume on termination of the instance.</li>'),
gettext('<li><b>Volume</b>: This option uses a volume that already exists. It does not create a new volume. You can choose to delete the volume on termination of the instance. <em>Note: when selecting Volume, you can only launch one instance.</em></li>'),
gettext('<li><b>Volume Snapshot</b>: This option uses a volume snapshot to boot the instance, and creates a new volume to persist instance data. You can choose to delete the volume on termination of the instance.</li>')
// jscs:enable maximumLineLength
];
}
})();

@ -1,83 +1,59 @@
/*
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function () {
'use strict';
var push = [].push;
/**
* @ngdoc overview
* @name hz.dashboard.launch-instance
* @description
*
* # hz.dashboard.launch-instance
*
* The `hz.dashboard.launch-instance` module allows a user
* to launch an instance via the multi-step wizard framework
*
*/
var module = angular.module('hz.dashboard.launch-instance');
/**
* @name bootSourceTypes
* @description Boot source types
*/
module.constant('bootSourceTypes', {
IMAGE: 'image',
INSTANCE_SNAPSHOT: 'snapshot',
VOLUME: 'volume',
VOLUME_SNAPSHOT: 'volume_snapshot'
});
/**
* @ngdoc filter
* @name diskFormat
* @description
* Expects object and returns disk_format property value.
* Returns empty string if input is null or not an object.
* Uniquely required for the source step implementation of transfer tables
*/
module.filter('diskFormat', function() {
return function(input) {
if (input === null || !angular.isObject(input) ||
!angular.isDefined(input.disk_format) || input.disk_format === null) {
return '';
} else {
return input.disk_format.toUpperCase();
}
};
});
/**
* @ngdoc controller
* @name LaunchInstanceSourceCtrl
* @name LaunchInstanceSourceController
* @description
* The `LaunchInstanceSourceCtrl` controller provides functions for
* The `LaunchInstanceSourceController` controller provides functions for
* configuring the source step of the Launch Instance Wizard.
*
*/
module.controller('LaunchInstanceSourceCtrl', [
'$scope',
'bootSourceTypes',
'bytesFilter',
'horizon.framework.widgets.charts.donutChartSettings',
'dateFilter',
'decodeFilter',
'diskFormatFilter',
'gbFilter',
'horizon.framework.widgets.charts.quotaChartDefaults',
LaunchInstanceSourceCtrl
]);
var push = [].push;
function LaunchInstanceSourceCtrl($scope,
bootSourceTypes,
bytesFilter,
donutChartSettings,
dateFilter,
decodeFilter,
diskFormatFilter,
gbFilter,
quotaChartDefaults) {
angular
.module('hz.dashboard.launch-instance')
.controller('LaunchInstanceSourceController', LaunchInstanceSourceController);
$scope.label = {
LaunchInstanceSourceController.$inject = [
'$scope',
'bootSourceTypes',
'bytesFilter',
'horizon.framework.widgets.charts.donutChartSettings',
'dateFilter',
'decodeFilter',
'diskFormatFilter',
'gbFilter',
'horizon.framework.widgets.charts.quotaChartDefaults'
];
function LaunchInstanceSourceController($scope,
bootSourceTypes,
bytesFilter,
donutChartSettings,
dateFilter,
decodeFilter,
diskFormatFilter,
gbFilter,
quotaChartDefaults) {
var ctrl = this;
ctrl.label = {
title: gettext('Instance Details'),
// jscs:disable maximumLineLength
subtitle: gettext('Please provide the initial host name for the instance, the availability zone where it will be deployed, and the instance count. Increase the Count to create multiple instances with the same settings.'),
@ -100,16 +76,16 @@
// Error text for invalid fields
// jscs:disable maximumLineLength
$scope.bootSourceTypeError = gettext('Volumes can only be attached to 1 active instance at a time. Please either set your instance count to 1 or select a different source type.');
ctrl.bootSourceTypeError = gettext('Volumes can only be attached to 1 active instance at a time. Please either set your instance count to 1 or select a different source type.');
// jscs:enable maximumLineLength
$scope.instanceNameError = gettext('A name is required for your instance.');
$scope.instanceCountError = gettext(
ctrl.instanceNameError = gettext('A name is required for your instance.');
ctrl.instanceCountError = gettext(
'Instance count is required and must be an integer of at least 1'
);
$scope.volumeSizeError = gettext('Volume size is required and must be an integer');
ctrl.volumeSizeError = gettext('Volume size is required and must be an integer');
// toggle button label/value defaults
$scope.toggleButtonOptions = [
ctrl.toggleButtonOptions = [
{ label: gettext('Yes'), value: true },
{ label: gettext('No'), value: false }
];
@ -117,35 +93,27 @@
/*
* Boot Sources
*/
$scope.bootSourcesOptions = [
ctrl.bootSourcesOptions = [
{ type: bootSourceTypes.IMAGE, label: gettext('Image') },
{ type: bootSourceTypes.INSTANCE_SNAPSHOT, label: gettext('Instance Snapshot') },
{ type: bootSourceTypes.VOLUME, label: gettext('Volume') },
{ type: bootSourceTypes.VOLUME_SNAPSHOT, label: gettext('Volume Snapshot') }
];
$scope.updateBootSourceSelection = function (selectedSource) {
$scope.currentBootSource = selectedSource;
$scope.model.newInstanceSpec.vol_create = false;
$scope.model.newInstanceSpec.vol_delete_on_terminate = false;
changeBootSource(selectedSource);
validateBootSourceType();
};
ctrl.updateBootSourceSelection = updateBootSourceSelection;
/*
* Transfer table
*/
$scope.tableHeadCells = [];
$scope.tableBodyCells = [];
$scope.tableData = {};
$scope.helpText = {};
$scope.maxInstanceCount = 1;
$scope.sourceDetails =
ctrl.tableHeadCells = [];
ctrl.tableBodyCells = [];
ctrl.tableData = {};
ctrl.helpText = {};
ctrl.maxInstanceCount = 1;
ctrl.sourceDetails =
'/static/dashboard/launch-instance/source/source-details.html';
var selection = $scope.selection = $scope.model.newInstanceSpec.source;
var selection = ctrl.selection = $scope.model.newInstanceSpec.source;
var bootSources = {
image: {
@ -244,52 +212,10 @@
]
};
// Dynamically update page based on boot source selection
function changeBootSource(key, preSelection) {
updateDataSource(key, preSelection);
updateHelpText(key);
updateTableHeadCells(key);
updateTableBodyCells(key);
updateChart();
updateMaxInstanceCount();
}
function updateDataSource(key, preSelection) {
selection.length = 0;
if (preSelection) {
push.apply(selection, preSelection);
}
angular.extend($scope.tableData, bootSources[key]);
}
function updateHelpText() {
angular.extend($scope.helpText, {
noneAllocText: gettext('Select a source from those listed below.'),
availHelpText: gettext('Select one'),
// jscs:disable maximumLineLength
volumeAZHelpText: gettext('When selecting volume as boot source, please ensure the instance\'s availability zone is compatible with your volume\'s availability zone.')
// jscs:enable maximumLineLength
});
}
function updateTableHeadCells(key) {
refillArray($scope.tableHeadCells, tableHeadCellsMap[key]);
}
function updateTableBodyCells(key) {
refillArray($scope.tableBodyCells, tableBodyCellsMap[key]);
}
function refillArray(arrayToRefill, contentArray) {
arrayToRefill.length = 0;
Array.prototype.push.apply(arrayToRefill, contentArray);
}
/*
* Donut chart
*/
$scope.chartSettings = donutChartSettings;
ctrl.chartSettings = donutChartSettings;
var maxTotalInstances = 1; // Must have default value > 0
var totalInstancesUsed = 0;
@ -301,7 +227,7 @@
totalInstancesUsed = $scope.model.novaLimits.totalInstancesUsed;
}
$scope.instanceStats = {
ctrl.instanceStats = {
title: gettext('Total Instances'),
maxLimit: maxTotalInstances,
label: '100%',
@ -364,7 +290,7 @@
$scope.$watch(
function () {
return $scope.tableData.allocated.length;
return ctrl.tableData.allocated.length;
},
function (newValue, oldValue) {
if (newValue !== oldValue) {
@ -374,29 +300,103 @@
}
);
$scope.$watchCollection(
function () {
return $scope.model.images;
},
function () {
$scope.initPromise.then(function () {
$scope.$applyAsync(function () {
if ($scope.launchContext.imageId) {
setSourceImageWithId($scope.launchContext.imageId);
}
});
});
}
);
// Initialize
changeBootSource(ctrl.bootSourcesOptions[0].type);
if (!$scope.model.newInstanceSpec.source_type) {
$scope.model.newInstanceSpec.source_type = ctrl.bootSourcesOptions[0];
ctrl.currentBootSource = ctrl.bootSourcesOptions[0].type;
}
////////////////////
function updateBootSourceSelection(selectedSource) {
ctrl.currentBootSource = selectedSource;
$scope.model.newInstanceSpec.vol_create = false;
$scope.model.newInstanceSpec.vol_delete_on_terminate = false;
changeBootSource(selectedSource);
validateBootSourceType();
}
// Dynamically update page based on boot source selection
function changeBootSource(key, preSelection) {
updateDataSource(key, preSelection);
updateHelpText(key);
updateTableHeadCells(key);
updateTableBodyCells(key);
updateChart();
updateMaxInstanceCount();
}
function updateDataSource(key, preSelection) {
selection.length = 0;
if (preSelection) {
push.apply(selection, preSelection);
}
angular.extend(ctrl.tableData, bootSources[key]);
}
function updateHelpText() {
angular.extend(ctrl.helpText, {
noneAllocText: gettext('Select a source from those listed below.'),
availHelpText: gettext('Select one'),
// jscs:disable maximumLineLength
volumeAZHelpText: gettext('When selecting volume as boot source, please ensure the instance\'s availability zone is compatible with your volume\'s availability zone.')
// jscs:enable maximumLineLength
});
}
function updateTableHeadCells(key) {
refillArray(ctrl.tableHeadCells, tableHeadCellsMap[key]);
}
function updateTableBodyCells(key) {
refillArray(ctrl.tableBodyCells, tableBodyCellsMap[key]);
}
function refillArray(arrayToRefill, contentArray) {
arrayToRefill.length = 0;
Array.prototype.push.apply(arrayToRefill, contentArray);
}
function updateChart() {
// Initialize instance_count to 1
if ($scope.model.newInstanceSpec.instance_count <= 0) {
$scope.model.newInstanceSpec.instance_count = 1;
}
var data = $scope.instanceStats.data;
var data = ctrl.instanceStats.data;
var added = $scope.model.newInstanceSpec.instance_count || 1;
var remaining = Math.max(0, maxTotalInstances - totalInstancesUsed - added);
$scope.instanceStats.maxLimit = maxTotalInstances;
ctrl.instanceStats.maxLimit = maxTotalInstances;
data[0].value = totalInstancesUsed;
data[1].value = added;
data[2].value = remaining;
var quotaCalc = Math.round((totalInstancesUsed + added) / maxTotalInstances * 100);
$scope.instanceStats.overMax = quotaCalc > 100 ? true : false;
$scope.instanceStats.label = quotaCalc + '%';
$scope.instanceStats = angular.extend({}, $scope.instanceStats);
ctrl.instanceStats.overMax = quotaCalc > 100 ? true : false;
ctrl.instanceStats.label = quotaCalc + '%';
ctrl.instanceStats = angular.extend({}, ctrl.instanceStats);
}
//
// Validations
//
/*
* Validation
*/
/*
* If boot source type is 'image' and 'Create New Volume' is checked, set the minimum volume
@ -405,33 +405,33 @@
function checkVolumeForImage() {
var source = selection ? selection[0] : undefined;
if (source && $scope.currentBootSource === bootSourceTypes.IMAGE) {
if (source && ctrl.currentBootSource === bootSourceTypes.IMAGE) {
var imageGb = source.size * 1e-9;
var imageDisk = source.min_disk;
$scope.minVolumeSize = Math.ceil(Math.max(imageGb, imageDisk));
ctrl.minVolumeSize = Math.ceil(Math.max(imageGb, imageDisk));
var volumeSizeText = gettext('The volume size must be at least %(minVolumeSize)s GB');
var volumeSizeObj = { minVolumeSize: $scope.minVolumeSize };
$scope.minVolumeSizeError = interpolate(volumeSizeText, volumeSizeObj, true);
var volumeSizeObj = { minVolumeSize: ctrl.minVolumeSize };
ctrl.minVolumeSizeError = interpolate(volumeSizeText, volumeSizeObj, true);
} else {
$scope.minVolumeSize = undefined;
ctrl.minVolumeSize = undefined;
}
}
// Update the maximum instance count based on nova limits
function updateMaxInstanceCount() {
$scope.maxInstanceCount = maxTotalInstances - totalInstancesUsed;
ctrl.maxInstanceCount = maxTotalInstances - totalInstancesUsed;
var instanceCountText = gettext(
'The instance count must not exceed your quota available of %(maxInstanceCount)s instances'
);
var instanceCountObj = { maxInstanceCount: $scope.maxInstanceCount };
$scope.instanceCountMaxError = interpolate(instanceCountText, instanceCountObj, true);
var instanceCountObj = { maxInstanceCount: ctrl.maxInstanceCount };
ctrl.instanceCountMaxError = interpolate(instanceCountText, instanceCountObj, true);
}
// Validator for boot source type. Instance count must to be 1 if volume selected
function validateBootSourceType() {
var bootSourceType = $scope.currentBootSource;
var bootSourceType = ctrl.currentBootSource;
var instanceCount = $scope.model.newInstanceSpec.instance_count;
/*
@ -461,73 +461,9 @@
var pre = findSourceById($scope.model.images, id);
if (pre) {
changeBootSource(bootSourceTypes.IMAGE, [pre]);
$scope.model.newInstanceSpec.source_type = $scope.bootSourcesOptions[0];
$scope.currentBootSource = $scope.bootSourcesOptions[0].type;
$scope.model.newInstanceSpec.source_type = ctrl.bootSourcesOptions[0];
ctrl.currentBootSource = ctrl.bootSourcesOptions[0].type;
}
}
$scope.$watchCollection(
function () {
return $scope.model.images;
},
function () {
$scope.initPromise.then(function () {
$scope.$applyAsync(function () {
if ($scope.launchContext.imageId) {
setSourceImageWithId($scope.launchContext.imageId);
}
});
});
}
);
// Initialize
changeBootSource($scope.bootSourcesOptions[0].type);
if (!$scope.model.newInstanceSpec.source_type) {
$scope.model.newInstanceSpec.source_type = $scope.bootSourcesOptions[0];
$scope.currentBootSource = $scope.bootSourcesOptions[0].type;
}
}
/**
* @ngdoc controller
* @name LaunchInstanceSourceHelpCtrl
* @description
* The `LaunchInstanceSourceHelpCtrl` controller provides functions for
* configuring the help text used within the source step of the
* Launch Instance Wizard.
*/
module.controller('LaunchInstanceSourceHelpCtrl', [
LaunchInstanceSourceHelpCtrl
]);
function LaunchInstanceSourceHelpCtrl() {
var ctrl = this;
ctrl.title = gettext('Select Source Help');
ctrl.instanceDetailsTitle = gettext('Instance Details');
ctrl.instanceDetailsParagraphs = [
// jscs:disable maximumLineLength
gettext('An instance name is required and used to help you uniquely identify your instance in the dashboard.'),
gettext('If you select an availability zone and plan to use the boot from volume option, make sure that the availability zone you select for the instance is the same availability zone where your bootable volume resides.')
// jscs:enable maximumLineLength
];
ctrl.instanceSourceTitle = gettext('Instance Source');
ctrl.instanceSourceParagraphs = [
// jscs:disable maximumLineLength
gettext('If you want to create an instance that uses ephemeral storage, meaning the instance data is lost when the instance is deleted, then choose one of the following boot sources:'),
gettext('<li><b>Image</b>: This option uses an image to boot the instance.</li>'),
gettext('<li><b>Instance Snapshot</b>: This option uses an instance snapshot to boot the instance.</li>'),
gettext('If you want to create an instance that uses persistent storage, meaning the instance data is saved when the instance is deleted, then select one of the following boot options:'),
gettext('<li><b>Image (with Create New Volume checked)</b>: This options uses an image to boot the instance, and creates a new volume to persist instance data. You can specify volume size and whether to delete the volume on termination of the instance.</li>'),
gettext('<li><b>Volume</b>: This option uses a volume that already exists. It does not create a new volume. You can choose to delete the volume on termination of the instance. <em>Note: when selecting Volume, you can only launch one instance.</em></li>'),
gettext('<li><b>Volume Snapshot</b>: This option uses a volume snapshot to boot the instance, and creates a new volume to persist instance data. You can choose to delete the volume on termination of the instance.</li>')
// jscs:enable maximumLineLength
];
}
})();

@ -1,4 +1,4 @@
<div ng-controller="LaunchInstanceSourceHelpCtrl as sourceHelpCtrl">
<div ng-controller="LaunchInstanceSourceHelpController as sourceHelpCtrl">
<h1>{$ ::sourceHelpCtrl.title $}</h1>
<h4>{$ ::sourceHelpCtrl.instanceDetailsTitle $}</h4>

@ -1,9 +1,9 @@
<div ng-controller="LaunchInstanceSourceCtrl">
<h1>{$ ::label.title $}</h1>
<div ng-controller="LaunchInstanceSourceController as ctrl">
<h1>{$ ::ctrl.label.title $}</h1>
<!--content-->
<div class="content">
<div class="subtitle">{$ ::label.subtitle $}</div>
<div class="subtitle">{$ ::ctrl.label.subtitle $}</div>
<!--selected-source form-->
<div class="selected-source clearfix">
@ -13,10 +13,10 @@
<div class="col-sm-12 col-md-5">
<div class="form-field required instance-name"
ng-class="{ 'has-warning': launchInstanceSourceForm['instance-name'].$invalid && launchInstanceSourceForm['instance-name'].$dirty }">
<label class="on-top">{$ ::label.instanceName $}</label>
<label class="on-top">{$ ::ctrl.label.instanceName $}</label>
<span class="fa fa-exclamation-triangle invalid"
ng-show="launchInstanceSourceForm['instance-name'].$invalid && launchInstanceSourceForm.$dirty"
popover="{$ instanceNameError $}"
popover="{$ ctrl.instanceNameError $}"
popover-placement="top" popover-append-to-body="true"
popover-trigger="hover"></span>
<input name="instance-name" type="text" class="form-control input-sm"
@ -26,7 +26,7 @@
<div class="col-sm-12 col-md-5">
<div class="form-field availability-zone">
<label class="on-top">{$ ::label.availabilityZone $}</label>
<label class="on-top">{$ ::ctrl.label.availabilityZone $}</label>
<select class="form-control input-sm"
ng-options="zone for zone in model.availabilityZones"
ng-model="model.newInstanceSpec.availability_zone">
@ -37,17 +37,17 @@
<div class="col-sm-6 col-md-2">
<div class="form-field instance_count"
ng-class="{ 'has-warning': launchInstanceSourceForm['instance-count'].$invalid }">
<label class="on-top">{$ ::label.instance_count $}</label>
<label class="on-top">{$ ::ctrl.label.instance_count $}</label>
<span class="fa fa-exclamation-triangle invalid"
ng-show="launchInstanceSourceForm['instance-count'].$invalid"
popover="{$ launchInstanceSourceForm['instance-count'].$error.validateNumberMax ? instanceCountMaxError : instanceCountError $}"
popover="{$ launchInstanceSourceForm['instance-count'].$error.validateNumberMax ? ctrl.instanceCountMaxError : ctrl.instanceCountError $}"
popover-placement="top" popover-append-to-body="true"
popover-trigger="hover"></span>
<input name="instance-count" type="number" class="form-control input-sm"
ng-model="model.newInstanceSpec.instance_count"
ng-required="true" ng-pattern="/^[0-9]+$/"
validate-number-min="1"
validate-number-max="{$ maxInstanceCount $}">
validate-number-max="{$ ctrl.maxInstanceCount $}">
</div>
</div>
</div>
@ -57,8 +57,8 @@
<!--instance chart-->
<div class="col-xs-12 col-sm-4">
<div class="chart">
<pie-chart chart-data="instanceStats"
chart-settings="chartSettings"></pie-chart>
<pie-chart chart-data="ctrl.instanceStats"
chart-settings="ctrl.chartSettings"></pie-chart>
</div>
</div>
<!--end instance chart-->
@ -66,8 +66,8 @@
</div>
<!--end selected-source form-->
<h2 class="section-title">{$ ::label.instanceSourceTitle $}</h2>
<div class="subtitle">{$ ::label.instanceSourceSubTitle $}</div>
<h2 class="section-title">{$ ::ctrl.label.instanceSourceTitle $}</h2>
<div class="subtitle">{$ ::ctrl.label.instanceSourceSubTitle $}</div>
<!--instance-source form-->
<div class="instance-source clearfix">
@ -76,15 +76,15 @@
<div class="col-xs-12 col-sm-3">
<div class="form-field image"
ng-class="{ 'has-warning': launchInstanceSourceForm['boot-source-type'].$invalid }">
<label class="on-top">{$ ::label.bootSource $}</label>
<label class="on-top">{$ ::ctrl.label.bootSource $}</label>
<span class="fa fa-exclamation-triangle invalid"
ng-show="launchInstanceSourceForm['boot-source-type'].$invalid"
popover="{$ bootSourceTypeError $}"
popover="{$ ctrl.bootSourceTypeError $}"
popover-placement="top" popover-append-to-body="true"
popover-trigger="hover"></span>
<select name="boot-source-type" class="form-control input-sm"
ng-options="src.label for src in bootSourcesOptions track by src.type"
ng-change="updateBootSourceSelection(model.newInstanceSpec.source_type.type)"
ng-options="src.label for src in ctrl.bootSourcesOptions track by src.type"
ng-change="ctrl.updateBootSourceSelection(model.newInstanceSpec.source_type.type)"
ng-model="model.newInstanceSpec.source_type">
</select>
</div>
@ -97,11 +97,11 @@
<div class="col-xs-12 col-sm-3">
<div class="form-group create-volume">
<label class="on-top">{$ ::label.volumeCreate $}</label>
<label class="on-top">{$ ::ctrl.label.volumeCreate $}</label>
<div class="form-field">
<div class="btn-group">
<label class="btn btn-toggle"
ng-repeat="option in toggleButtonOptions"
ng-repeat="option in ctrl.toggleButtonOptions"
ng-model="model.newInstanceSpec.vol_create"
btn-radio="option.value">{$ ::option.label $}</label>
</div>
@ -113,7 +113,7 @@
ng-if="model.newInstanceSpec.vol_create === true">
<div class="col-xs-12 col-sm-3">
<div class="form-field">
<label>{$ ::label.volumeDeviceName $}</label>
<label>{$ ::ctrl.label.volumeDeviceName $}</label>
<input class="form-control input-sm"
ng-model="model.newInstanceSpec.vol_device_name"
type="text">
@ -124,27 +124,28 @@
<div class="col-xs-12 col-sm-2 volume-size-wrapper" ng-if="model.newInstanceSpec.vol_create == true">
<div class="form-field volume-size"
ng-class="{ 'has-warning': launchInstanceSourceForm['volume-size'].$invalid }">
<label class="on-top">{$ ::label.volumeSize $}</label>
<label class="on-top">{$ ::ctrl.label.volumeSize $}</label>
<span class="fa fa-exclamation-triangle invalid"
ng-show="launchInstanceSourceForm['volume-size'].$invalid"
popover="{$ launchInstanceSourceForm['volume-size'].$error.validateNumberMin ? minVolumeSizeError : volumeSizeError $}"
popover="{$ launchInstanceSourceForm['volume-size'].$error.validateNumberMin ? ctrl.minVolumeSizeError :
ctrl.volumeSizeError $}"
popover-placement="top" popover-append-to-body="true"
popover-trigger="hover"></span>
<input name="volume-size" type="number"
class="form-control input-sm volume-size"
ng-model="model.newInstanceSpec.vol_size"
ng-pattern="/^[0-9]+$/" ng-required="true"
validate-number-min="{$ minVolumeSize $}">
validate-number-min="{$ ctrl.minVolumeSize $}">
</div>
</div>
<div class="col-xs-12 col-sm-4" ng-if="model.newInstanceSpec.vol_create == true">
<div class="form-group delete-volume">
<label class="on-top">{$ ::label.deleteVolumeOnTerminate $}</label>
<label class="on-top">{$ ::ctrl.label.deleteVolumeOnTerminate $}</label>
<div class="form-field">
<div class="btn-group">
<label class="btn btn-toggle"
ng-repeat="option in toggleButtonOptions"
ng-repeat="option in ctrl.toggleButtonOptions"
ng-model="model.newInstanceSpec.vol_delete_on_terminate"
btn-radio="option.value">{$ ::option.label $}</label>
</div>
@ -159,11 +160,11 @@
<div class="col-xs-12 col-sm-6">
<div class="form-group delete-volume">
<label class="on-top">{$ ::label.deleteVolumeOnTerminate $}</label>
<label class="on-top">{$ ::ctrl.label.deleteVolumeOnTerminate $}</label>
<div class="form-field">
<div class="btn-group">
<label class="btn btn-toggle"
ng-repeat="option in toggleButtonOptions"
ng-repeat="option in ctrl.toggleButtonOptions"
ng-model="model.newInstanceSpec.vol_delete_on_terminate"
btn-radio="option.value">{$ ::option.label $}</label>
</div>
@ -178,37 +179,37 @@
</div>
<!--end instance-source form-->
<transfer-table help-text="helpText"
tr-model="tableData">
<allocated validate-number-min="1" ng-model="tableData.allocated.length">
<transfer-table help-text="ctrl.helpText"
tr-model="ctrl.tableData">
<allocated validate-number-min="1" ng-model="ctrl.tableData.allocated.length">
<table class="table-striped table-rsp table-detail modern"
hz-table
st-safe-src="tableData.allocated"
st-table="tableData.displayAllocated">
st-safe-src="ctrl.tableData.allocated"
st-table="ctrl.tableData.displayAllocated">
<!-- transfer table, allocated table head -->
<thead>
<tr>
<th class="expander"></th>
<th ng-class="tableHeadCells[0].classList"
ng-style="tableHeadCells[0].style">
{$ tableHeadCells[0].text $}
<th ng-class="ctrl.tableHeadCells[0].classList"
ng-style="ctrl.tableHeadCells[0].style">
{$ ctrl.tableHeadCells[0].text $}
</th>
<th ng-class="tableHeadCells[1].classList"
ng-style="tableHeadCells[1].style">
{$ tableHeadCells[1].text $}
<th ng-class="ctrl.tableHeadCells[1].classList"
ng-style="ctrl.tableHeadCells[1].style">
{$ ctrl.tableHeadCells[1].text $}
</th>
<th ng-class="tableHeadCells[2].classList"
ng-style="tableHeadCells[2].style">
{$ tableHeadCells[2].text $}
<th ng-class="ctrl.tableHeadCells[2].classList"
ng-style="ctrl.tableHeadCells[2].style">
{$ ctrl.tableHeadCells[2].text $}
</th>
<th ng-class="tableHeadCells[3].classList"
ng-style="tableHeadCells[3].style">
{$ tableHeadCells[3].text $}
<th ng-class="ctrl.tableHeadCells[3].classList"
ng-style="ctrl.tableHeadCells[3].style">
{$ ctrl.tableHeadCells[3].text $}
</th>
<th ng-class="tableHeadCells[4].classList"
ng-style="tableHeadCells[4].style">
{$ tableHeadCells[4].text $}
<th ng-class="ctrl.tableHeadCells[4].classList"
ng-style="ctrl.tableHeadCells[4].style">
{$ ctrl.tableHeadCells[4].text $}
</th>
<th class="action"></th>
</tr>
@ -217,45 +218,45 @@
<!-- transfer table, allocated table body -->
<tbody>
<tr ng-if="tableData.allocated.length === 0">
<td colspan="{$ tableHeadCells.length + 2 $}">
<tr ng-if="ctrl.tableData.allocated.length === 0">
<td colspan="{$ ctrl.tableHeadCells.length + 2 $}">
<div class="no-rows-help">
{$ ::trCtrl.helpText.noneAllocText $}
</div>
</td>
</tr>
<tr ng-repeat-start="row in selection">
<tr ng-repeat-start="row in ctrl.selection">
<td class="expander">
<span class="fa fa-chevron-right"
hz-expand-detail
title="{$ ::trCtrl.helpText.expandDetailsText $}"></span>
</td>
<td ng-class="tableBodyCells[0].classList"
ng-style="tableBodyCells[0].style">
{$ tableBodyCells[0].filter ? tableBodyCells[0].filter(row[tableBodyCells[0].key], tableBodyCells[0].filterArg) : row[tableBodyCells[0].key] $}
<td ng-class="ctrl.tableBodyCells[0].classList"
ng-style="ctrl.tableBodyCells[0].style">
{$ ctrl.tableBodyCells[0].filter ? ctrl.tableBodyCells[0].filter(row[ctrl.tableBodyCells[0].key], ctrl.tableBodyCells[0].filterArg) : row[ctrl.tableBodyCells[0].key] $}
</td>
<td ng-class="tableBodyCells[1].classList"
ng-style="tableBodyCells[1].style">
{$ tableBodyCells[1].filter ? tableBodyCells[1].filter(row[tableBodyCells[1].key], tableBodyCells[1].filterArg) : row[tableBodyCells[1].key] $}
<td ng-class="ctrl.tableBodyCells[1].classList"
ng-style="ctrl.tableBodyCells[1].style">
{$ ctrl.tableBodyCells[1].filter ? ctrl.tableBodyCells[1].filter(row[ctrl.tableBodyCells[1].key], ctrl.tableBodyCells[1].filterArg) : row[ctrl.tableBodyCells[1].key] $}
</td>
<td ng-class="tableBodyCells[2].classList"
ng-style="tableBodyCells[2].style">
{$ tableBodyCells[2].filter ? tableBodyCells[2].filter(row[tableBodyCells[2].key], tableBodyCells[2].filterArg) : row[tableBodyCells[2].key] $}
<td ng-class="ctrl.tableBodyCells[2].classList"
ng-style="ctrl.tableBodyCells[2].style">
{$ ctrl.tableBodyCells[2].filter ? ctrl.tableBodyCells[2].filter(row[ctrl.tableBodyCells[2].key], ctrl.tableBodyCells[2].filterArg) : row[ctrl.tableBodyCells[2].key] $}
</td>
<td ng-class="tableBodyCells[3].classList"
ng-style="tableBodyCells[3].style">
{$ tableBodyCells[3].filter ? tableBodyCells[3].filter(row[tableBodyCells[3].key], tableBodyCells[3].filterArg) : row[tableBodyCells[3].key] $}
<td ng-class="ctrl.tableBodyCells[3].classList"
ng-style="ctrl.tableBodyCells[3].style">
{$ ctrl.tableBodyCells[3].filter ? ctrl.tableBodyCells[3].filter(row[ctrl.tableBodyCells[3].key], ctrl.tableBodyCells[3].filterArg) : row[ctrl.tableBodyCells[3].key] $}
</td>
<td ng-class="tableBodyCells[4].classList"
ng-style="tableBodyCells[4].style">
<td ng-class="ctrl.tableBodyCells[4].classList"
ng-style="ctrl.tableBodyCells[4].style">
<span ng-if="model.newInstanceSpec.source_type.type === 'volume' && row.availability_zone !== model.newInstanceSpec.availability_zone"
class="invalid fa fa-exclamation-triangle"
popover="{$ ::trCtrl.helpText.volumeAZHelpText $}"
popover-trigger="mouseenter mouseleave"
popover-append-to-body="true"
popover-placement="top"></span>
{$ tableBodyCells[4].filter ? tableBodyCells[4].filter(row[tableBodyCells[4].key], tableBodyCells[4].filterArg) : row[tableBodyCells[4].key] $}
{$ ctrl.tableBodyCells[4].filter ? ctrl.tableBodyCells[4].filter(row[ctrl.tableBodyCells[4].key], ctrl.tableBodyCells[4].filterArg) : row[ctrl.tableBodyCells[4].key] $}
</td>
<td class="action-col">
<action-list>
@ -269,15 +270,15 @@
</tr>
<tr class="detail-row"
ng-repeat-end
ng-include="sourceDetails">
ng-include="ctrl.sourceDetails">
</tr>
</tbody><!-- /transfer table, allocated table body -->
</table>
</allocated>
<available>
<table st-table="tableData.displayedAvailable"
st-safe-src="tableData.available"
<table st-table="ctrl.tableData.displayedAvailable"
st-safe-src="ctrl.tableData.available"
hz-table
class="table-striped table-rsp table-detail modern">
@ -285,7 +286,7 @@
<thead>
<tr>
<th class="search-header"
colspan="{$ tableHeadCells.length + 2 $}">
colspan="{$ ctrl.tableHeadCells.length + 2 $}">
<search-bar group-classes="input-group-sm"
icon-classes="fa-search">
</search-bar>
@ -293,35 +294,35 @@
</tr>
<tr>
<th class="expander"></th>
<th ng-attr-st-sort-default="{$ tableHeadCells[0].sortDefault $}"
ng-class="tableHeadCells[0].classList"
ng-style="tableHeadCells[0].style"
st-sort="{$ tableHeadCells[0].sortable && tableBodyCells[0].key $}">
{$ tableHeadCells[0].text $}
<th ng-attr-st-sort-default="{$ ctrl.tableHeadCells[0].sortDefault $}"
ng-class="ctrl.tableHeadCells[0].classList"
ng-style="ctrl.tableHeadCells[0].style"
st-sort="{$ ctrl.tableHeadCells[0].sortable && ctrl.tableBodyCells[0].key $}">
{$ ctrl.tableHeadCells[0].text $}
</th>
<th ng-attr-st-sort-default="{$ tableHeadCells[1].sortDefault $}"
ng-class="tableHeadCells[1].classList"
ng-style="tableHeadCells[1].style"
st-sort="{$ tableHeadCells[1].sortable && tableBodyCells[1].key $}">
{$ tableHeadCells[1].text $}
<th ng-attr-st-sort-default="{$ ctrl.tableHeadCells[1].sortDefault $}"
ng-class="ctrl.tableHeadCells[1].classList"
ng-style="ctrl.tableHeadCells[1].style"
st-sort="{$ ctrl.tableHeadCells[1].sortable && ctrl.tableBodyCells[1].key $}">
{$ ctrl.tableHeadCells[1].text $}
</th>
<th ng-attr-st-sort-default="{$ tableHeadCells[2].sortDefault $}"
ng-class="tableHeadCells[2].classList"
ng-style="tableHeadCells[2].style"
st-sort="{$ tableHeadCells[2].sortable && tableBodyCells[2].key $}">
{$ tableHeadCells[2].text $}
<th ng-attr-st-sort-default="{$ ctrl.tableHeadCells[2].sortDefault $}"
ng-class="ctrl.tableHeadCells[2].classList"
ng-style="ctrl.tableHeadCells[2].style"
st-sort="{$ ctrl.tableHeadCells[2].sortable && ctrl.tableBodyCells[2].key $}">
{$ ctrl.tableHeadCells[2].text $}
</th>
<th ng-attr-st-sort-default="{$ tableHeadCells[3].sortDefault $}"
ng-class="tableHeadCells[3].classList"
ng-style="tableHeadCells[3].style"
st-sort="{$ tableHeadCells[3].sortable && tableBodyCells[3].key $}">
{$ tableHeadCells[3].text $}
<th ng-attr-st-sort-default="{$ ctrl.tableHeadCells[3].sortDefault $}"
ng-class="ctrl.tableHeadCells[3].classList"
ng-style="ctrl.tableHeadCells[3].style"
st-sort="{$ ctrl.tableHeadCells[3].sortable && ctrl.tableBodyCells[3].key $}">
{$ ctrl.tableHeadCells[3].text $}
</th>
<th ng-attr-st-sort-default="{$ tableHeadCells[4].sortDefault $}"
ng-class="tableHeadCells[4].classList"
ng-style="tableHeadCells[4].style"
st-sort="{$ tableHeadCells[4].sortable && tableBodyCells[4].key $}">
{$ tableHeadCells[4].text $}
<th ng-attr-st-sort-default="{$ ctrl.tableHeadCells[4].sortDefault $}"
ng-class="ctrl.tableHeadCells[4].classList"
ng-style="ctrl.tableHeadCells[4].style"
st-sort="{$ ctrl.tableHeadCells[4].sortable && ctrl.tableBodyCells[4].key $}">
{$ ctrl.tableHeadCells[4].text $}
</th>
<th class="action"></th>
</tr>
@ -330,14 +331,14 @@
<tbody>
<tr ng-if="trCtrl.numDisplayedAvailable() === 0">
<td colspan="{$ tableHeadCells.length + 2 $}">
<td colspan="{$ ctrl.tableHeadCells.length + 2 $}">
<div class="no-rows-help">
{$ ::trCtrl.helpText.noneAvailText $}
</div>
</td>
</tr>
<tr ng-repeat-start="row in tableData.displayedAvailable track by row.id"
<tr ng-repeat-start="row in ctrl.tableData.displayedAvailable track by row.id"
ng-if="!trCtrl.allocatedIds[row.id]">
<td class="expander">
<span class="fa fa-chevron-right"
@ -345,31 +346,31 @@
title="{$ ::trCtrl.helpText.expandDetailsText $}">
</span>
</td>
<td ng-class="tableBodyCells[0].classList"
ng-style="tableBodyCells[0].style">
{$ tableBodyCells[0].filter ? tableBodyCells[0].filter(row[tableBodyCells[0].key], tableBodyCells[0].filterArg) : row[tableBodyCells[0].key] $}
<td ng-class="ctrl.tableBodyCells[0].classList"
ng-style="ctrl.tableBodyCells[0].style">
{$ ctrl.tableBodyCells[0].filter ? ctrl.tableBodyCells[0].filter(row[ctrl.tableBodyCells[0].key], ctrl.tableBodyCells[0].filterArg) : row[ctrl.tableBodyCells[0].key] $}
</td>
<td ng-class="tableBodyCells[1].classList"
ng-style="tableBodyCells[1].style">
{$ tableBodyCells[1].filter ? tableBodyCells[1].filter(row[tableBodyCells[1].key], tableBodyCells[1].filterArg) : row[tableBodyCells[1].key] $}
<td ng-class="ctrl.tableBodyCells[1].classList"
ng-style="ctrl.tableBodyCells[1].style">
{$ ctrl.tableBodyCells[1].filter ? ctrl.tableBodyCells[1].filter(row[ctrl.tableBodyCells[1].key], ctrl.tableBodyCells[1].filterArg) : row[ctrl.tableBodyCells[1].key] $}
</td>
<td ng-class="tableBodyCells[2].classList"
ng-style="tableBodyCells[2].style">
{$ tableBodyCells[2].filter ? tableBodyCells[2].filter(row[tableBodyCells[2].key], tableBodyCells[2].filterArg) : row[tableBodyCells[2].key] $}
<td ng-class="ctrl.tableBodyCells[2].classList"
ng-style="ctrl.tableBodyCells[2].style">
{$ ctrl.tableBodyCells[2].filter ? ctrl.tableBodyCells[2].filter(row[ctrl.tableBodyCells[2].key], ctrl.tableBodyCells[2].filterArg) : row[ctrl.tableBodyCells[2].key] $}
</td>
<td ng-class="tableBodyCells[3].classList"
ng-style="tableBodyCells[3].style">
{$ tableBodyCells[3].filter ? tableBodyCells[3].filter(row[tableBodyCells[3].key], tableBodyCells[3].filterArg) : row[tableBodyCells[3].key] $}
<td ng-class="ctrl.tableBodyCells[3].classList"
ng-style="ctrl.tableBodyCells[3].style">
{$ ctrl.tableBodyCells[3].filter ? ctrl.tableBodyCells[3].filter(row[ctrl.tableBodyCells[3].key], ctrl.tableBodyCells[3].filterArg) : row[ctrl.tableBodyCells[3].key] $}
</td>
<td ng-class="tableBodyCells[4].classList"
ng-style="tableBodyCells[4].style">
<td ng-class="ctrl.tableBodyCells[4].classList"
ng-style="ctrl.tableBodyCells[4].style">
<span ng-if="model.newInstanceSpec.source_type.type === 'volume' && row.availability_zone !== model.newInstanceSpec.availability_zone"
class="invalid fa fa-exclamation-triangle"
popover="{$ ::trCtrl.helpText.volumeAZHelpText $}"
popover-trigger="mouseenter mouseleave"
popover-append-to-body="true"
popover-placement="top"></span>
{$ tableBodyCells[4].filter ? tableBodyCells[4].filter(row[tableBodyCells[4].key], tableBodyCells[4].filterArg) : row[tableBodyCells[4].key] $}
{$ ctrl.tableBodyCells[4].filter ? ctrl.tableBodyCells[4].filter(row[ctrl.tableBodyCells[4].key], ctrl.tableBodyCells[4].filterArg) : row[ctrl.tableBodyCells[4].key] $}
</td>
<td class="action-col">
<action-list>
@ -383,7 +384,7 @@
</tr>
<tr class="detail-row"
ng-repeat-end
ng-include="sourceDetails">
ng-include="ctrl.sourceDetails">
</tr>
</tbody>
</table>

@ -1,4 +1,4 @@
[ng-controller="LaunchInstanceSourceCtrl"] {
[ng-controller="LaunchInstanceSourceController"] {
td.hi-light {
color: #0084d1;

@ -21,7 +21,7 @@
beforeEach(module('hz.dashboard.launch-instance'));
describe('LaunchInstanceSourceCtrl', function() {
describe('LaunchInstanceSourceController', function() {
var scope, ctrl, $browser, deferred;
beforeEach(module(function($provide) {
@ -56,55 +56,55 @@
'boot-source-type': { $setValidity: noop }
};
ctrl = $controller('LaunchInstanceSourceCtrl', { $scope: scope });
ctrl = $controller('LaunchInstanceSourceController', { $scope: scope });
scope.$digest();
}));
it('has its own labels', function() {
expect(scope.label).toBeDefined();
expect(Object.keys(scope.label).length).toBeGreaterThan(0);
expect(ctrl.label).toBeDefined();
expect(Object.keys(ctrl.label).length).toBeGreaterThan(0);
});
it('has defined error messages for invalid fields', function() {
expect(scope.bootSourceTypeError).toBeDefined();
expect(scope.instanceNameError).toBeDefined();
expect(scope.instanceCountError).toBeDefined();
expect(scope.volumeSizeError).toBeDefined();
expect(ctrl.bootSourceTypeError).toBeDefined();
expect(ctrl.instanceNameError).toBeDefined();
expect(ctrl.instanceCountError).toBeDefined();
expect(ctrl.volumeSizeError).toBeDefined();
});
it('defines the correct boot source options', function() {
expect(scope.bootSourcesOptions).toBeDefined();
expect(ctrl.bootSourcesOptions).toBeDefined();
var types = ['image', 'snapshot', 'volume', 'volume_snapshot'];
var opts = scope.bootSourcesOptions.map(function(x) { return x.type; });
var opts = ctrl.bootSourcesOptions.map(function(x) { return x.type; });
types.forEach(function(key) {
expect(opts).toContain(key);
});
expect(scope.bootSourcesOptions.length).toBe(types.length);
expect(ctrl.bootSourcesOptions.length).toBe(types.length);
});
it('initializes transfer table variables', function() {
// NOTE: these are set by the default, not the initial values.
// Arguably we shouldn't even set the original values.
expect(scope.tableHeadCells).toBeDefined();
expect(scope.tableHeadCells.length).toEqual(5);
expect(scope.tableBodyCells).toBeDefined();
expect(scope.tableBodyCells.length).toEqual(5);
expect(scope.tableData).toBeDefined();
expect(Object.keys(scope.tableData).length).toEqual(4);
expect(ctrl.tableHeadCells).toBeDefined();
expect(ctrl.tableHeadCells.length).toEqual(5);
expect(ctrl.tableBodyCells).toBeDefined();
expect(ctrl.tableBodyCells.length).toEqual(5);
expect(ctrl.tableData).toBeDefined();
expect(Object.keys(ctrl.tableData).length).toEqual(4);
// TODO really confused but the use of this helpText variable
// in the code, esp. the use of extend, rather than just setting it
// once.
expect(scope.helpText).toBeDefined();
expect(scope.helpText.noneAllocText).toBeDefined();
expect(scope.helpText.availHelpText).toBeDefined();
expect(ctrl.helpText).toBeDefined();
expect(ctrl.helpText.noneAllocText).toBeDefined();
expect(ctrl.helpText.availHelpText).toBeDefined();
});
it('initializes table data to reflect "image" selection', function() {
var list = [ { id: 'image-1'}, { id: 'image-2' } ]; // Use scope's values.
var sel = []; // None selected.
expect(scope.tableData).toEqual({
expect(ctrl.tableData).toEqual({
available: list,
allocated: sel,
displayedAvailable: list,
@ -114,7 +114,7 @@
it('defaults to first source type if none existing', function() {
expect(scope.model.newInstanceSpec.source_type.type).toBe('image');
expect(scope.currentBootSource).toBe('image');
expect(ctrl.currentBootSource).toBe('image');
});
it('defaults source to image-2 if launchContext.imageId = image-2', function() {
@ -123,9 +123,9 @@
$browser.defer.flush();
expect(scope.tableData.allocated[0]).toEqual({ id: 'image-2' });
expect(ctrl.tableData.allocated[0]).toEqual({ id: 'image-2' });
expect(scope.model.newInstanceSpec.source_type.type).toBe('image');
expect(scope.currentBootSource).toBe('image');
expect(ctrl.currentBootSource).toBe('image');
});
describe('Scope Functions', function() {
@ -136,25 +136,25 @@
it('updates the scope appropriately', function() {
var selSource = 'image';
scope.updateBootSourceSelection(selSource);
ctrl.updateBootSourceSelection(selSource);
expect(scope.currentBootSource).toEqual('image');
expect(ctrl.currentBootSource).toEqual('image');
expect(scope.model.newInstanceSpec.vol_create).toBe(false);
expect(scope.model.newInstanceSpec.vol_delete_on_terminate).toBe(false);
// check table data
expect(scope.tableData).toBeDefined();
expect(Object.keys(scope.tableData)).toEqual(tableKeys);
expect(scope.tableHeadCells.length).toBeGreaterThan(0);
expect(scope.tableBodyCells.length).toBeGreaterThan(0);
expect(ctrl.tableData).toBeDefined();
expect(Object.keys(ctrl.tableData)).toEqual(tableKeys);
expect(ctrl.tableHeadCells.length).toBeGreaterThan(0);
expect(ctrl.tableBodyCells.length).toBeGreaterThan(0);
expect(scope.maxInstanceCount).toBe(10);
expect(ctrl.maxInstanceCount).toBe(10);
// check chart data and labels
expect(scope.instanceStats.label).toBe('10%');
expect(scope.instanceStats.data[0].value).toEqual(0);
expect(scope.instanceStats.data[1].value).toEqual(1);
expect(scope.instanceStats.data[2].value).toEqual(9);
expect(ctrl.instanceStats.label).toBe('10%');
expect(ctrl.instanceStats.data[0].value).toEqual(0);
expect(ctrl.instanceStats.data[1].value).toEqual(1);
expect(ctrl.instanceStats.data[2].value).toEqual(9);
});
});
@ -164,13 +164,13 @@
scope.model.novaLimits.maxTotalInstances = 9;
scope.$digest();
expect(scope.maxInstanceCount).toBe(9);
expect(ctrl.maxInstanceCount).toBe(9);
// check chart data and labels
expect(scope.instanceStats.label).toBe('11%');
expect(scope.instanceStats.data[0].value).toEqual(0);
expect(scope.instanceStats.data[1].value).toEqual(1);
expect(scope.instanceStats.data[2].value).toEqual(8);
expect(ctrl.instanceStats.label).toBe('11%');
expect(ctrl.instanceStats.data[0].value).toEqual(0);
expect(ctrl.instanceStats.data[1].value).toEqual(1);
expect(ctrl.instanceStats.data[2].value).toEqual(8);
});
});
@ -180,39 +180,39 @@
scope.model.novaLimits.totalInstancesUsed = 1;
scope.$digest();
expect(scope.maxInstanceCount).toBe(9);
expect(ctrl.maxInstanceCount).toBe(9);
// check chart data and labels
expect(scope.instanceStats.label).toBe('20%');
expect(scope.instanceStats.data[0].value).toEqual(1);
expect(scope.instanceStats.data[1].value).toEqual(1);
expect(scope.instanceStats.data[2].value).toEqual(8);
expect(ctrl.instanceStats.label).toBe('20%');
expect(ctrl.instanceStats.data[0].value).toEqual(1);
expect(ctrl.instanceStats.data[1].value).toEqual(1);
expect(ctrl.instanceStats.data[2].value).toEqual(8);
});
});
describe('the instanceStats chart is set up correctly', function() {
it('chart should have a title of "Total Instances"', function() {
expect(scope.instanceStats.title).toBe('Total Instances');
expect(ctrl.instanceStats.title).toBe('Total Instances');
});
it('chart should have a maxLimit value defined', function() {
expect(scope.instanceStats.maxLimit).toBeDefined();
expect(ctrl.instanceStats.maxLimit).toBeDefined();
});
it('instanceStats.overMax should get set to true if instance_count exceeds maxLimit',
function() {
scope.tableData.allocated.push({ name: 'image-1', size: 0, min_disk: 0 });
ctrl.tableData.allocated.push({ name: 'image-1', size: 0, min_disk: 0 });
scope.model.newInstanceSpec.instance_count = 11;
scope.$digest();
// check chart data and labels
expect(scope.instanceStats.label).toBe('110%');
expect(scope.instanceStats.data[0].value).toEqual(0);
expect(scope.instanceStats.data[1].value).toEqual(11);
expect(scope.instanceStats.data[2].value).toEqual(0);
expect(ctrl.instanceStats.label).toBe('110%');
expect(ctrl.instanceStats.data[0].value).toEqual(0);
expect(ctrl.instanceStats.data[1].value).toEqual(11);
expect(ctrl.instanceStats.data[2].value).toEqual(0);
// check to ensure overMax
expect(scope.instanceStats.overMax).toBe(true);
expect(ctrl.instanceStats.overMax).toBe(true);
}
);
});
@ -238,102 +238,102 @@
scope.$digest();
// check chart data and labels
expect(scope.instanceStats.label).toBe('20%');
expect(scope.instanceStats.data[0].value).toEqual(0);
expect(scope.instanceStats.data[1].value).toEqual(2);
expect(scope.instanceStats.data[2].value).toEqual(8);
expect(ctrl.instanceStats.label).toBe('20%');
expect(ctrl.instanceStats.data[0].value).toEqual(0);
expect(ctrl.instanceStats.data[1].value).toEqual(2);
expect(ctrl.instanceStats.data[2].value).toEqual(8);
});
it('should update chart stats if instance count = 2 and source selected', function() {
scope.tableData.allocated.push({ name: 'image-1', size: 0, min_disk: 0 });
ctrl.tableData.allocated.push({ name: 'image-1', size: 0, min_disk: 0 });
scope.model.newInstanceSpec.instance_count = 2;
scope.$digest();
// check chart data and labels
expect(scope.instanceStats.label).toBe('20%');
expect(scope.instanceStats.data[0].value).toEqual(0);
expect(scope.instanceStats.data[1].value).toEqual(2);
expect(scope.instanceStats.data[2].value).toEqual(8);
expect(ctrl.instanceStats.label).toBe('20%');
expect(ctrl.instanceStats.data[0].value).toEqual(0);
expect(ctrl.instanceStats.data[1].value).toEqual(2);
expect(ctrl.instanceStats.data[2].value).toEqual(8);
});
});
describe('source allocation', function() {
it('should update chart stats if source allocated', function() {
scope.tableData.allocated.push({ name: 'image-1', size: 0, min_disk: 0 });
ctrl.tableData.allocated.push({ name: 'image-1', size: 0, min_disk: 0 });
scope.$digest();
// check chart data and labels
expect(scope.instanceStats.label).toBe('10%');
expect(scope.instanceStats.data[0].value).toEqual(0);
expect(scope.instanceStats.data[1].value).toEqual(1);
expect(scope.instanceStats.data[2].value).toEqual(9);
expect(ctrl.instanceStats.label).toBe('10%');
expect(ctrl.instanceStats.data[0].value).toEqual(0);
expect(ctrl.instanceStats.data[1].value).toEqual(1);
expect(ctrl.instanceStats.data[2].value).toEqual(9);
});
it('should set minVolumeSize to 1 if source allocated and size = min_disk = 1GB',
function() {
scope.tableData.allocated.push({ name: 'image-1', size: 1000000000, min_disk: 1 });
ctrl.tableData.allocated.push({ name: 'image-1', size: 1000000000, min_disk: 1 });
scope.$digest();
expect(scope.minVolumeSize).toBe(1);
expect(ctrl.minVolumeSize).toBe(1);
}
);
it('should set minVolumeSize to 1 if source allocated and size = 1GB and min_disk = 0GB',
function() {
scope.tableData.allocated.push({ name: 'image-1', size: 1000000000, min_disk: 0 });
ctrl.tableData.allocated.push({ name: 'image-1', size: 1000000000, min_disk: 0 });
scope.$digest();
expect(scope.minVolumeSize).toBe(1);
expect(ctrl.minVolumeSize).toBe(1);
}
);
it('should set minVolumeSize to 2 if source allocated and size = 1GB and min_disk = 2GB',
function() {
scope.tableData.allocated.push({ name: 'image-1', size: 1000000000, min_disk: 2 });
ctrl.tableData.allocated.push({ name: 'image-1', size: 1000000000, min_disk: 2 });
scope.$digest();
expect(scope.minVolumeSize).toBe(2);
expect(ctrl.minVolumeSize).toBe(2);
}
);
it('should set minVolumeSize to 0 if source allocated and size = min_disk = 0',
function() {
scope.tableData.allocated.push({ name: 'image-1', size: 0, min_disk: 0 });
ctrl.tableData.allocated.push({ name: 'image-1', size: 0, min_disk: 0 });
scope.$digest();
expect(scope.minVolumeSize).toBe(0);
expect(ctrl.minVolumeSize).toBe(0);
}
);
it('should set minVolumeSize to 2 if source allocated and size = 1.5GB and min_disk = 0',
function() {
scope.tableData.allocated.push({ name: 'image-1', size: 1500000000, min_disk: 0 });
ctrl.tableData.allocated.push({ name: 'image-1', size: 1500000000, min_disk: 0 });
scope.$digest();
// minVolumeSize should use Math.ceil()
expect(scope.minVolumeSize).toBe(2);
expect(ctrl.minVolumeSize).toBe(2);
}
);
it('should set minVolumeSize to undefined if boot source is not image', function() {
var selSource = 'volume';
scope.updateBootSourceSelection(selSource);
ctrl.updateBootSourceSelection(selSource);
expect(scope.currentBootSource).toEqual('volume');
expect(ctrl.currentBootSource).toEqual('volume');
scope.$digest();
expect(scope.minVolumeSize).toBeUndefined();
expect(ctrl.minVolumeSize).toBeUndefined();
});
});
});
});
describe('LaunchInstanceSourceHelpCtrl', function() {
describe('LaunchInstanceSourceHelpController', function() {
var ctrl;
beforeEach(inject(function($controller) {
ctrl = $controller('LaunchInstanceSourceHelpCtrl', {});
ctrl = $controller('LaunchInstanceSourceHelpController', {});
}));
it('defines the title', function() {