@burdenoff/microfe-planmagnet 2026.925.16 → 2026.925.18
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.
- package/dist/generated/wspace-types.d.ts +58 -0
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/node_modules/@apollo/client/errors/ServerError.js +17 -0
- package/dist/node_modules/@apollo/client/errors/ServerError.js.map +1 -0
- package/dist/planmagnet/pages/DocsDocDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js +67 -62
- package/dist/planmagnet/pages/DocsDocDetailPagePage.js.map +1 -1
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.d.ts.map +1 -1
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js +104 -117
- package/dist/planmagnet/pages/ProductsReleaseDetailPagePage.js.map +1 -1
- package/dist/planmagnet/releases/ReleaseGatesCard.d.ts +7 -0
- package/dist/planmagnet/releases/ReleaseGatesCard.d.ts.map +1 -1
- package/dist/planmagnet/releases/ReleaseGatesCard.js +1 -1
- package/dist/planmagnet/releases/ReleaseGatesCard.js.map +1 -1
- package/dist/planmagnet/releases/ReleaseReadinessCard.d.ts +7 -0
- package/dist/planmagnet/releases/ReleaseReadinessCard.d.ts.map +1 -0
- package/dist/planmagnet/releases/ReleaseReadinessCard.js +391 -0
- package/dist/planmagnet/releases/ReleaseReadinessCard.js.map +1 -0
- package/dist/planmagnet/releases/ReleaseWorkItemsCard.d.ts +13 -0
- package/dist/planmagnet/releases/ReleaseWorkItemsCard.d.ts.map +1 -0
- package/dist/planmagnet/releases/ReleaseWorkItemsCard.js +189 -0
- package/dist/planmagnet/releases/ReleaseWorkItemsCard.js.map +1 -0
- package/package.json +1 -1
|
@@ -2,15 +2,17 @@ import { usePlanMagnet as e } from "../providers/PlanMagnetProvider.js";
|
|
|
2
2
|
import { PersonName as t } from "../people/People.js";
|
|
3
3
|
import { ReleaseNotesCard as n } from "../releases/ReleaseNotesCard.js";
|
|
4
4
|
import { ReleaseGatesCard as r } from "../releases/ReleaseGatesCard.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
5
|
+
import { ReleaseReadinessCard as i } from "../releases/ReleaseReadinessCard.js";
|
|
6
|
+
import { ReleaseWorkItemsCard as a } from "../releases/ReleaseWorkItemsCard.js";
|
|
7
|
+
import { Calendar as o, Clock as s, GitBranch as c, Loader2 as l } from "lucide-react";
|
|
8
|
+
import { useParams as u } from "react-router";
|
|
9
|
+
import { gql as d } from "@apollo/client";
|
|
10
|
+
import { useQuery as f } from "@apollo/client/react";
|
|
11
|
+
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
12
|
+
import { Badge as h, Card as g, CardContent as _, CardHeader as v, CardTitle as y } from "@burdenoff/fe-libs/ui";
|
|
13
|
+
import { Markdown as b } from "@burdenoff/fe-libs/shared/components";
|
|
12
14
|
//#region src/planmagnet/pages/ProductsReleaseDetailPagePage.tsx
|
|
13
|
-
var
|
|
15
|
+
var x = d`
|
|
14
16
|
query GetRelease($id: ID!) {
|
|
15
17
|
planmagnetRelease(id: $id) {
|
|
16
18
|
id
|
|
@@ -37,7 +39,7 @@ var y = l`
|
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
|
-
`,
|
|
42
|
+
`, S = {
|
|
41
43
|
DRAFT: "bg-gray-100 text-gray-800",
|
|
42
44
|
PLANNING: "bg-yellow-100 text-yellow-800",
|
|
43
45
|
IN_PROGRESS: "bg-blue-100 text-blue-800",
|
|
@@ -46,89 +48,95 @@ var y = l`
|
|
|
46
48
|
CANCELLED: "bg-red-100 text-red-800",
|
|
47
49
|
ROLLED_BACK: "bg-red-100 text-red-800"
|
|
48
50
|
};
|
|
49
|
-
function
|
|
50
|
-
let { workspaceId:
|
|
51
|
-
variables: { id:
|
|
52
|
-
skip: !
|
|
51
|
+
function C() {
|
|
52
|
+
let { workspaceId: d } = e(), C = u().id ?? "", { data: w, loading: T, error: E } = f(x, {
|
|
53
|
+
variables: { id: C },
|
|
54
|
+
skip: !C
|
|
53
55
|
});
|
|
54
|
-
if (
|
|
56
|
+
if (T && !w) return /* @__PURE__ */ p("div", {
|
|
55
57
|
className: "flex items-center justify-center p-12",
|
|
56
|
-
children: /* @__PURE__ */
|
|
58
|
+
children: /* @__PURE__ */ p(l, { className: "h-8 w-8 animate-spin text-muted-foreground" })
|
|
57
59
|
});
|
|
58
|
-
if (
|
|
60
|
+
if (E) return /* @__PURE__ */ m("div", {
|
|
59
61
|
className: "p-6 text-destructive",
|
|
60
|
-
children: ["Failed to load release: ",
|
|
62
|
+
children: ["Failed to load release: ", E.message]
|
|
61
63
|
});
|
|
62
|
-
let
|
|
63
|
-
if (!
|
|
64
|
+
let D = w?.planmagnetRelease;
|
|
65
|
+
if (!D) return /* @__PURE__ */ p("div", {
|
|
64
66
|
className: "p-6 text-muted-foreground",
|
|
65
67
|
children: "Release not found."
|
|
66
68
|
});
|
|
67
|
-
let
|
|
68
|
-
return /* @__PURE__ */
|
|
69
|
+
let O = D.workItems ?? [], k = O.filter((e) => e.type === "STORY" || e.type === "FEATURE"), A = O.filter((e) => e.type === "BUG");
|
|
70
|
+
return /* @__PURE__ */ m("div", {
|
|
69
71
|
className: "space-y-6 p-6",
|
|
70
|
-
children: [/* @__PURE__ */
|
|
72
|
+
children: [/* @__PURE__ */ m("div", {
|
|
71
73
|
className: "flex items-center justify-between",
|
|
72
|
-
children: [/* @__PURE__ */
|
|
74
|
+
children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ m("div", {
|
|
73
75
|
className: "flex items-center gap-2",
|
|
74
76
|
children: [
|
|
75
|
-
/* @__PURE__ */
|
|
76
|
-
/* @__PURE__ */
|
|
77
|
+
/* @__PURE__ */ p(c, { className: "h-5 w-5 text-primary" }),
|
|
78
|
+
/* @__PURE__ */ p("h1", {
|
|
77
79
|
className: "text-2xl font-semibold",
|
|
78
|
-
children:
|
|
80
|
+
children: D.version
|
|
79
81
|
}),
|
|
80
|
-
/* @__PURE__ */
|
|
81
|
-
className: `text-xs px-2 py-0.5 rounded-full capitalize ${
|
|
82
|
-
children:
|
|
82
|
+
/* @__PURE__ */ p("span", {
|
|
83
|
+
className: `text-xs px-2 py-0.5 rounded-full capitalize ${S[D.status] ?? "bg-gray-100"}`,
|
|
84
|
+
children: D.status.toLowerCase().replace("_", " ")
|
|
83
85
|
})
|
|
84
86
|
]
|
|
85
|
-
}), /* @__PURE__ */
|
|
87
|
+
}), /* @__PURE__ */ p("p", {
|
|
86
88
|
className: "text-sm text-muted-foreground mt-1",
|
|
87
|
-
children:
|
|
88
|
-
})] }), /* @__PURE__ */
|
|
89
|
+
children: D.name
|
|
90
|
+
})] }), /* @__PURE__ */ p(h, {
|
|
89
91
|
variant: "outline",
|
|
90
92
|
className: "capitalize",
|
|
91
|
-
children:
|
|
93
|
+
children: D.status.toLowerCase().replace("_", " ")
|
|
92
94
|
})]
|
|
93
|
-
}), /* @__PURE__ */
|
|
95
|
+
}), /* @__PURE__ */ m("div", {
|
|
94
96
|
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
95
|
-
children: [/* @__PURE__ */
|
|
97
|
+
children: [/* @__PURE__ */ m("div", {
|
|
96
98
|
className: "lg:col-span-2 space-y-6",
|
|
97
99
|
children: [
|
|
98
|
-
/* @__PURE__ */
|
|
100
|
+
/* @__PURE__ */ p(i, {
|
|
101
|
+
releaseId: D.id,
|
|
102
|
+
projectId: D.projectId || null,
|
|
103
|
+
releaseStatus: D.status,
|
|
104
|
+
plannedDate: D.plannedDate ?? null
|
|
105
|
+
}),
|
|
106
|
+
/* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, { children: "Overview" }) }), /* @__PURE__ */ m(_, {
|
|
99
107
|
className: "space-y-4",
|
|
100
|
-
children: [/* @__PURE__ */
|
|
108
|
+
children: [/* @__PURE__ */ p("div", {
|
|
101
109
|
className: "text-sm prose prose-sm dark:prose-invert max-w-none",
|
|
102
|
-
children:
|
|
103
|
-
}), /* @__PURE__ */
|
|
110
|
+
children: D.description ? /* @__PURE__ */ p(b, { children: D.description }) : /* @__PURE__ */ p("p", { children: "No description provided." })
|
|
111
|
+
}), /* @__PURE__ */ m("div", {
|
|
104
112
|
className: "grid grid-cols-3 gap-4",
|
|
105
113
|
children: [
|
|
106
|
-
/* @__PURE__ */
|
|
114
|
+
/* @__PURE__ */ m("div", {
|
|
107
115
|
className: "text-center p-3 bg-muted rounded-md",
|
|
108
|
-
children: [/* @__PURE__ */
|
|
116
|
+
children: [/* @__PURE__ */ p("p", {
|
|
109
117
|
className: "text-lg font-semibold mt-1",
|
|
110
|
-
children:
|
|
111
|
-
}), /* @__PURE__ */
|
|
118
|
+
children: k.length
|
|
119
|
+
}), /* @__PURE__ */ p("p", {
|
|
112
120
|
className: "text-xs text-muted-foreground",
|
|
113
121
|
children: "Features"
|
|
114
122
|
})]
|
|
115
123
|
}),
|
|
116
|
-
/* @__PURE__ */
|
|
124
|
+
/* @__PURE__ */ m("div", {
|
|
117
125
|
className: "text-center p-3 bg-muted rounded-md",
|
|
118
|
-
children: [/* @__PURE__ */
|
|
126
|
+
children: [/* @__PURE__ */ p("p", {
|
|
119
127
|
className: "text-lg font-semibold mt-1",
|
|
120
|
-
children:
|
|
121
|
-
}), /* @__PURE__ */
|
|
128
|
+
children: A.length
|
|
129
|
+
}), /* @__PURE__ */ p("p", {
|
|
122
130
|
className: "text-xs text-muted-foreground",
|
|
123
131
|
children: "Bug Fixes"
|
|
124
132
|
})]
|
|
125
133
|
}),
|
|
126
|
-
/* @__PURE__ */
|
|
134
|
+
/* @__PURE__ */ m("div", {
|
|
127
135
|
className: "text-center p-3 bg-muted rounded-md",
|
|
128
|
-
children: [/* @__PURE__ */
|
|
136
|
+
children: [/* @__PURE__ */ p("p", {
|
|
129
137
|
className: "text-lg font-semibold mt-1",
|
|
130
|
-
children:
|
|
131
|
-
}), /* @__PURE__ */
|
|
138
|
+
children: O.length
|
|
139
|
+
}), /* @__PURE__ */ p("p", {
|
|
132
140
|
className: "text-xs text-muted-foreground",
|
|
133
141
|
children: "Total Items"
|
|
134
142
|
})]
|
|
@@ -136,125 +144,104 @@ function x() {
|
|
|
136
144
|
]
|
|
137
145
|
})]
|
|
138
146
|
})] }),
|
|
139
|
-
/* @__PURE__ */
|
|
140
|
-
releaseId:
|
|
141
|
-
projectId:
|
|
142
|
-
releaseStatus:
|
|
147
|
+
/* @__PURE__ */ p(r, {
|
|
148
|
+
releaseId: D.id,
|
|
149
|
+
projectId: D.projectId ?? null,
|
|
150
|
+
releaseStatus: D.status
|
|
143
151
|
}),
|
|
144
|
-
/* @__PURE__ */
|
|
145
|
-
releaseId:
|
|
146
|
-
notes:
|
|
152
|
+
/* @__PURE__ */ p(n, {
|
|
153
|
+
releaseId: D.id,
|
|
154
|
+
notes: D.releaseNotes ?? null
|
|
147
155
|
}),
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
className: "space-y-2",
|
|
154
|
-
children: E.map((e) => /* @__PURE__ */ f("div", {
|
|
155
|
-
className: "flex items-center justify-between border rounded-md p-3",
|
|
156
|
-
children: [/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("p", {
|
|
157
|
-
className: "text-sm font-medium",
|
|
158
|
-
children: e.title
|
|
159
|
-
}), /* @__PURE__ */ f("p", {
|
|
160
|
-
className: "text-xs text-muted-foreground capitalize",
|
|
161
|
-
children: [
|
|
162
|
-
e.type.toLowerCase(),
|
|
163
|
-
" · ",
|
|
164
|
-
e.priority.toLowerCase(),
|
|
165
|
-
" priority"
|
|
166
|
-
]
|
|
167
|
-
})] }), /* @__PURE__ */ d(p, {
|
|
168
|
-
variant: "outline",
|
|
169
|
-
className: "text-xs capitalize",
|
|
170
|
-
children: e.status.toLowerCase().replace("_", " ")
|
|
171
|
-
})]
|
|
172
|
-
}, e.id))
|
|
173
|
-
})] })
|
|
156
|
+
/* @__PURE__ */ p(a, {
|
|
157
|
+
releaseId: D.id,
|
|
158
|
+
projectId: D.projectId || null,
|
|
159
|
+
items: O
|
|
160
|
+
})
|
|
174
161
|
]
|
|
175
|
-
}), /* @__PURE__ */
|
|
162
|
+
}), /* @__PURE__ */ m("div", {
|
|
176
163
|
className: "space-y-6",
|
|
177
164
|
children: [
|
|
178
|
-
/* @__PURE__ */
|
|
165
|
+
/* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, { children: "Timeline" }) }), /* @__PURE__ */ m(_, {
|
|
179
166
|
className: "space-y-3 text-sm",
|
|
180
|
-
children: [/* @__PURE__ */
|
|
167
|
+
children: [/* @__PURE__ */ m("div", {
|
|
181
168
|
className: "flex items-center gap-2",
|
|
182
169
|
children: [
|
|
183
|
-
/* @__PURE__ */
|
|
184
|
-
/* @__PURE__ */
|
|
170
|
+
/* @__PURE__ */ p(o, { className: "h-4 w-4 text-muted-foreground" }),
|
|
171
|
+
/* @__PURE__ */ p("span", {
|
|
185
172
|
className: "text-muted-foreground",
|
|
186
173
|
children: "Planned:"
|
|
187
174
|
}),
|
|
188
|
-
/* @__PURE__ */
|
|
175
|
+
/* @__PURE__ */ p("span", {
|
|
189
176
|
className: "font-medium",
|
|
190
|
-
children:
|
|
177
|
+
children: D.plannedDate ? new Date(D.plannedDate).toLocaleDateString() : "Not set"
|
|
191
178
|
})
|
|
192
179
|
]
|
|
193
|
-
}),
|
|
180
|
+
}), D.releasedAt && /* @__PURE__ */ m("div", {
|
|
194
181
|
className: "flex items-center gap-2",
|
|
195
182
|
children: [
|
|
196
|
-
/* @__PURE__ */
|
|
197
|
-
/* @__PURE__ */
|
|
183
|
+
/* @__PURE__ */ p(s, { className: "h-4 w-4 text-green-500" }),
|
|
184
|
+
/* @__PURE__ */ p("span", {
|
|
198
185
|
className: "text-muted-foreground",
|
|
199
186
|
children: "Released:"
|
|
200
187
|
}),
|
|
201
|
-
/* @__PURE__ */
|
|
188
|
+
/* @__PURE__ */ p("span", {
|
|
202
189
|
className: "font-medium",
|
|
203
|
-
children: new Date(
|
|
190
|
+
children: new Date(D.releasedAt).toLocaleDateString()
|
|
204
191
|
})
|
|
205
192
|
]
|
|
206
193
|
})]
|
|
207
194
|
})] }),
|
|
208
|
-
/* @__PURE__ */
|
|
195
|
+
/* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, { children: "Details" }) }), /* @__PURE__ */ m(_, {
|
|
209
196
|
className: "space-y-2 text-sm",
|
|
210
197
|
children: [
|
|
211
|
-
/* @__PURE__ */
|
|
198
|
+
/* @__PURE__ */ m("div", {
|
|
212
199
|
className: "flex justify-between",
|
|
213
|
-
children: [/* @__PURE__ */
|
|
200
|
+
children: [/* @__PURE__ */ p("span", {
|
|
214
201
|
className: "text-muted-foreground",
|
|
215
202
|
children: "Version"
|
|
216
|
-
}), /* @__PURE__ */
|
|
203
|
+
}), /* @__PURE__ */ p("span", {
|
|
217
204
|
className: "font-medium",
|
|
218
|
-
children:
|
|
205
|
+
children: D.version
|
|
219
206
|
})]
|
|
220
207
|
}),
|
|
221
|
-
/* @__PURE__ */
|
|
208
|
+
/* @__PURE__ */ m("div", {
|
|
222
209
|
className: "flex justify-between",
|
|
223
|
-
children: [/* @__PURE__ */
|
|
210
|
+
children: [/* @__PURE__ */ p("span", {
|
|
224
211
|
className: "text-muted-foreground",
|
|
225
212
|
children: "Status"
|
|
226
|
-
}), /* @__PURE__ */
|
|
213
|
+
}), /* @__PURE__ */ p(h, {
|
|
227
214
|
variant: "outline",
|
|
228
215
|
className: "text-xs capitalize",
|
|
229
|
-
children:
|
|
216
|
+
children: D.status.toLowerCase().replace("_", " ")
|
|
230
217
|
})]
|
|
231
218
|
}),
|
|
232
|
-
/* @__PURE__ */
|
|
219
|
+
/* @__PURE__ */ m("div", {
|
|
233
220
|
className: "flex justify-between",
|
|
234
|
-
children: [/* @__PURE__ */
|
|
221
|
+
children: [/* @__PURE__ */ p("span", {
|
|
235
222
|
className: "text-muted-foreground",
|
|
236
223
|
children: "Owner"
|
|
237
|
-
}), /* @__PURE__ */
|
|
224
|
+
}), /* @__PURE__ */ p("span", { children: /* @__PURE__ */ p(t, { id: D.ownerId }) })]
|
|
238
225
|
}),
|
|
239
|
-
/* @__PURE__ */
|
|
226
|
+
/* @__PURE__ */ m("div", {
|
|
240
227
|
className: "flex justify-between",
|
|
241
|
-
children: [/* @__PURE__ */
|
|
228
|
+
children: [/* @__PURE__ */ p("span", {
|
|
242
229
|
className: "text-muted-foreground",
|
|
243
230
|
children: "Created"
|
|
244
|
-
}), /* @__PURE__ */
|
|
231
|
+
}), /* @__PURE__ */ p("span", { children: new Date(D.createdAt).toLocaleDateString() })]
|
|
245
232
|
}),
|
|
246
|
-
/* @__PURE__ */
|
|
233
|
+
/* @__PURE__ */ m("div", {
|
|
247
234
|
className: "flex justify-between",
|
|
248
|
-
children: [/* @__PURE__ */
|
|
235
|
+
children: [/* @__PURE__ */ p("span", {
|
|
249
236
|
className: "text-muted-foreground",
|
|
250
237
|
children: "Updated"
|
|
251
|
-
}), /* @__PURE__ */
|
|
238
|
+
}), /* @__PURE__ */ p("span", { children: new Date(D.updatedAt).toLocaleDateString() })]
|
|
252
239
|
})
|
|
253
240
|
]
|
|
254
241
|
})] }),
|
|
255
|
-
|
|
242
|
+
D.tags?.length > 0 && /* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(v, { children: /* @__PURE__ */ p(y, { children: "Tags" }) }), /* @__PURE__ */ p(_, {
|
|
256
243
|
className: "flex flex-wrap gap-1",
|
|
257
|
-
children:
|
|
244
|
+
children: D.tags.map((e) => /* @__PURE__ */ p(h, {
|
|
258
245
|
variant: "outline",
|
|
259
246
|
className: "text-xs",
|
|
260
247
|
children: e
|
|
@@ -266,6 +253,6 @@ function x() {
|
|
|
266
253
|
});
|
|
267
254
|
}
|
|
268
255
|
//#endregion
|
|
269
|
-
export {
|
|
256
|
+
export { C as ProductsReleaseDetailPagePage };
|
|
270
257
|
|
|
271
258
|
//# sourceMappingURL=ProductsReleaseDetailPagePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductsReleaseDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsReleaseDetailPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge } from '@burdenoff/fe-libs/ui';\nimport { Markdown } from '@burdenoff/fe-libs/shared/components';\nimport { GitBranch, Calendar, Clock, Server, Loader2 } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { useParams } from 'react-router';\nimport { PersonName } from '../people/People';\nimport { ReleaseNotesCard } from '../releases/ReleaseNotesCard';\nimport { ReleaseGatesCard } from '../releases/ReleaseGatesCard';\n\nconst GET_RELEASE = gql`\n query GetRelease($id: ID!) {\n planmagnetRelease(id: $id) {\n id\n workspaceId\n projectId\n name\n description\n version\n status\n plannedDate\n releasedAt\n releaseNotes\n ownerId\n tags\n metadata\n createdAt\n updatedAt\n workItems {\n id\n title\n status\n type\n priority\n }\n }\n }\n`;\n\ninterface WorkItemSummary {\n id: string;\n title: string;\n status: string;\n type: string;\n priority: string;\n}\n\nconst statusColor: Record<string, string> = {\n DRAFT: 'bg-gray-100 text-gray-800',\n PLANNING: 'bg-yellow-100 text-yellow-800',\n IN_PROGRESS: 'bg-blue-100 text-blue-800',\n READY: 'bg-orange-100 text-orange-800',\n RELEASED: 'bg-green-100 text-green-800',\n CANCELLED: 'bg-red-100 text-red-800',\n ROLLED_BACK: 'bg-red-100 text-red-800',\n};\n\nexport function ProductsReleaseDetailPagePage() {\n const { workspaceId } = usePlanMagnet();\n const params = useParams<{ id: string }>();\n const releaseId = params.id ?? '';\n\n const {\n data,\n loading,\n error,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(GET_RELEASE, {\n variables: { id: releaseId },\n skip: !releaseId,\n });\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\n\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load release: {error.message}</div>;\n }\n\n const release = data?.planmagnetRelease;\n if (!release) return <div className=\"p-6 text-muted-foreground\">Release not found.</div>;\n\n const workItems: WorkItemSummary[] = release.workItems ?? [];\n const featureItems = workItems.filter((w) => w.type === 'STORY' || w.type === 'FEATURE');\n const bugItems = workItems.filter((w) => w.type === 'BUG');\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"flex items-center gap-2\">\n <GitBranch className=\"h-5 w-5 text-primary\" />\n <h1 className=\"text-2xl font-semibold\">{release.version}</h1>\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${statusColor[release.status] ?? 'bg-gray-100'}`}\n >\n {release.status.toLowerCase().replace('_', ' ')}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground mt-1\">{release.name}</p>\n </div>\n <Badge variant=\"outline\" className=\"capitalize\">\n {release.status.toLowerCase().replace('_', ' ')}\n </Badge>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Overview</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"text-sm prose prose-sm dark:prose-invert max-w-none\">\n {release.description ? (\n <Markdown>{release.description}</Markdown>\n ) : (\n <p>No description provided.</p>\n )}\n </div>\n <div className=\"grid grid-cols-3 gap-4\">\n <div className=\"text-center p-3 bg-muted rounded-md\">\n <p className=\"text-lg font-semibold mt-1\">{featureItems.length}</p>\n <p className=\"text-xs text-muted-foreground\">Features</p>\n </div>\n <div className=\"text-center p-3 bg-muted rounded-md\">\n <p className=\"text-lg font-semibold mt-1\">{bugItems.length}</p>\n <p className=\"text-xs text-muted-foreground\">Bug Fixes</p>\n </div>\n <div className=\"text-center p-3 bg-muted rounded-md\">\n <p className=\"text-lg font-semibold mt-1\">{workItems.length}</p>\n <p className=\"text-xs text-muted-foreground\">Total Items</p>\n </div>\n </div>\n </CardContent>\n </Card>\n\n <ReleaseGatesCard\n releaseId={release.id}\n projectId={release.projectId ?? null}\n releaseStatus={release.status}\n />\n <ReleaseNotesCard releaseId={release.id} notes={release.releaseNotes ?? null} />\n\n {workItems.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Work Items ({workItems.length})</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n {workItems.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between border rounded-md p-3\"\n >\n <div>\n <p className=\"text-sm font-medium\">{item.title}</p>\n <p className=\"text-xs text-muted-foreground capitalize\">\n {item.type.toLowerCase()} · {item.priority.toLowerCase()} priority\n </p>\n </div>\n <Badge variant=\"outline\" className=\"text-xs capitalize\">\n {item.status.toLowerCase().replace('_', ' ')}\n </Badge>\n </div>\n ))}\n </CardContent>\n </Card>\n )}\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Timeline</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Planned:</span>\n <span className=\"font-medium\">\n {release.plannedDate\n ? new Date(release.plannedDate).toLocaleDateString()\n : 'Not set'}\n </span>\n </div>\n {release.releasedAt && (\n <div className=\"flex items-center gap-2\">\n <Clock className=\"h-4 w-4 text-green-500\" />\n <span className=\"text-muted-foreground\">Released:</span>\n <span className=\"font-medium\">\n {new Date(release.releasedAt).toLocaleDateString()}\n </span>\n </div>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Version</span>\n <span className=\"font-medium\">{release.version}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Status</span>\n <Badge variant=\"outline\" className=\"text-xs capitalize\">\n {release.status.toLowerCase().replace('_', ' ')}\n </Badge>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Owner</span>\n <span>\n <PersonName id={release.ownerId} />\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{new Date(release.createdAt).toLocaleDateString()}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{new Date(release.updatedAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </Card>\n\n {release.tags?.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Tags</CardTitle>\n </CardHeader>\n <CardContent className=\"flex flex-wrap gap-1\">\n {release.tags.map((tag: string) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n </CardContent>\n </Card>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProductsReleaseDetailPagePage.js","names":[],"sources":["../../../src/planmagnet/pages/ProductsReleaseDetailPagePage.tsx"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { gql } from '@apollo/client';\nimport { useQuery } from '@apollo/client/react';\nimport { Card, CardContent, CardHeader, CardTitle, Badge } from '@burdenoff/fe-libs/ui';\nimport { Markdown } from '@burdenoff/fe-libs/shared/components';\nimport { GitBranch, Calendar, Clock, Server, Loader2 } from 'lucide-react';\nimport { usePlanMagnet } from '../providers/PlanMagnetProvider';\nimport { useParams } from 'react-router';\nimport { PersonName } from '../people/People';\nimport { ReleaseNotesCard } from '../releases/ReleaseNotesCard';\nimport { ReleaseGatesCard } from '../releases/ReleaseGatesCard';\nimport { ReleaseReadinessCard } from '../releases/ReleaseReadinessCard';\nimport { ReleaseWorkItemsCard } from '../releases/ReleaseWorkItemsCard';\n\nconst GET_RELEASE = gql`\n query GetRelease($id: ID!) {\n planmagnetRelease(id: $id) {\n id\n workspaceId\n projectId\n name\n description\n version\n status\n plannedDate\n releasedAt\n releaseNotes\n ownerId\n tags\n metadata\n createdAt\n updatedAt\n workItems {\n id\n title\n status\n type\n priority\n }\n }\n }\n`;\n\ninterface WorkItemSummary {\n id: string;\n title: string;\n status: string;\n type: string;\n priority: string;\n}\n\nconst statusColor: Record<string, string> = {\n DRAFT: 'bg-gray-100 text-gray-800',\n PLANNING: 'bg-yellow-100 text-yellow-800',\n IN_PROGRESS: 'bg-blue-100 text-blue-800',\n READY: 'bg-orange-100 text-orange-800',\n RELEASED: 'bg-green-100 text-green-800',\n CANCELLED: 'bg-red-100 text-red-800',\n ROLLED_BACK: 'bg-red-100 text-red-800',\n};\n\nexport function ProductsReleaseDetailPagePage() {\n const { workspaceId } = usePlanMagnet();\n const params = useParams<{ id: string }>();\n const releaseId = params.id ?? '';\n\n const {\n data,\n loading,\n error,\n } = // eslint-disable-next-line @typescript-eslint/no-explicit-any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n useQuery<Record<string, any>>(GET_RELEASE, {\n variables: { id: releaseId },\n skip: !releaseId,\n });\n\n // Only before the first load: a refetch must not blank the page (it\n\n // unmounts every component on it and loses their state).\n\n if (loading && !data) {\n return (\n <div className=\"flex items-center justify-center p-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-muted-foreground\" />\n </div>\n );\n }\n\n if (error) {\n return <div className=\"p-6 text-destructive\">Failed to load release: {error.message}</div>;\n }\n\n const release = data?.planmagnetRelease;\n if (!release) return <div className=\"p-6 text-muted-foreground\">Release not found.</div>;\n\n const workItems: WorkItemSummary[] = release.workItems ?? [];\n const featureItems = workItems.filter((w) => w.type === 'STORY' || w.type === 'FEATURE');\n const bugItems = workItems.filter((w) => w.type === 'BUG');\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"flex items-center gap-2\">\n <GitBranch className=\"h-5 w-5 text-primary\" />\n <h1 className=\"text-2xl font-semibold\">{release.version}</h1>\n <span\n className={`text-xs px-2 py-0.5 rounded-full capitalize ${statusColor[release.status] ?? 'bg-gray-100'}`}\n >\n {release.status.toLowerCase().replace('_', ' ')}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground mt-1\">{release.name}</p>\n </div>\n <Badge variant=\"outline\" className=\"capitalize\">\n {release.status.toLowerCase().replace('_', ' ')}\n </Badge>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <ReleaseReadinessCard\n releaseId={release.id}\n projectId={release.projectId || null}\n releaseStatus={release.status}\n plannedDate={release.plannedDate ?? null}\n />\n <Card>\n <CardHeader>\n <CardTitle>Overview</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"text-sm prose prose-sm dark:prose-invert max-w-none\">\n {release.description ? (\n <Markdown>{release.description}</Markdown>\n ) : (\n <p>No description provided.</p>\n )}\n </div>\n <div className=\"grid grid-cols-3 gap-4\">\n <div className=\"text-center p-3 bg-muted rounded-md\">\n <p className=\"text-lg font-semibold mt-1\">{featureItems.length}</p>\n <p className=\"text-xs text-muted-foreground\">Features</p>\n </div>\n <div className=\"text-center p-3 bg-muted rounded-md\">\n <p className=\"text-lg font-semibold mt-1\">{bugItems.length}</p>\n <p className=\"text-xs text-muted-foreground\">Bug Fixes</p>\n </div>\n <div className=\"text-center p-3 bg-muted rounded-md\">\n <p className=\"text-lg font-semibold mt-1\">{workItems.length}</p>\n <p className=\"text-xs text-muted-foreground\">Total Items</p>\n </div>\n </div>\n </CardContent>\n </Card>\n\n <ReleaseGatesCard\n releaseId={release.id}\n projectId={release.projectId ?? null}\n releaseStatus={release.status}\n />\n <ReleaseNotesCard releaseId={release.id} notes={release.releaseNotes ?? null} />\n\n <ReleaseWorkItemsCard\n releaseId={release.id}\n projectId={release.projectId || null}\n items={workItems}\n />\n </div>\n\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Timeline</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3 text-sm\">\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"h-4 w-4 text-muted-foreground\" />\n <span className=\"text-muted-foreground\">Planned:</span>\n <span className=\"font-medium\">\n {release.plannedDate\n ? new Date(release.plannedDate).toLocaleDateString()\n : 'Not set'}\n </span>\n </div>\n {release.releasedAt && (\n <div className=\"flex items-center gap-2\">\n <Clock className=\"h-4 w-4 text-green-500\" />\n <span className=\"text-muted-foreground\">Released:</span>\n <span className=\"font-medium\">\n {new Date(release.releasedAt).toLocaleDateString()}\n </span>\n </div>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Details</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2 text-sm\">\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Version</span>\n <span className=\"font-medium\">{release.version}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Status</span>\n <Badge variant=\"outline\" className=\"text-xs capitalize\">\n {release.status.toLowerCase().replace('_', ' ')}\n </Badge>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Owner</span>\n <span>\n <PersonName id={release.ownerId} />\n </span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Created</span>\n <span>{new Date(release.createdAt).toLocaleDateString()}</span>\n </div>\n <div className=\"flex justify-between\">\n <span className=\"text-muted-foreground\">Updated</span>\n <span>{new Date(release.updatedAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </Card>\n\n {release.tags?.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Tags</CardTitle>\n </CardHeader>\n <CardContent className=\"flex flex-wrap gap-1\">\n {release.tags.map((tag: string) => (\n <Badge key={tag} variant=\"outline\" className=\"text-xs\">\n {tag}\n </Badge>\n ))}\n </CardContent>\n </Card>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality gates holding a release (P1.4). The service refuses RELEASED while
|
|
3
|
+
* any gate here is not PASSED or WAIVED; this card shows which gates hold the
|
|
4
|
+
* release, and lets a reviewer add, pass, fail or waive them. There was no
|
|
5
|
+
* quality-gate UI anywhere before this.
|
|
6
|
+
*/
|
|
7
|
+
export declare const RELEASE_GATES: import('graphql').DocumentNode;
|
|
1
8
|
export declare function ReleaseGatesCard({ releaseId, projectId, releaseStatus, }: {
|
|
2
9
|
releaseId: string;
|
|
3
10
|
projectId: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReleaseGatesCard.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/releases/ReleaseGatesCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ReleaseGatesCard.d.ts","sourceRoot":"","sources":["../../../src/planmagnet/releases/ReleaseGatesCard.tsx"],"names":[],"mappings":"AAQA;;;;;GAKG;AAEH,eAAO,MAAM,aAAa,gCAazB,CAAC;AAsDF,wBAAgB,gBAAgB,CAAC,EAC/B,SAAS,EACT,SAAS,EACT,aAAa,GACd,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;CACvB,2CA8HA"}
|
|
@@ -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":"AAuNA,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"}
|