Skip to content

Commit 01ac42f

Browse files
Add 'Select from Project' wizard to allow project templates/images to be imported
1 parent c9a5d1a commit 01ac42f

16 files changed

+568
-120
lines changed

app/scripts/controllers/landingPage.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ angular.module('openshiftConsole')
8585
_.set($scope, 'ordering.panelName', 'fromFile');
8686
};
8787

88+
$scope.fromProjectSelected = function() {
89+
_.set($scope, 'ordering.panelName', 'fromProject');
90+
};
91+
8892
AuthService.withUser().then(function() {
8993
var includeTemplates = !_.get(Constants, 'ENABLE_TECH_PREVIEW_FEATURE.template_service_broker');
9094
Catalog.getCatalogItems(includeTemplates).then(_.spread(function(items, errorMessage) {

app/scripts/directives/processTemplate.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
bindings: {
2323
template: '<',
2424
project: '<',
25+
onProjectSelected: '<',
26+
availableProjects: '<',
2527
prefillParameters: '<',
2628
isDialog: '<'
2729
},

app/scripts/directives/processTemplateDialog.js

Lines changed: 186 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,49 @@
44
angular.module('openshiftConsole').component('processTemplateDialog', {
55
controller: [
66
'$scope',
7+
'$filter',
8+
'Catalog',
79
'DataService',
10+
'KeywordService',
11+
'NotificationsService',
12+
'ProjectsService',
13+
'RecentlyViewedProjectsService',
814
ProcessTemplateDialog
915
],
1016
controllerAs: '$ctrl',
1117
bindings: {
1218
template: '<',
19+
project: '<',
20+
useProjectTemplate: '<',
1321
onDialogClosed: '&'
1422
},
1523
templateUrl: 'views/directives/process-template-dialog.html'
1624
});
1725

18-
function ProcessTemplateDialog($scope, DataService) {
26+
function ProcessTemplateDialog($scope,
27+
$filter,
28+
Catalog,
29+
DataService,
30+
KeywordService,
31+
NotificationsService,
32+
ProjectsService,
33+
RecentlyViewedProjectsService) {
1934
var ctrl = this;
2035
var validityWatcher;
36+
var projectListWatcher;
37+
38+
// TODO: Define a global constant for this setting (configurable?)
39+
var MAX_PROJECTS_TO_WATCH = 250;
40+
41+
ctrl.selectStep = {
42+
id: 'projectTemplates',
43+
label: 'Selection',
44+
view: 'views/directives/process-template-dialog/process-template-select.html',
45+
hidden: ctrl.useProjectTemplate !== true,
46+
allowed: true,
47+
valid: false,
48+
onShow: showSelect
49+
};
2150

2251
ctrl.configStep = {
2352
id: 'configuration',
@@ -43,6 +72,34 @@
4372

4473
ctrl.$onInit = function() {
4574
ctrl.loginBaseUrl = DataService.openshiftAPIBaseUrl();
75+
ctrl.preSelectedProject = ctrl.selectedProject = ctrl.project;
76+
listProjects();
77+
78+
ctrl.projectEmptyState = {
79+
icon: 'pficon pficon-info',
80+
title: 'No Project Selected',
81+
info: 'Please select a project from the dropdown to load Templates and Images from that project'
82+
};
83+
84+
ctrl.templatesEmptyState = {
85+
icon: 'pficon pficon-info',
86+
title: 'No Templates or Images',
87+
info: 'The selected project has no templates or images available to import'
88+
};
89+
90+
ctrl.filterConfig = {
91+
fields: [
92+
{
93+
id: 'keyword',
94+
title: 'Keyword',
95+
placeholder: 'Filter by Keyword',
96+
filterType: 'text'
97+
}
98+
],
99+
resultsCount: 0,
100+
appliedFilters: [],
101+
onFilterChange: filterChange
102+
};
46103
};
47104

48105
ctrl.$onChanges = function(changes) {
@@ -52,6 +109,9 @@
52109
ctrl.iconClass = getIconClass();
53110
}
54111
}
112+
if (changes.useProjectTemplate) {
113+
initializeSteps();
114+
}
55115
};
56116

57117
$scope.$on('templateInstantiated', function(event, message) {
@@ -61,6 +121,9 @@
61121

62122
ctrl.$onDestroy = function() {
63123
clearValidityWatcher();
124+
if (projectListWatcher) {
125+
DataService.unwatch(projectListWatcher);
126+
}
64127
};
65128

66129
ctrl.next = function(step) {
@@ -85,13 +148,49 @@
85148
}
86149
};
87150

151+
ctrl.onProjectSelected = function(project) {
152+
ctrl.selectedProject = project;
153+
ctrl.configStep.valid = $scope.$ctrl.form.$valid && ctrl.selectedProject;
154+
};
155+
156+
ctrl.templateSelected = function(template) {
157+
ctrl.selectedTemplate = template;
158+
ctrl.template = _.get(template, 'resource');
159+
ctrl.selectStep.valid = !!template;
160+
};
161+
162+
ctrl.templateProjectChange = function () {
163+
ctrl.templateProjectName = _.get(ctrl.templateProject, 'metadata.name');
164+
165+
// Get the templates for the selected project
166+
ctrl.catalogItems = {};
167+
ctrl.templateSelected();
168+
169+
Catalog.getProjectCatalogItems(ctrl.templateProjectName).then( _.spread(function(catalogServiceItems, errorMessage) {
170+
ctrl.catalogItems = catalogServiceItems;
171+
ctrl.totalCount = ctrl.catalogItems.length;
172+
filterItems();
173+
174+
if (errorMessage) {
175+
NotificationsService.addNotification(
176+
{
177+
type: "error",
178+
message: errorMessage
179+
}
180+
);
181+
}
182+
}));
183+
};
184+
88185
function getIconClass() {
89186
var icon = _.get(ctrl, 'template.metadata.annotations.iconClass', 'fa fa-clone');
90187
return (icon.indexOf('icon-') !== -1) ? 'font-icon ' + icon : icon;
91188
}
92189

93190
function initializeSteps() {
94-
ctrl.steps = [ctrl.configStep, ctrl.resultsStep];
191+
if (!ctrl.steps) {
192+
ctrl.steps = [ctrl.selectStep, ctrl.configStep, ctrl.resultsStep];
193+
}
95194
}
96195

97196
function clearValidityWatcher() {
@@ -101,19 +200,30 @@
101200
}
102201
}
103202

203+
function showSelect() {
204+
ctrl.selectStep.selected = true;
205+
ctrl.configStep.selected = false;
206+
ctrl.resultsStep.selected = false;
207+
ctrl.nextTitle = "Next >";
208+
clearValidityWatcher();
209+
listProjects();
210+
}
211+
104212
function showConfig() {
213+
ctrl.selectStep.selected = false;
105214
ctrl.configStep.selected = true;
106215
ctrl.resultsStep.selected = false;
107216
ctrl.nextTitle = "Create";
108217
ctrl.resultsStep.allowed = ctrl.configStep.valid;
109218

110219
validityWatcher = $scope.$watch("$ctrl.form.$valid", function(isValid) {
111-
ctrl.configStep.valid = isValid;
220+
ctrl.configStep.valid = isValid && ctrl.selectedProject;
112221
ctrl.resultsStep.allowed = isValid;
113222
});
114223
}
115224

116225
function showResults() {
226+
ctrl.selectStep.selected = false;
117227
ctrl.configStep.selected = false;
118228
ctrl.resultsStep.selected = true;
119229
ctrl.nextTitle = "Close";
@@ -124,5 +234,78 @@
124234
function instantiateTemplate() {
125235
$scope.$broadcast('instantiateTemplate');
126236
}
237+
238+
function filterForKeywords(searchText, items) {
239+
return KeywordService.filterForKeywords(items, ['name', 'tags'], KeywordService.generateKeywords(searchText));
240+
}
241+
242+
function filterChange(filters) {
243+
// only use filters which have a filter criteria 'value'
244+
// prevents applying an empty keyword filter
245+
// TODO: can remove the following line of code after angular-patternfly issue is fixed:
246+
// https://github.com/patternfly/angular-patternfly/issues/509
247+
filters = _.filter(filters, 'value');
248+
249+
ctrl.filterConfig.appliedFilters = filters;
250+
filterItems();
251+
}
252+
253+
function filterItems() {
254+
ctrl.filteredItems = ctrl.catalogItems;
255+
if (ctrl.filterConfig.appliedFilters && ctrl.filterConfig.appliedFilters.length > 0) {
256+
_.each(ctrl.filterConfig.appliedFilters, function(filter) {
257+
ctrl.filteredItems = filterForKeywords(filter.value, ctrl.filteredItems);
258+
});
259+
}
260+
261+
// Deselect the currently selected template if it was filtered out
262+
if (!_.includes(ctrl.filteredItems, ctrl.selectedTemplate)) {
263+
ctrl.templateSelected();
264+
}
265+
266+
updateFilterControls();
267+
}
268+
269+
function updateFilterControls() {
270+
if (ctrl.filterConfig.appliedFilters.length > 0) {
271+
ctrl.filterConfig.resultsCount = ctrl.filteredItems.length;
272+
$('.toolbar-pf-results h5').text(ctrl.filterConfig.resultsCount + ' of ' + ctrl.totalCount + ' items');
273+
} else {
274+
$('.toolbar-pf-results h5').text(ctrl.totalCount + (ctrl.totalCount === 1 ? ' item' : ' items'));
275+
if (ctrl.totalCount <= 1) {
276+
$('.filter-pf.filter-fields input').attr('disabled', '');
277+
} else {
278+
$('.filter-pf.filter-fields input').removeAttr("disabled");
279+
}
280+
}
281+
}
282+
283+
var updateProjects = function() {
284+
var filteredProjects = _.reject(ctrl.unfilteredProjects, 'metadata.deletionTimestamp');
285+
var projects = _.sortBy(filteredProjects, $filter('displayName'));
286+
ctrl.searchEnabled = !_.isEmpty(filteredProjects);
287+
288+
ctrl.templateProjects = RecentlyViewedProjectsService.orderByMostRecentlyViewed(projects);
289+
};
290+
291+
var updateProjectData = function(projectData) {
292+
ctrl.unfilteredProjects = _.toArray(projectData.by("metadata.name"));
293+
updateProjects();
294+
};
295+
296+
function listProjects() {
297+
if (!projectListWatcher) {
298+
ProjectsService.list().then(function(projectData) {
299+
updateProjectData(projectData);
300+
301+
if (!ProjectsService.isProjectListIncomplete() && _.size(ctrl.unfilteredProjects) <= MAX_PROJECTS_TO_WATCH) {
302+
projectListWatcher = ProjectsService.watch($scope, updateProjectData);
303+
}
304+
}, function() {
305+
ctrl.unfilteredProjects = [];
306+
updateProjects();
307+
});
308+
}
309+
}
127310
}
128311
})();

app/styles/_core.less

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,3 +1259,51 @@ pre.clipped {
12591259
width: 30px;
12601260
}
12611261
}
1262+
1263+
.order-service-config {
1264+
.order-services-filter {
1265+
margin-left: 0;
1266+
}
1267+
.blank-slate-pf {
1268+
margin-bottom: 0;
1269+
padding-bottom: 0;
1270+
}
1271+
.select-project-for-template {
1272+
border-bottom: solid 1px @color-pf-black-300;
1273+
padding-bottom: 10px;
1274+
1275+
> h2 {
1276+
margin-bottom: 20px;
1277+
margin-top: 0;
1278+
}
1279+
.ui-select-container {
1280+
display: inline-block;
1281+
width: 275px;
1282+
}
1283+
}
1284+
.services-item {
1285+
&.show-selection {
1286+
// Clear focus settings, keep before active settings
1287+
&:focus {
1288+
color: @text-color;
1289+
.services-item-icon:after {
1290+
border: none;
1291+
}
1292+
1293+
.services-item-name {
1294+
color: @text-color;
1295+
}
1296+
}
1297+
&.active {
1298+
color: @link-hover-color;
1299+
.services-item-icon:after {
1300+
border: 2px solid @link-color;
1301+
}
1302+
.services-item-name {
1303+
color: @link-hover-color;
1304+
}
1305+
}
1306+
}
1307+
}
1308+
}
1309+

app/styles/_overlay-forms.less

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
.order-service-config-single-column {
22
width: 100%;
3+
@media (min-width: 768px) {
4+
padding-left: 0;
5+
}
36
}
47

58
.wizard-pf-main {

app/views/directives/header/project-header.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<li ng-if-start="catalogLandingPageEnabled" role="menuitem"><a href="/">Browse Catalog</a></li>
3939
<li role="menuitem"><a href="" ng-click="showOrderingPanel('deployImage')">Deploy Image</a></li>
4040
<li ng-if-end role="menuitem"><a href="" ng-click="showOrderingPanel('fromFile')">Import YAML / JSON</a></li>
41+
<li ng-if-end role="menuitem"><a href="" ng-click="showOrderingPanel('fromProject')">Select from Project</a></li>
4142
</ul>
4243
</div>
4344
<div row
@@ -51,4 +52,5 @@
5152
<overlay-panel show-panel="ordering.panelName" show-close="true" handle-close="closeOrderingPanel">
5253
<deploy-image-dialog ng-if="ordering.panelName === 'deployImage'" project="project" context="context" on-dialog-closed="closeOrderingPanel"></deploy-image-dialog>
5354
<from-file-dialog ng-if="ordering.panelName === 'fromFile'" project="project" context="context" on-dialog-closed="closeOrderingPanel"></from-file-dialog>
55+
<process-template-dialog ng-if="ordering.panelName === 'fromProject'" project="project" use-project-template="true" on-dialog-closed="closeOrderingPanel"></process-template-dialog>
5456
</overlay-panel>

app/views/directives/process-template-dialog.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<pf-wizard
33
hide-header="true"
44
hide-sidebar="true"
5-
hide-back-button="true"
5+
hide-back-button="!$ctrl.useProjectTemplate"
66
step-class="order-service-wizard-step"
77
wizard-ready="$ctrl.wizardReady"
88
next-title="$ctrl.nextTitle"
@@ -11,7 +11,7 @@
1111
on-cancel="$ctrl.close()"
1212
wizard-done="$ctrl.wizardDone"
1313
current-step="$ctrl.currentStep"
14-
class="pf-wizard-no-back">
14+
ng-class="{'pf-wizard-no-back': !$ctrl.useProjectTemplate}">
1515
<pf-wizard-step ng-repeat="step in $ctrl.steps track by step.id"
1616
step-title="{{step.label}}"
1717
wz-disabled="{{step.hidden}}"
@@ -22,7 +22,7 @@
2222
step-id="{{step.id}}"
2323
step-priority="{{$index}}">
2424
<div class="wizard-pf-main-inner-shadow-covers">
25-
<div class="order-service-details">
25+
<div class="order-service-details" ng-if="!$ctrl.selectStep.selected">
2626
<div class="order-service-details-top">
2727
<div class="service-icon">
2828
<span class="icon {{$ctrl.iconClass}}"></span>
@@ -42,7 +42,7 @@
4242
<p ng-bind-html="$ctrl.template | description | linky : '_blank'" class="description"></p>
4343
</div>
4444
</div>
45-
<div class="order-service-config">
45+
<div class="order-service-config" ng-class="{'order-service-config-single-column': $ctrl.selectStep.selected}">
4646
<div ng-if="step.selected" ng-include="step.view" class="wizard-pf-main-form-contents"></div>
4747
</div>
4848
</div>

0 commit comments

Comments
 (0)