feat: per-project workflow settings, statistics and "my work" home
Per-project settings (owners/admins, «Configuración» tab):
- Statuses with category (TODO, IN_PROGRESS, QA, DONE, CANCELLED), colour and
"requires reason"; configurable transitions; ticket types (icon, colour) and
areas. Defaults are the previous fixed values; projects created before keep
them without migration.
- Values are archived, never deleted: tickets, history and statistics keep
referring to them. Validated in the domain (ProjectSettingsChangedEvent).
- Releases, board, tracking and "my work" use categories, so custom statuses
keep working. Status changes carry name/category metadata for history.
- GraphQL: status/type/area are strings (breaking for clients sending enum
literals); Project.settings, updateProjectSettings, statusCategory,
myIssues(categories). REST: PUT /projects/{project}/settings, category filters.
- Read model: statusCategory on issues (backfilled at startup).
Statistics («Estadísticas» tab):
- New issue_timeline projection; pure functions compute created vs resolved,
cumulative flow, cycle/lead time (avg + p85), throughput per person, QA
pass/fail and release burnup. GraphQL Project.analytics(days),
Release.burnup; REST /projects/{project}/analytics, /releases/{id}/burnup.
- Web: SVG charts with validated light/dark palette, crosshair tooltip,
keyboard navigation, legend and data table.
Home "Mi trabajo": my open tickets across projects grouped by category (QA
failures first), tickets waiting for QA, upcoming releases and "Ir a…".
Tests: domain/application specs, e2e scenario for settings and analytics,
facade tests. Docs, READMEs, schema.gql/openapi.json and Word/PDF updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -37,7 +37,7 @@ export const boardPageEvents = eventGroup({
|
||||
source: 'Board Page',
|
||||
events: {
|
||||
opened: type<BoardFilter>(),
|
||||
issueMoved: type<{ issueId: string; to: IssueStatus; reason?: string | null }>(),
|
||||
issueMoved: type<{ issueId: string; to: string; reason?: string | null }>(),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -84,7 +84,7 @@ export class BoardFacade {
|
||||
else this.dispatch.opened(filter);
|
||||
}
|
||||
|
||||
moveIssue(issueId: string, to: IssueStatus, reason?: string | null): void {
|
||||
moveIssue(issueId: string, to: string, reason?: string | null): void {
|
||||
this.dispatch.issueMoved({ issueId, to, reason });
|
||||
}
|
||||
}
|
||||
@@ -109,13 +109,14 @@ Qué gana la página:
|
||||
- **Un sitio para la lógica de presentación**: abrir o refiltrar el tablero, buscar usuarios a partir de 2 caracteres…
|
||||
- **Tests sin plantilla**: la facade se prueba con gateways falsos (`facades.spec.ts`).
|
||||
|
||||
| Facade | Store | Ámbito | Responsabilidad |
|
||||
| ---------------- | --------------- | ----------------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| `AuthFacade` | `AuthStore` | raíz | Sesión, usuario, login (PKCE o email y contraseña de Pireon), renovación del token, restaurar al arrancar |
|
||||
| `ProjectsFacade` | `ProjectsStore` | raíz | Mis proyectos; se actualiza cuando me añaden o quitan (`myProjectsChanged`) |
|
||||
| `ProjectFacade` | `ProjectStore` | ruta `/p/:key` | Proyecto, miembros, stats, releases, actividad en vivo y búsqueda de usuarios |
|
||||
| `BoardFacade` | `BoardStore` | ruta `board` | Tablero kanban, filtros, movimiento optimista, nuevo ticket, cambios en vivo |
|
||||
| `IssueFacade` | `IssueStore` | ruta `issues/:issueKey` | Ticket, comentarios en vivo, historial y todas sus acciones |
|
||||
| Facade | Store | Ámbito | Responsabilidad |
|
||||
| ---------------- | --------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `AuthFacade` | `AuthStore` | raíz | Sesión, usuario, login (PKCE o email y contraseña de Pireon), renovación del token, restaurar al arrancar |
|
||||
| `ProjectsFacade` | `ProjectsStore` | raíz | Mis proyectos; se actualiza cuando me añaden o quitan (`myProjectsChanged`) |
|
||||
| `HomeFacade` | `HomeStore` | raíz | «Mi trabajo» en la portada: mis tickets abiertos por categoría (QA fallidos primero), esperando QA y próximas releases; se recarga al volver a la pestaña |
|
||||
| `ProjectFacade` | `ProjectStore` | ruta `/p/:key` | Proyecto, miembros, stats, releases, actividad en vivo, búsqueda de usuarios, configuración (`workflow`, `saveSettings`) y estadísticas (`loadAnalytics`, `loadBurnup`) |
|
||||
| `BoardFacade` | `BoardStore` | ruta `board` | Tablero kanban, filtros, movimiento optimista, nuevo ticket, cambios en vivo |
|
||||
| `IssueFacade` | `IssueStore` | ruta `issues/:issueKey` | Ticket, comentarios en vivo, historial y todas sus acciones |
|
||||
|
||||
Las de ámbito de ruta se registran con `provideProjectFacade()`, `provideBoardFacade()` o `provideIssueFacade()` en los
|
||||
`providers` del componente de la página (store + facade) y desaparecen al salir (con sus subscriptions). Las páginas
|
||||
@@ -145,7 +146,9 @@ de identidad (`SessionTokenSource`) le pide el token para el cliente GraphQL.
|
||||
npx nx run web:codegen # regenera generated.ts (TypedDocumentNode)
|
||||
```
|
||||
|
||||
- Los adaptadores convierten los tipos generados en modelos de dominio en un único sitio (`adapters/mappers.ts`).
|
||||
- Los adaptadores convierten los tipos generados en modelos de dominio en un único sitio (`adapters/mappers.ts`). Lo
|
||||
que se lee y luego se reenvía como input (la configuración del proyecto) se copia campo a campo: Apollo añade
|
||||
`__typename` a cada objeto y los input types de GraphQL lo rechazan.
|
||||
|
||||
## 4.4 `@pi/ui`
|
||||
|
||||
@@ -193,19 +196,48 @@ versiones) y un color por estado (`--ts-s-*`) y por tipo de ticket (`--ts-t-*`),
|
||||
gráficas e historial. Salen de `tone` en `ui/copy.ts` y las plantillas los aplican con `[style.--tone]`; utilidades
|
||||
globales como `.ts-pill` o `.ts-key` leen esa variable. Las animaciones se desactivan con `prefers-reduced-motion`.
|
||||
|
||||
### Configuración del proyecto en la UI
|
||||
|
||||
Estados, tipos y áreas son de cada proyecto, así que la UI nunca los tiene fijos: `ProjectFacade.workflow()` envuelve
|
||||
`project.settings` (`Workflow` en el dominio web) y da nombre, color, icono, categoría, transiciones permitidas y si un
|
||||
estado pide motivo. `statusLook`/`typeLook` de `ui/copy.ts` lo convierten en etiqueta y tono (`--ts-c-<color>`, con su
|
||||
versión oscura). Un id que ya no está configurado sigue mostrándose (`IN_REVIEW` → «In review»). «Mi trabajo» mezcla
|
||||
proyectos: cada fila usa el workflow de su proyecto y los grupos son por categoría.
|
||||
|
||||
En el tablero, un ticket solo se puede soltar en las columnas a las que su workflow le deja pasar, y los tipos
|
||||
archivados no se ofrecen al crear. Los tipos con icono `palette` piden un frame de Figma.
|
||||
|
||||
### Gráficas
|
||||
|
||||
Las de Estadísticas son SVG propios (`ui/series-chart.ts`), no `pi-line-chart`: hacen falta áreas apiladas y una
|
||||
paleta con versión oscura, que los gráficos de `@pi/ui` (Chart.js con colores fijos) no traen. Siguen las reglas de
|
||||
visualización de la plataforma:
|
||||
|
||||
- Colores `--ts-viz-*` en `styles.scss`, validados contra la superficie clara y la oscura (separación para daltonismo,
|
||||
contraste): dos series categóricas (azul y naranja), una rampa ordinal de un solo tono para las etapas del flujo y
|
||||
bueno/crítico reservados para QA superado/fallido, siempre con icono y texto.
|
||||
- Líneas de 2 px, rejilla de 1 px continua, separación de 2 px entre bandas apiladas, una sola escala por gráfica.
|
||||
- Cruceta que se ajusta al día o semana más cercano y un único tooltip con todas las series; con teclado, ← → Inicio
|
||||
Fin sobre la gráfica.
|
||||
- Leyenda con dos o más series, etiqueta de valor al final de cada línea solo si no se solapan, y «Ver los datos» con
|
||||
la tabla, para que nada dependa del color ni del ratón.
|
||||
- Mientras se recalcula un periodo, se queda la gráfica anterior atenuada.
|
||||
|
||||
## 4.6 Rutas
|
||||
|
||||
| Ruta | Página |
|
||||
| -------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| `/login` | Inicio de sesión (solo sin sesión) |
|
||||
| `/auth/callback` | Retorno de Pireon (PKCE) |
|
||||
| `/` | Mis proyectos |
|
||||
| `/p/:key/board` | Tablero kanban (`?release=` y `?assignee=` preseleccionan los filtros) |
|
||||
| `/p/:key/issues/:issueKey` | Ticket |
|
||||
| `/p/:key/releases` | Releases |
|
||||
| `/p/:key/dashboard` | Seguimiento: cifras, flujo por estado, equipo (cada persona enlaza a su tablero) y tipos |
|
||||
| `/p/:key/activity` | Actividad en vivo |
|
||||
| `/p/:key/members` | Miembros |
|
||||
| Ruta | Página |
|
||||
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `/login` | Inicio de sesión (solo sin sesión) |
|
||||
| `/auth/callback` | Retorno de Pireon (PKCE) |
|
||||
| `/` | Inicio: «Ir a…» (`WEB-12`), mi trabajo (`ts-my-work`) y mis proyectos |
|
||||
| `/p/:key/board` | Tablero kanban (`?release=` y `?assignee=` preseleccionan los filtros) |
|
||||
| `/p/:key/issues/:issueKey` | Ticket |
|
||||
| `/p/:key/releases` | Releases |
|
||||
| `/p/:key/dashboard` | Seguimiento: cifras, flujo por estado, equipo (cada persona enlaza a su tablero) y tipos |
|
||||
| `/p/:key/analytics` | Estadísticas: creados vs. resueltos, flujo acumulado, burnup de una release, tiempos de ciclo y entrega, QA y resueltos por persona (30 días a 1 año) |
|
||||
| `/p/:key/settings` | Configuración (propietario y admins; pestaña oculta para el resto): estados (orden, categoría, color, motivo), transiciones, tipos y áreas; archivar en lugar de borrar |
|
||||
| `/p/:key/activity` | Actividad en vivo |
|
||||
| `/p/:key/members` | Miembros |
|
||||
|
||||
Todo salvo `/login` y `/auth/callback` está protegido por `authGuard`. Los parámetros de ruta llegan como inputs de
|
||||
los componentes (`withComponentInputBinding`).
|
||||
|
||||
Reference in New Issue
Block a user