@kuadrant/kuadrant-backstage-plugin-frontend 0.0.2-dev-844db4d → 0.0.2-dev-d4f65ae

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.
Files changed (39) hide show
  1. package/README.md +4 -4
  2. package/dist/components/ApiKeyManagementTab/ApiKeyManagementTab.esm.js +69 -22
  3. package/dist/components/ApiKeyManagementTab/ApiKeyManagementTab.esm.js.map +1 -1
  4. package/dist/components/ApprovalQueueCard/ApprovalQueueCard.esm.js +93 -53
  5. package/dist/components/ApprovalQueueCard/ApprovalQueueCard.esm.js.map +1 -1
  6. package/dist/components/ConfirmDeleteDialog/ConfirmDeleteDialog.esm.js +65 -0
  7. package/dist/components/ConfirmDeleteDialog/ConfirmDeleteDialog.esm.js.map +1 -0
  8. package/dist/components/CreateAPIProductDialog/CreateAPIProductDialog.esm.js +28 -15
  9. package/dist/components/CreateAPIProductDialog/CreateAPIProductDialog.esm.js.map +1 -1
  10. package/dist/components/EditAPIKeyRequestDialog/EditAPIKeyRequestDialog.esm.js +6 -4
  11. package/dist/components/EditAPIKeyRequestDialog/EditAPIKeyRequestDialog.esm.js.map +1 -1
  12. package/dist/components/EditAPIProductDialog/EditAPIProductDialog.esm.js +26 -14
  13. package/dist/components/EditAPIProductDialog/EditAPIProductDialog.esm.js.map +1 -1
  14. package/dist/components/KuadrantPage/KuadrantPage.esm.js +51 -22
  15. package/dist/components/KuadrantPage/KuadrantPage.esm.js.map +1 -1
  16. package/dist/components/MyApiKeysCard/MyApiKeysCard.esm.js +50 -12
  17. package/dist/components/MyApiKeysCard/MyApiKeysCard.esm.js.map +1 -1
  18. package/dist-scalprum/{internal.plugin-kuadrant.58f9452dd6d088d4816c.js → internal.plugin-kuadrant.810174392d0016a5a388.js} +2 -2
  19. package/dist-scalprum/{internal.plugin-kuadrant.58f9452dd6d088d4816c.js.map → internal.plugin-kuadrant.810174392d0016a5a388.js.map} +1 -1
  20. package/dist-scalprum/plugin-manifest.json +2 -2
  21. package/dist-scalprum/static/4306.4587e025.chunk.js +2 -0
  22. package/dist-scalprum/static/4306.4587e025.chunk.js.map +1 -0
  23. package/dist-scalprum/static/6281.b000c79f.chunk.js +2 -0
  24. package/dist-scalprum/static/6281.b000c79f.chunk.js.map +1 -0
  25. package/dist-scalprum/static/7632.daef27e4.chunk.js +2 -0
  26. package/dist-scalprum/static/7632.daef27e4.chunk.js.map +1 -0
  27. package/dist-scalprum/static/8441.62394cfd.chunk.js +2 -0
  28. package/dist-scalprum/static/8441.62394cfd.chunk.js.map +1 -0
  29. package/dist-scalprum/static/{exposed-PluginRoot.9522bcfa.chunk.js → exposed-PluginRoot.0ac7fe4b.chunk.js} +2 -2
  30. package/dist-scalprum/static/{exposed-PluginRoot.9522bcfa.chunk.js.map → exposed-PluginRoot.0ac7fe4b.chunk.js.map} +1 -1
  31. package/package.json +1 -1
  32. package/dist-scalprum/static/4306.bc289e67.chunk.js +0 -2
  33. package/dist-scalprum/static/4306.bc289e67.chunk.js.map +0 -1
  34. package/dist-scalprum/static/6281.e7e36f6a.chunk.js +0 -2
  35. package/dist-scalprum/static/6281.e7e36f6a.chunk.js.map +0 -1
  36. package/dist-scalprum/static/7632.4bb9bd69.chunk.js +0 -2
  37. package/dist-scalprum/static/7632.4bb9bd69.chunk.js.map +0 -1
  38. package/dist-scalprum/static/9555.59e77806.chunk.js +0 -2
  39. package/dist-scalprum/static/9555.59e77806.chunk.js.map +0 -1
@@ -1,15 +1,15 @@
1
1
  import React, { useState, useEffect } from 'react';
2
- import { useApi, configApiRef, fetchApiRef, identityApiRef } from '@backstage/core-plugin-api';
2
+ import { useApi, configApiRef, fetchApiRef, identityApiRef, alertApiRef } from '@backstage/core-plugin-api';
3
3
  import { useAsync } from 'react-use';
4
4
  import { Progress, ResponseErrorPanel, InfoCard, Table } from '@backstage/core-components';
5
5
  import { kuadrantApiKeyRequestUpdateAllPermission, kuadrantApiKeyRequestUpdateOwnPermission } from '../../permissions.esm.js';
6
6
  import { useKuadrantPermission } from '../../utils/permissions.esm.js';
7
- import { Box, Typography, Tabs, Tab, Button, Accordion, AccordionSummary, Chip, AccordionDetails, Dialog, DialogTitle, DialogContent, TextField, DialogActions, Tooltip } from '@material-ui/core';
7
+ import { Box, Typography, Tabs, Tab, Button, Accordion, AccordionSummary, Chip, AccordionDetails, Dialog, DialogTitle, DialogContent, TextField, DialogActions, CircularProgress, Tooltip } from '@material-ui/core';
8
8
  import CheckCircleIcon from '@material-ui/icons/CheckCircle';
9
9
  import CancelIcon from '@material-ui/icons/Cancel';
10
10
  import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
11
11
 
12
- const ApprovalDialog = ({ open, request, action, onClose, onConfirm }) => {
12
+ const ApprovalDialog = ({ open, request, action, processing, onClose, onConfirm }) => {
13
13
  const [comment, setComment] = useState("");
14
14
  useEffect(() => {
15
15
  if (!open) {
@@ -18,9 +18,10 @@ const ApprovalDialog = ({ open, request, action, onClose, onConfirm }) => {
18
18
  }, [open]);
19
19
  const handleConfirm = () => {
20
20
  onConfirm(comment);
21
- onClose();
22
21
  };
23
- return /* @__PURE__ */ React.createElement(Dialog, { open, onClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React.createElement(DialogTitle, null, action === "approve" ? "Approve" : "Reject", " API Key Request"), /* @__PURE__ */ React.createElement(DialogContent, null, request && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", null, "User:"), " ", request.spec.requestedBy.userId), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", null, "API:"), " ", request.spec.apiName), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", null, "Plan:"), " ", request.spec.planTier), /* @__PURE__ */ React.createElement(Box, { mb: 2 }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2", component: "span", style: { fontWeight: "bold" } }, "Use Case:"), " ", /* @__PURE__ */ React.createElement(Typography, { variant: "body2", component: "span", style: { whiteSpace: "pre-wrap" } }, request.spec.useCase || "-")), /* @__PURE__ */ React.createElement(
22
+ const actionLabel = action === "approve" ? "Approve" : "Reject";
23
+ const processingLabel = action === "approve" ? "Approving..." : "Rejecting...";
24
+ return /* @__PURE__ */ React.createElement(Dialog, { open, onClose: processing ? undefined : onClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ React.createElement(DialogTitle, null, actionLabel, " API Key Request"), /* @__PURE__ */ React.createElement(DialogContent, null, request && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", null, "User:"), " ", request.spec.requestedBy.userId), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", null, "API:"), " ", request.spec.apiName), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("strong", null, "Tier:"), " ", request.spec.planTier), /* @__PURE__ */ React.createElement(Box, { mb: 2 }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2", component: "span", style: { fontWeight: "bold" } }, "Use Case:"), " ", /* @__PURE__ */ React.createElement(Typography, { variant: "body2", component: "span", style: { whiteSpace: "pre-wrap" } }, request.spec.useCase || "-")), /* @__PURE__ */ React.createElement(
24
25
  TextField,
25
26
  {
26
27
  label: "Comment (optional)",
@@ -29,19 +30,22 @@ const ApprovalDialog = ({ open, request, action, onClose, onConfirm }) => {
29
30
  fullWidth: true,
30
31
  margin: "normal",
31
32
  value: comment,
32
- onChange: (e) => setComment(e.target.value)
33
+ onChange: (e) => setComment(e.target.value),
34
+ disabled: processing
33
35
  }
34
- ))), /* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(Button, { onClick: onClose }, "Cancel"), /* @__PURE__ */ React.createElement(
36
+ ))), /* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(Button, { onClick: onClose, disabled: processing }, "Cancel"), /* @__PURE__ */ React.createElement(
35
37
  Button,
36
38
  {
37
39
  onClick: handleConfirm,
38
40
  color: action === "approve" ? "primary" : "secondary",
39
- variant: "contained"
41
+ variant: "contained",
42
+ disabled: processing,
43
+ startIcon: processing ? /* @__PURE__ */ React.createElement(CircularProgress, { size: 16, color: "inherit" }) : undefined
40
44
  },
41
- action === "approve" ? "Approve" : "Reject"
45
+ processing ? processingLabel : actionLabel
42
46
  )));
43
47
  };
44
- const BulkActionDialog = ({ open, requests, action, onClose, onConfirm }) => {
48
+ const BulkActionDialog = ({ open, requests, action, processing, onClose, onConfirm }) => {
45
49
  const [comment, setComment] = useState("");
46
50
  useEffect(() => {
47
51
  if (!open) {
@@ -50,10 +54,11 @@ const BulkActionDialog = ({ open, requests, action, onClose, onConfirm }) => {
50
54
  }, [open]);
51
55
  const handleConfirm = () => {
52
56
  onConfirm(comment);
53
- onClose();
54
57
  };
55
58
  const isApprove = action === "approve";
56
- return /* @__PURE__ */ React.createElement(Dialog, { open, onClose, maxWidth: "md", fullWidth: true }, /* @__PURE__ */ React.createElement(DialogTitle, null, isApprove ? "Approve" : "Reject", " ", requests.length, " API Key Requests"), /* @__PURE__ */ React.createElement(DialogContent, null, /* @__PURE__ */ React.createElement(Typography, { variant: "body2", paragraph: true }, "You are about to ", isApprove ? "approve" : "reject", " the following requests:"), /* @__PURE__ */ React.createElement(Box, { mb: 2, maxHeight: 200, overflow: "auto" }, requests.map((request) => /* @__PURE__ */ React.createElement(Box, { key: `${request.metadata.namespace}/${request.metadata.name}`, mb: 1, p: 1, bgcolor: "background.default" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, /* @__PURE__ */ React.createElement("strong", null, request.spec.requestedBy.userId), " - ", request.spec.apiName, " (", request.spec.planTier, ")")))), /* @__PURE__ */ React.createElement(
59
+ const actionLabel = isApprove ? "Approve All" : "Reject All";
60
+ const processingLabel = isApprove ? "Approving..." : "Rejecting...";
61
+ return /* @__PURE__ */ React.createElement(Dialog, { open, onClose: processing ? undefined : onClose, maxWidth: "md", fullWidth: true }, /* @__PURE__ */ React.createElement(DialogTitle, null, isApprove ? "Approve" : "Reject", " ", requests.length, " API Key Requests"), /* @__PURE__ */ React.createElement(DialogContent, null, /* @__PURE__ */ React.createElement(Typography, { variant: "body2", paragraph: true }, "You are about to ", isApprove ? "approve" : "reject", " the following requests:"), /* @__PURE__ */ React.createElement(Box, { mb: 2, maxHeight: 200, overflow: "auto" }, requests.map((request) => /* @__PURE__ */ React.createElement(Box, { key: `${request.metadata.namespace}/${request.metadata.name}`, mb: 1, p: 1, bgcolor: "background.default" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, /* @__PURE__ */ React.createElement("strong", null, request.spec.requestedBy.userId), " - ", request.spec.apiName, " (", request.spec.planTier, ")")))), /* @__PURE__ */ React.createElement(
57
62
  TextField,
58
63
  {
59
64
  label: "Comment (optional)",
@@ -62,22 +67,26 @@ const BulkActionDialog = ({ open, requests, action, onClose, onConfirm }) => {
62
67
  margin: "normal",
63
68
  value: comment,
64
69
  onChange: (e) => setComment(e.target.value),
65
- helperText: `This comment will be applied to all ${isApprove ? "approved" : "rejected"} requests`
70
+ helperText: `This comment will be applied to all ${isApprove ? "approved" : "rejected"} requests`,
71
+ disabled: processing
66
72
  }
67
- )), /* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(Button, { onClick: onClose }, "Cancel"), /* @__PURE__ */ React.createElement(
73
+ )), /* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(Button, { onClick: onClose, disabled: processing }, "Cancel"), /* @__PURE__ */ React.createElement(
68
74
  Button,
69
75
  {
70
76
  onClick: handleConfirm,
71
77
  color: isApprove ? "primary" : "secondary",
72
- variant: "contained"
78
+ variant: "contained",
79
+ disabled: processing,
80
+ startIcon: processing ? /* @__PURE__ */ React.createElement(CircularProgress, { size: 16, color: "inherit" }) : undefined
73
81
  },
74
- isApprove ? "Approve All" : "Reject All"
82
+ processing ? processingLabel : actionLabel
75
83
  )));
76
84
  };
77
85
  const ApprovalQueueCard = () => {
78
86
  const config = useApi(configApiRef);
79
87
  const fetchApi = useApi(fetchApiRef);
80
88
  const identityApi = useApi(identityApiRef);
89
+ const alertApi = useApi(alertApiRef);
81
90
  const backendUrl = config.getString("backend.baseUrl");
82
91
  const [refresh, setRefresh] = useState(0);
83
92
  const [selectedTab, setSelectedTab] = useState(0);
@@ -85,12 +94,14 @@ const ApprovalQueueCard = () => {
85
94
  const [dialogState, setDialogState] = useState({
86
95
  open: false,
87
96
  request: null,
88
- action: "approve"
97
+ action: "approve",
98
+ processing: false
89
99
  });
90
100
  const [bulkDialogState, setBulkDialogState] = useState({
91
101
  open: false,
92
102
  requests: [],
93
- action: "approve"
103
+ action: "approve",
104
+ processing: false
94
105
  });
95
106
  const {
96
107
  allowed: canUpdateAllRequests,
@@ -102,29 +113,39 @@ const ApprovalQueueCard = () => {
102
113
  loading: updateOwnPermissionLoading,
103
114
  error: updateOwnPermissionError
104
115
  } = useKuadrantPermission(kuadrantApiKeyRequestUpdateOwnPermission);
105
- const canUpdateRequests = canUpdateAllRequests || canUpdateOwnRequests;
106
116
  const updatePermissionLoading = updateAllPermissionLoading || updateOwnPermissionLoading;
107
117
  const updatePermissionError = updateAllPermissionError || updateOwnPermissionError;
108
118
  const { value, loading, error } = useAsync(async () => {
109
119
  const identity = await identityApi.getBackstageIdentity();
110
120
  const reviewedBy = identity.userEntityRef;
111
121
  console.log("ApprovalQueueCard: fetching all requests from", `${backendUrl}/api/kuadrant/requests`);
112
- const response = await fetchApi.fetch(
113
- `${backendUrl}/api/kuadrant/requests`
114
- );
115
- if (!response.ok) {
116
- console.log("ApprovalQueueCard: failed to fetch requests, status:", response.status);
117
- return { pending: [], approved: [], rejected: [], reviewedBy };
122
+ const [requestsResponse, apiProductsResponse] = await Promise.all([
123
+ fetchApi.fetch(`${backendUrl}/api/kuadrant/requests`),
124
+ fetchApi.fetch(`${backendUrl}/api/kuadrant/apiproducts`)
125
+ ]);
126
+ if (!requestsResponse.ok) {
127
+ console.log("ApprovalQueueCard: failed to fetch requests, status:", requestsResponse.status);
128
+ return { pending: [], approved: [], rejected: [], reviewedBy, ownedApiProducts: /* @__PURE__ */ new Set() };
118
129
  }
119
- const contentType = response.headers.get("content-type");
130
+ const contentType = requestsResponse.headers.get("content-type");
120
131
  if (!contentType || !contentType.includes("application/json")) {
121
132
  console.log("ApprovalQueueCard: received non-json response");
122
- return { pending: [], approved: [], rejected: [], reviewedBy };
133
+ return { pending: [], approved: [], rejected: [], reviewedBy, ownedApiProducts: /* @__PURE__ */ new Set() };
123
134
  }
124
- const data = await response.json();
135
+ const data = await requestsResponse.json();
125
136
  const allRequests = data.items || [];
137
+ const ownedApiProducts = /* @__PURE__ */ new Set();
138
+ if (apiProductsResponse.ok) {
139
+ const apiProductsData = await apiProductsResponse.json();
140
+ for (const product of apiProductsData.items || []) {
141
+ const owner = product.metadata?.annotations?.["backstage.io/owner"];
142
+ if (owner === reviewedBy) {
143
+ ownedApiProducts.add(`${product.metadata.namespace}/${product.metadata.name}`);
144
+ }
145
+ }
146
+ }
126
147
  console.log("ApprovalQueueCard: received", allRequests.length, "total requests");
127
- console.log("ApprovalQueueCard: raw requests:", allRequests);
148
+ console.log("ApprovalQueueCard: user owns", ownedApiProducts.size, "api products");
128
149
  const pending2 = allRequests.filter((r) => {
129
150
  const phase = r.status?.phase || "Pending";
130
151
  return phase === "Pending";
@@ -142,16 +163,17 @@ const ApprovalQueueCard = () => {
142
163
  approved: approved2.length,
143
164
  rejected: rejected2.length
144
165
  });
145
- return { pending: pending2, approved: approved2, rejected: rejected2, reviewedBy };
166
+ return { pending: pending2, approved: approved2, rejected: rejected2, reviewedBy, ownedApiProducts };
146
167
  }, [backendUrl, fetchApi, identityApi, refresh]);
147
168
  const handleApprove = (request) => {
148
- setDialogState({ open: true, request, action: "approve" });
169
+ setDialogState({ open: true, request, action: "approve", processing: false });
149
170
  };
150
171
  const handleReject = (request) => {
151
- setDialogState({ open: true, request, action: "reject" });
172
+ setDialogState({ open: true, request, action: "reject", processing: false });
152
173
  };
153
174
  const handleConfirm = async (comment) => {
154
175
  if (!dialogState.request || !value) return;
176
+ setDialogState((prev) => ({ ...prev, processing: true }));
155
177
  const endpoint = dialogState.action === "approve" ? `${backendUrl}/api/kuadrant/requests/${dialogState.request.metadata.namespace}/${dialogState.request.metadata.name}/approve` : `${backendUrl}/api/kuadrant/requests/${dialogState.request.metadata.namespace}/${dialogState.request.metadata.name}/reject`;
156
178
  try {
157
179
  const response = await fetchApi.fetch(endpoint, {
@@ -165,22 +187,27 @@ const ApprovalQueueCard = () => {
165
187
  if (!response.ok) {
166
188
  throw new Error(`failed to ${dialogState.action} request`);
167
189
  }
168
- setDialogState({ open: false, request: null, action: "approve" });
190
+ setDialogState({ open: false, request: null, action: "approve", processing: false });
169
191
  setRefresh((r) => r + 1);
192
+ const action = dialogState.action === "approve" ? "approved" : "rejected";
193
+ alertApi.post({ message: `Request ${action}`, severity: "success", display: "transient" });
170
194
  } catch (err) {
171
195
  console.error(`error ${dialogState.action}ing request:`, err);
196
+ setDialogState((prev) => ({ ...prev, processing: false }));
197
+ alertApi.post({ message: `Failed to ${dialogState.action} request`, severity: "error", display: "transient" });
172
198
  }
173
199
  };
174
200
  const handleBulkApprove = () => {
175
201
  if (selectedRequests.length === 0) return;
176
- setBulkDialogState({ open: true, requests: selectedRequests, action: "approve" });
202
+ setBulkDialogState({ open: true, requests: selectedRequests, action: "approve", processing: false });
177
203
  };
178
204
  const handleBulkReject = () => {
179
205
  if (selectedRequests.length === 0) return;
180
- setBulkDialogState({ open: true, requests: selectedRequests, action: "reject" });
206
+ setBulkDialogState({ open: true, requests: selectedRequests, action: "reject", processing: false });
181
207
  };
182
208
  const handleBulkConfirm = async (comment) => {
183
209
  if (!value || bulkDialogState.requests.length === 0) return;
210
+ setBulkDialogState((prev) => ({ ...prev, processing: true }));
184
211
  const isApprove = bulkDialogState.action === "approve";
185
212
  const endpoint = isApprove ? `${backendUrl}/api/kuadrant/requests/bulk-approve` : `${backendUrl}/api/kuadrant/requests/bulk-reject`;
186
213
  try {
@@ -199,11 +226,16 @@ const ApprovalQueueCard = () => {
199
226
  if (!response.ok) {
200
227
  throw new Error(`failed to bulk ${bulkDialogState.action} requests`);
201
228
  }
202
- setBulkDialogState({ open: false, requests: [], action: "approve" });
229
+ const count = bulkDialogState.requests.length;
230
+ const action = isApprove ? "approved" : "rejected";
231
+ setBulkDialogState({ open: false, requests: [], action: "approve", processing: false });
203
232
  setSelectedRequests([]);
204
233
  setRefresh((r) => r + 1);
234
+ alertApi.post({ message: `${count} requests ${action}`, severity: "success", display: "transient" });
205
235
  } catch (err) {
206
236
  console.error(`error bulk ${bulkDialogState.action}ing requests:`, err);
237
+ setBulkDialogState((prev) => ({ ...prev, processing: false }));
238
+ alertApi.post({ message: `Failed to bulk ${bulkDialogState.action} requests`, severity: "error", display: "transient" });
207
239
  }
208
240
  };
209
241
  if (loading || updatePermissionLoading) {
@@ -250,7 +282,7 @@ const ApprovalQueueCard = () => {
250
282
  render: (row) => /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, row.spec.apiNamespace)
251
283
  },
252
284
  {
253
- title: "Plan",
285
+ title: "Tier",
254
286
  field: "spec.planTier",
255
287
  render: (row) => /* @__PURE__ */ React.createElement(
256
288
  Chip,
@@ -291,7 +323,10 @@ const ApprovalQueueCard = () => {
291
323
  title: "Actions",
292
324
  filtering: false,
293
325
  render: (row) => {
294
- if (!canUpdateRequests) return null;
326
+ const apiProductKey = `${row.spec.apiNamespace}/${row.spec.apiName}`;
327
+ const ownsApiProduct = value?.ownedApiProducts?.has(apiProductKey) ?? false;
328
+ const canUpdate = canUpdateAllRequests || canUpdateOwnRequests && ownsApiProduct;
329
+ if (!canUpdate) return null;
295
330
  return /* @__PURE__ */ React.createElement(Box, { display: "flex", style: { gap: 8 } }, /* @__PURE__ */ React.createElement(
296
331
  Button,
297
332
  {
@@ -338,7 +373,7 @@ const ApprovalQueueCard = () => {
338
373
  render: (row) => /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, row.spec.apiNamespace)
339
374
  },
340
375
  {
341
- title: "Plan",
376
+ title: "Tier",
342
377
  field: "spec.planTier",
343
378
  render: (row) => /* @__PURE__ */ React.createElement(
344
379
  Chip,
@@ -401,7 +436,7 @@ const ApprovalQueueCard = () => {
401
436
  render: (row) => /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, row.spec.apiNamespace)
402
437
  },
403
438
  {
404
- title: "Plan",
439
+ title: "Tier",
405
440
  field: "spec.planTier",
406
441
  render: (row) => /* @__PURE__ */ React.createElement(
407
442
  Chip,
@@ -475,16 +510,16 @@ const ApprovalQueueCard = () => {
475
510
  const groupByApiProduct = (requests) => {
476
511
  const grouped = /* @__PURE__ */ new Map();
477
512
  requests.forEach((request) => {
478
- const apiName = request.spec.apiName;
479
- if (!grouped.has(apiName)) {
480
- grouped.set(apiName, []);
513
+ const key = `${request.spec.apiNamespace}/${request.spec.apiName}`;
514
+ if (!grouped.has(key)) {
515
+ grouped.set(key, []);
481
516
  }
482
- grouped.get(apiName).push(request);
517
+ grouped.get(key).push(request);
483
518
  });
484
519
  return grouped;
485
520
  };
486
521
  const groupedData = groupByApiProduct(tabData.data);
487
- const apiProducts = Array.from(groupedData.keys()).sort();
522
+ const apiProductKeys = Array.from(groupedData.keys()).sort();
488
523
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
489
524
  InfoCard,
490
525
  {
@@ -506,7 +541,7 @@ const ApprovalQueueCard = () => {
506
541
  /* @__PURE__ */ React.createElement(Tab, { label: `Pending (${pending.length})` }),
507
542
  /* @__PURE__ */ React.createElement(Tab, { label: `Rejected (${rejected.length})` })
508
543
  )),
509
- canUpdateRequests && selectedTab === 1 && selectedRequests.length > 0 && /* @__PURE__ */ React.createElement(Box, { mb: 2, display: "flex", alignItems: "center", justifyContent: "space-between", p: 2, bgcolor: "background.default" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, selectedRequests.length, " request", selectedRequests.length !== 1 ? "s" : "", " selected"), /* @__PURE__ */ React.createElement(Box, { display: "flex", style: { gap: 8 } }, /* @__PURE__ */ React.createElement(
544
+ selectedTab === 1 && selectedRequests.length > 0 && /* @__PURE__ */ React.createElement(Box, { mb: 2, display: "flex", alignItems: "center", justifyContent: "space-between", p: 2, bgcolor: "background.default" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, selectedRequests.length, " request", selectedRequests.length !== 1 ? "s" : "", " selected"), /* @__PURE__ */ React.createElement(Box, { display: "flex", style: { gap: 8 } }, /* @__PURE__ */ React.createElement(
510
545
  Button,
511
546
  {
512
547
  size: "small",
@@ -527,9 +562,12 @@ const ApprovalQueueCard = () => {
527
562
  },
528
563
  "Reject Selected"
529
564
  ))),
530
- tabData.data.length === 0 ? /* @__PURE__ */ React.createElement(Box, { p: 3, textAlign: "center" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body1", color: "textSecondary" }, selectedTab === 0 && "No approved requests.", selectedTab === 1 && "No pending requests.", selectedTab === 2 && "No rejected requests.")) : /* @__PURE__ */ React.createElement(Box, null, apiProducts.map((apiName) => {
531
- const requests = groupedData.get(apiName) || [];
532
- return /* @__PURE__ */ React.createElement(Accordion, { key: apiName, defaultExpanded: apiProducts.length === 1 }, /* @__PURE__ */ React.createElement(AccordionSummary, { expandIcon: /* @__PURE__ */ React.createElement(ExpandMoreIcon, null) }, /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React.createElement(Typography, { variant: "h6" }, apiName), /* @__PURE__ */ React.createElement(
565
+ tabData.data.length === 0 ? /* @__PURE__ */ React.createElement(Box, { p: 3, textAlign: "center" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body1", color: "textSecondary" }, selectedTab === 0 && "No approved requests.", selectedTab === 1 && "No pending requests.", selectedTab === 2 && "No rejected requests.")) : /* @__PURE__ */ React.createElement(Box, null, apiProductKeys.map((apiProductKey) => {
566
+ const requests = groupedData.get(apiProductKey) || [];
567
+ const displayName = requests[0]?.spec.apiName || apiProductKey;
568
+ const ownsThisApiProduct = value?.ownedApiProducts?.has(apiProductKey) ?? false;
569
+ const canSelectRows = canUpdateAllRequests || canUpdateOwnRequests && ownsThisApiProduct;
570
+ return /* @__PURE__ */ React.createElement(Accordion, { key: apiProductKey, defaultExpanded: apiProductKeys.length === 1 }, /* @__PURE__ */ React.createElement(AccordionSummary, { expandIcon: /* @__PURE__ */ React.createElement(ExpandMoreIcon, null) }, /* @__PURE__ */ React.createElement(Box, { display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%" }, /* @__PURE__ */ React.createElement(Typography, { variant: "h6" }, displayName), /* @__PURE__ */ React.createElement(
533
571
  Chip,
534
572
  {
535
573
  label: `${requests.length} request${requests.length !== 1 ? "s" : ""}`,
@@ -541,7 +579,7 @@ const ApprovalQueueCard = () => {
541
579
  Table,
542
580
  {
543
581
  options: {
544
- selection: canUpdateRequests && tabData.showSelection,
582
+ selection: canSelectRows && tabData.showSelection,
545
583
  paging: requests.length > 5,
546
584
  pageSize: 20,
547
585
  search: true,
@@ -555,7 +593,7 @@ const ApprovalQueueCard = () => {
555
593
  columns: tabData.columns,
556
594
  onSelectionChange: (rows) => {
557
595
  const otherSelections = selectedRequests.filter(
558
- (r) => r.spec.apiName !== apiName
596
+ (r) => `${r.spec.apiNamespace}/${r.spec.apiName}` !== apiProductKey
559
597
  );
560
598
  setSelectedRequests([...otherSelections, ...rows]);
561
599
  }
@@ -568,7 +606,8 @@ const ApprovalQueueCard = () => {
568
606
  open: dialogState.open,
569
607
  request: dialogState.request,
570
608
  action: dialogState.action,
571
- onClose: () => setDialogState({ open: false, request: null, action: "approve" }),
609
+ processing: dialogState.processing,
610
+ onClose: () => setDialogState({ open: false, request: null, action: "approve", processing: false }),
572
611
  onConfirm: handleConfirm
573
612
  }
574
613
  ), /* @__PURE__ */ React.createElement(
@@ -577,7 +616,8 @@ const ApprovalQueueCard = () => {
577
616
  open: bulkDialogState.open,
578
617
  requests: bulkDialogState.requests,
579
618
  action: bulkDialogState.action,
580
- onClose: () => setBulkDialogState({ open: false, requests: [], action: "approve" }),
619
+ processing: bulkDialogState.processing,
620
+ onClose: () => setBulkDialogState({ open: false, requests: [], action: "approve", processing: false }),
581
621
  onConfirm: handleBulkConfirm
582
622
  }
583
623
  ));