diff --git a/src/common/db-helper.js b/src/common/db-helper.js index 4836b90..a839948 100644 --- a/src/common/db-helper.js +++ b/src/common/db-helper.js @@ -70,6 +70,28 @@ async function scan(model, scanParams) { }); } +/** + * Get data collection by scan parameters with paging + * @param {Object} model The dynamoose model to scan + * @param {Object} scanParams The scan parameters object + * @param {String} size The size of result + * @param {String} lastKey The lastKey param + * @returns {Promise} + */ +async function scanWithLimit(model, scanParams, size, lastKey) { + return await new Promise((resolve, reject) => { + const scanMethod = model.scan(scanParams).limit(size); + if (lastKey) scanMethod.startAt(lastKey); + scanMethod.exec((err, result) => { + if (err) { + logger.error(`DynamoDB scan error ${err}`); + return reject(err); + } + return resolve(result.count === 0 ? [] : result); + }); + }); +} + /** * Get data collection by scan parameters with paging * @param {Object} model The dynamoose model to scan @@ -91,6 +113,25 @@ async function scanAll(model, size, lastKey) { }); } +/** + * Get data collection by scan parameters + * @param {Object} model The dynamoose model to scan + * @param {Object} scanParams The scan parameters object + * @returns {Promise} + */ +async function scanAllWithParams(model, scanParams) { + return await new Promise((resolve, reject) => { + model.scan(scanParams).all().exec((err, result) => { + if (err) { + logger.error(`DynamoDB scan error ${err}`); + return reject(err); + } + + return resolve(result.count === 0 ? [] : result); + }); + }); +} + /** * Get data collection by scan with search * @param {Object} model The dynamoose model to scan @@ -544,6 +585,8 @@ module.exports = { scan, scanAll, scanAllWithSearch, + scanWithLimit, + scanAllWithParams, create, update, removeById, diff --git a/src/controllers/ProjectController.js b/src/controllers/ProjectController.js index e37416e..ec4e276 100644 --- a/src/controllers/ProjectController.js +++ b/src/controllers/ProjectController.js @@ -42,6 +42,15 @@ async function getAll(req) { return await ProjectService.getAll(req.query, req.currentUser); } +/** + * search all projects + * @param {Object} req the request + * @returns {Array} the result + */ +async function search(req) { + return await ProjectService.search(req.query, req.currentUser); +} + /** * create label * @param {Object} req the request @@ -126,6 +135,7 @@ module.exports = { createHook, addWikiRules, transferOwnerShip, + search, }; helper.buildController(module.exports); diff --git a/src/front/src/app/main/main.controller.js b/src/front/src/app/main/main.controller.js index 7999df6..b4ac4bd 100644 --- a/src/front/src/app/main/main.controller.js +++ b/src/front/src/app/main/main.controller.js @@ -151,7 +151,7 @@ angular.module('topcoderX') //private function to get projects. $scope.getProjects = function (status) { - ProjectService.getProjects(status, false).then(function (response) { + ProjectService.getProjects(status, false, 1).then(function (response) { $scope.projects = response.data; if (!$scope.projects || $scope.projects.length === 0) { $scope.showTutorial(); diff --git a/src/front/src/app/projects/project.service.js b/src/front/src/app/projects/project.service.js index a28d572..615f5c1 100644 --- a/src/front/src/app/projects/project.service.js +++ b/src/front/src/app/projects/project.service.js @@ -23,10 +23,15 @@ angular.module('topcoderX') }; /** - * Get all projects + * Get projects */ - ProjectService.getProjects = function (status, showAll) { - var url = Helper.baseUrl + '/api/v1/projects?status=' + status + '&showAll=' + showAll; + ProjectService.getProjects = function (status, showAll, perPage, lastKey, query) { + var url = Helper.baseUrl + '/api/v1/projects?status=' + status + '&showAll=' + showAll + '&perPage=' + perPage + + (lastKey ? '&lastKey=' + lastKey : '' ); + if (query) { + url = Helper.baseUrl + '/api/v1/projects/search?status=' + status + '&showAll=' + showAll + '&perPage=' + perPage + + '&query=' + query; + } if (projectsGetLock[url]) { return projectsDataPromise[url]; } diff --git a/src/front/src/app/projects/projects.controller.js b/src/front/src/app/projects/projects.controller.js index b833f0c..0b557d7 100644 --- a/src/front/src/app/projects/projects.controller.js +++ b/src/front/src/app/projects/projects.controller.js @@ -1,8 +1,8 @@ 'use strict'; angular.module('topcoderX') - .controller('ProjectsController', ['currentUser', '$scope', '$state', 'ProjectService', '$filter', '$rootScope', 'Alert', 'Helper', '$timeout', - function (currentUser, $scope, $state, ProjectService, $filter, $rootScope, Alert, Helper, $timeout) { + .controller('ProjectsController', ['currentUser', '$scope', '$state', 'ProjectService', '$filter', '$rootScope', 'Alert', 'Helper', + function (currentUser, $scope, $state, ProjectService, $filter, $rootScope, Alert, Helper) { //Current title $scope.title = 'Project Management'; @@ -15,6 +15,15 @@ angular.module('topcoderX') $scope.state = { status: 'active', }; + $scope.tableConfig = { + pageNumber: 1, + pageSize: 10, + isLoading: false, + initialized: false, + query: '', + lastKey: [], + pages: 1 + }; //go to a project detail $scope.goProject = function (project) { @@ -44,13 +53,27 @@ angular.module('topcoderX') $scope.state.status = status; $scope.isLoaded = false; $scope.projects = []; - ProjectService.getProjects(status, $scope.filter.showAll).then(function (response) { + ProjectService.getProjects( + status, $scope.filter.showAll, + $scope.tableConfig.pageSize, $scope.tableConfig.lastKey[$scope.tableConfig.pageNumber], + $scope.tableConfig.query).then(function (response) { + var config = $scope.tableConfig; + + if (config.query) { + config.allItems = response.data.docs; + $scope.projects = config.allItems.slice(0, config.pageSize); + config.pages = Math.ceil(config.allItems.length / config.pageSize); + } + else { + $scope.projects = response.data.docs; + } + if (response.data.lastKey) { + config.lastKey[config.pageNumber + 1] = response.data.lastKey; + if (!config.pages || config.pages <= config.pageNumber) { + config.pages = config.pageNumber + 1; + } + } $scope.isLoaded = true; - $scope.projects = response.data; - $scope.allProjects = angular.copy($scope.projects); - $timeout(function () { - $scope.init(); - }, 1000); }).catch(function (error) { $scope.isLoaded = true; if (error.data) { @@ -61,6 +84,51 @@ angular.module('topcoderX') }); }; + /** + * get the number array that shows the pagination bar + */ + $scope.getPageArray = function () { + var res = []; + + var pageNo = $scope.tableConfig.pageNumber; + var i = pageNo - 5; + for (i; i <= pageNo; i++) { + if (i > 0) { + res.push(i); + } + } + var j = pageNo + 1; + for (j; j <= $scope.tableConfig.pages && j <= pageNo + 5; j++) { + res.push(j); + } + return res; + }; + + /** + * handles the change page click + * @param {Number} pageNumber the page number + */ + $scope.changePage = function (pageNumber) { + if (pageNumber === 0 || pageNumber > $scope.tableConfig.pages || + (pageNumber === $scope.tableConfig.pages && + $scope.tableConfig.pageNumber === pageNumber)) { + return false; + } + $scope.tableConfig.pageNumber = pageNumber; + if ($scope.tableConfig.query && $scope.tableConfig.allItems) { + var start = ($scope.tableConfig.pageNumber - 1) * $scope.tableConfig.pageSize - 1; + if (pageNumber === 1) { + start = 0; + } + $scope.projects = $scope.tableConfig.allItems.slice( + start, $scope.tableConfig.pageSize); + $scope.isLoaded = true; + } + else { + $scope.getProjects($scope.state.status); + } + }; + $scope.repoType = function (repo) { if (repo.toLocaleLowerCase().indexOf("github") >= 0) { return "Github"; @@ -82,31 +150,19 @@ angular.module('topcoderX') $scope.getProjects($scope.state.status); }; - - $scope.onSearchChange = function (obj) { - $scope.searchText = obj.searchText; - if (!obj.searchText || obj.searchText.length === 0) { - $scope.getProjects($scope.state.status); - } - - if ($scope.allProjects.length > 0) { - _searchLocal(obj.searchText); - } - }; - $scope.onSearchIconClicked = function () { - if ($scope.allProjects.length > 0 && $scope.searchText) { - _searchLocal($scope.searchText); - } + $scope.tableConfig.pageNumber = 1; + $scope.tableConfig.pages = 1; + $scope.tableConfig.allItems = []; + $scope.getProjects($scope.state.status); }; - function _searchLocal(query) { - $scope.projects = $scope.allProjects.filter(function(value) { - return value['title'].toLowerCase().includes(query.toLowerCase()); - }) - $timeout(function () { - $('.footable').filter('[data-page="0"]').trigger('click'); - }, 1000); - } - + $scope.onSearchReset = function () { + var config = $scope.tableConfig; + config.query = ''; + config.pageNumber = 1; + config.pages = 1; + config.allItems = []; + $scope.getProjects($scope.state.status); + }; }]); diff --git a/src/front/src/app/projects/projects.html b/src/front/src/app/projects/projects.html index e38c85e..593281b 100644 --- a/src/front/src/app/projects/projects.html +++ b/src/front/src/app/projects/projects.html @@ -30,7 +30,7 @@

{{title}}


-
+

You don't have active projects right now. Please

-
+
- + - - + +
You don't have active projects right now. Please - + - @@ -108,17 +125,20 @@

You don't have active projects right now. Please
-
+

No projects have been archived.

-
+
- + +
@@ -148,10 +168,24 @@

You don't have active projects right now. Please -

+ - diff --git a/src/routes.js b/src/routes.js index 53e6884..1b569bb 100644 --- a/src/routes.js +++ b/src/routes.js @@ -142,6 +142,12 @@ module.exports = { method: 'update', }, }, + '/projects/search': { + get: { + controller: 'ProjectController', + method: 'search', + }, + }, '/projects/label': { post: { controller: 'ProjectController', diff --git a/src/services/ProjectService.js b/src/services/ProjectService.js index a23ac94..db68fcf 100644 --- a/src/services/ProjectService.js +++ b/src/services/ProjectService.js @@ -235,17 +235,113 @@ async function getAll(query, currentUser) { } // if show all is checked user must be admin if (query.showAll && await securityService.isAdminUser(currentUser.roles)) { - let projects = await dbHelper.scan(models.Project, condition); - projects = _.map(projects, (project) => { + const fetchedProjects = await dbHelper.scanAllWithParams( + models.Project, condition); + const projects = _.map(fetchedProjects, (project) => { if (!project.updatedAt) { project.updatedAt = 0; } return project; }); + const count = projects.length; + if (!query.lastKey) { + query.lastKey = 0; + } else { + query.lastKey = parseInt(query.lastKey, 10); + } + const slicedProjects = _.slice(projects, query.lastKey, query.lastKey + query.perPage); + // console.log(projects); + for (const project of slicedProjects) { // eslint-disable-line + project.repoUrls = await dbHelper.populateRepoUrls(project.id); + } + const lastKey = query.lastKey + slicedProjects.length; + return { + lastKey : lastKey < count ? lastKey : undefined, + docs: _.orderBy(slicedProjects, ['updatedAt', 'title'], ['desc', 'asc']) + }; + } + + const filter = { + FilterExpression: '(#owner= :handle or copilot = :handle) AND #archived = :status', + ExpressionAttributeNames: { + '#owner': 'owner', + '#archived': 'archived', + }, + ExpressionAttributeValues: { + ':handle': currentUser.handle, + ':status': condition.archived, + }, + }; + + const fetchedProjects = await dbHelper.scanAllWithParams( + models.Project, filter); + const projects = _.map(fetchedProjects, (project) => { + if (!project.updatedAt) { + project.updatedAt = 0; + } + return project; + }); + const count = projects.length; + if (!query.lastKey) { + query.lastKey = 0; + } else { + query.lastKey = parseInt(query.lastKey, 10); + } + const slicedProjects = _.slice(projects, query.lastKey, query.lastKey + query.perPage); + for (const project of slicedProjects) { // eslint-disable-line + project.repoUrls = await dbHelper.populateRepoUrls(project.id); + } + const lastKey = query.lastKey + slicedProjects.length; + return { + lastKey : lastKey < count ? lastKey : undefined, + docs: _.orderBy(slicedProjects, ['updatedAt', 'title'], ['desc', 'asc']) + }; +} + +getAll.schema = Joi.object().keys({ + query: Joi.object().keys({ + status: Joi.string().required().allow('active', 'archived').default('active'), + showAll: Joi.bool().optional().default(false), + perPage: Joi.number().integer().min(1).required(), + lastKey: Joi.string(), + }), + currentUser: currentUserSchema, +}); + +/** + * search projects + * @param {Object} query the query filter object + * @param {String} currentUser the topcoder current user + * @returns {Array} all projects + */ +async function search(query, currentUser) { + const condition = { + archived: 'false', + }; + + if (query.status === 'archived') { + condition.archived = 'true'; + } + // if show all is checked user must be admin + if (query.showAll && await securityService.isAdminUser(currentUser.roles)) { + const fetchedProjects = await dbHelper.scanAllWithParams( + models.Project, condition); + let projects = _.map(fetchedProjects, (project) => { + if (!project.updatedAt) { + project.updatedAt = 0; + } + return project; + }); + projects = _.filter(projects, project => { + return project.title.toLowerCase().indexOf(query.query.toLowerCase()) !== -1; // eslint-disable-line lodash/prefer-includes + }); for (const project of projects) { // eslint-disable-line project.repoUrls = await dbHelper.populateRepoUrls(project.id); } - return _.orderBy(projects, ['updatedAt', 'title'], ['desc', 'asc']); + return { + lastKey: (fetchedProjects.lastKey ? JSON.stringify(fetchedProjects.lastKey) : undefined), // eslint-disable-line + docs: _.orderBy(projects, ['updatedAt', 'title'], ['desc', 'asc']) + }; } const filter = { @@ -260,23 +356,32 @@ async function getAll(query, currentUser) { }, }; - let projects = await dbHelper.scan(models.Project, filter); - projects = _.map(projects, (project) => { + const fetchedProjects = await dbHelper.scanAllWithParams( + models.Project, filter); + let projects = _.map(fetchedProjects, (project) => { if (!project.updatedAt) { project.updatedAt = 0; } return project; }); + projects = _.filter(projects, project => { + return project.title.toLowerCase().indexOf(query.query.toLowerCase()) !== -1; // eslint-disable-line lodash/prefer-includes + }); for (const project of projects) { // eslint-disable-line project.repoUrls = await dbHelper.populateRepoUrls(project.id); } - return _.orderBy(projects, ['updatedAt', 'title'], ['desc', 'asc']); + return { + lastKey: (fetchedProjects.lastKey ? JSON.stringify(fetchedProjects.lastKey) : undefined), // eslint-disable-line + docs: _.orderBy(projects, ['updatedAt', 'title'], ['desc', 'asc']) + } } -getAll.schema = Joi.object().keys({ +search.schema = Joi.object().keys({ query: Joi.object().keys({ status: Joi.string().required().allow('active', 'archived').default('active'), showAll: Joi.bool().optional().default(false), + perPage: Joi.number().integer().min(1).required(), + query: Joi.string().required(), }), currentUser: currentUserSchema, }); @@ -583,6 +688,7 @@ module.exports = { createHook, addWikiRules, transferOwnerShip, + search, }; helper.buildService(module.exports);
-
    +
    +
    -
      +
      +