Skip to content

Commit 977c3aa

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

15 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: 190 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+
'$timeout',
9+
'Catalog',
710
'DataService',
11+
'KeywordService',
12+
'NotificationsService',
13+
'ProjectsService',
14+
'RecentlyViewedProjectsService',
815
ProcessTemplateDialog
916
],
1017
controllerAs: '$ctrl',
1118
bindings: {
1219
template: '<',
20+
project: '<',
21+
useProjectTemplate: '<',
1322
onDialogClosed: '&'
1423
},
1524
templateUrl: 'views/directives/process-template-dialog.html'
1625
});
1726

18-
function ProcessTemplateDialog($scope, DataService) {
27+
function ProcessTemplateDialog($scope,
28+
$filter,
29+
$timeout,
30+
Catalog,
31+
DataService,
32+
KeywordService,
33+
NotificationsService,
34+
ProjectsService,
35+
RecentlyViewedProjectsService) {
1936
var ctrl = this;
2037
var validityWatcher;
38+
var projectListWatcher;
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,82 @@
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+
updateFilterControls();
262+
263+
// Deselect the currently selected template if it was filtered out
264+
if (!_.includes(ctrl.filteredItems, ctrl.selectedTemplate)) {
265+
ctrl.templateSelected();
266+
}
267+
}
268+
269+
function updateFilterControls() {
270+
$timeout(function() {
271+
if (ctrl.filterConfig.appliedFilters.length > 0) {
272+
ctrl.filterConfig.resultsCount = ctrl.filteredItems.length;
273+
$('.toolbar-pf-results h5').text(ctrl.filterConfig.resultsCount + ' of ' + ctrl.totalCount + ' items');
274+
} else {
275+
$('.toolbar-pf-results h5').text(ctrl.totalCount + (ctrl.totalCount === 1 ? ' item' : ' items'));
276+
if (ctrl.totalCount <= 1) {
277+
$('.filter-pf.filter-fields input').attr('disabled', '');
278+
} else {
279+
$('.filter-pf.filter-fields input').removeAttr("disabled");
280+
}
281+
}
282+
}, 0);
283+
}
284+
285+
var updateProjects = function() {
286+
var filteredProjects = _.reject(ctrl.unfilteredProjects, 'metadata.deletionTimestamp');
287+
var projects = _.sortBy(filteredProjects, $filter('displayName'));
288+
ctrl.searchEnabled = !_.isEmpty(filteredProjects);
289+
290+
ctrl.templateProjects = RecentlyViewedProjectsService.orderByMostRecentlyViewed(projects);
291+
};
292+
293+
var updateProjectData = function(projectData) {
294+
ctrl.unfilteredProjects = _.toArray(projectData.by("metadata.name"));
295+
updateProjects();
296+
};
297+
298+
function listProjects() {
299+
if (!projectListWatcher) {
300+
ProjectsService.list().then(function(projectData) {
301+
$scope.isProjectListIncomplete = ProjectsService.isProjectListIncomplete();
302+
updateProjectData(projectData);
303+
304+
if (!$scope.isProjectListIncomplete && _.size(ctrl.unfilteredProjects) <= MAX_PROJECTS_TO_WATCH) {
305+
projectListWatcher = ProjectsService.watch($scope, updateProjectData);
306+
}
307+
}, function() {
308+
$scope.isProjectListIncomplete = true;
309+
ctrl.unfilteredProjects = [];
310+
updateProjects();
311+
});
312+
}
313+
}
127314
}
128315
})();

app/styles/_core.less

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,3 +1259,50 @@ 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+
}
1270+
.select-project-for-template {
1271+
border-bottom: solid 1px @color-pf-black-300;
1272+
padding-bottom: 10px;
1273+
1274+
> h2 {
1275+
margin-bottom: 20px;
1276+
margin-top: 0;
1277+
}
1278+
.ui-select-container {
1279+
display: inline-block;
1280+
width: 275px;
1281+
}
1282+
}
1283+
.services-item {
1284+
&.show-selection {
1285+
// Clear focus settings, keep before active settings
1286+
&:focus {
1287+
color: @text-color;
1288+
.services-item-icon:after {
1289+
border: none;
1290+
}
1291+
1292+
.services-item-name {
1293+
color: @text-color;
1294+
}
1295+
}
1296+
&.active {
1297+
color: @link-hover-color;
1298+
.services-item-icon:after {
1299+
border: 2px solid @link-color;
1300+
}
1301+
.services-item-name {
1302+
color: @link-hover-color;
1303+
}
1304+
}
1305+
}
1306+
}
1307+
}
1308+

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>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div class="osc-form">
22
<form name="$ctrl.form">
3-
<process-template template="$ctrl.template" is-dialog="true"></process-template>
3+
<process-template template="$ctrl.template" project="$ctrl.preSelectedProject" on-project-selected="$ctrl.onProjectSelected" available-projects="$ctrl.unfilteredProjects" is-dialog="true"></process-template>
44
</form>
55
</div>

0 commit comments

Comments
 (0)