Introducing project groups
Project groups are convenient ways to display tasks that matter to you. All views support listing per-project or per-projectgroup, so you can create a "Nova program" group that will include nova and python-novaclient, so that the Nova team can do all its bug triaging in a single view. Change-Id: Iaa32ab2c528033bcd4917fc5df5ab30839c5d3d7
This commit is contained in:
parent
b72dea7c38
commit
f521b399df
35
README.rst
35
README.rst
@ -15,6 +15,12 @@ investing more into developing it.
|
||||
Current features
|
||||
----------------
|
||||
|
||||
*Project views*
|
||||
Basic project views that let you retrieve the list of tasks for a given
|
||||
project, as well as an example of a workflow-oriented view (the 'Triage
|
||||
bugs' view). The current POC is is also just a minimal stub of the project
|
||||
view feature set.
|
||||
|
||||
*Bug tracking*
|
||||
Like Launchpad Bugs, StoryBoard implements bugs as stories, with tasks that
|
||||
may affect various project/branch combinations. You can currently create
|
||||
@ -23,11 +29,18 @@ Current features
|
||||
and is missing search features, pagination, results ordering. This should
|
||||
definitely be improved if we go forward with this.
|
||||
|
||||
*Project views*
|
||||
Basic project views that let you retrieve the list of tasks for a given
|
||||
project, as well as an example of a workflow-oriented view (the 'Triage
|
||||
bugs' view). The current POC is is also just a minimal stub of the project
|
||||
view feature set.
|
||||
|
||||
*Feature tracking*
|
||||
The equivalent of Launchpad Blueprints, they inherit the same 'story'
|
||||
framework as bugs. That means they don't have most of the limitations of
|
||||
LP blueprints: you can comment in them, you can have tasks affecting multiple
|
||||
projects, you can even have multiple tasks affecting the same project and
|
||||
order them !
|
||||
|
||||
*Project groups*
|
||||
Projects can be grouped together arbitrarily, and all 'project' views can
|
||||
be reused by project groups. That makes it easy to triage or track all
|
||||
tasks for projects within a given OpenStack program.
|
||||
|
||||
*Markdown descriptions and comments*
|
||||
Story descriptions and comments can use markdown for richer interaction.
|
||||
@ -52,18 +65,6 @@ No invalid/wontfix/opinion status
|
||||
Future features
|
||||
---------------
|
||||
|
||||
*Feature tracking*
|
||||
The equivalent of Launchpad Blueprints, they inherit the same 'story'
|
||||
framework as bugs. That means they don't have most of the limitations of
|
||||
LP blueprints: you can comment in them, you can have tasks affecting multiple
|
||||
projects, you can even have multiple tasks affecting the same project and
|
||||
order them !
|
||||
|
||||
*Project groups*
|
||||
Projects can be grouped together arbitrarily, and all 'project' views can
|
||||
be reused by project groups. That makes it easy to triage or track all
|
||||
tasks for projects within a given OpenStack program.
|
||||
|
||||
*Subscription*
|
||||
Users should be able to subscribe to tasks (and get them in a specific view)
|
||||
as well as subscribe to projects (have their own customized project group).
|
||||
|
@ -18,8 +18,10 @@ from django.contrib import admin
|
||||
from storyboard.projects.models import Branch
|
||||
from storyboard.projects.models import Milestone
|
||||
from storyboard.projects.models import Project
|
||||
from storyboard.projects.models import ProjectGroup
|
||||
|
||||
|
||||
admin.site.register(Branch)
|
||||
admin.site.register(Project)
|
||||
admin.site.register(ProjectGroup)
|
||||
admin.site.register(Milestone)
|
||||
|
@ -24,6 +24,15 @@ class Project(models.Model):
|
||||
return self.name
|
||||
|
||||
|
||||
class ProjectGroup(models.Model):
|
||||
name = models.CharField(max_length=50, primary_key=True)
|
||||
title = models.CharField(max_length=100)
|
||||
members = models.ManyToManyField(Project)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Branch(models.Model):
|
||||
BRANCH_STATUS = (
|
||||
('M', 'master'),
|
||||
|
@ -2,8 +2,13 @@
|
||||
{% block content %}
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<h3>{{ project.title }} ({{ project.name }})</h3>
|
||||
Interesting graphs and information shall be placed here.
|
||||
<h3>{{ ref.title }} ({{ ref.name }})</h3>
|
||||
<h4>Groups</h4>
|
||||
<ul>
|
||||
{% for group in ref.projectgroup_set.all %}
|
||||
<li><a href="/projectgroup/{{group.name}}">{{group.title}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
14
storyboard/projects/templates/projects.group.html
Normal file
14
storyboard/projects/templates/projects.group.html
Normal file
@ -0,0 +1,14 @@
|
||||
{% extends "projects.project.html" %}
|
||||
{% block content %}
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<h3>Project group: {{ ref.title }} ({{ ref.name }})</h3>
|
||||
<h4>Projects</h4>
|
||||
<ul>
|
||||
{% for project in ref.members.all %}
|
||||
<li><a href="/project/{{ project.name }}">{{ project.title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -3,7 +3,7 @@
|
||||
{% block content %}
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<h3>{{ title }} for {{ project.title }}</h3>
|
||||
<h3>{{ title }} for {{ ref.name }}</h3>
|
||||
<table class="table table-condensed table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -12,6 +12,7 @@
|
||||
<th>Story</th>
|
||||
<th>Task</th>
|
||||
{% if is_bug %}<th>Branch</th>{% endif %}
|
||||
{% if is_group %}<th>Project</th>{% endif %}
|
||||
<th>Assignee</th>
|
||||
<th>Milestone</th>
|
||||
</tr>
|
||||
@ -25,6 +26,7 @@
|
||||
<td><small><a href="/story/{{task.story.id}}">{{ task.story.title }}</a></small></td>
|
||||
<td>{{ task.title }}</td>
|
||||
{% if is_bug %}<td>{{ task.milestone.branch.name }}</td>{% endif %}
|
||||
{% if is_group %}<td>{{ task.project.name }}</td>{% endif %}
|
||||
<td>{{ task.assignee.username }}</td>
|
||||
<td>{% if not task.milestone.undefined %}{{ task.milestone.name }}{% endif %}</td>
|
||||
</tr>
|
||||
|
@ -2,22 +2,28 @@
|
||||
{% block extranav %}
|
||||
<div class="well sidebar-nav">
|
||||
<ul class="nav nav-list">
|
||||
<li class="nav-header">{{project.name}}</li>
|
||||
<li><a href="/project/{{project.name}}">Dashboard</a></li>
|
||||
<li class="nav-header">{{ref.name}}</li>
|
||||
<li><a href="/project{% if is_group %}group{% endif %}/{{ref.name}}">Dashboard</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="/project/{{project.name}}/bugs">List bug tasks</a></li>
|
||||
<li><a href="/project/{{project.name}}/bugs/triage">Triage bugs
|
||||
<li><a href="/project{% if is_group %}group{% endif %}/{{ref.name}}/bugs">List bug tasks</a></li>
|
||||
<li><a href="/project{% if is_group %}group{% endif %}/{{ref.name}}/bugs/triage">Triage bugs
|
||||
{% if bugtriagecount > 0 %}<span class="badge
|
||||
{% if bugtriagecount < 20 %}badge-success{% else %}{% if bugtriagecount < 50 %}badge-warning{% else %}badge-important{% endif %}{% endif %}">
|
||||
{{ bugtriagecount }}</span>{% endif %}</a></li>
|
||||
{% if not is_group %}
|
||||
<li><a href="#addbug" data-toggle="modal">Report new bug</a></li>
|
||||
{% endif %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="/project/{{project.name}}/features">List feature tasks</a></li>
|
||||
<li><a href="/project{% if is_group %}group{% endif %}/{{ref.name}}/features">List feature tasks</a></li>
|
||||
{% if not is_group %}
|
||||
<li><a href="#addfeature" data-toggle="modal">Propose new feature</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div><!--/.well -->
|
||||
{% endblock %}
|
||||
{% block modals %}
|
||||
{% include "stories.modal_addstory.html" with project=project.name story_type='bug' %}
|
||||
{% include "stories.modal_addstory.html" with project=project.name story_type='feature' %}
|
||||
{% if not is_group %}
|
||||
{% include "stories.modal_addstory.html" with project=ref.name story_type='bug' %}
|
||||
{% include "stories.modal_addstory.html" with project=ref.name story_type='feature' %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
26
storyboard/projects/utils.py
Normal file
26
storyboard/projects/utils.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Copyright 2013 Thierry Carrez <thierry@openstack.org>
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from storyboard.projects.models import Project
|
||||
from storyboard.projects.models import ProjectGroup
|
||||
|
||||
|
||||
def retrieve_projects(name, group):
|
||||
if group:
|
||||
ref = ProjectGroup.objects.get(name=name)
|
||||
return ref, ref.members.all()
|
||||
else:
|
||||
ref = Project.objects.get(name=name)
|
||||
return ref, [ref]
|
@ -16,6 +16,7 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
from storyboard.projects.models import Project
|
||||
from storyboard.projects.utils import retrieve_projects
|
||||
from storyboard.stories.models import Task
|
||||
|
||||
|
||||
@ -25,60 +26,71 @@ def default_list(request):
|
||||
})
|
||||
|
||||
|
||||
def dashboard(request, projectname):
|
||||
project = Project.objects.get(name=projectname)
|
||||
bugcount = Task.objects.filter(project=project,
|
||||
def dashboard(request, projectname, group=False):
|
||||
ref, projects = retrieve_projects(projectname, group)
|
||||
bugcount = Task.objects.filter(project__in=projects,
|
||||
story__is_bug=True,
|
||||
story__priority=0).count()
|
||||
if group:
|
||||
return render(request, "projects.group.html", {
|
||||
'ref': ref,
|
||||
'is_group': group,
|
||||
'bugtriagecount': bugcount,
|
||||
})
|
||||
return render(request, "projects.dashboard.html", {
|
||||
'project': project,
|
||||
'ref': ref,
|
||||
'is_group': group,
|
||||
'bugtriagecount': bugcount,
|
||||
})
|
||||
|
||||
|
||||
def list_featuretasks(request, projectname):
|
||||
project = Project.objects.get(name=projectname)
|
||||
bugcount = Task.objects.filter(project=project,
|
||||
def list_featuretasks(request, projectname, group=False):
|
||||
ref, projects = retrieve_projects(projectname, group)
|
||||
bugcount = Task.objects.filter(project__in=projects,
|
||||
story__is_bug=True,
|
||||
story__priority=0).count()
|
||||
featuretasks = Task.objects.filter(project=project,
|
||||
featuretasks = Task.objects.filter(project__in=projects,
|
||||
story__is_bug=False,
|
||||
status__in=['T', 'R'])
|
||||
return render(request, "projects.list_tasks.html", {
|
||||
'title': "Active feature tasks",
|
||||
'project': project,
|
||||
'ref': ref,
|
||||
'is_group': group,
|
||||
'name': projectname,
|
||||
'bugtriagecount': bugcount,
|
||||
'tasks': featuretasks,
|
||||
'is_bug': False,
|
||||
})
|
||||
|
||||
|
||||
def list_bugtasks(request, projectname):
|
||||
project = Project.objects.get(name=projectname)
|
||||
bugcount = Task.objects.filter(project=project,
|
||||
def list_bugtasks(request, projectname, group=False):
|
||||
ref, projects = retrieve_projects(projectname, group)
|
||||
bugcount = Task.objects.filter(project__in=projects,
|
||||
story__is_bug=True,
|
||||
story__priority=0).count()
|
||||
bugtasks = Task.objects.filter(project=project,
|
||||
bugtasks = Task.objects.filter(project__in=projects,
|
||||
story__is_bug=True,
|
||||
status__in=['T', 'R'])
|
||||
return render(request, "projects.list_tasks.html", {
|
||||
'title': "Active bug tasks",
|
||||
'project': project,
|
||||
'ref': ref,
|
||||
'is_group': group,
|
||||
'bugtriagecount': bugcount,
|
||||
'tasks': bugtasks,
|
||||
'is_bug': True,
|
||||
})
|
||||
|
||||
|
||||
def list_bugtriage(request, projectname):
|
||||
project = Project.objects.get(name=projectname)
|
||||
tasks = Task.objects.filter(project=project,
|
||||
def list_bugtriage(request, projectname, group=False):
|
||||
ref, projects = retrieve_projects(projectname, group)
|
||||
tasks = Task.objects.filter(project__in=projects,
|
||||
story__is_bug=True,
|
||||
story__priority=0)
|
||||
bugcount = tasks.count()
|
||||
return render(request, "projects.list_tasks.html", {
|
||||
'title': "Bugs needing triage",
|
||||
'project': project,
|
||||
'ref': ref,
|
||||
'is_group': group,
|
||||
'bugtriagecount': bugcount,
|
||||
'tasks': tasks,
|
||||
'is_bug': True,
|
||||
|
@ -26,6 +26,7 @@ urlpatterns = patterns('',
|
||||
(r'^$', 'storyboard.about.views.welcome'),
|
||||
(r'^about/', include('storyboard.about.urls')),
|
||||
(r'^project/', include('storyboard.projects.urls')),
|
||||
(r'^projectgroup/', include('storyboard.projects.urls'), {'group': True}),
|
||||
(r'^story/', include('storyboard.stories.urls')),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
(r'^logout$', 'storyboard.about.views.dologout'),
|
||||
|
Loading…
Reference in New Issue
Block a user