Added project group list and detail view.

Reused several components from the existing list search, as well
as other parts from project views, to create a rudimentary list of
project groups. Also created a detail page that shows member
projects and stories, and lastly added a quicknav option into the
header quicknav.

Change-Id: Ie29dacb6c6fda47c0e59fa054da9c259a9a59797
This commit is contained in:
Michael Krotscheck
2014-10-13 15:58:45 -07:00
parent cb9852f032
commit edc090a209
9 changed files with 406 additions and 5 deletions

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
/**
* Project group detail controller, for general user use of project groups.
* From a feature standpoint this really just means viewing the group, member
* projects, and any stories that belong under this project group.
*/
angular.module('sb.project_group').controller('ProjectGroupDetailController',
function ($scope, projectGroup, projects, stories) {
'use strict';
/**
* The project group we're viewing right now.
*
* @type ProjectGroup
*/
$scope.projectGroup = projectGroup;
/**
* The list of projects in this group
*
* @type [Project]
*/
$scope.projects = projects;
/**
* The list of stories in this project group
*
* @type [Story]
*/
$scope.stories = stories;
});

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
/**
* A Project
*/
angular.module('sb.project_group').controller('ProjectGroupListController',
function ($scope) {
'use strict';
// search results must be of type "ProjectGroup"
$scope.resourceTypes = ['ProjectGroup'];
// Projects have no default criteria
$scope.defaultCriteria = [];
});

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* 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.
*/
/**
* The StoryBoard project group submodule handles most activity involving
* searching for and reviewing project groups. Administration of project groups
* lives in the admin module.
*/
angular.module('sb.project_group',
['ui.router', 'sb.services', 'sb.util', 'sb.auth'])
.config(function ($stateProvider, $urlRouterProvider, PermissionResolver) {
'use strict';
// Routing Defaults.
$urlRouterProvider.when('/project_group', '/project_group/list');
// Set our page routes.
$stateProvider
.state('project_group', {
abstract: true,
url: '/project_group',
template: '<div ui-view></div>',
resolve: {
isSuperuser: PermissionResolver
.resolvePermission('is_superuser', true)
}
})
.state('project_group.list', {
url: '/list',
templateUrl: 'app/project_group/template/list.html',
controller: 'ProjectGroupListController'
})
.state('project_group.detail', {
url: '/{id:[0-9]+}',
templateUrl: 'app/project_group/template/detail.html',
controller: 'ProjectGroupDetailController',
resolve: {
projectGroup: function ($stateParams, ProjectGroup, $q) {
var deferred = $q.defer();
ProjectGroup.get({id: $stateParams.id},
function (result) {
deferred.resolve(result);
}, function (error) {
deferred.reject(error);
});
return deferred.promise;
},
projects: function($stateParams, Project, $q) {
var deferred = $q.defer();
Project.query({project_group_id: $stateParams.id},
function (result) {
deferred.resolve(result);
}, function (error) {
deferred.reject(error);
});
return deferred.promise;
},
stories: function($stateParams, Story, $q) {
var deferred = $q.defer();
Story.query({project_group_id: $stateParams.id},
function (result) {
deferred.resolve(result);
}, function (error) {
deferred.reject(error);
});
return deferred.promise;
}
}
});
})
;

View File

@@ -0,0 +1,63 @@
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>
<i class="fa fa-sb-project-group"></i>
{{projectGroup.name}}
</h1>
</div>
</div>
<div class="row">
<div class="col-xs-2 text-center text-muted">
<span class="hidden-xs">
<i class="fa fa-3x fa-sb-project"></i>
<br/>
<small>Projects</small>
</span>
<i class="fa fa-2x fa-sb-project visible-xs"></i>
</div>
<div class="col-xs-10">
<table class="table table-condensed table-striped table-clean">
<tbody ng-if="projects.length != 0">
<tr ng-repeat="project in projects"
ng-include="'app/search/template/project_search_item.html'">
</tr>
</tbody>
<tbody ng-if=\projects.length == 0">
<td class="text-center text-muted">
<em>No projects found.</em>
</td>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<hr/>
</div>
<div class="col-xs-2 text-center text-muted">
<span class="hidden-xs">
<i class="fa fa-3x fa-sb-story"></i>
<br/>
<small>Stories</small>
</span>
<i class="fa fa-2x fa-sb-story visible-xs"></i>
</div>
<div class="col-xs-10">
<table class="table table-condensed table-striped table-clean">
<tbody ng-if="stories.length != 0">
<tr ng-repeat="story in stories"
ng-include="'app/search/template/story_search_item.html'">
</tr>
</tbody>
<tbody ng-if="stories.length == 0">
<td class="text-center text-muted">
<em>No stories found.</em>
</td>
</tbody>
</table>
</div>
</div>
</div>

View File

@@ -0,0 +1,132 @@
<!--
~ Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
~
~ 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.
-->
<div class="container"
ng-controller="SearchCriteriaController"
ng-init="init(resourceTypes, defaultCriteria)"
search-results
search-resource="ProjectGroup"
search-criteria="criteria"
search-without-criteria="true">
<div class="row">
<div class="col-xs-12">
<h1><i class="fa fa-sb-project"></i> Project Groups</h1>
</div>
</div>
<div class="row">
<div class="col-sm-10 col-xs-12">
<div class="form-group has-feedback has-feedback-no-label">
<div id="search-criteria"
tag-complete="criteria as criteria.title for criteria in searchForCriteria($viewValue)"
tag-complete-tags="criteria"
tag-complete-label-field="title"
tag-complete-option-template-url="'app/search/template/typeahead_criteria_item.html'"
tag-complete-tag-template-url="'app/search/template/criteria_tag_item.html'"
tag-complete-loading="loadingCriteria = isLoading"
tag-complete-on-select="addCriteria(tag)"
placeholder="Search Project Groups">
</div>
<span class="form-control-feedback text-muted">
<i class="fa fa-search"
ng-hide="loadingCriteria"></i>
<i class="fa fa-refresh fa-spin"
ng-show="loadingCriteria"></i>
</span>
</div>
</div>
<div class="col-xs-2 hidden-xs">
<div class="form form-horizontal">
<p class="form-control-static text-muted">
({{searchResults.length}} found)
</p>
</div>
</div>
</div>
<div class="row"
ng-if="!hasValidCriteria">
<div class="col-xs-12 text-muted text-center">
<em>What would you like to search by?</em>
</div>
</div>
<div class="row"
ng-if="hasValidCriteria">
<div class="col-xs-12">
<br/>
</div>
<div class="col-xs-12">
<table class="table table-condensed table-striped">
<thead>
<tr>
<th>
<a ng-click="toggleFilter('name')">
Project Group
<i class="fa fa-caret-down"
ng-if="sortField == 'name' && sortDirection == 'desc'">
</i>
<i class="fa fa-caret-up"
ng-if="sortField == 'name' && sortDirection == 'asc'">
</i>
</a>
</th>
<th class="hidden-xs">
<a ng-click="toggleFilter('created_at')">
Created
<i class="fa fa-caret-down"
ng-if="sortField == 'created_at' && sortDirection == 'desc'">
</i>
<i class="fa fa-caret-up"
ng-if="sortField == 'created_at' && sortDirection == 'asc'">
</i>
</a>
</th>
<th class="hidden-xs">
<a ng-click="toggleFilter('updated_at')">
Updated
<i class="fa fa-caret-down"
ng-if="sortField == 'updated_at' && sortDirection == 'desc'">
</i>
<i class="fa fa-caret-up"
ng-if="sortField == 'updated_at' && sortDirection == 'asc'">
</i>
</a>
</th>
<th></th>
</tr>
</thead>
<tbody ng-if="searchResults.length != 0">
<tr ng-repeat="projectGroup in searchResults"
ng-include="'app/search/template/project_group_search_item.html'">
</tr>
</tbody>
<tbody ng-if="searchResults.length == 0 &amp;&amp; !isSearching">
<td class="text-center text-muted" colspan="3">
<em>No project groups found.</em>
</td>
</tbody>
<tbody ng-if="isSearching">
<td colspan="3">
<small class="fa fa-spin fa-refresh text-muted">
</small>
</td>
</tbody>
</table>
</div>
</div>
</div>

View File

@@ -0,0 +1,24 @@
<td>
<a href="#!/project_group/{{projectGroup.id}}">
{{projectGroup.name | truncate: 97}}
</a>
</td>
<td class="hidden-xs">
<span time-moment eventdate="projectGroup.created_at">
</span>
</td>
<td class="hidden-xs">
<span time-moment
ng-if="projectGroup.updated_at"
eventdate="projectGroup.updated_at">
</span>
<em ng-if="!projectGroup.updated_at"
class="text-muted">
Not updated
</em>
</td>
<td>
<subscribe class="pull-right"
resource="project_group"
resource-id="projectGroup.id"></subscribe>
</td>

View File

@@ -20,8 +20,8 @@
*/
angular.module('storyboard').controller('HeaderController',
function ($q, $scope, $rootScope, $state, NewStoryService, Session,
SessionState, CurrentUser, Criteria, Notification,
Priority, Project, Story) {
SessionState, CurrentUser, Criteria, Notification, Priority,
Project, Story, ProjectGroup) {
'use strict';
function resolveCurrentUser() {
@@ -75,6 +75,9 @@ angular.module('storyboard').controller('HeaderController',
case 'Text':
$state.go('search', {q: criteria.value});
break;
case 'ProjectGroup':
$state.go('project_group.detail', {id: criteria.value});
break;
case 'Project':
$state.go('project.detail', {id: criteria.value});
break;
@@ -100,9 +103,18 @@ angular.module('storyboard').controller('HeaderController',
var searches = [];
if (searchString.match(/^[0-9]+$/)) {
var getProjectGroupDeferred = $q.defer();
var getProjectDeferred = $q.defer();
var getStoryDeferred = $q.defer();
ProjectGroup.get({id: searchString},
function (result) {
getProjectGroupDeferred.resolve(Criteria.create(
'ProjectGroup', result.id, result.name
));
}, function () {
getProjectGroupDeferred.resolve(null);
});
Project.get({id: searchString},
function (result) {
getProjectDeferred.resolve(Criteria.create(
@@ -121,10 +133,12 @@ angular.module('storyboard').controller('HeaderController',
});
// If the search string is entirely numeric, do a GET.
searches.push(getProjectGroupDeferred.promise);
searches.push(getProjectDeferred.promise);
searches.push(getStoryDeferred.promise);
} else {
searches.push(ProjectGroup.criteriaResolver(searchString, 5));
searches.push(Project.criteriaResolver(searchString, 5));
searches.push(Story.criteriaResolver(searchString, 5));
}

View File

@@ -25,8 +25,8 @@
angular.module('storyboard',
[ 'sb.services', 'sb.templates', 'sb.dashboard', 'sb.pages', 'sb.projects',
'sb.auth', 'sb.story', 'sb.profile', 'sb.notification', 'sb.search',
'sb.admin', 'sb.subscription', 'ui.router', 'ui.bootstrap',
'monospaced.elastic', 'angularMoment'])
'sb.admin', 'sb.subscription', 'sb.project_group', 'ui.router',
'ui.bootstrap', 'monospaced.elastic', 'angularMoment'])
.constant('angularMomentConfig', {
preprocess: 'utc',
timezone: 'UTC'

View File

@@ -35,7 +35,14 @@
<small class="visible-lg visible-md">Dashboard</small>
</a>
</li>
<li active-path="^\/project\/*">
<li active-path="^\/project_group\/*">
<a href="#!/project_group">
<i class="fa fa-sb-project-group fa-lg visible-sm visible-xs"></i>
<i class="fa fa-sb-project-group fa-3x visible-lg visible-md"></i>
<small class="visible-lg visible-md">Project Groups</small>
</a>
</li>
<li active-path="^\/project[^_]\/*">
<a href="#!/project">
<i class="fa fa-sb-project fa-lg visible-sm visible-xs"></i>
<i class="fa fa-sb-project fa-3x visible-lg visible-md"></i>