146 lines
8.3 KiB
Markdown
146 lines
8.3 KiB
Markdown
|
|
# Changelog
|
|||
|
|
|
|||
|
|
All notable changes to **FORGE** are documented here.
|
|||
|
|
Format based on [Keep a Changelog](https://keepachangelog.com/); this project uses
|
|||
|
|
[Semantic Versioning](https://semver.org/).
|
|||
|
|
|
|||
|
|
## [2.3.0] — 2026-08-27
|
|||
|
|
|
|||
|
|
### Added
|
|||
|
|
- **Jira status durations (chart #17)** — a new "Jira status durations (avg days)"
|
|||
|
|
chart on the Active-stats page: average time each Jira workflow status is held,
|
|||
|
|
in the board's column order, min-2-tickets. Server endpoint
|
|||
|
|
`GET /api/analytics/jira-durations` (PM+) aggregates `jira.statusDurations` (ms)
|
|||
|
|
across tickets.
|
|||
|
|
- The extension's Jira collector now fetches each issue's **changelog**
|
|||
|
|
(`expand=changelog`) and computes **`statusDurations`** (time per status) +
|
|||
|
|
**`movements`** (`{at, who, from, to}`), sent inside the `jira` object.
|
|||
|
|
- Seed enrichment: `statusDurations` + the Jira **workflow column order** are ingested
|
|||
|
|
from the dump's `jira_board_state` (so the chart is populated before any live sync —
|
|||
|
|
73 seeded tickets carry durations). `JiraInfo` type extended with `statusDurations`/`movements`.
|
|||
|
|
|
|||
|
|
## [2.2.0] — 2026-08-27
|
|||
|
|
|
|||
|
|
### Added
|
|||
|
|
- **Jira sync in the extension** (per the sync ADR). One "Sync now" now refreshes
|
|||
|
|
**SNOW then Jira**: SNOW stays as the in-page same-origin session collector →
|
|||
|
|
`/api/sync`; Jira is pulled by a new service-worker collector via **direct REST**
|
|||
|
|
(`/rest/agile/1.0/board/{id}/issue`) authenticated with a **Jira API token** —
|
|||
|
|
Basic (email+token, Cloud) or **Bearer PAT** (Server/DC, e.g. `support.dataart.com`).
|
|||
|
|
Issue links are built as `<baseUrl>/browse/<KEY>` (custom Jira domains supported).
|
|||
|
|
- New extension options: Jira base URL, email, API token/PAT, and board id(s).
|
|||
|
|
- New server endpoint **`POST /api/sync/jira`** — **attach-only**: updates only the
|
|||
|
|
`jira` JSONB (merged) + `jira_key` on existing tickets, keyed by RITM number; never
|
|||
|
|
touches status/state/assignee/activity (avoids the ticket-upsert clobber). Token-authed.
|
|||
|
|
Jira RITM↔issue resolved via `customfield_26001` or a `RITM\d+` regex; Jira failures are
|
|||
|
|
best-effort and never fail a successful SNOW sync.
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
### Added
|
|||
|
|
- **Polished Excel export** — the board's Excel export is now filterable and readable:
|
|||
|
|
the **ticket Number is a clickable ServiceNow hyperlink** (a real OOXML hyperlink
|
|||
|
|
relationship — locale-proof, unlike a `HYPERLINK()` formula which breaks in `;`-locale
|
|||
|
|
Excel), **auto-filter** on every column, **zebra-striped rows**, taller rows + wider
|
|||
|
|
columns, a frozen indigo header, right-aligned numbers, `#,##0` cost, short dates, and
|
|||
|
|
a **dated filename** (`forge-active-tickets-YYYY-MM-DD.xlsx`). The standalone Link column
|
|||
|
|
was folded into the Number cell. (auto-filter + hyperlinks injected via `fflate`.)
|
|||
|
|
- App **favicon** (indigo snowflake) + `theme-color`.
|
|||
|
|
- A proper **Excel-brand button icon** (green tile) replacing the placeholder glyph.
|
|||
|
|
|
|||
|
|
### Changed
|
|||
|
|
- RITM number links out to ServiceNow in the ticket table + board cards (new tab).
|
|||
|
|
- Deploy: ported Husky's `push-to-nas.sh` + on-NAS `deploy.sh` (SSH key pinning,
|
|||
|
|
openrsync→tar fallback, Synology PATH/sudo, health poll, `forge-db` volume preserved
|
|||
|
|
on `--fresh`); macOS AppleDouble (`._*`) files excluded from sync/build/tests.
|
|||
|
|
|
|||
|
|
### Dependencies
|
|||
|
|
- Added `fflate` (xlsx auto-filter post-processing). `write-excel-file` pinned at 2.x
|
|||
|
|
(v4 drops the default export + still has no hyperlink support).
|
|||
|
|
|
|||
|
|
## [2.0.0] — 2026-08-27 · **FORGE 2.0**
|
|||
|
|
|
|||
|
|
The analytics release. FORGE grows from a ticket board into a full ServiceNow
|
|||
|
|
analytics portal ported from the *Let it Snow* Chrome extension, with role-based
|
|||
|
|
access, admin tooling, and the complete statistics suite.
|
|||
|
|
|
|||
|
|
### Added
|
|||
|
|
- **Full analytics data ingest.** The seed now loads the entire `analytics_data`
|
|||
|
|
master (954 ticket records) merged with the operational cache, plus SLA norms,
|
|||
|
|
FX rates, size thresholds, brand palette, and the colleague roster. New ticket
|
|||
|
|
columns (all additive): `currency_code, opened_by, opened_date, closed_date,
|
|||
|
|
to_do_at, in_uat_at, jira_key, ticket_year, size, po_number, invoiced`. A
|
|||
|
|
reproducible `dump-to-archives` → `reseed` pipeline regenerates seed data from a
|
|||
|
|
storage dump. `app_config` key/value table for norms/thresholds/palettes.
|
|||
|
|
- **Server analytics engine** (`server/analytics.ts`, `server/insights.ts`):
|
|||
|
|
`/api/analytics/overview`, `/api/analytics/sla`, `/api/insights`, `/api/config`.
|
|||
|
|
- **Overall statistics** page — year-overlay grouped bars (opened/closed/revenue,
|
|||
|
|
bars↔line + YoY%), a Cost↔Tickets toggle, a ticket-share donut (#/% + top-N),
|
|||
|
|
brand→market and requester→brand drill-downs, KPI tiles, lifetime histogram.
|
|||
|
|
- **Active statistics** page — by-status, age distribution, opened-per-month,
|
|||
|
|
median days-in-status, by-brand.
|
|||
|
|
- **PM KPIs — SLA** page — six PM×size heatmaps (TTFR, PM response, avg-close,
|
|||
|
|
OTD, assign, preview) scored against per-size norms, green/red vs target.
|
|||
|
|
- **PM Insights** page — problem/alert KPIs over the active backlog (unassigned,
|
|||
|
|
on-hold, WIP-stalled, customer-replied, awaiting, lifetime, inactive), Jira-breach
|
|||
|
|
and client-owed rules, waiting-PO with L1/L2/L3 escalation, revenue-at-risk, and
|
|||
|
|
a per-PM roll-up. (Rev-at-risk matches the initial app's own figure within rounding.)
|
|||
|
|
- **Board redesign** — 7-state kanban (Unassigned → Open/Assigned → On Hold → WIP →
|
|||
|
|
Customer replied → Awaiting) with the WIP→"Customer replied" pseudo-status, SLA
|
|||
|
|
subtitles, per-column counts, brand/market/PM filters, board/list toggle, and a
|
|||
|
|
ticket detail modal.
|
|||
|
|
- **Role-based access control** — four roles (Viewer, PM, Project Leadership, Admin)
|
|||
|
|
enforced server-side per route; the client hides nav/pages above a user's role.
|
|||
|
|
Only admins can create admins.
|
|||
|
|
- **Admin page** — user management (Project Leadership+) and API-token management
|
|||
|
|
(Admin only): create/list/revoke `fg_…` sync tokens from the UI.
|
|||
|
|
- **Filtered Excel export** — the board's ⭳ Excel button exports the currently
|
|||
|
|
filtered tickets as a styled `.xlsx` (branded indigo frozen header, per-column
|
|||
|
|
widths, right-aligned numbers, `#,##0` cost, short dates, grid borders).
|
|||
|
|
- **Finance ingest** — cost + PO + invoiced per ticket from the finance sheet,
|
|||
|
|
enriching active tickets with cost/size and powering waiting-PO.
|
|||
|
|
- Config bundle exposed via `/api/config` (brand colours, colleagues, norms, FX).
|
|||
|
|
|
|||
|
|
### Changed
|
|||
|
|
- All analytics pages are **full-width** (removed the 1280px cap).
|
|||
|
|
- `/api/tokens` is now gated by the **admin session role** instead of an `ADMIN_KEY`
|
|||
|
|
header. The `mint-token` CLI is unchanged.
|
|||
|
|
- Non-destructive sync upsert: a thin sync no longer nulls seeded-rich fields
|
|||
|
|
(`COALESCE`/`CASE` preserve brand/market/description/jira/activity/analytics).
|
|||
|
|
|
|||
|
|
### Fixed
|
|||
|
|
- `due_date` timezone off-by-one (emit local `YYYY-MM-DD`, not `toISOString()`).
|
|||
|
|
- Activity render-crash vector (ingest sanitizes activity entries; `parseDate` guards).
|
|||
|
|
- Seed "active wins" ordering on tickets present in both archives.
|
|||
|
|
- Ticket-share donut used a truncated distribution (center 375); now the full 966.
|
|||
|
|
|
|||
|
|
### Security
|
|||
|
|
- Read-API authentication (session, username+password, bcrypt, Postgres session store).
|
|||
|
|
- Least-privilege database role (`forge_app`, not a superuser) owns the app tables.
|
|||
|
|
- API tokens: `fg_<id>_<secret>`, only the SHA-256 stored, constant-time verify,
|
|||
|
|
revocable; `token_hash` never leaked by the list endpoint.
|
|||
|
|
- RBAC hardening (post-audit): only an admin can delete an admin; the **last admin**
|
|||
|
|
cannot be deleted (lockout guard); deleting a user **purges their sessions**; the
|
|||
|
|
session id is **regenerated on login** (anti session-fixation); delete of a missing
|
|||
|
|
user returns 404.
|
|||
|
|
|
|||
|
|
## [1.0.0] — 2026-08-27
|
|||
|
|
|
|||
|
|
Initial full-stack scaffold, ported from the *Let it Snow* Chrome extension onto the
|
|||
|
|
*Husky repo list* template.
|
|||
|
|
|
|||
|
|
### Added
|
|||
|
|
- Vite + React 18 + TypeScript client (SCSS modules, react-router) with a Sidebar/
|
|||
|
|
TopBar layout; Active board, Closed, and basic Statistics pages.
|
|||
|
|
- Express + TypeScript server on Postgres (`forge`); self-bootstrapping schema
|
|||
|
|
(`initDB`) that seeds bundled ticket archives on first boot.
|
|||
|
|
- REST API: `/api/tickets`, `/api/tickets/:number`, `/api/stats`, `/healthz`, and a
|
|||
|
|
token-authed `/api/sync` ingest.
|
|||
|
|
- Chrome sync extension ("FORGE Snow Sync") — reads active RITM tickets from the
|
|||
|
|
ServiceNow Table API and pushes them to the portal with a bearer token.
|
|||
|
|
- Multi-stage Dockerfile + docker-compose (bundled Postgres) targeting
|
|||
|
|
`forge.mycloud.dp.ua`; README + SETUP docs.
|
|||
|
|
|
|||
|
|
[2.0.0]: #200--2026-08-27--forge-20
|
|||
|
|
[1.0.0]: #100--2026-08-27
|