@burdenoff/microfe-planmagnet 2026.925.16 → 2026.925.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"ReleaseGatesCard.js","names":[],"sources":["../../../src/planmagnet/releases/ReleaseGatesCard.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { Button, Card, CardContent, CardHeader, CardTitle, Input } from '@burdenoff/fe-libs/ui';\nimport { CheckCircle2, CircleDashed, ShieldAlert, XCircle } from 'lucide-react';\nimport { PersonName } from '../people/People';\nimport { throwOnError } from '../utils/mutationResult';\n\n/**\n * Quality gates holding a release (P1.4). The service refuses RELEASED while\n * any gate here is not PASSED or WAIVED; this card shows which gates hold the\n * release, and lets a reviewer add, pass, fail or waive them. There was no\n * quality-gate UI anywhere before this.\n */\n\nconst GATES = gql`\n query ReleaseGates($id: ID!) {\n planmagnetRelease(id: $id) {\n id\n qualityGates {\n id\n name\n status\n reviewerId\n reviewedAt\n }\n }\n }\n`;\nconst CREATE_GATE = gql`\n mutation ReleaseGateCreate($input: PlanmagnetCreateQualityGateInput!) {\n planmagnetCreateQualityGate(input: $input) {\n id\n }\n }\n`;\nconst REVIEW_GATE = gql`\n mutation ReleaseGateReview($id: ID!, $input: PlanmagnetReviewQualityGateInput!) {\n planmagnetReviewQualityGate(id: $id, input: $input) {\n id\n status\n }\n }\n`;\n\ninterface Gate {\n id: string;\n name: string;\n status: string;\n reviewerId: string | null;\n reviewedAt: string | null;\n}\n\nconst CLEARING = new Set(['PASSED', 'WAIVED']);\n\nfunction StatusMark({ status }: { status: string }) {\n // Status carries an icon AND a label, never colour alone.\n if (status === 'PASSED')\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-status-success-text\">\n <CheckCircle2 className=\"h-3.5 w-3.5\" /> Passed\n </span>\n );\n if (status === 'WAIVED')\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-muted-foreground\">\n <CheckCircle2 className=\"h-3.5 w-3.5\" /> Waived\n </span>\n );\n if (status === 'FAILED')\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-status-error-text\">\n <XCircle className=\"h-3.5 w-3.5\" /> Failed\n </span>\n );\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-status-warning-text\">\n <CircleDashed className=\"h-3.5 w-3.5\" /> {status === 'IN_REVIEW' ? 'In review' : 'Pending'}\n </span>\n );\n}\n\nexport function ReleaseGatesCard({\n releaseId,\n projectId,\n releaseStatus,\n}: {\n releaseId: string;\n projectId: string | null;\n releaseStatus: string;\n}) {\n const [name, setName] = useState('');\n const [error, setError] = useState<string | null>(null);\n const { data } = useQuery<{ planmagnetRelease: { qualityGates: Gate[] } | null }>(GATES, {\n variables: { id: releaseId },\n });\n const refetch = { refetchQueries: ['ReleaseGates', 'GetRelease'], awaitRefetchQueries: true };\n const [create, createState] = useMutation(CREATE_GATE, refetch);\n const [review, reviewState] = useMutation(REVIEW_GATE, refetch);\n\n const gates = data?.planmagnetRelease?.qualityGates ?? [];\n const open = gates.filter((g) => !CLEARING.has(g.status));\n const shipped = releaseStatus === 'RELEASED';\n\n const run = async (\n action: () => Promise<{ error?: unknown }>,\n fallback: string,\n after?: () => void\n ) => {\n setError(null);\n try {\n await throwOnError(action());\n after?.();\n } catch (e) {\n setError(e instanceof Error ? e.message : fallback);\n }\n };\n\n return (\n <Card data-testid=\"release-gates\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <ShieldAlert className=\"h-4 w-4\" /> Quality Gates\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {error}\n </p>\n )}\n {!shipped && open.length > 0 && (\n <p className=\"text-sm text-status-warning-text\" data-testid=\"release-held\">\n This release cannot be released until {open.length} gate{open.length === 1 ? '' : 's'}{' '}\n {open.length === 1 ? 'is' : 'are'} passed or waived:{' '}\n {open.map((g) => g.name).join(', ')}.\n </p>\n )}\n {gates.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">No quality gates hold this release.</p>\n )}\n <ul className=\"space-y-2\">\n {gates.map((gate) => (\n <li\n key={gate.id}\n className=\"rounded-md border p-2 space-y-1\"\n data-testid=\"release-gate\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium\">{gate.name}</span>\n <StatusMark status={gate.status} />\n </div>\n {gate.reviewerId && (\n <p className=\"text-xs text-muted-foreground\">\n Reviewed by <PersonName id={gate.reviewerId} />\n {gate.reviewedAt ? ` · ${new Date(gate.reviewedAt).toLocaleString()}` : ''}\n </p>\n )}\n <div className=\"flex gap-1\">\n {(['PASSED', 'FAILED', 'WAIVED'] as const).map((status) => (\n <Button\n key={status}\n size=\"sm\"\n variant=\"outline\"\n className=\"h-7 text-xs\"\n disabled={reviewState.loading || gate.status === status}\n onClick={() =>\n run(\n () => review({ variables: { id: gate.id, input: { status } } }),\n 'The gate could not be reviewed.'\n )\n }\n >\n {status === 'PASSED' ? 'Pass' : status === 'FAILED' ? 'Fail' : 'Waive'}\n </Button>\n ))}\n </div>\n </li>\n ))}\n </ul>\n {projectId ? (\n <div className=\"flex gap-2 border-t pt-3\">\n <Input\n aria-label=\"New gate name\"\n placeholder=\"Add a gate, e.g. Security review\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n />\n <Button\n size=\"sm\"\n disabled={!name.trim() || createState.loading}\n data-testid=\"release-gate-add\"\n onClick={() =>\n run(\n () =>\n create({\n variables: {\n input: { projectId, releaseId, name: name.trim(), criteria: {} },\n },\n }),\n 'The gate could not be added.',\n () => setName('')\n )\n }\n >\n Add gate\n </Button>\n </div>\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n This release has no project, so gates cannot be added here.\n </p>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ReleaseGatesCard.js","names":[],"sources":["../../../src/planmagnet/releases/ReleaseGatesCard.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { Button, Card, CardContent, CardHeader, CardTitle, Input } from '@burdenoff/fe-libs/ui';\nimport { CheckCircle2, CircleDashed, ShieldAlert, XCircle } from 'lucide-react';\nimport { PersonName } from '../people/People';\nimport { throwOnError } from '../utils/mutationResult';\n\n/**\n * Quality gates holding a release (P1.4). The service refuses RELEASED while\n * any gate here is not PASSED or WAIVED; this card shows which gates hold the\n * release, and lets a reviewer add, pass, fail or waive them. There was no\n * quality-gate UI anywhere before this.\n */\n\nexport const RELEASE_GATES = gql`\n query ReleaseGates($id: ID!) {\n planmagnetRelease(id: $id) {\n id\n qualityGates {\n id\n name\n status\n reviewerId\n reviewedAt\n }\n }\n }\n`;\nconst CREATE_GATE = gql`\n mutation ReleaseGateCreate($input: PlanmagnetCreateQualityGateInput!) {\n planmagnetCreateQualityGate(input: $input) {\n id\n }\n }\n`;\nconst REVIEW_GATE = gql`\n mutation ReleaseGateReview($id: ID!, $input: PlanmagnetReviewQualityGateInput!) {\n planmagnetReviewQualityGate(id: $id, input: $input) {\n id\n status\n }\n }\n`;\n\ninterface Gate {\n id: string;\n name: string;\n status: string;\n reviewerId: string | null;\n reviewedAt: string | null;\n}\n\nconst CLEARING = new Set(['PASSED', 'WAIVED']);\n\nfunction StatusMark({ status }: { status: string }) {\n // Status carries an icon AND a label, never colour alone.\n if (status === 'PASSED')\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-status-success-text\">\n <CheckCircle2 className=\"h-3.5 w-3.5\" /> Passed\n </span>\n );\n if (status === 'WAIVED')\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-muted-foreground\">\n <CheckCircle2 className=\"h-3.5 w-3.5\" /> Waived\n </span>\n );\n if (status === 'FAILED')\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-status-error-text\">\n <XCircle className=\"h-3.5 w-3.5\" /> Failed\n </span>\n );\n return (\n <span className=\"inline-flex items-center gap-1 text-xs text-status-warning-text\">\n <CircleDashed className=\"h-3.5 w-3.5\" /> {status === 'IN_REVIEW' ? 'In review' : 'Pending'}\n </span>\n );\n}\n\nexport function ReleaseGatesCard({\n releaseId,\n projectId,\n releaseStatus,\n}: {\n releaseId: string;\n projectId: string | null;\n releaseStatus: string;\n}) {\n const [name, setName] = useState('');\n const [error, setError] = useState<string | null>(null);\n const { data } = useQuery<{ planmagnetRelease: { qualityGates: Gate[] } | null }>(RELEASE_GATES, {\n variables: { id: releaseId },\n });\n const refetch = { refetchQueries: ['ReleaseGates', 'GetRelease'], awaitRefetchQueries: true };\n const [create, createState] = useMutation(CREATE_GATE, refetch);\n const [review, reviewState] = useMutation(REVIEW_GATE, refetch);\n\n const gates = data?.planmagnetRelease?.qualityGates ?? [];\n const open = gates.filter((g) => !CLEARING.has(g.status));\n const shipped = releaseStatus === 'RELEASED';\n\n const run = async (\n action: () => Promise<{ error?: unknown }>,\n fallback: string,\n after?: () => void\n ) => {\n setError(null);\n try {\n await throwOnError(action());\n after?.();\n } catch (e) {\n setError(e instanceof Error ? e.message : fallback);\n }\n };\n\n return (\n <Card data-testid=\"release-gates\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <ShieldAlert className=\"h-4 w-4\" /> Quality Gates\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {error && (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {error}\n </p>\n )}\n {!shipped && open.length > 0 && (\n <p className=\"text-sm text-status-warning-text\" data-testid=\"release-held\">\n This release cannot be released until {open.length} gate{open.length === 1 ? '' : 's'}{' '}\n {open.length === 1 ? 'is' : 'are'} passed or waived:{' '}\n {open.map((g) => g.name).join(', ')}.\n </p>\n )}\n {gates.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">No quality gates hold this release.</p>\n )}\n <ul className=\"space-y-2\">\n {gates.map((gate) => (\n <li\n key={gate.id}\n className=\"rounded-md border p-2 space-y-1\"\n data-testid=\"release-gate\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium\">{gate.name}</span>\n <StatusMark status={gate.status} />\n </div>\n {gate.reviewerId && (\n <p className=\"text-xs text-muted-foreground\">\n Reviewed by <PersonName id={gate.reviewerId} />\n {gate.reviewedAt ? ` · ${new Date(gate.reviewedAt).toLocaleString()}` : ''}\n </p>\n )}\n <div className=\"flex gap-1\">\n {(['PASSED', 'FAILED', 'WAIVED'] as const).map((status) => (\n <Button\n key={status}\n size=\"sm\"\n variant=\"outline\"\n className=\"h-7 text-xs\"\n disabled={reviewState.loading || gate.status === status}\n onClick={() =>\n run(\n () => review({ variables: { id: gate.id, input: { status } } }),\n 'The gate could not be reviewed.'\n )\n }\n >\n {status === 'PASSED' ? 'Pass' : status === 'FAILED' ? 'Fail' : 'Waive'}\n </Button>\n ))}\n </div>\n </li>\n ))}\n </ul>\n {projectId ? (\n <div className=\"flex gap-2 border-t pt-3\">\n <Input\n aria-label=\"New gate name\"\n placeholder=\"Add a gate, e.g. Security review\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n />\n <Button\n size=\"sm\"\n disabled={!name.trim() || createState.loading}\n data-testid=\"release-gate-add\"\n onClick={() =>\n run(\n () =>\n create({\n variables: {\n input: { projectId, releaseId, name: name.trim(), criteria: {} },\n },\n }),\n 'The gate could not be added.',\n () => setName('')\n )\n }\n >\n Add gate\n </Button>\n </div>\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n This release has no project, so gates cannot be added here.\n </p>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export declare function ReleaseReadinessCard({ releaseId, projectId, releaseStatus, plannedDate, }: {
2
+ releaseId: string;
3
+ projectId: string | null;
4
+ releaseStatus: string;
5
+ plannedDate: string | null;
6
+ }): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=ReleaseReadinessCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReleaseReadinessCard.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/releases/ReleaseReadinessCard.tsx"],"names":[],"mappings":"AA6MA,wBAAgB,oBAAoB,CAAC,EACnC,SAAS,EACT,SAAS,EACT,aAAa,EACb,WAAW,GACZ,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,2CAwRA"}
@@ -0,0 +1,390 @@
1
+ import { CombinedGraphQLErrors as e } from "../../node_modules/@apollo/client/errors/CombinedGraphQLErrors.js";
2
+ import { usePlanMagnet as t, usePlanMagnetNavigate as n } from "../providers/PlanMagnetProvider.js";
3
+ import { RELEASE_GATES as r } from "./ReleaseGatesCard.js";
4
+ import { useState as i } from "react";
5
+ import { AlertTriangle as a, CheckCircle2 as o, Clock as s, Link2 as c, ShieldAlert as l, Users as u, Wallet as d } from "lucide-react";
6
+ import { gql as f } from "@apollo/client";
7
+ import { useQuery as p } from "@apollo/client/react";
8
+ import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
9
+ import { Card as _, CardContent as v, CardHeader as y, CardTitle as b } from "@burdenoff/fe-libs/ui";
10
+ //#region src/planmagnet/releases/ReleaseReadinessCard.tsx
11
+ var x = f`
12
+ query ReleaseReadiness($releaseId: ID!) {
13
+ planmagnetReleaseReadiness(releaseId: $releaseId) {
14
+ scope
15
+ totalItems
16
+ doneItems
17
+ openWorkItemIds
18
+ blockers {
19
+ workItem {
20
+ id
21
+ key
22
+ title
23
+ status
24
+ }
25
+ waitingOn {
26
+ id
27
+ key
28
+ title
29
+ status
30
+ projectId
31
+ }
32
+ }
33
+ }
34
+ }
35
+ `, S = f`
36
+ query ReleaseBudgets($workspaceId: ID!, $projectId: String!) {
37
+ planmagnetBudgets(
38
+ workspaceId: $workspaceId
39
+ filter: { projectId: $projectId }
40
+ pagination: { first: 50 }
41
+ ) {
42
+ items {
43
+ id
44
+ name
45
+ status
46
+ currency
47
+ plannedAmount
48
+ spentAmount
49
+ remainingAmount
50
+ approvedAt
51
+ }
52
+ }
53
+ }
54
+ `, C = f`
55
+ query ReleaseRisks($workspaceId: ID!, $projectId: String!) {
56
+ planmagnetRisks(
57
+ workspaceId: $workspaceId
58
+ filter: { projectId: $projectId }
59
+ pagination: { first: 100 }
60
+ ) {
61
+ items {
62
+ id
63
+ title
64
+ severity
65
+ status
66
+ riskScore
67
+ }
68
+ }
69
+ }
70
+ `, w = f`
71
+ query ReleaseCustomerRequests($workspaceId: ID!, $projectId: String!) {
72
+ planmagnetFeatureRequests(
73
+ workspaceId: $workspaceId
74
+ filter: { projectId: $projectId }
75
+ pagination: { first: 100 }
76
+ ) {
77
+ items {
78
+ id
79
+ title
80
+ status
81
+ voteCount
82
+ workItemId
83
+ }
84
+ }
85
+ }
86
+ `, T = new Set([
87
+ "RESOLVED",
88
+ "ACCEPTED",
89
+ "CLOSED"
90
+ ]), E = new Set([
91
+ "COMPLETED",
92
+ "DECLINED",
93
+ "DUPLICATE"
94
+ ]), D = 1440 * 60 * 1e3;
95
+ function O(t) {
96
+ return e.is(t) && t.errors.some((e) => e.extensions?.code === "FORBIDDEN");
97
+ }
98
+ function k({ icon: e, title: t, testId: n, loading: r, error: i, children: a }) {
99
+ let o = a;
100
+ return i ? o = /* @__PURE__ */ h("p", {
101
+ className: "text-sm text-muted-foreground",
102
+ children: O(i) ? "Your role cannot see this." : `Could not be loaded: ${i instanceof Error ? i.message : "unknown error"}`
103
+ }) : r && (o = /* @__PURE__ */ h("p", {
104
+ className: "text-sm text-muted-foreground",
105
+ children: "Loading…"
106
+ })), /* @__PURE__ */ g("section", {
107
+ className: "space-y-1.5",
108
+ "data-testid": n,
109
+ children: [/* @__PURE__ */ g("h3", {
110
+ className: "flex items-center gap-2 text-sm font-medium",
111
+ children: [e, t]
112
+ }), o]
113
+ });
114
+ }
115
+ var A = (e, t) => {
116
+ try {
117
+ return new Intl.NumberFormat(void 0, {
118
+ style: "currency",
119
+ currency: t
120
+ }).format(e);
121
+ } catch {
122
+ return `${e.toFixed(2)} ${t}`;
123
+ }
124
+ };
125
+ function j({ releaseId: e, projectId: f, releaseStatus: O, plannedDate: j }) {
126
+ let { workspaceId: M } = t(), [N] = i(() => Date.now()), P = n(), F = {
127
+ variables: {
128
+ workspaceId: M,
129
+ projectId: f
130
+ },
131
+ skip: !M || !f
132
+ }, I = p(x, { variables: { releaseId: e } }), L = p(r, { variables: { id: e } }), R = p(S, F), z = p(C, F), B = p(w, F), V = O === "RELEASED", H = I.data?.planmagnetReleaseReadiness, U = (L.data?.planmagnetRelease?.qualityGates ?? []).filter((e) => e.status !== "PASSED" && e.status !== "WAIVED"), W = R.data?.planmagnetBudgets.items ?? [], G = (z.data?.planmagnetRisks.items ?? []).filter((e) => !T.has(e.status)).sort((e, t) => t.riskScore - e.riskScore), K = new Set(H?.openWorkItemIds ?? []), q = new Set((H?.blockers ?? []).map((e) => e.workItem.id)), J = (B.data?.planmagnetFeatureRequests.items ?? []).filter((e) => e.workItemId && K.has(e.workItemId) && !E.has(e.status)), Y = j && !V ? Math.floor((N - new Date(j).getTime()) / D) : null, X = [];
133
+ Y !== null && Y > 0 && X.push(`${Y} day${Y === 1 ? "" : "s"} past its planned date`), H && H.blockers.length > 0 && X.push(`${H.blockers.length} blocked item${H.blockers.length === 1 ? "" : "s"}`), H && H.openWorkItemIds.length > 0 && X.push(`${H.openWorkItemIds.length} unfinished item${H.openWorkItemIds.length === 1 ? "" : "s"}`), U.length > 0 && X.push(`${U.length} gate${U.length === 1 ? "" : "s"} not passed`), W.filter((e) => e.remainingAmount < 0 || e.status === "EXCEEDED").length > 0 && X.push("over budget");
134
+ let Z = !I.loading && !L.loading;
135
+ return /* @__PURE__ */ g(_, {
136
+ "data-testid": "release-readiness",
137
+ children: [/* @__PURE__ */ h(y, { children: /* @__PURE__ */ h(b, { children: "What stands between this release and shipping" }) }), /* @__PURE__ */ g(v, {
138
+ className: "space-y-5",
139
+ children: [
140
+ V ? /* @__PURE__ */ g("p", {
141
+ className: "flex items-center gap-2 text-sm text-status-success-text",
142
+ children: [/* @__PURE__ */ h(o, { className: "h-4 w-4" }), " Released."]
143
+ }) : Z && X.length === 0 && H && H.scope !== "NONE" ? /* @__PURE__ */ g("p", {
144
+ className: "flex items-center gap-2 text-sm text-status-success-text",
145
+ "data-testid": "readiness-headline",
146
+ children: [/* @__PURE__ */ h(o, { className: "h-4 w-4" }), " Nothing is holding this release."]
147
+ }) : Z && X.length > 0 ? /* @__PURE__ */ g("p", {
148
+ className: "flex items-center gap-2 text-sm text-status-warning-text",
149
+ "data-testid": "readiness-headline",
150
+ children: [
151
+ /* @__PURE__ */ h(a, { className: "h-4 w-4 shrink-0" }),
152
+ " Held by: ",
153
+ X.join(", "),
154
+ "."
155
+ ]
156
+ }) : null,
157
+ /* @__PURE__ */ h(k, {
158
+ icon: /* @__PURE__ */ h(c, { className: "h-4 w-4" }),
159
+ title: "Work and blockers",
160
+ testId: "readiness-work",
161
+ loading: I.loading && !I.data,
162
+ error: I.error,
163
+ children: H && /* @__PURE__ */ g("div", {
164
+ className: "space-y-2",
165
+ children: [
166
+ /* @__PURE__ */ h("p", {
167
+ className: "text-sm text-muted-foreground",
168
+ children: H.scope === "NONE" ? "This release has no work items and no project, so there is nothing to track." : H.scope === "PROJECTS" ? `No work items are assigned to this release, so everything open in its project counts: ${H.totalItems} open.` : `${H.doneItems} of ${H.totalItems} work items done.`
169
+ }),
170
+ H.blockers.length === 0 && H.scope !== "NONE" && /* @__PURE__ */ h("p", {
171
+ className: "text-sm text-muted-foreground",
172
+ children: "No item is waiting on unfinished work."
173
+ }),
174
+ /* @__PURE__ */ h("ul", {
175
+ className: "space-y-1.5",
176
+ children: H.blockers.map((e) => /* @__PURE__ */ g("li", {
177
+ className: "rounded-md border p-2 text-sm",
178
+ "data-testid": "readiness-blocker",
179
+ children: [
180
+ /* @__PURE__ */ g("button", {
181
+ type: "button",
182
+ className: "font-medium hover:underline text-left",
183
+ onClick: () => P(`/tasks/task-detail-page?taskId=${encodeURIComponent(e.workItem.id)}`),
184
+ children: [
185
+ e.workItem.key,
186
+ " ",
187
+ e.workItem.title
188
+ ]
189
+ }),
190
+ /* @__PURE__ */ h("span", {
191
+ className: "text-muted-foreground",
192
+ children: " waits on "
193
+ }),
194
+ e.waitingOn.map((e, t) => /* @__PURE__ */ g("span", { children: [
195
+ t > 0 && ", ",
196
+ /* @__PURE__ */ g("button", {
197
+ type: "button",
198
+ className: "hover:underline",
199
+ onClick: () => P(`/tasks/task-detail-page?taskId=${encodeURIComponent(e.id)}`),
200
+ children: [
201
+ e.key,
202
+ " ",
203
+ e.title
204
+ ]
205
+ }),
206
+ " ",
207
+ /* @__PURE__ */ g("span", {
208
+ className: "text-xs text-muted-foreground",
209
+ children: [
210
+ "(",
211
+ e.status.toLowerCase().replace("_", " "),
212
+ f && e.projectId && e.projectId !== f ? ", another project" : "",
213
+ ")"
214
+ ]
215
+ })
216
+ ] }, e.id))
217
+ ]
218
+ }, e.workItem.id))
219
+ })
220
+ ]
221
+ })
222
+ }),
223
+ /* @__PURE__ */ h(k, {
224
+ icon: /* @__PURE__ */ h(l, { className: "h-4 w-4" }),
225
+ title: "Approvals",
226
+ testId: "readiness-approvals",
227
+ loading: L.loading && !L.data,
228
+ error: L.error,
229
+ children: /* @__PURE__ */ h("p", {
230
+ className: "text-sm text-muted-foreground",
231
+ children: U.length === 0 ? "No quality gate is waiting for a decision." : `Waiting on: ${U.map((e) => `${e.name} (${e.status.toLowerCase().replace("_", " ")})`).join(", ")}.`
232
+ })
233
+ }),
234
+ f ? /* @__PURE__ */ g(m, { children: [
235
+ /* @__PURE__ */ h(k, {
236
+ icon: /* @__PURE__ */ h(d, { className: "h-4 w-4" }),
237
+ title: "Budget",
238
+ testId: "readiness-budget",
239
+ loading: R.loading && !R.data,
240
+ error: R.error,
241
+ children: W.length === 0 ? /* @__PURE__ */ h("p", {
242
+ className: "text-sm text-muted-foreground",
243
+ children: "This release's project has no budget."
244
+ }) : /* @__PURE__ */ h("ul", {
245
+ className: "space-y-1",
246
+ children: W.map((e) => {
247
+ let t = e.remainingAmount < 0;
248
+ return /* @__PURE__ */ g("li", {
249
+ className: "text-sm",
250
+ "data-testid": "readiness-budget-row",
251
+ children: [
252
+ /* @__PURE__ */ h("span", {
253
+ className: "font-medium",
254
+ children: e.name
255
+ }),
256
+ ":",
257
+ " ",
258
+ A(e.spentAmount, e.currency),
259
+ " of ",
260
+ A(e.plannedAmount, e.currency),
261
+ " ",
262
+ "spent",
263
+ t ? /* @__PURE__ */ g("span", {
264
+ className: "text-status-error-text",
265
+ children: [
266
+ " ",
267
+ "— over by ",
268
+ A(-e.remainingAmount, e.currency)
269
+ ]
270
+ }) : /* @__PURE__ */ g("span", {
271
+ className: "text-muted-foreground",
272
+ children: [
273
+ " ",
274
+ "— ",
275
+ A(e.remainingAmount, e.currency),
276
+ " left"
277
+ ]
278
+ }),
279
+ !e.approvedAt && /* @__PURE__ */ h("span", {
280
+ className: "text-status-warning-text",
281
+ children: " · not approved"
282
+ })
283
+ ]
284
+ }, e.id);
285
+ })
286
+ })
287
+ }),
288
+ /* @__PURE__ */ h(k, {
289
+ icon: /* @__PURE__ */ h(a, { className: "h-4 w-4" }),
290
+ title: "Open risks",
291
+ testId: "readiness-risks",
292
+ loading: z.loading && !z.data,
293
+ error: z.error,
294
+ children: G.length === 0 ? /* @__PURE__ */ h("p", {
295
+ className: "text-sm text-muted-foreground",
296
+ children: "No open risks on this release's project."
297
+ }) : /* @__PURE__ */ g("ul", {
298
+ className: "space-y-1",
299
+ children: [G.slice(0, 5).map((e) => /* @__PURE__ */ g("li", {
300
+ className: "text-sm",
301
+ children: [
302
+ /* @__PURE__ */ h("span", {
303
+ className: "font-medium",
304
+ children: e.title
305
+ }),
306
+ " ",
307
+ /* @__PURE__ */ g("span", {
308
+ className: "text-xs text-muted-foreground",
309
+ children: [
310
+ "(",
311
+ e.severity.toLowerCase(),
312
+ ", score ",
313
+ e.riskScore,
314
+ ",",
315
+ " ",
316
+ e.status.toLowerCase(),
317
+ ")"
318
+ ]
319
+ })
320
+ ]
321
+ }, e.id)), G.length > 5 && /* @__PURE__ */ g("li", {
322
+ className: "text-xs text-muted-foreground",
323
+ children: [
324
+ "and ",
325
+ G.length - 5,
326
+ " more"
327
+ ]
328
+ })]
329
+ })
330
+ }),
331
+ /* @__PURE__ */ h(k, {
332
+ icon: /* @__PURE__ */ h(u, { className: "h-4 w-4" }),
333
+ title: "Customer requests riding on unfinished work",
334
+ testId: "readiness-requests",
335
+ loading: B.loading && !B.data,
336
+ error: B.error,
337
+ children: J.length === 0 ? /* @__PURE__ */ h("p", {
338
+ className: "text-sm text-muted-foreground",
339
+ children: "No open customer request is tied to unfinished work in this release."
340
+ }) : /* @__PURE__ */ h("ul", {
341
+ className: "space-y-1",
342
+ children: J.map((e) => /* @__PURE__ */ g("li", {
343
+ className: "text-sm",
344
+ "data-testid": "readiness-request",
345
+ children: [
346
+ /* @__PURE__ */ h("span", {
347
+ className: "font-medium",
348
+ children: e.title
349
+ }),
350
+ " ",
351
+ /* @__PURE__ */ g("span", {
352
+ className: "text-xs text-muted-foreground",
353
+ children: [
354
+ "(",
355
+ e.voteCount,
356
+ " vote",
357
+ e.voteCount === 1 ? "" : "s",
358
+ ")"
359
+ ]
360
+ }),
361
+ e.workItemId && q.has(e.workItemId) && /* @__PURE__ */ h("span", {
362
+ className: "text-status-error-text",
363
+ children: " — its work is blocked"
364
+ })
365
+ ]
366
+ }, e.id))
367
+ })
368
+ })
369
+ ] }) : /* @__PURE__ */ h("p", {
370
+ className: "text-sm text-muted-foreground",
371
+ children: "This release has no project, so budget, risks and customer requests cannot be shown."
372
+ }),
373
+ j && Y !== null && /* @__PURE__ */ g("p", {
374
+ className: "flex items-center gap-2 text-xs text-muted-foreground",
375
+ children: [
376
+ /* @__PURE__ */ h(s, { className: "h-3.5 w-3.5" }),
377
+ "Planned for ",
378
+ new Date(j).toLocaleDateString(),
379
+ Y > 0 ? ` (${Y} day${Y === 1 ? "" : "s"} ago)` : "",
380
+ "."
381
+ ]
382
+ })
383
+ ]
384
+ })]
385
+ });
386
+ }
387
+ //#endregion
388
+ export { j as ReleaseReadinessCard };
389
+
390
+ //# sourceMappingURL=ReleaseReadinessCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReleaseReadinessCard.js","names":[],"sources":["../../../src/planmagnet/releases/ReleaseReadinessCard.tsx"],"sourcesContent":["import { useState, type ReactNode } from 'react';\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { CombinedGraphQLErrors } from '@apollo/client/errors';\nimport { Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport {\n AlertTriangle,\n CheckCircle2,\n Link2,\n Wallet,\n Users,\n ShieldAlert,\n Clock,\n} from 'lucide-react';\nimport { usePlanMagnet, usePlanMagnetNavigate } from '../providers/PlanMagnetProvider';\nimport { RELEASE_GATES } from './ReleaseGatesCard';\n\n/**\n * Release overview (P1.4): what stands between this release and shipping, in\n * one place: unfinished and dependency-blocked work, gates not yet passed,\n * the budget position of its project, its open risks, and the customer\n * requests that ride on its unfinished work.\n *\n * Each part is read through its OWN gated query. Budgets, risks and feature\n * requests are separate RBAC resources (a product manager can read releases\n * but not budgets), so a part the viewer may not see says so, instead of the\n * release leaking it or the whole card failing.\n */\n\nconst READINESS = gql`\n query ReleaseReadiness($releaseId: ID!) {\n planmagnetReleaseReadiness(releaseId: $releaseId) {\n scope\n totalItems\n doneItems\n openWorkItemIds\n blockers {\n workItem {\n id\n key\n title\n status\n }\n waitingOn {\n id\n key\n title\n status\n projectId\n }\n }\n }\n }\n`;\nconst BUDGETS = gql`\n query ReleaseBudgets($workspaceId: ID!, $projectId: String!) {\n planmagnetBudgets(\n workspaceId: $workspaceId\n filter: { projectId: $projectId }\n pagination: { first: 50 }\n ) {\n items {\n id\n name\n status\n currency\n plannedAmount\n spentAmount\n remainingAmount\n approvedAt\n }\n }\n }\n`;\nconst RISKS = gql`\n query ReleaseRisks($workspaceId: ID!, $projectId: String!) {\n planmagnetRisks(\n workspaceId: $workspaceId\n filter: { projectId: $projectId }\n pagination: { first: 100 }\n ) {\n items {\n id\n title\n severity\n status\n riskScore\n }\n }\n }\n`;\nconst REQUESTS = gql`\n query ReleaseCustomerRequests($workspaceId: ID!, $projectId: String!) {\n planmagnetFeatureRequests(\n workspaceId: $workspaceId\n filter: { projectId: $projectId }\n pagination: { first: 100 }\n ) {\n items {\n id\n title\n status\n voteCount\n workItemId\n }\n }\n }\n`;\n\ninterface ItemRef {\n id: string;\n key: string;\n title: string;\n status: string;\n projectId?: string;\n}\ninterface Readiness {\n scope: 'WORK_ITEMS' | 'PROJECTS' | 'NONE';\n totalItems: number;\n doneItems: number;\n openWorkItemIds: string[];\n blockers: { workItem: ItemRef; waitingOn: ItemRef[] }[];\n}\ninterface Budget {\n id: string;\n name: string;\n status: string;\n currency: string;\n plannedAmount: number;\n spentAmount: number;\n remainingAmount: number;\n approvedAt: string | null;\n}\ninterface Risk {\n id: string;\n title: string;\n severity: string;\n status: string;\n riskScore: number;\n}\ninterface Request {\n id: string;\n title: string;\n status: string;\n voteCount: number;\n workItemId: string | null;\n}\n\nconst CLOSED_RISK = new Set(['RESOLVED', 'ACCEPTED', 'CLOSED']);\nconst CLOSED_REQUEST = new Set(['COMPLETED', 'DECLINED', 'DUPLICATE']);\nconst DAY = 24 * 60 * 60 * 1000;\n\nfunction denied(error: unknown): boolean {\n return (\n CombinedGraphQLErrors.is(error) && error.errors.some((e) => e.extensions?.code === 'FORBIDDEN')\n );\n}\n\n/** One part of the overview: its own title, and its own loading/denied/error state. */\nfunction Part({\n icon,\n title,\n testId,\n loading,\n error,\n children,\n}: {\n icon: ReactNode;\n title: string;\n testId: string;\n loading: boolean;\n error: unknown;\n children: ReactNode;\n}) {\n let body = children;\n if (error) {\n body = (\n <p className=\"text-sm text-muted-foreground\">\n {denied(error)\n ? 'Your role cannot see this.'\n : `Could not be loaded: ${error instanceof Error ? error.message : 'unknown error'}`}\n </p>\n );\n } else if (loading) {\n body = <p className=\"text-sm text-muted-foreground\">Loading…</p>;\n }\n return (\n <section className=\"space-y-1.5\" data-testid={testId}>\n <h3 className=\"flex items-center gap-2 text-sm font-medium\">\n {icon}\n {title}\n </h3>\n {body}\n </section>\n );\n}\n\nconst money = (amount: number, currency: string) => {\n try {\n return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(amount);\n } catch {\n return `${amount.toFixed(2)} ${currency}`;\n }\n};\n\nexport function ReleaseReadinessCard({\n releaseId,\n projectId,\n releaseStatus,\n plannedDate,\n}: {\n releaseId: string;\n projectId: string | null;\n releaseStatus: string;\n plannedDate: string | null;\n}) {\n const { workspaceId } = usePlanMagnet();\n // Read once: lateness is a property of the page view, not of each render.\n const [now] = useState(() => Date.now());\n const navigate = usePlanMagnetNavigate();\n const byProject = { variables: { workspaceId, projectId }, skip: !workspaceId || !projectId };\n\n const readiness = useQuery<{ planmagnetReleaseReadiness: Readiness }>(READINESS, {\n variables: { releaseId },\n });\n const gates = useQuery<{\n planmagnetRelease: { qualityGates: { id: string; name: string; status: string }[] } | null;\n }>(RELEASE_GATES, { variables: { id: releaseId } });\n const budgets = useQuery<{ planmagnetBudgets: { items: Budget[] } }>(BUDGETS, byProject);\n const risks = useQuery<{ planmagnetRisks: { items: Risk[] } }>(RISKS, byProject);\n const requests = useQuery<{ planmagnetFeatureRequests: { items: Request[] } }>(\n REQUESTS,\n byProject\n );\n\n const shipped = releaseStatus === 'RELEASED';\n const r = readiness.data?.planmagnetReleaseReadiness;\n const openGates = (gates.data?.planmagnetRelease?.qualityGates ?? []).filter(\n (g) => g.status !== 'PASSED' && g.status !== 'WAIVED'\n );\n const budgetRows = budgets.data?.planmagnetBudgets.items ?? [];\n const openRisks = (risks.data?.planmagnetRisks.items ?? [])\n .filter((risk) => !CLOSED_RISK.has(risk.status))\n .sort((a, b) => b.riskScore - a.riskScore);\n const openIds = new Set(r?.openWorkItemIds ?? []);\n const blockedIds = new Set((r?.blockers ?? []).map((b) => b.workItem.id));\n const riding = (requests.data?.planmagnetFeatureRequests.items ?? []).filter(\n (req) => req.workItemId && openIds.has(req.workItemId) && !CLOSED_REQUEST.has(req.status)\n );\n\n const daysLate =\n plannedDate && !shipped ? Math.floor((now - new Date(plannedDate).getTime()) / DAY) : null;\n\n // The headline lists every reason, not just the first one found.\n const reasons: string[] = [];\n if (daysLate !== null && daysLate > 0)\n reasons.push(`${daysLate} day${daysLate === 1 ? '' : 's'} past its planned date`);\n if (r && r.blockers.length > 0)\n reasons.push(`${r.blockers.length} blocked item${r.blockers.length === 1 ? '' : 's'}`);\n if (r && r.openWorkItemIds.length > 0)\n reasons.push(\n `${r.openWorkItemIds.length} unfinished item${r.openWorkItemIds.length === 1 ? '' : 's'}`\n );\n if (openGates.length > 0)\n reasons.push(`${openGates.length} gate${openGates.length === 1 ? '' : 's'} not passed`);\n const overBudget = budgetRows.filter((b) => b.remainingAmount < 0 || b.status === 'EXCEEDED');\n if (overBudget.length > 0) reasons.push('over budget');\n const settled = !readiness.loading && !gates.loading;\n\n return (\n <Card data-testid=\"release-readiness\">\n <CardHeader>\n <CardTitle>What stands between this release and shipping</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n {shipped ? (\n <p className=\"flex items-center gap-2 text-sm text-status-success-text\">\n <CheckCircle2 className=\"h-4 w-4\" /> Released.\n </p>\n ) : settled && reasons.length === 0 && r && r.scope !== 'NONE' ? (\n <p\n className=\"flex items-center gap-2 text-sm text-status-success-text\"\n data-testid=\"readiness-headline\"\n >\n <CheckCircle2 className=\"h-4 w-4\" /> Nothing is holding this release.\n </p>\n ) : settled && reasons.length > 0 ? (\n <p\n className=\"flex items-center gap-2 text-sm text-status-warning-text\"\n data-testid=\"readiness-headline\"\n >\n <AlertTriangle className=\"h-4 w-4 shrink-0\" /> Held by: {reasons.join(', ')}.\n </p>\n ) : null}\n\n <Part\n icon={<Link2 className=\"h-4 w-4\" />}\n title=\"Work and blockers\"\n testId=\"readiness-work\"\n loading={readiness.loading && !readiness.data}\n error={readiness.error}\n >\n {r && (\n <div className=\"space-y-2\">\n <p className=\"text-sm text-muted-foreground\">\n {r.scope === 'NONE'\n ? 'This release has no work items and no project, so there is nothing to track.'\n : r.scope === 'PROJECTS'\n ? `No work items are assigned to this release, so everything open in its project counts: ${r.totalItems} open.`\n : `${r.doneItems} of ${r.totalItems} work items done.`}\n </p>\n {r.blockers.length === 0 && r.scope !== 'NONE' && (\n <p className=\"text-sm text-muted-foreground\">\n No item is waiting on unfinished work.\n </p>\n )}\n <ul className=\"space-y-1.5\">\n {r.blockers.map((b) => (\n <li\n key={b.workItem.id}\n className=\"rounded-md border p-2 text-sm\"\n data-testid=\"readiness-blocker\"\n >\n <button\n type=\"button\"\n className=\"font-medium hover:underline text-left\"\n onClick={() =>\n navigate(\n `/tasks/task-detail-page?taskId=${encodeURIComponent(b.workItem.id)}`\n )\n }\n >\n {b.workItem.key} {b.workItem.title}\n </button>\n <span className=\"text-muted-foreground\"> waits on </span>\n {b.waitingOn.map((w, i) => (\n <span key={w.id}>\n {i > 0 && ', '}\n <button\n type=\"button\"\n className=\"hover:underline\"\n onClick={() =>\n navigate(`/tasks/task-detail-page?taskId=${encodeURIComponent(w.id)}`)\n }\n >\n {w.key} {w.title}\n </button>{' '}\n <span className=\"text-xs text-muted-foreground\">\n ({w.status.toLowerCase().replace('_', ' ')}\n {projectId && w.projectId && w.projectId !== projectId\n ? ', another project'\n : ''}\n )\n </span>\n </span>\n ))}\n </li>\n ))}\n </ul>\n </div>\n )}\n </Part>\n\n <Part\n icon={<ShieldAlert className=\"h-4 w-4\" />}\n title=\"Approvals\"\n testId=\"readiness-approvals\"\n loading={gates.loading && !gates.data}\n error={gates.error}\n >\n <p className=\"text-sm text-muted-foreground\">\n {openGates.length === 0\n ? 'No quality gate is waiting for a decision.'\n : `Waiting on: ${openGates.map((g) => `${g.name} (${g.status.toLowerCase().replace('_', ' ')})`).join(', ')}.`}\n </p>\n </Part>\n\n {projectId ? (\n <>\n <Part\n icon={<Wallet className=\"h-4 w-4\" />}\n title=\"Budget\"\n testId=\"readiness-budget\"\n loading={budgets.loading && !budgets.data}\n error={budgets.error}\n >\n {budgetRows.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n This release&apos;s project has no budget.\n </p>\n ) : (\n <ul className=\"space-y-1\">\n {budgetRows.map((b) => {\n const over = b.remainingAmount < 0;\n return (\n <li key={b.id} className=\"text-sm\" data-testid=\"readiness-budget-row\">\n <span className=\"font-medium\">{b.name}</span>:{' '}\n {money(b.spentAmount, b.currency)} of {money(b.plannedAmount, b.currency)}{' '}\n spent\n {over ? (\n <span className=\"text-status-error-text\">\n {' '}\n — over by {money(-b.remainingAmount, b.currency)}\n </span>\n ) : (\n <span className=\"text-muted-foreground\">\n {' '}\n — {money(b.remainingAmount, b.currency)} left\n </span>\n )}\n {!b.approvedAt && (\n <span className=\"text-status-warning-text\"> · not approved</span>\n )}\n </li>\n );\n })}\n </ul>\n )}\n </Part>\n\n <Part\n icon={<AlertTriangle className=\"h-4 w-4\" />}\n title=\"Open risks\"\n testId=\"readiness-risks\"\n loading={risks.loading && !risks.data}\n error={risks.error}\n >\n {openRisks.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n No open risks on this release&apos;s project.\n </p>\n ) : (\n <ul className=\"space-y-1\">\n {openRisks.slice(0, 5).map((risk) => (\n <li key={risk.id} className=\"text-sm\">\n <span className=\"font-medium\">{risk.title}</span>{' '}\n <span className=\"text-xs text-muted-foreground\">\n ({risk.severity.toLowerCase()}, score {risk.riskScore},{' '}\n {risk.status.toLowerCase()})\n </span>\n </li>\n ))}\n {openRisks.length > 5 && (\n <li className=\"text-xs text-muted-foreground\">\n and {openRisks.length - 5} more\n </li>\n )}\n </ul>\n )}\n </Part>\n\n <Part\n icon={<Users className=\"h-4 w-4\" />}\n title=\"Customer requests riding on unfinished work\"\n testId=\"readiness-requests\"\n loading={requests.loading && !requests.data}\n error={requests.error}\n >\n {riding.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n No open customer request is tied to unfinished work in this release.\n </p>\n ) : (\n <ul className=\"space-y-1\">\n {riding.map((req) => (\n <li key={req.id} className=\"text-sm\" data-testid=\"readiness-request\">\n <span className=\"font-medium\">{req.title}</span>{' '}\n <span className=\"text-xs text-muted-foreground\">\n ({req.voteCount} vote{req.voteCount === 1 ? '' : 's'})\n </span>\n {req.workItemId && blockedIds.has(req.workItemId) && (\n <span className=\"text-status-error-text\"> — its work is blocked</span>\n )}\n </li>\n ))}\n </ul>\n )}\n </Part>\n </>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n This release has no project, so budget, risks and customer requests cannot be shown.\n </p>\n )}\n\n {plannedDate && daysLate !== null && (\n <p className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <Clock className=\"h-3.5 w-3.5\" />\n Planned for {new Date(plannedDate).toLocaleDateString()}\n {daysLate > 0 ? ` (${daysLate} day${daysLate === 1 ? '' : 's'} ago)` : ''}.\n </p>\n )}\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-planmagnet",
3
- "version": "2026.925.16",
3
+ "version": "2026.925.17",
4
4
  "description": "Burdenoff PlanMagnet Microfrontend",
5
5
  "type": "module",
6
6
  "files": [