Merge "Collate story metadata for status"

This commit is contained in:
Jenkins
2014-04-23 19:15:57 +00:00
committed by Gerrit Code Review
4 changed files with 127 additions and 24 deletions

View File

@@ -46,23 +46,40 @@ class Story(base.APIBase):
is_bug = bool
"""Is this a bug or a feature :)"""
is_active = bool
"""Is this an active story, or has it been deleted?"""
project_id = int
"""Optional parameter"""
creator_id = int
"""User ID of the Story creator"""
todo = int
"""The number of tasks remaining to be worked on."""
inprogress = int
"""The number of in-progress tasks for this story."""
review = int
"""The number of tasks in review for this story."""
merged = int
"""The number of merged tasks for this story."""
invalid = int
"""The number of invalid tasks for this story."""
status = unicode
"""The derived status of the story, one of 'active', 'merged', 'invalid'"""
@classmethod
def sample(cls):
return cls(
title="Use Storyboard to manage Storyboard",
description="We should use Storyboard to manage Storyboard.",
is_bug=False,
is_active=True,
creator_id=1)
creator_id=1,
todo=0,
inprogress=1,
review=1,
merged=0,
invalid=0,
status="active")
class StoriesController(rest.RestController):
@@ -150,7 +167,7 @@ class StoriesController(rest.RestController):
raise ClientSideError("Story %s not found" % id,
status_code=404)
@secure(checks.authenticated)
@secure(checks.superuser)
@wsme_pecan.wsexpose(Story, int)
def delete(self, story_id):
"""Delete this story.

View File

@@ -19,7 +19,7 @@ from storyboard.db import models
def story_get(story_id):
return api_base.entity_get(models.Story, story_id)
return api_base.entity_get(models.StorySummary, story_id)
def story_get_all(marker=None, limit=None, project_id=None):
@@ -28,7 +28,7 @@ def story_get_all(marker=None, limit=None, project_id=None):
limit=limit,
project_id=project_id)
else:
return api_base.entity_get_all(models.Story, is_active=True,
return api_base.entity_get_all(models.StorySummary,
marker=marker, limit=limit)
@@ -36,7 +36,7 @@ def story_get_count(project_id=None):
if project_id:
return _story_get_count_in_project(project_id)
else:
return api_base.entity_get_count(models.Story, is_active=True)
return api_base.entity_get_count(models.StorySummary)
def _story_get_all_in_project(project_id, marker=None, limit=None):
@@ -45,14 +45,13 @@ def _story_get_all_in_project(project_id, marker=None, limit=None):
sub_query = api_base.model_query(models.Task.story_id, session) \
.filter_by(project_id=project_id) \
.distinct(True) \
.subquery()
.subquery('project_tasks')
query = api_base.model_query(models.Story, session) \
.filter_by(is_active=True) \
.join(sub_query, models.Story.tasks)
query = api_base.model_query(models.StorySummary, session) \
.join(sub_query, models.StorySummary.id == sub_query.c.story_id)
query = api_base.paginate_query(query=query,
model=models.Story,
model=models.StorySummary,
limit=limit,
sort_keys=['id'],
marker=marker,
@@ -67,11 +66,10 @@ def _story_get_count_in_project(project_id):
sub_query = api_base.model_query(models.Task.story_id, session) \
.filter_by(project_id=project_id) \
.distinct(True) \
.subquery()
.subquery('project_tasks')
query = api_base.model_query(models.Story, session) \
.filter_by(is_active=True) \
.join(sub_query, models.Story.tasks)
query = api_base.model_query(models.StorySummary, session) \
.join(sub_query, models.StorySummary.id == sub_query.c.story_id)
return query.count()
@@ -88,5 +86,4 @@ def story_delete(story_id):
story = story_get(story_id)
if story:
story.is_active = False
api_base.entity_update(models.Story, story_id, story.as_dict())
api_base.entity_hard_delete(models.Story, story_id, story.as_dict())

View File

@@ -0,0 +1,48 @@
# 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.
#
"""Remove is_active from stories.
Revision ID: 015
Revises: 014
Create Date: 2014-04-09 16:52:36.375926
"""
# revision identifiers, used by Alembic.
revision = '015'
down_revision = '014'
from alembic import op
import sqlalchemy as sa
def upgrade(active_plugins=None, options=None):
### commands auto generated by Alembic - please adjust! ###
op.drop_column(u'stories', u'is_active')
### end Alembic commands ###
def downgrade(active_plugins=None, options=None):
### commands auto generated by Alembic - please adjust! ###
op.add_column(u'stories',
sa.Column('is_active', sa.Boolean(), default=True,
server_default="1",
nullable=False))
### end Alembic commands ###

View File

@@ -28,6 +28,9 @@ from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy.orm import relationship
from sqlalchemy import schema
from sqlalchemy import select
import sqlalchemy.sql.expression as expr
import sqlalchemy.sql.functions as func
from sqlalchemy import String
from sqlalchemy import Table
from sqlalchemy import Unicode
@@ -171,7 +174,6 @@ class Story(Base):
tasks = relationship('Task', backref='story')
comments = relationship('Comment', backref='story')
tags = relationship('StoryTag', backref='story')
is_active = Column(Boolean, default=True)
_public_fields = ["id", "creator_id", "title", "description", "is_bug",
"tasks", "comments", "tags"]
@@ -234,3 +236,42 @@ class RefreshToken(Base):
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
refresh_token = Column(Unicode(100), nullable=False)
def _story_build_summary_query():
return select([Story,
func.cast(
func.sum(Task.status == 'todo'), Integer
).label('todo'),
func.cast(
func.sum(Task.status == 'inprogress'), Integer
).label('inprogress'),
func.cast(
func.sum(Task.status == 'review'), Integer
).label('review'),
func.cast(
func.sum(Task.status == 'merged'), Integer
).label('merged'),
func.cast(
func.sum(Task.status == 'invalid'), Integer
).label('invalid'),
expr.case(
[(func.sum(Task.status.in_(
['todo', 'inprogress', 'review'])) > 0,
'active'),
((func.sum(Task.status == 'merged')) > 0, 'merged')],
else_='invalid'
).label('status')],
None,
expr.Join(Story, Task, onclause=Story.id == Task.story_id,
isouter=True)) \
.group_by(Task.story_id) \
.alias('story_summary')
class StorySummary(Base):
__table__ = _story_build_summary_query()
_public_fields = ["id", "creator_id", "title", "description", "is_bug",
"tasks", "comments", "tags", "todo", "inprogress",
"review", "merged", "invalid", "status"]