Skip to content

Bug 1481127 - More robust handling of large numbers of projects #1956

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions app/scripts/controllers/createFromURL.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,7 @@ angular.module('openshiftConsole')
$scope.projects = {};
$scope.canCreateProject = undefined;

DataService
.list("projects", $scope)
ProjectsService.list()
.then(function(items) {
$scope.loaded = true;
$scope.projects = $filter('orderByDisplayName')(items.by("metadata.name"));
Expand Down
3 changes: 1 addition & 2 deletions app/scripts/controllers/membership.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,7 @@ angular
$scope.user = resp;
});

DataService
.list('projects', {})
ProjectsService.list()
.then(function(resp) {
var projects = _.keys(resp.by('metadata.name')).sort();
angular.extend($scope, {
Expand Down
65 changes: 51 additions & 14 deletions app/scripts/controllers/projects.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ angular.module('openshiftConsole')
AuthService,
DataService,
KeywordService,
Navigate,
Logger,
ProjectsService) {
var MAX_PROJETS_TO_WATCH = 250;

var projects, sortedProjects;
var watches = [];
var filterKeywords = [];
var watchingProjects = false;

$scope.alerts = $scope.alerts || {};
$scope.loading = true;
Expand All @@ -30,6 +34,9 @@ angular.module('openshiftConsole')
text: ''
};

// Only show the first `MAX_PROJETS_TO_WATCH` on the page. Users can always filter.
$scope.limitListTo = MAX_PROJETS_TO_WATCH;

var filterFields = [
'metadata.name',
'metadata.annotations["openshift.io/display-name"]',
Expand Down Expand Up @@ -62,19 +69,19 @@ angular.module('openshiftConsole')
// Sort by display name. Use `metadata.name` as a secondary sort when
// projects have the same display name.
sortedProjects = _.orderBy(projects,
[ displayNameLower, 'metadata.name' ],
[ primarySortOrder ]);
[ displayNameLower, 'metadata.name' ],
[ primarySortOrder ]);
break;
case 'metadata.annotations["openshift.io/requester"]':
// Sort by requester, then display name. Secondary sort is always ascending.
sortedProjects = _.orderBy(projects,
[ sortID, displayNameLower ],
[ primarySortOrder, 'asc' ]);
[ sortID, displayNameLower ],
[ primarySortOrder, 'asc' ]);
break;
default:
sortedProjects = _.orderBy(projects,
[ sortID ],
[ primarySortOrder ]);
[ sortID ],
[ primarySortOrder ]);
}

// Remember the previous sort ID.
Expand Down Expand Up @@ -109,6 +116,21 @@ angular.module('openshiftConsole')
onSortChange: update
};

var updateProjects = function(projectData) {
projects = _.toArray(projectData.by("metadata.name"));
$scope.loading = false;
$scope.showGetStarted = _.isEmpty(projects) && !$scope.isProjectListIncomplete;
update();
};

// On create / edit / delete, manually update the project list if not
// watching. This uses cached project data, so is not expensive.
var onChanges = function() {
if (!watchingProjects) {
ProjectsService.list().then(updateProjects);
}
};

$scope.newProjectPanelShown = false;

$scope.createProject = function() {
Expand All @@ -121,6 +143,7 @@ angular.module('openshiftConsole')

$scope.onNewProject = function() {
$scope.newProjectPanelShown = false;
onChanges();
};

$scope.editProjectPanelShown = false;
Expand All @@ -136,24 +159,38 @@ angular.module('openshiftConsole')

$scope.onEditProject = function() {
$scope.editProjectPanelShown = false;
onChanges();
};

$scope.onDeleteProject = onChanges;

$scope.goToProject = function(projectName) {
Navigate.toProjectOverview(projectName);
};

$scope.$watch('search.text', _.debounce(function(searchText) {
$scope.keywords = filterKeywords = KeywordService.generateKeywords(searchText);
$scope.$apply(filterProjects);
}, 50, { maxWait: 250 }));
$scope.$applyAsync(filterProjects);
}, 350));

AuthService.withUser().then(function() {
watches.push(DataService.watch("projects", $scope, function(projectData) {
projects = _.toArray(projectData.by("metadata.name"));
ProjectsService.list().then(function(projectData) {
$scope.isProjectListIncomplete = ProjectsService.isProjectListIncomplete();
updateProjects(projectData);
if (!$scope.isProjectListIncomplete && _.size(projects) <= MAX_PROJETS_TO_WATCH) {
watches.push(ProjectsService.watch($scope, updateProjects));
watchingProjects = true;
}
}, function() {
$scope.isProjectListIncomplete = true;
$scope.loading = false;
$scope.showGetStarted = _.isEmpty(projects);
projects = [];
update();
}));
});
});

// Test if the user can submit project requests. Handle error notifications
// ourselves because 403 responses are expected.
// Test if the user can submit project requests. Handle error notifications
// ourselves because 403 responses are expected.
ProjectsService
.canCreate()
.then(function() {
Expand Down
4 changes: 2 additions & 2 deletions app/scripts/directives/istagSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ angular.module("openshiftConsole")
* selectDisabled:
* An expression that will disable the form (default: false)
*/
.directive("istagSelect", function(DataService) {
.directive("istagSelect", function(DataService, ProjectsService) {
return {
require: '^form',
restrict: 'E',
Expand Down Expand Up @@ -81,7 +81,7 @@ angular.module("openshiftConsole")
});
};

DataService.list("projects", {}, function(projectData) {
ProjectsService.list().then(function(projectData) {
$scope.namespaces = _.keys(projectData.by('metadata.name'));

if ($scope.includeSharedNamespace) {
Expand Down
42 changes: 32 additions & 10 deletions app/scripts/directives/nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,21 @@ angular.module('openshiftConsole')
}
};
})
.directive('projectHeader', function($timeout, $location, $filter, DataService, projectOverviewURLFilter, Constants) {
.directive('projectHeader', function($timeout, $location, $filter, ProjectsService, projectOverviewURLFilter, Constants) {

// cache these to eliminate flicker
var projects = {};
var sortedProjects = [];

var displayName = $filter('displayName');
var uniqueDisplayName = $filter('uniqueDisplayName');

return {
restrict: 'EA',
templateUrl: 'views/directives/header/project-header.html',
link: function($scope, $elem) {
var MAX_PROJETS_TO_DISPLAY = 100;

$scope.closeOrderingPanel = function() {
_.set($scope, 'ordering.panelName', "");
};
Expand Down Expand Up @@ -108,23 +113,40 @@ angular.module('openshiftConsole')
projects[name] = project;
}

sortedProjects = $filter('orderByDisplayName')(projects);
options = _.map(sortedProjects, function(item) {
return $('<option>')
.attr("value", item.metadata.name)
.attr("selected", item.metadata.name === name)
.text($filter("uniqueDisplayName")(item, sortedProjects));
});
var makeOption = function(project, skipUniqueCheck) {
var option = $('<option>').attr("value", project.metadata.name).attr("selected", project.metadata.name === name);
if (skipUniqueCheck) {
option.text(displayName(project));
} else {
// FIXME: This is pretty inefficient, but probably OK if
// MAX_PROJETS_TO_DISPLAY is not too large.
option.text(uniqueDisplayName(project, sortedProjects));
}

return option;
};

// Only show all projects in the dropdown if less than a max number.
// Otherwise it's not usable and might impact performance.
if (_.size(projects) <= MAX_PROJETS_TO_DISPLAY) {
sortedProjects = $filter('orderByDisplayName')(projects);
options = _.map(sortedProjects, function(project) {
return makeOption(project, false);
});
} else {
// Show the current project and a "View all Projects" link.
options = [ makeOption(projects[name], true) ];
}

select.empty();
select.append(options);
select.append($('<option data-divider="true"></option>'));
select.append($('<option value="">View all projects</option>'));
select.append($('<option value="">View all Projects</option>'));
select.selectpicker('refresh');
};


DataService.list("projects", $scope, function(items) {
ProjectsService.list().then(function(items) {
projects = items.by("metadata.name");
updateOptions();
});
Expand Down
54 changes: 45 additions & 9 deletions app/views/projects.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,46 @@ <h1>My Projects</h1>
</div>
</div>
</div>
<div ng-if="!projects.length" class="h3">
<div ng-if="isProjectListIncomplete">
<div class="alert alert-warning">
<span class="pficon pficon-warning-triangle-o" aria-hidden="true"></span>
<span class="sr-only">Warning:</span>
The complete list of your projects could not be loaded.
Type a project name to go to that project.
</div>
<form>
<div class="form-group">
<label for="typed-project-name">Project Name</label>
<div class="input-group">
<input
class="form-control"
type="text"
id="typed-project-name"
required
minlength="2"
ng-model="input.typedProjectName"
autocorrect="off"
autocapitalize="none"
spellcheck="false">
<span class="input-group-btn">
<button class="btn btn-default"
type="submit"
ng-disabled="!input.typedProjectName"
ng-click="goToProject(input.typedProjectName)">
<i class="fa fa-arrow-right" aria-hidden="true"></i>
<span class="sr-only">Go to Project</span>
</button>
</span>
</div>
</div>
</form>
</div>
<div ng-if="!projects.length && !isProjectListIncomplete" class="h3">
The current filter is hiding all projects.
<a href="" ng-click="search.text = ''" role="button">Clear Filter</a>
</div>
<div class="list-group list-view-pf projects-list">
<div ng-repeat="project in projects" class="list-group-item project-info tile-click">
<div ng-repeat="project in projects | limitTo: limitListTo track by (project | uid)" class="list-group-item project-info tile-click">
<div class="list-view-pf-main-info">
<div class="list-view-pf-description project-names">
<div class="list-group-item-heading project-name-item">
Expand Down Expand Up @@ -110,16 +144,13 @@ <h2 class="h1">
</a>
</li>
<li role="menuitem">
<delete-link
kind="Project"
<delete-project
label="Delete Project"
resource-name="{{project.metadata.name}}"
project-name="{{project.metadata.name}}"
display-name="{{(project | displayName)}}"
project="project"
type-name-to-confirm="true"
stay-on-current-page="true"
alerts="alerts">
</delete-link>
success="onDeleteProject">
</delete-project>
</li>
</ul>
</div>
Expand All @@ -131,6 +162,11 @@ <h2 class="h1">
</origin-modal-popup>
</div>
</div>
<p ng-if="projects.length > limitListTo">
Only the first {{limitListTo}} projects are displayed.
Filter by keyword or change sort options to see other
projects.
</p>
<p class="projects-instructions" ng-if="canCreate === false" ng-include="'views/_cannot-create-project.html'"></p>
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"angular-bootstrap": "0.14.3",
"angular-patternfly": "4.3.0",
"angular-gettext": "2.3.9",
"uri.js": "1.18.0",
"uri.js": "1.18.12",
"moment": "2.14.2",
"moment-timezone": "0.5.3",
"patternfly": "3.26.1",
Expand Down Expand Up @@ -46,8 +46,8 @@
"angular-moment": "1.0.0",
"angular-utf8-base64": "0.0.5",
"file-saver": "1.3.3",
"origin-web-common": "0.0.46",
"origin-web-catalog": "0.0.38"
"origin-web-common": "0.0.47",
"origin-web-catalog": "0.0.39"
},
"devDependencies": {
"angular-mocks": "1.5.11",
Expand Down
Loading