Commit 1cd15ddb by Sam Padgett Committed by GitHub

Merge pull request #150 from spadgett/project-list

Cache project list
parents eb1c5e84 32eadb7c
...@@ -2921,8 +2921,28 @@ angular.module('openshiftCommonServices') ...@@ -2921,8 +2921,28 @@ angular.module('openshiftCommonServices')
angular.module('openshiftCommonServices') angular.module('openshiftCommonServices')
.factory('ProjectsService', .factory('ProjectsService',
function($location, $q, AuthService, DataService, annotationNameFilter, AuthorizationService, RecentlyViewedProjectsService) { function($location,
$q,
$rootScope,
AuthService,
AuthorizationService,
DataService,
Logger,
RecentlyViewedProjectsService,
annotationNameFilter) {
// Cache project data when we can so we don't request it on every page load.
var cachedProjectData;
var cachedProjectDataIncomplete = false;
var clearCachedProjectData = function() {
Logger.debug('ProjectsService: clearing project cache');
cachedProjectData = null;
cachedProjectDataIncomplete = false;
};
AuthService.onUserChanged(clearCachedProjectData);
AuthService.onLogout(clearCachedProjectData);
var cleanEditableAnnotations = function(resource) { var cleanEditableAnnotations = function(resource) {
var paths = [ var paths = [
...@@ -2957,6 +2977,10 @@ angular.module('openshiftCommonServices') ...@@ -2957,6 +2977,10 @@ angular.module('openshiftCommonServices')
context.project = project; context.project = project;
context.projectPromise.resolve(project); context.projectPromise.resolve(project);
RecentlyViewedProjectsService.addProjectUID(project.metadata.uid); RecentlyViewedProjectsService.addProjectUID(project.metadata.uid);
if (cachedProjectData) {
cachedProjectData.update(project, 'MODIFIED');
}
// TODO: fix need to return context & projectPromise // TODO: fix need to return context & projectPromise
return [project, context]; return [project, context];
}); });
...@@ -2983,10 +3007,56 @@ angular.module('openshiftCommonServices') ...@@ -2983,10 +3007,56 @@ angular.module('openshiftCommonServices')
}); });
}); });
}, },
// List the projects the user has access to. This method returns
// cached data if the projects had previously been fetched to avoid
// requesting them again and again, which is a problem for admins who
// might have hundreds or more.
list: function(forceRefresh) {
if (cachedProjectData && !forceRefresh) {
Logger.debug('ProjectsService: returning cached project data');
return $q.when(cachedProjectData);
}
Logger.debug('ProjectsService: listing projects, force refresh', forceRefresh);
return DataService.list('projects', {}).then(function(projectData) {
cachedProjectData = projectData;
return projectData;
}, function(error) {
// If the request fails, don't try to list projects again without `forceRefresh`.
cachedProjectData = {};
cachedProjectDataIncomplete = true;
});
},
isProjectListIncomplete: function() {
return cachedProjectDataIncomplete;
},
watch: function(context, callback) {
// Wrap `DataService.watch` so we can update the cached projects
// list on changes. TODO: We might want to disable watches entirely
// if we know the project list is large.
return DataService.watch('projects', context, function(projectData) {
cachedProjectData = projectData;
callback(projectData);
});
},
update: function(projectName, data) { update: function(projectName, data) {
return DataService return DataService.update('projects', projectName, cleanEditableAnnotations(data), {
.update('projects', projectName, cleanEditableAnnotations(data), {projectName: projectName}, {errorNotification: false}); projectName: projectName
}, {
errorNotification: false
}).then(function(updatedProject) {
if (cachedProjectData) {
cachedProjectData.update(updatedProject, 'MODIFIED');
}
return updatedProject;
});
}, },
create: function(name, displayName, description) { create: function(name, displayName, description) {
var projectRequest = { var projectRequest = {
apiVersion: "v1", apiVersion: "v1",
...@@ -3001,11 +3071,25 @@ angular.module('openshiftCommonServices') ...@@ -3001,11 +3071,25 @@ angular.module('openshiftCommonServices')
.create('projectrequests', null, projectRequest, {}) .create('projectrequests', null, projectRequest, {})
.then(function(project) { .then(function(project) {
RecentlyViewedProjectsService.addProjectUID(project.metadata.uid); RecentlyViewedProjectsService.addProjectUID(project.metadata.uid);
if (cachedProjectData) {
cachedProjectData.update(project, 'ADDED');
}
return project; return project;
}); });
}, },
canCreate: function() { canCreate: function() {
return DataService.get("projectrequests", null, {}, { errorNotification: false}); return DataService.get("projectrequests", null, {}, { errorNotification: false});
},
delete: function(project) {
return DataService.delete('projects', project.metadata.name, {}).then(function(deletedProject) {
if (cachedProjectData) {
cachedProjectData.update(project, 'DELETED');
}
return deletedProject;
});
} }
}; };
}); });
......
...@@ -310,11 +310,10 @@ hawtioPluginLoader.addModule('openshiftCommonUI'); ...@@ -310,11 +310,10 @@ hawtioPluginLoader.addModule('openshiftCommonUI');
" <!-- Use a form so that the enter key submits when typing a project name to confirm. -->\n" + " <!-- Use a form so that the enter key submits when typing a project name to confirm. -->\n" +
" <form>\n" + " <form>\n" +
" <div class=\"modal-body\">\n" + " <div class=\"modal-body\">\n" +
" <h1>Are you sure you want to delete the project\n" + " <h1>Are you sure you want to delete the project '<strong>{{project | displayName}}</strong>'?</h1>\n" +
" '<strong>{{displayName ? displayName : projectName}}</strong>'?</h1>\n" +
" <p>\n" + " <p>\n" +
" This will <strong>delete all resources</strong> associated with\n" + " This will <strong>delete all resources</strong> associated with\n" +
" the project {{displayName ? displayName : projectName}} and <strong>cannot be\n" + " the project {{project | displayName}} and <strong>cannot be\n" +
" undone</strong>. Make sure this is something you really want to do!\n" + " undone</strong>. Make sure this is something you really want to do!\n" +
" </p>\n" + " </p>\n" +
" <div ng-show=\"typeNameToConfirm\">\n" + " <div ng-show=\"typeNameToConfirm\">\n" +
...@@ -334,8 +333,8 @@ hawtioPluginLoader.addModule('openshiftCommonUI'); ...@@ -334,8 +333,8 @@ hawtioPluginLoader.addModule('openshiftCommonUI');
" </div>\n" + " </div>\n" +
" </div>\n" + " </div>\n" +
" <div class=\"modal-footer\">\n" + " <div class=\"modal-footer\">\n" +
" <button ng-disabled=\"typeNameToConfirm && confirmName !== projectName && confirmName !== displayName\" class=\"btn btn-lg btn-danger\" type=\"submit\" ng-click=\"delete();\">Delete</button>\n" + " <button ng-disabled=\"typeNameToConfirm && confirmName !== project.metadata.name && confirmName !== (project | displayName : false)\" class=\"btn btn-lg btn-danger\" type=\"submit\" ng-click=\"delete()\">Delete</button>\n" +
" <button class=\"btn btn-lg btn-default\" type=\"button\" ng-click=\"cancel();\">Cancel</button>\n" + " <button class=\"btn btn-lg btn-default\" type=\"button\" ng-click=\"cancel()\">Cancel</button>\n" +
" </div>\n" + " </div>\n" +
" </form>\n" + " </form>\n" +
"</div>\n" "</div>\n"
...@@ -625,14 +624,12 @@ angular.module("openshiftCommonUI") ...@@ -625,14 +624,12 @@ angular.module("openshiftCommonUI")
;'use strict'; ;'use strict';
angular.module("openshiftCommonUI") angular.module("openshiftCommonUI")
.directive("deleteProject", function ($uibModal, $location, $filter, $q, hashSizeFilter, APIService, DataService, NotificationsService, Logger) { .directive("deleteProject", function($uibModal, $location, $filter, $q, hashSizeFilter, APIService, NotificationsService, ProjectsService, Logger) {
return { return {
restrict: "E", restrict: "E",
scope: { scope: {
// The name of project to delete // The project to delete
projectName: "@", project: "=",
// Optional display name of the project to delete.
displayName: "@",
// Set to true to disable the delete button. // Set to true to disable the delete button.
disableDelete: "=?", disableDelete: "=?",
// Force the user to enter the name before we'll delete the project. // Force the user to enter the name before we'll delete the project.
...@@ -658,6 +655,7 @@ angular.module("openshiftCommonUI") ...@@ -658,6 +655,7 @@ angular.module("openshiftCommonUI")
// Replace so ".dropdown-menu > li > a" styles are applied. // Replace so ".dropdown-menu > li > a" styles are applied.
replace: true, replace: true,
link: function(scope, element, attrs) { link: function(scope, element, attrs) {
var displayName = $filter('displayName');
var navigateToList = function() { var navigateToList = function() {
if (scope.stayOnCurrentPage) { if (scope.stayOnCurrentPage) {
return; return;
...@@ -692,14 +690,9 @@ angular.module("openshiftCommonUI") ...@@ -692,14 +690,9 @@ angular.module("openshiftCommonUI")
modalInstance.result.then(function() { modalInstance.result.then(function() {
// upon clicking delete button, delete resource and send alert // upon clicking delete button, delete resource and send alert
var projectName = scope.projectName; var formattedResource = "Project \'" + displayName(scope.project) + "\'";
var formattedResource = "Project \'" + (scope.displayName || projectName) + "\'";
var context = {}; ProjectsService.delete(scope.project).then(function() {
DataService.delete({
resource: APIService.kindToResource("Project")
}, projectName, context)
.then(function() {
NotificationsService.addNotification({ NotificationsService.addNotification({
type: "success", type: "success",
message: formattedResource + " was marked for deletion." message: formattedResource + " was marked for deletion."
...@@ -758,7 +751,14 @@ angular.module("openshiftCommonUI") ...@@ -758,7 +751,14 @@ angular.module("openshiftCommonUI")
isDialog: '@' isDialog: '@'
}, },
templateUrl: 'src/components/edit-project/editProject.html', templateUrl: 'src/components/edit-project/editProject.html',
controller: function($scope, $filter, $location, DataService, NotificationsService, annotationNameFilter, displayNameFilter, Logger) { controller: function($scope,
$filter,
$location,
Logger,
NotificationsService,
ProjectsService,
annotationNameFilter,
displayNameFilter) {
if(!($scope.submitButtonLabel)) { if(!($scope.submitButtonLabel)) {
$scope.submitButtonLabel = 'Save'; $scope.submitButtonLabel = 'Save';
} }
...@@ -800,13 +800,10 @@ angular.module("openshiftCommonUI") ...@@ -800,13 +800,10 @@ angular.module("openshiftCommonUI")
$scope.update = function() { $scope.update = function() {
$scope.disableInputs = true; $scope.disableInputs = true;
if ($scope.editProjectForm.$valid) { if ($scope.editProjectForm.$valid) {
DataService ProjectsService
.update( .update(
'projects',
$scope.project.metadata.name, $scope.project.metadata.name,
cleanEditableAnnotations(mergeEditable($scope.project, $scope.editableFields)), cleanEditableAnnotations(mergeEditable($scope.project, $scope.editableFields)))
{projectName: $scope.project.name},
{errorNotification: false})
.then(function(project) { .then(function(project) {
// angular is actually wrapping the redirect action :/ // angular is actually wrapping the redirect action :/
var cb = $scope.redirectAction(); var cb = $scope.redirectAction();
......
...@@ -281,11 +281,10 @@ angular.module('openshiftCommonUI').run(['$templateCache', function($templateCac ...@@ -281,11 +281,10 @@ angular.module('openshiftCommonUI').run(['$templateCache', function($templateCac
" <!-- Use a form so that the enter key submits when typing a project name to confirm. -->\n" + " <!-- Use a form so that the enter key submits when typing a project name to confirm. -->\n" +
" <form>\n" + " <form>\n" +
" <div class=\"modal-body\">\n" + " <div class=\"modal-body\">\n" +
" <h1>Are you sure you want to delete the project\n" + " <h1>Are you sure you want to delete the project '<strong>{{project | displayName}}</strong>'?</h1>\n" +
" '<strong>{{displayName ? displayName : projectName}}</strong>'?</h1>\n" +
" <p>\n" + " <p>\n" +
" This will <strong>delete all resources</strong> associated with\n" + " This will <strong>delete all resources</strong> associated with\n" +
" the project {{displayName ? displayName : projectName}} and <strong>cannot be\n" + " the project {{project | displayName}} and <strong>cannot be\n" +
" undone</strong>. Make sure this is something you really want to do!\n" + " undone</strong>. Make sure this is something you really want to do!\n" +
" </p>\n" + " </p>\n" +
" <div ng-show=\"typeNameToConfirm\">\n" + " <div ng-show=\"typeNameToConfirm\">\n" +
...@@ -305,8 +304,8 @@ angular.module('openshiftCommonUI').run(['$templateCache', function($templateCac ...@@ -305,8 +304,8 @@ angular.module('openshiftCommonUI').run(['$templateCache', function($templateCac
" </div>\n" + " </div>\n" +
" </div>\n" + " </div>\n" +
" <div class=\"modal-footer\">\n" + " <div class=\"modal-footer\">\n" +
" <button ng-disabled=\"typeNameToConfirm && confirmName !== projectName && confirmName !== displayName\" class=\"btn btn-lg btn-danger\" type=\"submit\" ng-click=\"delete();\">Delete</button>\n" + " <button ng-disabled=\"typeNameToConfirm && confirmName !== project.metadata.name && confirmName !== (project | displayName : false)\" class=\"btn btn-lg btn-danger\" type=\"submit\" ng-click=\"delete()\">Delete</button>\n" +
" <button class=\"btn btn-lg btn-default\" type=\"button\" ng-click=\"cancel();\">Cancel</button>\n" + " <button class=\"btn btn-lg btn-default\" type=\"button\" ng-click=\"cancel()\">Cancel</button>\n" +
" </div>\n" + " </div>\n" +
" </form>\n" + " </form>\n" +
"</div>\n" "</div>\n"
......
...@@ -2,11 +2,10 @@ ...@@ -2,11 +2,10 @@
<!-- Use a form so that the enter key submits when typing a project name to confirm. --> <!-- Use a form so that the enter key submits when typing a project name to confirm. -->
<form> <form>
<div class="modal-body"> <div class="modal-body">
<h1>Are you sure you want to delete the project <h1>Are you sure you want to delete the project '<strong>{{project | displayName}}</strong>'?</h1>
'<strong>{{displayName ? displayName : projectName}}</strong>'?</h1>
<p> <p>
This will <strong>delete all resources</strong> associated with This will <strong>delete all resources</strong> associated with
the project {{displayName ? displayName : projectName}} and <strong>cannot be the project {{project | displayName}} and <strong>cannot be
undone</strong>. Make sure this is something you really want to do! undone</strong>. Make sure this is something you really want to do!
</p> </p>
<div ng-show="typeNameToConfirm"> <div ng-show="typeNameToConfirm">
...@@ -26,8 +25,8 @@ ...@@ -26,8 +25,8 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button ng-disabled="typeNameToConfirm && confirmName !== projectName && confirmName !== displayName" class="btn btn-lg btn-danger" type="submit" ng-click="delete();">Delete</button> <button ng-disabled="typeNameToConfirm && confirmName !== project.metadata.name && confirmName !== (project | displayName : false)" class="btn btn-lg btn-danger" type="submit" ng-click="delete()">Delete</button>
<button class="btn btn-lg btn-default" type="button" ng-click="cancel();">Cancel</button> <button class="btn btn-lg btn-default" type="button" ng-click="cancel()">Cancel</button>
</div> </div>
</form> </form>
</div> </div>
'use strict'; 'use strict';
angular.module("openshiftCommonUI") angular.module("openshiftCommonUI")
.directive("deleteProject", function ($uibModal, $location, $filter, $q, hashSizeFilter, APIService, DataService, NotificationsService, Logger) { .directive("deleteProject", function($uibModal, $location, $filter, $q, hashSizeFilter, APIService, NotificationsService, ProjectsService, Logger) {
return { return {
restrict: "E", restrict: "E",
scope: { scope: {
// The name of project to delete // The project to delete
projectName: "@", project: "=",
// Optional display name of the project to delete.
displayName: "@",
// Set to true to disable the delete button. // Set to true to disable the delete button.
disableDelete: "=?", disableDelete: "=?",
// Force the user to enter the name before we'll delete the project. // Force the user to enter the name before we'll delete the project.
...@@ -34,6 +32,7 @@ angular.module("openshiftCommonUI") ...@@ -34,6 +32,7 @@ angular.module("openshiftCommonUI")
// Replace so ".dropdown-menu > li > a" styles are applied. // Replace so ".dropdown-menu > li > a" styles are applied.
replace: true, replace: true,
link: function(scope, element, attrs) { link: function(scope, element, attrs) {
var displayName = $filter('displayName');
var navigateToList = function() { var navigateToList = function() {
if (scope.stayOnCurrentPage) { if (scope.stayOnCurrentPage) {
return; return;
...@@ -68,14 +67,9 @@ angular.module("openshiftCommonUI") ...@@ -68,14 +67,9 @@ angular.module("openshiftCommonUI")
modalInstance.result.then(function() { modalInstance.result.then(function() {
// upon clicking delete button, delete resource and send alert // upon clicking delete button, delete resource and send alert
var projectName = scope.projectName; var formattedResource = "Project \'" + displayName(scope.project) + "\'";
var formattedResource = "Project \'" + (scope.displayName || projectName) + "\'";
var context = {};
DataService.delete({ ProjectsService.delete(scope.project).then(function() {
resource: APIService.kindToResource("Project")
}, projectName, context)
.then(function() {
NotificationsService.addNotification({ NotificationsService.addNotification({
type: "success", type: "success",
message: formattedResource + " was marked for deletion." message: formattedResource + " was marked for deletion."
......
...@@ -13,7 +13,14 @@ angular.module("openshiftCommonUI") ...@@ -13,7 +13,14 @@ angular.module("openshiftCommonUI")
isDialog: '@' isDialog: '@'
}, },
templateUrl: 'src/components/edit-project/editProject.html', templateUrl: 'src/components/edit-project/editProject.html',
controller: function($scope, $filter, $location, DataService, NotificationsService, annotationNameFilter, displayNameFilter, Logger) { controller: function($scope,
$filter,
$location,
Logger,
NotificationsService,
ProjectsService,
annotationNameFilter,
displayNameFilter) {
if(!($scope.submitButtonLabel)) { if(!($scope.submitButtonLabel)) {
$scope.submitButtonLabel = 'Save'; $scope.submitButtonLabel = 'Save';
} }
...@@ -55,13 +62,10 @@ angular.module("openshiftCommonUI") ...@@ -55,13 +62,10 @@ angular.module("openshiftCommonUI")
$scope.update = function() { $scope.update = function() {
$scope.disableInputs = true; $scope.disableInputs = true;
if ($scope.editProjectForm.$valid) { if ($scope.editProjectForm.$valid) {
DataService ProjectsService
.update( .update(
'projects',
$scope.project.metadata.name, $scope.project.metadata.name,
cleanEditableAnnotations(mergeEditable($scope.project, $scope.editableFields)), cleanEditableAnnotations(mergeEditable($scope.project, $scope.editableFields)))
{projectName: $scope.project.name},
{errorNotification: false})
.then(function(project) { .then(function(project) {
// angular is actually wrapping the redirect action :/ // angular is actually wrapping the redirect action :/
var cb = $scope.redirectAction(); var cb = $scope.redirectAction();
......
...@@ -2,8 +2,28 @@ ...@@ -2,8 +2,28 @@
angular.module('openshiftCommonServices') angular.module('openshiftCommonServices')
.factory('ProjectsService', .factory('ProjectsService',
function($location, $q, AuthService, DataService, annotationNameFilter, AuthorizationService, RecentlyViewedProjectsService) { function($location,
$q,
$rootScope,
AuthService,
AuthorizationService,
DataService,
Logger,
RecentlyViewedProjectsService,
annotationNameFilter) {
// Cache project data when we can so we don't request it on every page load.
var cachedProjectData;
var cachedProjectDataIncomplete = false;
var clearCachedProjectData = function() {
Logger.debug('ProjectsService: clearing project cache');
cachedProjectData = null;
cachedProjectDataIncomplete = false;
};
AuthService.onUserChanged(clearCachedProjectData);
AuthService.onLogout(clearCachedProjectData);
var cleanEditableAnnotations = function(resource) { var cleanEditableAnnotations = function(resource) {
var paths = [ var paths = [
...@@ -38,6 +58,10 @@ angular.module('openshiftCommonServices') ...@@ -38,6 +58,10 @@ angular.module('openshiftCommonServices')
context.project = project; context.project = project;
context.projectPromise.resolve(project); context.projectPromise.resolve(project);
RecentlyViewedProjectsService.addProjectUID(project.metadata.uid); RecentlyViewedProjectsService.addProjectUID(project.metadata.uid);
if (cachedProjectData) {
cachedProjectData.update(project, 'MODIFIED');
}
// TODO: fix need to return context & projectPromise // TODO: fix need to return context & projectPromise
return [project, context]; return [project, context];
}); });
...@@ -64,10 +88,56 @@ angular.module('openshiftCommonServices') ...@@ -64,10 +88,56 @@ angular.module('openshiftCommonServices')
}); });
}); });
}, },
// List the projects the user has access to. This method returns
// cached data if the projects had previously been fetched to avoid
// requesting them again and again, which is a problem for admins who
// might have hundreds or more.
list: function(forceRefresh) {
if (cachedProjectData && !forceRefresh) {
Logger.debug('ProjectsService: returning cached project data');
return $q.when(cachedProjectData);
}
Logger.debug('ProjectsService: listing projects, force refresh', forceRefresh);
return DataService.list('projects', {}).then(function(projectData) {
cachedProjectData = projectData;
return projectData;
}, function(error) {
// If the request fails, don't try to list projects again without `forceRefresh`.
cachedProjectData = {};
cachedProjectDataIncomplete = true;
});
},
isProjectListIncomplete: function() {
return cachedProjectDataIncomplete;
},
watch: function(context, callback) {
// Wrap `DataService.watch` so we can update the cached projects
// list on changes. TODO: We might want to disable watches entirely
// if we know the project list is large.
return DataService.watch('projects', context, function(projectData) {
cachedProjectData = projectData;
callback(projectData);
});
},
update: function(projectName, data) { update: function(projectName, data) {
return DataService return DataService.update('projects', projectName, cleanEditableAnnotations(data), {
.update('projects', projectName, cleanEditableAnnotations(data), {projectName: projectName}, {errorNotification: false}); projectName: projectName
}, {
errorNotification: false
}).then(function(updatedProject) {
if (cachedProjectData) {
cachedProjectData.update(updatedProject, 'MODIFIED');
}
return updatedProject;
});
}, },
create: function(name, displayName, description) { create: function(name, displayName, description) {
var projectRequest = { var projectRequest = {
apiVersion: "v1", apiVersion: "v1",
...@@ -82,11 +152,25 @@ angular.module('openshiftCommonServices') ...@@ -82,11 +152,25 @@ angular.module('openshiftCommonServices')
.create('projectrequests', null, projectRequest, {}) .create('projectrequests', null, projectRequest, {})
.then(function(project) { .then(function(project) {
RecentlyViewedProjectsService.addProjectUID(project.metadata.uid); RecentlyViewedProjectsService.addProjectUID(project.metadata.uid);
if (cachedProjectData) {
cachedProjectData.update(project, 'ADDED');
}
return project; return project;
}); });
}, },
canCreate: function() { canCreate: function() {
return DataService.get("projectrequests", null, {}, { errorNotification: false}); return DataService.get("projectrequests", null, {}, { errorNotification: false});
},
delete: function(project) {
return DataService.delete('projects', project.metadata.name, {}).then(function(deletedProject) {
if (cachedProjectData) {
cachedProjectData.update(project, 'DELETED');
}
return deletedProject;
});
} }
}; };
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment