From 9e5ef0126ede4586b21e34b5cce31532e40fc5fa Mon Sep 17 00:00:00 2001 From: Julien Bramary Date: Tue, 9 Aug 2016 14:42:49 +0100 Subject: [PATCH 1/3] Use inlined data in space-model * Also correctly update roles cache in various scenarios --- .../actions/roles-tables/roles.service.js | 3 +- .../model/organization/organization.model.js | 127 ++++++++++++----- .../cloud-foundry/model/space/space.model.js | 134 +++++++++++++++--- 3 files changed, 208 insertions(+), 56 deletions(-) diff --git a/src/app/view/endpoints/clusters/cluster/actions/roles-tables/roles.service.js b/src/app/view/endpoints/clusters/cluster/actions/roles-tables/roles.service.js index bd870c7ebe..0e0d781533 100644 --- a/src/app/view/endpoints/clusters/cluster/actions/roles-tables/roles.service.js +++ b/src/app/view/endpoints/clusters/cluster/actions/roles-tables/roles.service.js @@ -613,8 +613,7 @@ var cachePromises = []; // Refresh org cache if (changes.organization) { - var org = _.get(organizationModel, organizationModel.fetchOrganizationPath(clusterGuid, orgGuid)); - cachePromises.push(organizationModel.getOrganizationDetails(clusterGuid, org.details.org)); + cachePromises.push(organizationModel.refreshOrganizationUserRoles(clusterGuid, orgGuid)); } // Refresh space caches diff --git a/src/plugins/cloud-foundry/model/organization/organization.model.js b/src/plugins/cloud-foundry/model/organization/organization.model.js index 1bbb4a5160..28d3a2a939 100644 --- a/src/plugins/cloud-foundry/model/organization/organization.model.js +++ b/src/plugins/cloud-foundry/model/organization/organization.model.js @@ -197,30 +197,25 @@ this.organizationNames[cnsiGuid].push(details.org.entity.name); }, - cacheOrganizationUsersRoles: function (cnsiGuid, orgGuid, roles) { + cacheOrganizationUsersRoles: function (cnsiGuid, orgGuid, allUsersRoles) { var that = this; + this.initOrganizationCache(cnsiGuid, orgGuid); // Empty the cache without changing the Object reference - var rolesCache = that.organizations[cnsiGuid][orgGuid].roles; - for (var role in rolesCache) { - if (rolesCache.hasOwnProperty(role)) { - delete rolesCache[role]; - } - } + this.uncacheOrganizationUserRoles(cnsiGuid, orgGuid); - _.forEach(roles, function (user) { - rolesCache[user.metadata.guid] = user.entity.organization_roles; + _.forEach(allUsersRoles, function (user) { + that.organizations[cnsiGuid][orgGuid].roles[user.metadata.guid] = user.entity.organization_roles; }); }, cacheOrganizationServices: function (cnsiGuid, orgGuid, services) { - var that = this; this.initOrganizationCache(cnsiGuid, orgGuid); // Empty the cache without changing the Object reference - var servicesCache = that.organizations[cnsiGuid][orgGuid].services; + var servicesCache = this.organizations[cnsiGuid][orgGuid].services; for (var service in servicesCache) { if (servicesCache.hasOwnProperty(service)) { delete servicesCache[service]; @@ -233,12 +228,11 @@ }, cacheOrganizationSpaces: function (cnsiGuid, orgGuid, spaces) { - var that = this; this.initOrganizationCache(cnsiGuid, orgGuid); // Empty the cache without changing the Object reference - var spaceCache = that.organizations[cnsiGuid][orgGuid].spaces; + var spaceCache = this.organizations[cnsiGuid][orgGuid].spaces; for (var space in spaceCache) { if (spaceCache.hasOwnProperty(space)) { delete spaceCache[space]; @@ -264,6 +258,15 @@ delete this.organizations[cnsiGuid][orgGuid]; }, + uncacheOrganizationUserRoles: function (cnsiGuid, orgGuid) { + var rolesCache = this.organizations[cnsiGuid][orgGuid].roles; + for (var role in rolesCache) { + if (rolesCache.hasOwnProperty(role)) { + delete rolesCache[role]; + } + } + }, + /** * @function getOrganizationDetails * @memberof cloud-foundry.model.organization @@ -277,26 +280,6 @@ var that = this; - function assembleOrgRoles(users, role, usersHash) { - _.forEach(users, function (orgUser) { - var userKey = orgUser.metadata.guid; - if (!usersHash.hasOwnProperty(userKey)) { - usersHash[userKey] = orgUser; - } - usersHash[userKey].entity.organization_roles = usersHash[userKey].entity.organization_roles || []; - usersHash[userKey].entity.organization_roles.push(role); - }); - } - - function unsplitOrgRoles(anOrg) { - var usersHash = {}; - assembleOrgRoles(anOrg.entity.users, 'org_user', usersHash); - assembleOrgRoles(anOrg.entity.managers, 'org_manager', usersHash); - assembleOrgRoles(anOrg.entity.billing_managers, 'billing_manager', usersHash); - assembleOrgRoles(anOrg.entity.auditors, 'org_auditor', usersHash); - return _.values(usersHash); - } - var httpConfig = this.makeHttpConfig(cnsiGuid); var orgGuid = org.metadata.guid; var orgQuotaGuid = org.entity.quota_definition_guid; @@ -387,7 +370,7 @@ // The users roles may be returned inline if (org.entity.users) { // Reconstruct all user roles from inline data - rolesP = that.$q.resolve(unsplitOrgRoles(org)); + rolesP = that.$q.resolve(_unsplitOrgRoles(org)); } else { rolesP = that.orgsApi.RetrievingRolesOfAllUsersInOrganization(orgGuid, params, httpConfig).then(function (val) { return val.data.resources; @@ -508,8 +491,82 @@ that.organizations[cnsiGuid][orgGuid].details.org = val.data; return val; }); - } + }, + refreshOrganizationUserRoles: function (cnsiGuid, orgGuid) { + var that = this; + return this.orgsApi.RetrievingRolesOfAllUsersInOrganization(orgGuid, null, this.makeHttpConfig(cnsiGuid)) + .then(function (val) { + var allUsersRoles = val.data.resources; + that.cacheOrganizationUsersRoles(cnsiGuid, orgGuid, allUsersRoles); + // Ensures we also update inlined data for getDetails to pick up + _splitOrgRoles(that.organizations[cnsiGuid][orgGuid].details.org, allUsersRoles); + return allUsersRoles; + }); + } }); + var ORG_ROLE_TO_KEY = { + org_user: 'users', + org_manager: 'managers', + billing_manager: 'billing_managers', + org_auditor: 'auditors' + }; + + function _shallowCloneUser(user) { + var clone = { + entity: _.clone(user.entity), + metadata: _.clone(user.metadata) + }; + if (clone.entity.organization_roles) { + delete clone.entity.organization_roles; + } + return clone; + } + + function _hasRole(user, role) { + return user.entity.organization_roles.indexOf(role) > -1; + } + + function _assembleOrgRoles(users, role, usersHash) { + _.forEach(users, function (user) { + var userKey = user.metadata.guid; + if (!usersHash.hasOwnProperty(userKey)) { + usersHash[userKey] = _shallowCloneUser(user); + } + usersHash[userKey].entity.organization_roles = usersHash[userKey].entity.organization_roles || []; + usersHash[userKey].entity.organization_roles.push(role); + }); + } + + /** + * Transform split organization role properties into an array of users with an organization_roles property such as + * returned by the: RetrievingRolesOfAllUsersInOrganization() cloud foundry API + * @param {Object} anOrg organization object containing inlined managers etc. + * @returns {Array} a list of Users of the organization with their organization_roles property populated + * */ + function _unsplitOrgRoles(anOrg) { + var usersHash = {}; + _.forEach(ORG_ROLE_TO_KEY, function (key, role) { + _assembleOrgRoles(anOrg.entity[key], role, usersHash); + }); + return _.values(usersHash); + } + + function _splitOrgRoles(anOrg, usersRoles) { + _.forEach(ORG_ROLE_TO_KEY, function (key, role) { + // Clean while preserving ref in case directives are bound to it + if (angular.isDefined(anOrg.entity[key])) { + anOrg.entity[key].length = 0; + } else { + anOrg.entity[key] = []; + } + _.forEach(usersRoles, function (user) { + if (_hasRole(user, role)) { + anOrg.entity[key].push(user); + } + }); + }); + } + })(); diff --git a/src/plugins/cloud-foundry/model/space/space.model.js b/src/plugins/cloud-foundry/model/space/space.model.js index 4352594f6a..a965cc03ce 100644 --- a/src/plugins/cloud-foundry/model/space/space.model.js +++ b/src/plugins/cloud-foundry/model/space/space.model.js @@ -10,31 +10,31 @@ .run(registerSpaceModel); registerSpaceModel.$inject = [ + '$q', 'app.model.modelManager', - 'app.api.apiManager', - '$q' + 'app.api.apiManager' ]; - function registerSpaceModel(modelManager, apiManager, $q) { - modelManager.register('cloud-foundry.model.space', new Space(apiManager, modelManager, $q)); + function registerSpaceModel($q, modelManager, apiManager) { + modelManager.register('cloud-foundry.model.space', new Space($q, apiManager, modelManager)); } /** * @memberof cloud-foundry.model * @name Space + * @param {object} $q - angular $q service + * @property {object} $q - angular $q service * @param {app.api.apiManager} apiManager - the API manager * @property {app.api.apiManager} apiManager - the API manager * @param {object} modelManager - the model manager * @property {object} stackatoInfoModel - the stackatoInfoModel service - * @param {object} $q - angular $q service - * @property {object} $q - angular $q service * @class */ - function Space(apiManager, modelManager, $q) { + function Space($q, apiManager, modelManager) { + this.$q = $q; this.apiManager = apiManager; this.stackatoInfoModel = modelManager.retrieve('app.model.stackatoInfo'); this.organizationModel = modelManager.retrieve('cloud-foundry.model.organization'); - this.$q = $q; this.data = { }; @@ -83,6 +83,13 @@ */ onListAllAppsForSpace: function (cnsiGuid, guid, apps) { _.set(this, 'spaces.' + cnsiGuid + '.' + guid + '.apps', apps); + + // Ensures we also update inlined data for getDetails to pick up + var cachedSpace = _.get(this, 'spaces.' + cnsiGuid + '.' + guid + '.details.space'); + if (angular.isDefined(cachedSpace)) { + cachedSpace.entity.apps = apps; + } + return apps; }, @@ -236,17 +243,24 @@ * @description Cache response * @param {string} cnsiGuid - The GUID of the cloud-foundry server. * @param {string} guid - space GUID. - * @param {object} roles - list of apps + * @param {object} allUsersRoles - list of apps * @returns {object} roles * @public */ - onListRolesOfAllUsersInSpace: function (cnsiGuid, guid, roles) { + onListRolesOfAllUsersInSpace: function (cnsiGuid, guid, allUsersRoles) { var rolesByUserGuid = {}; - _.forEach(roles, function (user) { + _.forEach(allUsersRoles, function (user) { _.set(rolesByUserGuid, user.metadata.guid, user.entity.space_roles); }); _.set(this, 'spaces.' + cnsiGuid + '.' + guid + '.roles', rolesByUserGuid); - return roles; + + // Ensures we also update inlined data for getDetails to pick up + var cachedSpace = _.get(this, 'spaces.' + cnsiGuid + '.' + guid + '.details.space'); + if (angular.isDefined(cachedSpace)) { + _splitSpaceRoles(cachedSpace, allUsersRoles); + } + + return allUsersRoles; }, /** @@ -322,13 +336,19 @@ var spaceQuotaApi = that.apiManager.retrieve('cloud-foundry.api.SpaceQuotaDefinitions'); // var usedMemP = orgsApi.RetrievingOrganizationMemoryUsage(orgGuid, params, httpConfig); - var serviceInstancesP; + var serviceInstancesP, rolesP, appP; if (space.entity.service_instances) { - var spaces = space.entity.service_instances; - serviceInstancesP = this.$q.resolve(spaces); - that.onListAllServiceInstancesForSpace(cnsiGuid, spaceGuid, spaces); + var serviceInstances = space.entity.service_instances; + // For now filter out user_service_instances to get the same list as before + serviceInstances = _.filter(serviceInstances, function (si) { + return si.entity.type === 'managed_service_instance'; + }); + serviceInstancesP = this.$q.resolve(serviceInstances); + that.onListAllServiceInstancesForSpace(cnsiGuid, spaceGuid, serviceInstances); } else { - serviceInstancesP = this.listAllServiceInstancesForSpace(cnsiGuid, spaceGuid).then(function (val) { + serviceInstancesP = this.listAllServiceInstancesForSpace(cnsiGuid, spaceGuid, { + return_user_provided_service_instances: false + }).then(function (val) { return val; }); } @@ -339,7 +359,15 @@ // Find our user's GUID var userGuid = that.stackatoInfoModel.info; - var rolesP = this.listRolesOfAllUsersInSpace(cnsiGuid, spaceGuid, params); + + // Space roles can be inlined + if (space.entity.managers) { + var unsplitRoles = _unsplitSpaceRoles(space); + rolesP = that.$q.resolve(unsplitRoles); + that.onListRolesOfAllUsersInSpace(cnsiGuid, spaceGuid, unsplitRoles); + } else { + rolesP = this.listRolesOfAllUsersInSpace(cnsiGuid, spaceGuid, params); + } var spaceRolesP = rolesP.then(function () { // Find my user's roles @@ -350,9 +378,17 @@ return myRoles; }); - var appP = this.listAllAppsForSpace(cnsiGuid, spaceGuid); + if (space.entity.apps) { + appP = that.$q.resolve(space.entity.apps); + that.onListAllAppsForSpace(cnsiGuid, spaceGuid, space.entity.apps); + } else { + appP = this.listAllAppsForSpace(cnsiGuid, spaceGuid); + } + + // Services are never inlined var servicesP = this.listAllServicesForSpace(cnsiGuid, spaceGuid); + // We cannot rely on inline routes as they lack the depth we need later on var routesP = this.listAllRoutesForSpace(cnsiGuid, spaceGuid); return this.$q.all({ @@ -468,4 +504,64 @@ }); + var SPACE_ROLE_TO_KEY = { + space_developer: 'developers', + space_manager: 'managers', + space_auditor: 'auditors' + }; + + function _shallowCloneUser(user) { + var clone = { + entity: _.clone(user.entity), + metadata: _.clone(user.metadata) + }; + if (clone.entity.space_roles) { + delete clone.entity.space_roles; + } + return clone; + } + + function _hasRole(user, role) { + return user.entity.space_roles.indexOf(role) > -1; + } + + function assembleSpaceRoles(users, role, usersHash) { + _.forEach(users, function (user) { + var userKey = user.metadata.guid; + if (!usersHash.hasOwnProperty(userKey)) { + usersHash[userKey] = _shallowCloneUser(user); + } + usersHash[userKey].entity.space_roles = usersHash[userKey].entity.space_roles || []; + usersHash[userKey].entity.space_roles.push(role); + }); + } + + /** + * Transform split space role properties into an array of users with a space_roles property such as + * returned by the: RetrievingRolesOfAllUsersInSpace() cloud foundry API + * @param {Object} aSpace space object containing inlined managers etc. + * @returns {Array} a list of Users of the space with their space_roles property populated + * */ + function _unsplitSpaceRoles(aSpace) { + var usersHash = {}; + _.forEach(SPACE_ROLE_TO_KEY, function (key, role) { + assembleSpaceRoles(aSpace.entity[key], role, usersHash); + }); + return _.values(usersHash); + } + + function _splitSpaceRoles(aSpace, usersRoles) { + _.forEach(SPACE_ROLE_TO_KEY, function (key, role) { + // Clean while preserving ref in case directives are bound to it + if (angular.isDefined(aSpace.entity[key])) { + aSpace.entity[key].length = 0; + } else { + aSpace.entity[key] = []; + } + _.forEach(usersRoles, function (user) { + if (_hasRole(user, role)) { aSpace.entity[key].push(user); } + }); + }); + } + })(); From b6d6099cda90f707213422e3b32f650488c57bf2 Mon Sep 17 00:00:00 2001 From: Julien Bramary Date: Tue, 9 Aug 2016 15:06:45 +0100 Subject: [PATCH 2/3] Lint complexity --- .../model/organization/organization.model.js | 118 +++++++++++------- 1 file changed, 74 insertions(+), 44 deletions(-) diff --git a/src/plugins/cloud-foundry/model/organization/organization.model.js b/src/plugins/cloud-foundry/model/organization/organization.model.js index 28d3a2a939..85915900c0 100644 --- a/src/plugins/cloud-foundry/model/organization/organization.model.js +++ b/src/plugins/cloud-foundry/model/organization/organization.model.js @@ -286,51 +286,100 @@ var createdDate = moment(org.metadata.created_at, "YYYY-MM-DDTHH:mm:ssZ"); var userGuid = that.stackatoInfoModel.info.endpoints.hcf[cnsiGuid].user.guid; - // Memory and instance usages are never returned inline but we are able to derive from inline apps - var usedMemP, instancesP, quotaP, rolesP, routesCountP, allSpacesP, allUsersRoles; - if (org.entity.spaces) { + function getRoles(org) { + // The users roles may be returned inline + if (org.entity.users) { + // Reconstruct all user roles from inline data + return that.$q.resolve(_unsplitOrgRoles(org)); + } + return that.orgsApi.RetrievingRolesOfAllUsersInOrganization(orgGuid, params, httpConfig).then(function (val) { + return val.data.resources; + }); + } - if (org.entity.spaces.length === 0) { - usedMemP = that.$q.resolve(0); - instancesP = that.$q.resolve(0); - routesCountP = that.$q.resolve(0); - } else { + function getUsedMem(org) { + if (org.entity.spaces) { + if (org.entity.spaces.length === 0) { + return that.$q.resolve(0); + } if (org.entity.spaces[0].entity.apps) { // check if apps were inlined in the spaces var totalMem = 0; - var totalInstances = 0; _.forEach(org.entity.spaces, function (space) { var apps = space.entity.apps; _.forEach(apps, function (app) { // Only count running apps, like the CF API would do if (app.entity.state === 'STARTED') { totalMem += parseInt(app.entity.memory, 10); - totalInstances += parseInt(app.entity.instances, 10); } }); }); - usedMemP = that.$q.resolve(totalMem); - instancesP = that.$q.resolve(totalInstances); + return that.$q.resolve(totalMem); + } + } + return that.orgsApi.RetrievingOrganizationMemoryUsage(orgGuid, params, httpConfig).then(function (res) { + return res.data.memory_usage_in_mb; + }); + } + + function getInstances(org) { + if (org.entity.spaces) { + if (org.entity.spaces.length === 0) { + return that.$q.resolve(0); } - if (org.entity.spaces[0].entity.routes) { // check if routes were inlined in the spaces - var totalRoutes = 0; + if (org.entity.spaces[0].entity.apps) { // check if apps were inlined in the spaces + var totalInstances = 0; _.forEach(org.entity.spaces, function (space) { - totalRoutes += space.entity.routes.length; + var apps = space.entity.apps; + _.forEach(apps, function (app) { + // Only count running apps, like the CF API would do + if (app.entity.state === 'STARTED') { + totalInstances += parseInt(app.entity.instances, 10); + } + }); }); - routesCountP = that.$q.resolve(totalRoutes); + return that.$q.resolve(totalInstances); } } + return that.orgsApi.RetrievingOrganizationInstanceUsage(orgGuid, params, httpConfig).then(function (res) { + return res.data.instance_usage; + }); + } - allSpacesP = that.$q.resolve(org.entity.spaces); + function getRouteCount(org) { + if (org.entity.spaces) { + if (org.entity.spaces.length === 0) { + return that.$q.resolve(0); + } else { + if (org.entity.spaces[0].entity.routes) { // check if routes were inlined in the spaces + var totalRoutes = 0; + _.forEach(org.entity.spaces, function (space) { + totalRoutes += space.entity.routes.length; + }); + return that.$q.resolve(totalRoutes); + } + } + } + } + function getQuota(org) { + if (org.entity.quota_definition) { + return that.$q.resolve(org.entity.quota_definition); + } + return that.orgsQuotaApi.RetrieveOrganizationQuotaDefinition(orgQuotaGuid, params, httpConfig).then(function (val) { + return val.data; + }); } - instancesP = instancesP || that.orgsApi.RetrievingOrganizationInstanceUsage(orgGuid, params, httpConfig).then(function (res) { - return res.data.instance_usage; - }); + var rolesP = getRoles(org); // Roles can be returned inline + var usedMemP = getUsedMem(org); // Memory usage can be inferred from inlined apps + var instancesP = getInstances(org); // Instance usage can be inferred from inlined apps + var routesCountP = getRouteCount(org); // Routes can be returned inline + var quotaP = getQuota(org); // The quota can be returned inline - usedMemP = usedMemP || that.orgsApi.RetrievingOrganizationMemoryUsage(orgGuid, params, httpConfig).then(function (res) { - return res.data.memory_usage_in_mb; - }); + var allSpacesP, allUsersRoles; + if (org.entity.spaces) { + allSpacesP = that.$q.resolve(org.entity.spaces); + } allSpacesP = allSpacesP || this.apiManager.retrieve('cloud-foundry.api.Organizations') .ListAllSpacesForOrganization(orgGuid, params, httpConfig).then(function (res) { @@ -358,33 +407,14 @@ }); }); - // The quota may be returned inline - if (org.entity.quota_definition) { - quotaP = that.$q.resolve(org.entity.quota_definition); - } else { - quotaP = that.orgsQuotaApi.RetrieveOrganizationQuotaDefinition(orgQuotaGuid, params, httpConfig).then(function (val) { - return val.data; - }); - } - - // The users roles may be returned inline - if (org.entity.users) { - // Reconstruct all user roles from inline data - rolesP = that.$q.resolve(_unsplitOrgRoles(org)); - } else { - rolesP = that.orgsApi.RetrievingRolesOfAllUsersInOrganization(orgGuid, params, httpConfig).then(function (val) { - return val.data.resources; - }); - } - var orgRolesP = rolesP.then(function (usersRoles) { var i, myRoles; allUsersRoles = usersRoles; // Cached later! // Find the connected user's roles in each org - for (i = 0; i < allUsersRoles.length; i++) { - var user = allUsersRoles[i]; + for (i = 0; i < usersRoles.length; i++) { + var user = usersRoles[i]; if (user.metadata.guid === userGuid) { myRoles = user.entity.organization_roles; break; From 309bae4e4385e23c3528c1942edb54a5975d1e0c Mon Sep 17 00:00:00 2001 From: Julien Bramary Date: Tue, 9 Aug 2016 16:08:58 +0100 Subject: [PATCH 3/3] Drop trailing zeroes from fractional memory usage --- src/app/utils/utils.service.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/utils/utils.service.js b/src/app/utils/utils.service.js index e79c4a6596..9924f9a1fe 100644 --- a/src/app/utils/utils.service.js +++ b/src/app/utils/utils.service.js @@ -32,10 +32,11 @@ precision = 1; } var floored = Math.floor(size); - if (floored === size) { + var fixed = Number(size.toFixed(precision)); + if (floored === fixed) { return floored; } - return size.toFixed(precision); + return fixed; } function mbToHumanSize(sizeMb) {