Skip to content

Commit

Permalink
feat(sprints): add deployment report view
Browse files Browse the repository at this point in the history
  • Loading branch information
pablolmedorado committed Feb 7, 2021
1 parent a62593a commit fa3c35b
Show file tree
Hide file tree
Showing 19 changed files with 494 additions and 122 deletions.
1 change: 1 addition & 0 deletions backend/scrum/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ class UserStoryAdmin(ImportExportActionModelAdmin):
)
},
),
(_("Despliegue"), {"fields": ("use_migrations", "deployment_notes")}),
(_("Miscelánea"), {"fields": ("cvs_reference",)}),
(
_("Autoría"),
Expand Down
1 change: 1 addition & 0 deletions backend/scrum/api/filtersets.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class Meta:
"support_user_id": ["exact", "in"],
"cvs_reference": ["icontains"],
"risk_level": ["exact", "in"],
"use_migrations": ["exact"],
"tags__name": ["in", "iexact", "icontains", "istartswith"],
}

Expand Down
12 changes: 11 additions & 1 deletion backend/scrum/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ class Meta:
"cvs_reference",
"risk_level",
"risk_comments",
"use_migrations",
"deployment_notes",
"tags",
)
read_only_fields = (
Expand Down Expand Up @@ -205,7 +207,15 @@ class Meta(UserStorySerializer.Meta):
read_only_fields = tuple( # type: ignore
field
for field in UserStorySerializer.Meta.fields
if field not in ["development_comments", "cvs_reference", "risk_level", "risk_comments"]
if field
not in [
"development_comments",
"cvs_reference",
"risk_level",
"risk_comments",
"use_migrations",
"deployment_notes",
]
)


Expand Down
15 changes: 15 additions & 0 deletions backend/scrum/api/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,19 @@ def gantt_chart(self, request, *args, **kwargs):
chart_data = gantt_chart_data(instance)
return Response(chart_data)

@action(detail=True, methods=["GET"])
def deployment_report(self, request, *args, **kwargs):
instance = self.get_object()
report_data = {
"user_story_count": instance.user_stories.count(),
"user_stories_with_migrations": instance.user_stories.filter(use_migrations=True).values("id", "name"),
"development_users": instance.user_stories.values_list("development_user", flat=True).distinct(),
"deployment_notes": instance.user_stories.exclude(deployment_notes="").values(
"id", "name", "deployment_notes"
),
}
return Response(report_data)


class EpicViewSet(AuthorshipMixin, AtomicFlexFieldsModelViewSet):
permission_classes = (permissions.IsAuthenticated, IsAdminUserOrReadOnly)
Expand Down Expand Up @@ -113,6 +126,7 @@ class UserStoryViewSet(AuthorshipMixin, AtomicFlexFieldsModelViewSet):
"support_comments",
"cvs_reference",
"risk_comments",
"deployment_notes",
)
ordering_fields = (
"name",
Expand All @@ -128,6 +142,7 @@ class UserStoryViewSet(AuthorshipMixin, AtomicFlexFieldsModelViewSet):
"priority",
"risk_level",
"cvs_reference",
"use_migrations",
)
ordering = ("-start_date",)
permit_list_expands = ["type", "epic", "sprint", "development_user", "validation_user", "support_user"]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("scrum", "0002_tags"),
]

operations = [
migrations.AddField(
model_name="userstory",
name="deployment_notes",
field=models.CharField(blank=True, max_length=2000, verbose_name="notas de despliegue"),
),
migrations.AddField(
model_name="userstory",
name="use_migrations",
field=models.BooleanField(default=False, verbose_name="usa migraciones"),
),
]
3 changes: 3 additions & 0 deletions backend/scrum/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ class RiskLevel(models.IntegerChoices):
_("nivel de riesgo"), choices=RiskLevel.choices, default=RiskLevel.GREEN, blank=False
)
risk_comments = models.CharField(_("comentarios de riesgo"), max_length=2000, blank=True)
use_migrations = models.BooleanField(_("usa migraciones"), default=False)
deployment_notes = models.CharField(_("notas de despliegue"), max_length=2000, blank=True)

@property
def actual_effort(self):
Expand All @@ -193,6 +195,7 @@ def get_copy(cls, instance, owner=None):
"technical_description": instance.technical_description,
"planned_effort": instance.planned_effort,
"priority": instance.priority,
"use_migrations": instance.use_migrations,
"creation_user": owner if owner else instance.creation_user,
}
)
Expand Down
2 changes: 2 additions & 0 deletions backend/scrum/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ class Meta:
"cvs_reference",
"risk_level",
"risk_comments",
"use_migrations",
"deployment_notes",
)
export_order = fields

Expand Down
69 changes: 69 additions & 0 deletions frontend/src/components/scrum/SprintViewSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<template>
<span class="d-inline-flex">
<v-menu v-if="menu" bottom left offset-y>
<template #activator="{ on: onMenu }">
<v-tooltip bottom>
<template #activator="{ on: onTooltip }">
<v-btn icon v-on="{ ...onTooltip, ...onMenu }">
<v-icon>mdi-view-array</v-icon>
</v-btn>
</template>
<span>Vistas</span>
</v-tooltip>
</template>
<v-list dense>
<v-list-item
v-for="view in availableViews"
:key="view.routeName"
:to="{ name: view.routeName, params: { sprintId } }"
>
<v-list-item-icon>
<v-icon>{{ view.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ view.label }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-tooltip v-for="view in availableViews" v-else :key="view.routeName" bottom>
<template #activator="{ on, attrs }">
<v-btn icon :to="{ name: view.routeName, params: { sprintId } }" v-bind="attrs" v-on="on">
<v-icon>{{ view.icon }}</v-icon>
</v-btn>
</template>
<span>
{{ view.label }}
</span>
</v-tooltip>
</span>
</template>

<script>
export default {
name: "SprintViewSelector",
props: {
sprintId: {
type: String,
required: true,
},
menu: {
type: Boolean,
default: false,
},
},
data() {
return {
views: [
{ label: "Kanban", routeName: "sprint-kanban", icon: "mdi-teach" },
{ label: "Diagrama de quemado (Burn-down/up)", routeName: "sprint-chart", icon: "mdi-fire" },
{ label: "Diagrama de Gantt", routeName: "sprint-gantt", icon: "mdi-chart-timeline" },
{ label: "Informe de despliegue", routeName: "sprint-deployment-report", icon: "mdi-rocket-launch" },
],
};
},
computed: {
availableViews() {
return this.views.filter((view) => view.routeName !== this.$route.name);
},
},
};
</script>
66 changes: 40 additions & 26 deletions frontend/src/components/scrum/filters/UserStoryFilters.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<v-text-field
:value="filters.search"
label="Buscar"
placeholder="Id, nombre, descripciones, comentarios, scv..."
placeholder="Id, nombre, descripciones, comentarios, notas, scv..."
prepend-icon="mdi-magnify"
clearable
@input="updateFilters({ search: $event })"
Expand Down Expand Up @@ -70,6 +70,7 @@
<v-tab href="#status">Estado</v-tab>
<v-tab href="#dates">Fechas</v-tab>
<v-tab href="#people">Personas</v-tab>
<v-tab href="#others">Otros</v-tab>
</v-tabs>
<v-tabs-items v-model="tab">
<v-tab-item value="general">
Expand All @@ -78,7 +79,7 @@
<v-text-field
:value="filters.search"
label="Buscar"
placeholder="Id, nombre, descripciones, comentarios, scv..."
placeholder="Id, nombre, descripciones, comentarios, notas, scv..."
prepend-icon="mdi-magnify"
clearable
@input="updateFilters({ search: $event })"
Expand Down Expand Up @@ -150,30 +151,6 @@
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
:value="filters.planned_effort__gte"
label="Esfuerzo (desde)"
type="number"
min="0"
prepend-icon="mdi-dumbbell"
clearable
@input="updateFilters({ planned_effort__gte: $event })"
/>
</v-col>
<v-col>
<v-text-field
:value="filters.planned_effort__lte"
label="Esfuerzo (hasta)"
type="number"
min="0"
prepend-icon="mdi-dumbbell"
clearable
@input="updateFilters({ planned_effort__lte: $event })"
/>
</v-col>
</v-row>
</v-tab-item>
<v-tab-item value="status">
<v-row>
Expand Down Expand Up @@ -404,6 +381,43 @@
</v-col>
</v-row>
</v-tab-item>
<v-tab-item value="others">
<v-row>
<v-col>
<v-text-field
:value="filters.planned_effort__gte"
label="Esfuerzo (desde)"
type="number"
min="0"
prepend-icon="mdi-dumbbell"
clearable
@input="updateFilters({ planned_effort__gte: $event })"
/>
</v-col>
<v-col>
<v-text-field
:value="filters.planned_effort__lte"
label="Esfuerzo (hasta)"
type="number"
min="0"
prepend-icon="mdi-dumbbell"
clearable
@input="updateFilters({ planned_effort__lte: $event })"
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-switch
class="pl-1"
:value="filters.use_migrations"
label="Usa migraciones"
inset
@change="updateFilters({ use_migrations: $event })"
/>
</v-col>
</v-row>
</v-tab-item>
</v-tabs-items>
</v-card-text>
<v-divider />
Expand Down
45 changes: 44 additions & 1 deletion frontend/src/components/scrum/forms/UserStoryForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@
</v-col>
</v-row>
<v-row>
<v-col>
<v-col cols="12" lg="6">
<v-card>
<v-card-title class="text-h6">
Gestión de riesgo
Expand Down Expand Up @@ -436,6 +436,43 @@
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" lg="6">
<v-card>
<v-card-title class="text-h6">
Información de despliegue
</v-card-title>
<v-card-text>
<v-row>
<v-col cols="12" md="6">
<v-select
v-model.number="item.use_migrations"
:items="useMigrationsOptions"
label="Usa migraciones*"
prepend-icon="mdi-database-arrow-right"
:readonly="!loggedUser.is_superuser && loggedUser.id !== item.development_user"
:error-messages="buildValidationErrorMessages($v.item.use_migrations)"
@change="$v.item.use_migrations.$touch()"
@blur="$v.item.use_migrations.$touch()"
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-textarea
v-model="item.deployment_notes"
label="Notas de despliegue"
counter="2000"
prepend-icon="mdi-note-text"
:readonly="!loggedUser.is_superuser && loggedUser.id !== item.development_user"
:error-messages="buildValidationErrorMessages($v.item.deployment_notes)"
@input="$v.item.deployment_notes.$touch()"
@blur="$v.item.deployment_notes.$touch()"
/>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
</v-row>
<small>* indica campo obligatorio</small>
</form>
Expand Down Expand Up @@ -505,6 +542,8 @@ export default {
support_comments: { maxLength: maxLength(2000) },
risk_level: { required },
risk_comments: { maxLength: maxLength(2000) },
use_migrations: { required },
deployment_notes: { maxLength: maxLength(2000) },
},
},
data() {
Expand All @@ -518,6 +557,10 @@ export default {
{ value: false, text: "Rechazada" },
{ value: true, text: "Validada" },
],
useMigrationsOptions: [
{ value: false, text: "No" },
{ value: true, text: "" },
],
validationErrorMessages: {
endDateBeforeStartDate: "Fecha de fin anterior a la de inicio",
outOfSprint: "Fecha fuera del sprint",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/plugins/reverse.js

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions frontend/src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Sprints from "@/views/scrum/sprints/Sprints";
import SprintKanban from "@/views/scrum/sprints/SprintKanban";
import SprintChart from "@/views/scrum/sprints/SprintChart";
import SprintGantt from "@/views/scrum/sprints/SprintGantt";
import SprintDeploymentReport from "@/views/scrum/sprints/SprintDeploymentReport";
import UserStories from "@/views/scrum/user-stories/UserStories";
import UserStoryDetail from "@/views/scrum/user-stories/UserStoryDetail";
import Epics from "@/views/scrum/Epics";
Expand Down Expand Up @@ -114,6 +115,15 @@ const router = new Router({
keepAlive: true,
},
},
{
path: "/scrum/sprints/:sprintId/deployment-report",
name: "sprint-deployment-report",
component: SprintDeploymentReport,
props: true,
meta: {
keepAlive: false,
},
},
{
path: "/scrum/sprints/:sprintId/user-stories",
name: "sprint-user-stories",
Expand Down
Loading

0 comments on commit fa3c35b

Please sign in to comment.