/* * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. * Copyright (c) 2016 Codethink Ltd. * Copyright (c) 2017 Adam Coldrick * * Licensed under the Apache License, Version 2.0 (the 'License'); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ /** * Controller for the New Story view. * * This view is accessible from /#!/story/new, and can understand the * following parameters in the URL: * * - title: The initial story title * - description: The initial content of the story description * - force_private: If truthy, the story will be forced to be private * (i.e. private with the option to change privacy hidden). * - private: If truthy, the story will begin set to private. * Unlike force_private, this allows the user to change to public. * - security: If truthy, the story will begin set as security-related. * - tags: Tags to set on the story. Can be given multiple times. * - team_id: A team ID to grant permissions for this story to. Can be * given multiple times. * - user_id: A user ID to grant permissions for this story to. Can be * given multiple times. */ angular.module('sb.story').controller('StoryNewController', function ($scope, $state, $stateParams, Story, Project, Branch, Tags, Task, Team, User, StoryHelper, $q, storyboardApiBase, currentUser) { 'use strict'; /** * Handle any change to whether or not the story is security-related */ $scope.updateSecurity = function(forcePrivate, update) { $scope.privacyLocked = StoryHelper.updateSecurity( forcePrivate, update, $scope.story, $scope.tasks); }; var story = new Story({ title: $stateParams.title, description: $stateParams.description, private: (!!$stateParams.private || !!$stateParams.force_private || !!$stateParams.security), security: !!$stateParams.security, users: [currentUser], teams: [] }); // Convert the user_id and team_id parameters to arrays if needed var stateTeamIds = []; if (!!$stateParams.team_id) { if ($stateParams.team_id.constructor !== Array) { stateTeamIds.push($stateParams.team_id); } else { stateTeamIds.push.apply(stateTeamIds, $stateParams.team_id); } } var stateUserIds = []; if (!!$stateParams.user_id) { if ($stateParams.user_id.constructor !== Array) { stateUserIds.push($stateParams.user_id); } else { stateUserIds.push.apply(stateUserIds, $stateParams.user_id); } } // Populate the story's permission lists as requested angular.forEach(stateTeamIds, function(team_id) { Team.get({team_id: team_id}).$promise.then(function(team) { story.teams.push(team); }); }); angular.forEach(stateUserIds, function(user_id) { User.get({id: user_id}).$promise.then(function(user) { story.users.push(user); }); }); $scope.story = story; // Convert the tags parameter into an array if it isn't already if (!!$stateParams.tags && $stateParams.tags.constructor !== Array) { $stateParams.tags = [$stateParams.tags]; } // List of tags to give the story $scope.tags = $stateParams.tags || []; /** * If the force_private query parameter is set, then enforce story * privacy and hide the option from the user. **/ $scope.forcePrivate = !!$stateParams.force_private; /** * All tasks associated with this story, resolved in the state. * * @type {[Task]} */ $scope.projectNames = []; $scope.projects = {}; $scope.tasks = []; $scope.updateSecurity(true, false); /** * UI flag for when we're initially loading the view. * * @type {boolean} */ $scope.isLoading = true; /** * UI view for when a change is round-tripping to the server. * * @type {boolean} */ $scope.isUpdating = false; $scope.isValid = function() { return !!$scope.tasks.length && !!$scope.story.title; }; /** * Create the tasks for the story. */ function createTasks(createdStory) { var resolvingTasks = $scope.tasks.length; angular.forEach($scope.tasks, function(task) { task.story_id = createdStory.id; task.$create(function() { resolvingTasks--; if (resolvingTasks === 0) { // Finished, navigate to the new story $state.go('sb.story.detail', {storyId: createdStory.id}); } }); }); } /** * Create the story. */ $scope.createStory = function() { $scope.isUpdating = true; // Ensure story is private if force_private is set. if ($scope.forcePrivate) { $scope.story.private = true; } $scope.story.$create(function(createdStory) { var tagsController = new Story.TagsController({ id: createdStory.id }); if ($scope.tags.length > 0) { // Create tags for the story, then tasks tagsController.$update({tags: $scope.tags}, createTasks); } else { // Create tasks for the story createTasks(createdStory); } }, function() { $scope.isUpdating = false; }); }; /** * User/team typeahead search method. */ $scope.searchActors = function (value, users, teams) { var userIds = users.map(function(user){return user.id;}); var teamIds = teams.map(function(team){return team.id;}); var deferred = $q.defer(); var usersDeferred = $q.defer(); var teamsDeferred = $q.defer(); User.browse({full_name: value, limit: 10}, function(searchResults) { var results = []; angular.forEach(searchResults, function(result) { if (userIds.indexOf(result.id) === -1) { result.name = result.full_name; result.type = 'user'; results.push(result); } }); usersDeferred.resolve(results); } ); Team.browse({name: value, limit: 10}, function(searchResults) { var results = []; angular.forEach(searchResults, function(result) { if (teamIds.indexOf(result.id) === -1) { result.type = 'team'; results.push(result); } }); teamsDeferred.resolve(results); } ); var searches = [teamsDeferred.promise, usersDeferred.promise]; $q.all(searches).then(function(searchResults) { var results = []; angular.forEach(searchResults, function(promise) { angular.forEach(promise, function(result) { results.push(result); }); }); deferred.resolve(results); }); return deferred.promise; }; /** * Add a new user or team to one of the permission levels. */ $scope.addActor = function (model) { if (model.type === 'user') { $scope.story.users.push(model); } else if (model.type === 'team') { $scope.story.teams.push(model); } }; /** * Remove a user from the permissions. */ $scope.removeUser = function (model) { var idx = $scope.story.users.indexOf(model); $scope.story.users.splice(idx, 1); }; /** * Remove a team from the permissions. */ $scope.removeTeam = function(model) { var idx = $scope.story.teams.indexOf(model); $scope.story.teams.splice(idx, 1); }; // ################################################################### // Task Management // ################################################################### /** * The new task for the task form. */ $scope.newTask = new Task({ project_id: $stateParams.project_id, show: true, status: 'todo', title: $stateParams.title }); function mapTaskToProject(task) { Project.get({id: task.project_id}).$promise.then(function(project) { var idx = $scope.projectNames.indexOf(project.name); if (idx < 0) { $scope.projectNames.push(project.name); $scope.projects[project.name] = project; $scope.projects[project.name].branchNames = []; $scope.projects[project.name].branches = {}; } Branch.get({id: task.branch_id}).$promise.then( function(branch) { var branchIdx = $scope.projects[project.name] .branchNames.indexOf(branch.name); if (branchIdx > -1) { $scope.projects[project.name].branches[branch.name] .tasks.push(task); } else { $scope.projects[project.name] .branches[branch.name] = branch; $scope.projects[project.name] .branches[branch.name].tasks = [task]; $scope.projects[project.name] .branches[branch.name].newTask = new Task({ story_id: $scope.story.id, branch_id: branch.id, project_id: project.id, status: 'todo' }); $scope.projects[project.name] .branchNames.push(branch.name); } }); }); } $scope.validTask = function(task) { return !isNaN(task.project_id) && !!task.title; }; /** * Adds a task. */ $scope.createTask = function(task, branch) { // Make a copy to save, so that the next task retains the // old information (for easier continuous editing). var savedTask = new Task(angular.copy(task)); if (branch) { branch.tasks.push(savedTask); } else { var params = {project_id: task.project_id, name: 'master'}; Branch.browse(params).$promise.then(function(result) { if (result) { savedTask.branch_id = result[0].id; mapTaskToProject(savedTask); } }); } $scope.tasks.push(savedTask); $scope.updateSecurity(true, false); task.title = ''; }; /** * Cleans up the project/branch/task tree. * * If there are no tasks remaining in the given branch in the given * project, then remove that branch from the project's list of * branches to display tasks for. * * If there are then no branches left in the project, remove the * project from the list of projects to display tasks for. */ function cleanBranchAndProject(projectName, branchName) { var project = $scope.projects[projectName]; var branch = project.branches[branchName]; var nameIdx = -1; if (branch.tasks.length === 0) { nameIdx = project.branchNames.indexOf(branchName); if (nameIdx > -1) { project.branchNames.splice(nameIdx, 1); } delete project.branches[branchName]; } if (project.branchNames.length === 0) { nameIdx = $scope.projectNames.indexOf(projectName); if (nameIdx > -1) { $scope.projectNames.splice(nameIdx, 1); } delete $scope.projects[projectName]; } } /** * Updates the task list. */ $scope.updateTask = function (task, fieldName, value, projectName, branchName) { var params = {id: task.id}; params[fieldName] = value; task[fieldName] = value; if (fieldName === 'project_id') { var project = $scope.projects[projectName]; var branch = project.branches[branchName]; var branchTaskIndex = branch.tasks.indexOf(task); if (branchTaskIndex > -1) { branch.tasks.splice(branchTaskIndex, 1); } cleanBranchAndProject(projectName, branchName); mapTaskToProject(task); $scope.updateSecurity(true, false); } }; $scope.editNotes = function(task) { task.tempNotes = task.link; task.editing = true; }; $scope.cancelEditNotes = function(task) { task.tempNotes = ''; task.editing = false; }; /** * Removes this task */ $scope.removeTask = function (task, projectName, branchName) { var taskIndex = $scope.tasks.indexOf(task); if (taskIndex > -1) { $scope.tasks.splice(taskIndex, 1); } var project = $scope.projects[projectName]; var branch = project.branches[branchName]; var branchTaskIndex = branch.tasks.indexOf(task); if (branchTaskIndex > -1) { branch.tasks.splice(branchTaskIndex, 1); } cleanBranchAndProject(projectName, branchName); }; // ################################################################### // Tags Management // ################################################################### /** * Show an input for a new tag * * @type {boolean} */ $scope.showAddTag = false; $scope.toggleAddTag = function() { $scope.showAddTag = !$scope.showAddTag; }; $scope.addTag = function (tag_name) { if(!!tag_name) { $scope.showAddTag = false; $scope.tags.push(tag_name); } }; $scope.removeTag = function (tag_name) { var tagIndex = $scope.tags.indexOf(tag_name); if (tagIndex > -1) { $scope.tags.splice(tagIndex, 1); } }; $scope.searchTags = function (value) { return Tags.browse({name: value, limit: 10}).$promise; }; $scope.updateViewValue = function (value) { $scope.newTag.name = value; }; $scope.newTag = {}; });