@anyshift/backstage-plugin-anyshift 0.1.0-beta.10 → 0.1.0-beta.12

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 (31) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +7 -0
  3. package/dist/api/AnyshiftApi.esm.js +38 -3
  4. package/dist/api/AnyshiftApi.esm.js.map +1 -1
  5. package/dist/components/ask/AskAnyshiftCard.esm.js +342 -0
  6. package/dist/components/ask/AskAnyshiftCard.esm.js.map +1 -0
  7. package/dist/components/ask/askPresenters.esm.js +1174 -0
  8. package/dist/components/ask/askPresenters.esm.js.map +1 -0
  9. package/dist/components/ask/askScope.esm.js +25 -0
  10. package/dist/components/ask/askScope.esm.js.map +1 -0
  11. package/dist/components/ask/askSuggestions.esm.js +41 -0
  12. package/dist/components/ask/askSuggestions.esm.js.map +1 -0
  13. package/dist/components/cloud/CloudImpactSection.esm.js +54 -0
  14. package/dist/components/cloud/CloudImpactSection.esm.js.map +1 -0
  15. package/dist/components/cloud/CloudRelationshipSections.esm.js +43 -0
  16. package/dist/components/cloud/CloudRelationshipSections.esm.js.map +1 -0
  17. package/dist/components/cloud/CloudResourceContent.esm.js +299 -172
  18. package/dist/components/cloud/CloudResourceContent.esm.js.map +1 -1
  19. package/dist/components/entity/AnyshiftEntityContent.esm.js +30 -12
  20. package/dist/components/entity/AnyshiftEntityContent.esm.js.map +1 -1
  21. package/dist/components/entity/SectionPresenters.esm.js +14 -7
  22. package/dist/components/entity/SectionPresenters.esm.js.map +1 -1
  23. package/dist/components/entity/entityIdentity.esm.js +30 -0
  24. package/dist/components/entity/entityIdentity.esm.js.map +1 -0
  25. package/dist/components/explorer/AnyshiftExplorerPage.esm.js +8 -3
  26. package/dist/components/explorer/AnyshiftExplorerPage.esm.js.map +1 -1
  27. package/dist/index.d.ts +3 -2
  28. package/dist/package.json.esm.js +1 -1
  29. package/dist/plugin.esm.js +6 -2
  30. package/dist/plugin.esm.js.map +1 -1
  31. package/package.json +2 -2
@@ -1,44 +1,82 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { useState, useCallback, useEffect } from 'react';
2
+ import { useState, useRef, useMemo, useLayoutEffect, useCallback, useEffect } from 'react';
3
3
  import { Button, Grid, Typography, CircularProgress, Chip } from '@material-ui/core';
4
4
  import { Alert } from '@material-ui/lab';
5
5
  import { Progress, InfoCard, Link } from '@backstage/core-components';
6
6
  import { useApi } from '@backstage/frontend-plugin-api';
7
- import { ANYSHIFT_RESOURCE_ID_ANNOTATION, ANYSHIFT_CLOUD_RESOURCE_ARN_ANNOTATION, ANYSHIFT_CLOUD_PROVIDER_ANNOTATION, ANYSHIFT_RESOURCE_TYPE_ANNOTATION, ANYSHIFT_CLOUD_REGION_ANNOTATION, ANYSHIFT_CLOUD_ACCOUNT_ANNOTATION } from '@anyshift/backstage-plugin-anyshift-common';
8
- import { useEntity, EntityRefLink } from '@backstage/plugin-catalog-react';
7
+ import { ANYSHIFT_CLOUD_PROVIDER_ANNOTATION, ANYSHIFT_RESOURCE_TYPE_ANNOTATION, ANYSHIFT_CLOUD_REGION_ANNOTATION, ANYSHIFT_CLOUD_ACCOUNT_ANNOTATION } from '@anyshift/backstage-plugin-anyshift-common';
8
+ import { useEntity } from '@backstage/plugin-catalog-react';
9
9
  import { anyshiftApiRef } from '../../api/AnyshiftApi.esm.js';
10
+ import { AskAnyshiftCard } from '../ask/AskAnyshiftCard.esm.js';
11
+ import { askScopeForEntity } from '../ask/askScope.esm.js';
12
+ import { cloudResourceIdentity } from '../entity/entityIdentity.esm.js';
13
+ import { CloudImpactSection } from './CloudImpactSection.esm.js';
14
+ import { CloudDependenciesSection, CloudDependentsSection } from './CloudRelationshipSections.esm.js';
10
15
 
11
- function cloudResourceIdentity(entity) {
12
- const annotations = entity.metadata.annotations ?? {};
13
- return annotations[ANYSHIFT_RESOURCE_ID_ANNOTATION] || annotations[ANYSHIFT_CLOUD_RESOURCE_ARN_ANNOTATION];
14
- }
15
- const CloudResourceContent = () => {
16
+ const CloudResourceContent = () => /* @__PURE__ */ jsx(CloudResourceEvidenceContent, {});
17
+ const CloudAnyshiftEntityContent = () => /* @__PURE__ */ jsx(CloudResourceEvidenceContent, { includeAsk: true });
18
+ const CloudResourceEvidenceContent = ({ includeAsk = false }) => {
16
19
  const { entity } = useEntity();
17
20
  const api = useApi(anyshiftApiRef);
18
21
  const resourceId = cloudResourceIdentity(entity);
19
- const [brief, setBrief] = useState();
22
+ const [loadedBrief, setLoadedBrief] = useState();
23
+ const brief = loadedBrief && loadedBrief.resourceId === resourceId ? loadedBrief.value : void 0;
20
24
  const [error, setError] = useState();
21
25
  const [refreshing, setRefreshing] = useState(false);
22
26
  const [loadingMore, setLoadingMore] = useState(false);
27
+ const [paginationError, setPaginationError] = useState();
28
+ const [capabilities, setCapabilities] = useState({
29
+ askEnabled: false,
30
+ queryEnabled: false
31
+ });
32
+ const operations = useRef({ identity: 0, primary: 0, pagination: 0 });
33
+ const askScope = useMemo(
34
+ () => includeAsk ? askScopeForEntity(entity) : void 0,
35
+ [entity, includeAsk]
36
+ );
37
+ useLayoutEffect(() => {
38
+ operations.current.identity += 1;
39
+ operations.current.primary += 1;
40
+ operations.current.pagination += 1;
41
+ setLoadedBrief(void 0);
42
+ setError(void 0);
43
+ setRefreshing(false);
44
+ setLoadingMore(false);
45
+ setPaginationError(void 0);
46
+ }, [resourceId]);
23
47
  const load = useCallback(
24
48
  async (refresh = false) => {
25
49
  if (!resourceId) return;
50
+ const identity = operations.current.identity;
51
+ const primary = ++operations.current.primary;
52
+ ++operations.current.pagination;
26
53
  setRefreshing(refresh);
54
+ setLoadingMore(false);
55
+ setPaginationError(void 0);
27
56
  setError(void 0);
28
57
  try {
29
- setBrief(
30
- await api.cloudResourceBrief(resourceId, {
31
- since: "24h",
32
- diff: true,
33
- refresh
34
- })
35
- );
58
+ const next = await api.cloudResourceBrief(resourceId, {
59
+ since: "24h",
60
+ diff: true,
61
+ ...refresh ? { refresh: true } : {}
62
+ });
63
+ if (operations.current.identity === identity && operations.current.primary === primary) {
64
+ ++operations.current.pagination;
65
+ setLoadingMore(false);
66
+ setLoadedBrief({ resourceId, value: next });
67
+ }
36
68
  } catch (loadError) {
37
- setError(
38
- loadError instanceof Error ? loadError.message : "Unable to load cloud evidence"
39
- );
69
+ if (operations.current.identity === identity && operations.current.primary === primary) {
70
+ ++operations.current.pagination;
71
+ setLoadingMore(false);
72
+ setError(
73
+ loadError instanceof Error ? loadError.message : "Unable to load cloud evidence"
74
+ );
75
+ }
40
76
  } finally {
41
- setRefreshing(false);
77
+ if (operations.current.identity === identity && operations.current.primary === primary) {
78
+ setRefreshing(false);
79
+ }
42
80
  }
43
81
  },
44
82
  [api, resourceId]
@@ -46,41 +84,71 @@ const CloudResourceContent = () => {
46
84
  useEffect(() => {
47
85
  void load();
48
86
  }, [load]);
87
+ useEffect(() => {
88
+ if (!includeAsk) return void 0;
89
+ let active = true;
90
+ api.capabilities().then((next) => active && setCapabilities(next)).catch(
91
+ () => active && setCapabilities({ askEnabled: false, queryEnabled: false })
92
+ );
93
+ return () => {
94
+ active = false;
95
+ };
96
+ }, [api, includeAsk]);
49
97
  const loadMore = useCallback(async () => {
50
98
  const cursor = brief?.changes.data?.page.nextCursor;
51
99
  if (!resourceId || !cursor) return;
100
+ const identity = operations.current.identity;
101
+ const primary = operations.current.primary;
102
+ const pagination = ++operations.current.pagination;
52
103
  setLoadingMore(true);
104
+ setPaginationError(void 0);
53
105
  try {
54
106
  const next = await api.cloudResourceBrief(resourceId, {
55
107
  since: brief.since,
56
108
  diff: true,
57
109
  cursor
58
110
  });
59
- setBrief((current) => {
60
- if (!current?.changes.data || next.changes.state !== "data" || !next.changes.data) {
111
+ if (operations.current.identity !== identity || operations.current.primary !== primary || operations.current.pagination !== pagination) {
112
+ return;
113
+ }
114
+ if (next.changes.state === "error") {
115
+ setPaginationError(
116
+ next.changes.message || "Unable to load more cloud changes"
117
+ );
118
+ return;
119
+ }
120
+ if (next.changes.state === "data" && next.changes.data) {
121
+ setPaginationError(void 0);
122
+ }
123
+ setLoadedBrief((current) => {
124
+ if (!current || current.resourceId !== resourceId || !current.value.changes.data || next.changes.state !== "data" || !next.changes.data) {
61
125
  return current;
62
126
  }
63
127
  return {
64
- ...current,
65
- refreshedAt: next.refreshedAt,
66
- changes: {
67
- state: "data",
68
- data: {
69
- ...next.changes.data,
70
- items: [
71
- ...current.changes.data.items,
72
- ...next.changes.data.items
73
- ]
128
+ resourceId,
129
+ value: {
130
+ ...current.value,
131
+ refreshedAt: next.refreshedAt,
132
+ changes: {
133
+ state: "data",
134
+ data: mergeCloudEventsEvidence(
135
+ current.value.changes.data,
136
+ next.changes.data
137
+ )
74
138
  }
75
139
  }
76
140
  };
77
141
  });
78
142
  } catch (loadError) {
79
- setError(
80
- loadError instanceof Error ? loadError.message : "Unable to load more cloud changes"
81
- );
143
+ if (operations.current.identity === identity && operations.current.primary === primary && operations.current.pagination === pagination) {
144
+ setPaginationError(
145
+ loadError instanceof Error ? loadError.message : "Unable to load more cloud changes"
146
+ );
147
+ }
82
148
  } finally {
83
- setLoadingMore(false);
149
+ if (operations.current.identity === identity && operations.current.primary === primary && operations.current.pagination === pagination) {
150
+ setLoadingMore(false);
151
+ }
84
152
  }
85
153
  }, [api, brief, resourceId]);
86
154
  if (!resourceId) {
@@ -103,10 +171,50 @@ const CloudResourceContent = () => {
103
171
  onRefresh: () => void load(true),
104
172
  loadingMore,
105
173
  onLoadMore: () => void loadMore(),
106
- error
174
+ error,
175
+ paginationError,
176
+ askEnabled: includeAsk && capabilities.askEnabled && Boolean(askScope),
177
+ askScope
107
178
  }
108
179
  );
109
180
  };
181
+ function mergeCloudEventsEvidence(current, next) {
182
+ return {
183
+ ...next,
184
+ availability: mergeCloudEventAvailability(
185
+ current.availability,
186
+ next.availability
187
+ ),
188
+ warnings: [.../* @__PURE__ */ new Set([...current.warnings, ...next.warnings])],
189
+ items: [...current.items, ...next.items]
190
+ };
191
+ }
192
+ function mergeCloudEventAvailability(current, next) {
193
+ const availability = {};
194
+ for (const field of /* @__PURE__ */ new Set([
195
+ ...Object.keys(current),
196
+ ...Object.keys(next)
197
+ ])) {
198
+ availability[field] = leastCompleteAvailability(
199
+ current[field],
200
+ next[field]
201
+ );
202
+ }
203
+ return availability;
204
+ }
205
+ function leastCompleteAvailability(current, next) {
206
+ const normalizedCurrent = normalizeAvailability(current);
207
+ const normalizedNext = normalizeAvailability(next);
208
+ return AVAILABILITY_RANK[normalizedCurrent] <= AVAILABILITY_RANK[normalizedNext] ? normalizedCurrent : normalizedNext;
209
+ }
210
+ const AVAILABILITY_RANK = {
211
+ unavailable: 0,
212
+ partial: 1,
213
+ available: 2
214
+ };
215
+ function normalizeAvailability(value) {
216
+ return value && value in AVAILABILITY_RANK ? value : "unavailable";
217
+ }
110
218
  const CloudResourceBriefView = ({
111
219
  entity,
112
220
  brief,
@@ -114,12 +222,12 @@ const CloudResourceBriefView = ({
114
222
  onRefresh,
115
223
  loadingMore,
116
224
  onLoadMore,
117
- error
225
+ error,
226
+ paginationError,
227
+ askEnabled = false,
228
+ askScope
118
229
  }) => {
119
230
  const annotations = entity.metadata.annotations ?? {};
120
- const relationships = (entity.relations ?? []).filter(
121
- (relation) => ["dependencyOf", "dependsOn"].includes(relation.type)
122
- );
123
231
  const consoleLink = entity.metadata.links?.find(
124
232
  (link) => link.url.startsWith("https://console.aws.amazon.com/")
125
233
  );
@@ -134,7 +242,6 @@ const CloudResourceBriefView = ({
134
242
  {
135
243
  size: "small",
136
244
  onClick: onRefresh,
137
- disabled: refreshing,
138
245
  startIcon: refreshing ? /* @__PURE__ */ jsx(CircularProgress, { size: 14 }) : void 0,
139
246
  children: "Refresh"
140
247
  }
@@ -148,17 +255,7 @@ const CloudResourceBriefView = ({
148
255
  ] })
149
256
  }
150
257
  ) }),
151
- /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, children: /* @__PURE__ */ jsx(
152
- ChangesSection,
153
- {
154
- section: brief.changes,
155
- loadingMore,
156
- onLoadMore
157
- }
158
- ) }),
159
- /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, md: 6, children: /* @__PURE__ */ jsx(ProvenanceSection, { section: brief.provenance }) }),
160
- /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, md: 6, children: /* @__PURE__ */ jsx(DriftSection, { section: brief.drift }) }),
161
- /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, md: 6, children: /* @__PURE__ */ jsxs(InfoCard, { title: "Cloud identity", children: [
258
+ /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, children: /* @__PURE__ */ jsxs(InfoCard, { title: "Cloud identity", children: [
162
259
  /* @__PURE__ */ jsx(
163
260
  Definition,
164
261
  {
@@ -187,137 +284,167 @@ const CloudResourceBriefView = ({
187
284
  value: annotations[ANYSHIFT_CLOUD_ACCOUNT_ANNOTATION]
188
285
  }
189
286
  ),
287
+ /* @__PURE__ */ jsx(Definition, { label: "Stable resource ID", value: brief.resourceId }),
190
288
  consoleLink && /* @__PURE__ */ jsx(Link, { to: consoleLink.url, children: "Open in AWS Console" })
191
289
  ] }) }),
192
- /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, md: 6, children: /* @__PURE__ */ jsxs(InfoCard, { title: "Catalog context", children: [
193
- /* @__PURE__ */ jsx(
194
- Definition,
195
- {
196
- label: "Owner",
197
- value: typeof entity.spec?.owner === "string" ? entity.spec.owner : void 0
198
- }
199
- ),
200
- /* @__PURE__ */ jsx(
201
- Definition,
202
- {
203
- label: "Namespace",
204
- value: entity.metadata.namespace || "default"
205
- }
206
- ),
207
- /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", children: "Relationships" }),
208
- relationships.length === 0 ? /* @__PURE__ */ jsx(Typography, { color: "textSecondary", children: "No component or resource relationships are available yet." }) : /* @__PURE__ */ jsx("ul", { children: relationships.map((relation) => /* @__PURE__ */ jsxs("li", { children: [
209
- /* @__PURE__ */ jsx(
210
- Chip,
211
- {
212
- size: "small",
213
- label: relation.type,
214
- style: { marginRight: 8 }
215
- }
216
- ),
217
- /* @__PURE__ */ jsx(EntityRefLink, { entityRef: relation.targetRef })
218
- ] }, `${relation.type}:${relation.targetRef}`)) })
219
- ] }) }),
220
- /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, children: /* @__PURE__ */ jsx(InfoCard, { title: "Stable resource identity", children: /* @__PURE__ */ jsx(
221
- Typography,
290
+ /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, md: 6, children: /* @__PURE__ */ jsx(CloudDependenciesSection, { entity }) }),
291
+ /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, md: 6, children: /* @__PURE__ */ jsx(CloudDependentsSection, { entity }) }),
292
+ /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, children: /* @__PURE__ */ jsx(CloudImpactSection, { section: brief.impact, onRetry: onRefresh }) }),
293
+ /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, children: /* @__PURE__ */ jsx(
294
+ ChangesSection,
222
295
  {
223
- component: "code",
224
- style: { overflowWrap: "anywhere", whiteSpace: "pre-wrap" },
225
- children: brief.resourceId
296
+ section: brief.changes,
297
+ loadingMore,
298
+ onLoadMore,
299
+ paginationError
226
300
  }
227
- ) }) })
301
+ ) }),
302
+ /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, md: 6, children: /* @__PURE__ */ jsx(ProvenanceSection, { section: brief.provenance }) }),
303
+ /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, md: 6, children: /* @__PURE__ */ jsx(DriftSection, { section: brief.drift }) }),
304
+ askEnabled && /* @__PURE__ */ jsx(Grid, { item: true, xs: 12, children: /* @__PURE__ */ jsx(AskAnyshiftCard, { context: "entity", scope: askScope }) })
228
305
  ] });
229
306
  };
230
307
  const ChangesSection = ({
231
308
  section,
232
309
  loadingMore,
233
- onLoadMore
234
- }) => /* @__PURE__ */ jsx(InfoCard, { title: "Recent cloud changes", children: /* @__PURE__ */ jsxs(SectionState, { section, children: [
235
- section.data?.warnings?.map((warning) => /* @__PURE__ */ jsx(Alert, { severity: "warning", style: { marginBottom: 8 }, children: humanize(warning) }, warning)),
236
- section.data?.items?.length ? section.data.items.map((event) => /* @__PURE__ */ jsxs("div", { style: { marginBottom: 20 }, children: [
237
- /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", children: event.summary || humanize(event.type) }),
238
- /* @__PURE__ */ jsxs(Typography, { color: "textSecondary", children: [
239
- formatCloudTimestamp(event.timestamp),
240
- " \xB7 ",
241
- event.category,
242
- " \xB7",
243
- " ",
244
- event.evidence.source,
245
- " \xB7 ",
246
- event.evidence.status,
247
- event.actor?.name || event.actor?.id ? ` \xB7 ${event.actor.name || event.actor.id}` : ""
248
- ] }),
249
- event.change.fields.length > 0 && /* @__PURE__ */ jsxs("details", { children: [
250
- /* @__PURE__ */ jsx("summary", { children: "View sanitized field changes" }),
251
- /* @__PURE__ */ jsx("ul", { children: event.change.fields.map((field) => {
252
- const detail = event.change.details.find(
253
- (candidate) => candidate.field === field
254
- );
255
- return /* @__PURE__ */ jsxs("li", { children: [
256
- /* @__PURE__ */ jsx("code", { children: field }),
257
- detail ? `: ${detail.before ?? "\u2205"} \u2192 ${detail.after ?? "\u2205"}` : ""
258
- ] }, field);
259
- }) })
260
- ] })
261
- ] }, event.id)) : /* @__PURE__ */ jsx(Typography, { color: "textSecondary", children: "No cloud changes were found in this window." }),
262
- section.data?.page.hasMore && onLoadMore && /* @__PURE__ */ jsx(
263
- Button,
264
- {
265
- size: "small",
266
- onClick: onLoadMore,
267
- disabled: loadingMore,
268
- startIcon: loadingMore ? /* @__PURE__ */ jsx(CircularProgress, { size: 14 }) : void 0,
269
- children: "Load more changes"
270
- }
271
- )
272
- ] }) });
310
+ onLoadMore,
311
+ paginationError
312
+ }) => {
313
+ const { hasEvidenceLimitations, actionableWarnings } = partitionCloudEventWarnings(section.data?.warnings);
314
+ if (section.state === "empty") {
315
+ return /* @__PURE__ */ jsx(InfoCard, { title: "Recent cloud changes", children: /* @__PURE__ */ jsx(Typography, { color: "textSecondary", children: section.message || "No cloud changes were found in this window." }) });
316
+ }
317
+ return /* @__PURE__ */ jsx(InfoCard, { title: "Recent cloud changes", children: /* @__PURE__ */ jsxs(SectionState, { section, children: [
318
+ paginationError && /* @__PURE__ */ jsx(Alert, { severity: "error", style: { marginBottom: 8 }, children: paginationError }),
319
+ hasEvidenceLimitations && /* @__PURE__ */ jsx(
320
+ Typography,
321
+ {
322
+ "aria-label": "Cloud event evidence limitations",
323
+ variant: "caption",
324
+ color: "textSecondary",
325
+ display: "block",
326
+ style: { marginBottom: 8 },
327
+ children: "Event source is derived from graph relationships, and event status may be incomplete."
328
+ }
329
+ ),
330
+ actionableWarnings.map((warning) => /* @__PURE__ */ jsx(Alert, { severity: "warning", style: { marginBottom: 8 }, children: humanize(warning) }, warning)),
331
+ section.data?.items?.length ? section.data.items.map((event) => /* @__PURE__ */ jsxs("div", { style: { marginBottom: 20 }, children: [
332
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", children: event.summary || humanize(event.type) }),
333
+ /* @__PURE__ */ jsxs(Typography, { color: "textSecondary", children: [
334
+ formatCloudTimestamp(event.timestamp),
335
+ " \xB7 ",
336
+ event.category,
337
+ " \xB7",
338
+ " ",
339
+ event.evidence.source,
340
+ " \xB7 ",
341
+ event.evidence.status,
342
+ event.actor?.name || event.actor?.id ? ` \xB7 ${event.actor.name || event.actor.id}` : ""
343
+ ] }),
344
+ event.change.fields.length > 0 && /* @__PURE__ */ jsxs("details", { children: [
345
+ /* @__PURE__ */ jsx("summary", { children: "View sanitized field changes" }),
346
+ /* @__PURE__ */ jsx("ul", { children: event.change.fields.map((field) => {
347
+ const detail = event.change.details.find(
348
+ (candidate) => candidate.field === field
349
+ );
350
+ return /* @__PURE__ */ jsxs("li", { children: [
351
+ /* @__PURE__ */ jsx("code", { children: field }),
352
+ detail ? `: ${detail.before ?? "\u2205"} \u2192 ${detail.after ?? "\u2205"}` : ""
353
+ ] }, field);
354
+ }) })
355
+ ] })
356
+ ] }, event.id)) : /* @__PURE__ */ jsx(Typography, { color: "textSecondary", children: "No cloud changes were found in this window." }),
357
+ section.data?.page.hasMore && onLoadMore && /* @__PURE__ */ jsx(
358
+ Button,
359
+ {
360
+ size: "small",
361
+ onClick: onLoadMore,
362
+ disabled: loadingMore,
363
+ startIcon: loadingMore ? /* @__PURE__ */ jsx(CircularProgress, { size: 14 }) : void 0,
364
+ children: "Load more changes"
365
+ }
366
+ )
367
+ ] }) });
368
+ };
369
+ const QUIET_EVIDENCE_LIMITATIONS = /* @__PURE__ */ new Set([
370
+ "event_source_derived",
371
+ "event_status_incomplete"
372
+ ]);
373
+ function partitionCloudEventWarnings(warnings) {
374
+ const allWarnings = warnings ?? [];
375
+ return {
376
+ hasEvidenceLimitations: allWarnings.some(
377
+ (warning) => QUIET_EVIDENCE_LIMITATIONS.has(warning)
378
+ ),
379
+ actionableWarnings: allWarnings.filter(
380
+ (warning) => !QUIET_EVIDENCE_LIMITATIONS.has(warning)
381
+ )
382
+ };
383
+ }
273
384
  const ProvenanceSection = ({
274
385
  section
275
- }) => /* @__PURE__ */ jsx(InfoCard, { title: "IaC provenance", children: /* @__PURE__ */ jsx(SectionState, { section, children: section.data?.resources?.length ? section.data.resources.map((resource) => /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
276
- /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", children: resource.terraform.address }),
277
- /* @__PURE__ */ jsx(Chip, { size: "small", label: humanize(resource.status) }),
278
- /* @__PURE__ */ jsx(Definition, { label: "File", value: resource.terraform.fileRef ?? "" }),
279
- /* @__PURE__ */ jsx(
280
- Definition,
281
- {
282
- label: "State links",
283
- value: String(resource.states.length)
284
- }
285
- ),
286
- /* @__PURE__ */ jsx(
287
- Definition,
288
- {
289
- label: "Cloud links",
290
- value: String(resource.clouds.length)
291
- }
292
- ),
293
- resource.terraform.githubUrl && /* @__PURE__ */ jsx(Link, { to: resource.terraform.githubUrl, children: "Open source" })
294
- ] }, resource.terraform.hashedID)) : /* @__PURE__ */ jsx(Typography, { color: "textSecondary", children: "No Terraform provenance is linked to this resource." }) }) });
386
+ }) => {
387
+ if (section.state === "empty") {
388
+ return /* @__PURE__ */ jsx(InfoCard, { title: "IaC provenance", children: /* @__PURE__ */ jsx(Typography, { color: "textSecondary", children: section.message || "No Terraform provenance is linked to this resource." }) });
389
+ }
390
+ return /* @__PURE__ */ jsx(InfoCard, { title: "IaC provenance", children: /* @__PURE__ */ jsx(SectionState, { section, children: section.data?.resources?.length ? section.data.resources.map((resource) => /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
391
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", children: resource.terraform.address }),
392
+ /* @__PURE__ */ jsx(Chip, { size: "small", label: humanize(resource.status) }),
393
+ /* @__PURE__ */ jsx(
394
+ Definition,
395
+ {
396
+ label: "File",
397
+ value: resource.terraform.fileRef ?? ""
398
+ }
399
+ ),
400
+ /* @__PURE__ */ jsx(
401
+ Definition,
402
+ {
403
+ label: "State links",
404
+ value: String(resource.states.length)
405
+ }
406
+ ),
407
+ /* @__PURE__ */ jsx(
408
+ Definition,
409
+ {
410
+ label: "Cloud links",
411
+ value: String(resource.clouds.length)
412
+ }
413
+ ),
414
+ resource.terraform.githubUrl && /* @__PURE__ */ jsx(Link, { to: resource.terraform.githubUrl, children: "Open source" })
415
+ ] }, resource.terraform.hashedID)) : /* @__PURE__ */ jsx(Typography, { color: "textSecondary", children: "No Terraform provenance is linked to this resource." }) }) });
416
+ };
295
417
  const DriftSection = ({
296
418
  section
297
- }) => /* @__PURE__ */ jsx(InfoCard, { title: "IaC drift evidence", children: /* @__PURE__ */ jsx(SectionState, { section, children: section.data?.items?.length ? section.data.items.map((item) => /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
298
- /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", children: item.terraform.address }),
299
- /* @__PURE__ */ jsx(
300
- Chip,
301
- {
302
- size: "small",
303
- color: item.status === "drifted" ? "secondary" : "default",
304
- label: humanize(item.status)
305
- }
306
- ),
307
- /* @__PURE__ */ jsx(Typography, { color: "textSecondary", children: item.status === "unknown" ? `Unknown is not drift: ${humanize(item.reason)}.` : `${item.comparedFields} supported fields compared.` }),
308
- item.differences.length > 0 && /* @__PURE__ */ jsxs("details", { children: [
309
- /* @__PURE__ */ jsx("summary", { children: "View sanitized differences" }),
310
- /* @__PURE__ */ jsx("ul", { children: item.differences.map((difference) => /* @__PURE__ */ jsxs("li", { children: [
311
- /* @__PURE__ */ jsx("code", { children: difference.field }),
312
- ":",
313
- " ",
314
- difference.lastApplied ?? "\u2205",
315
- " \u2192",
316
- " ",
317
- difference.observed ?? "\u2205"
318
- ] }, difference.field)) })
319
- ] })
320
- ] }, item.terraform.hashedID)) : /* @__PURE__ */ jsx(Typography, { color: "textSecondary", children: "No comparable Terraform evidence is available." }) }) });
419
+ }) => {
420
+ if (section.state === "empty") {
421
+ return /* @__PURE__ */ jsx(InfoCard, { title: "IaC drift evidence", children: /* @__PURE__ */ jsx(Typography, { color: "textSecondary", children: section.message || "No comparable Terraform evidence is available." }) });
422
+ }
423
+ return /* @__PURE__ */ jsx(InfoCard, { title: "IaC drift evidence", children: /* @__PURE__ */ jsx(SectionState, { section, children: section.data?.items?.length ? section.data.items.map((item) => /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
424
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", children: item.terraform.address }),
425
+ /* @__PURE__ */ jsx(
426
+ Chip,
427
+ {
428
+ size: "small",
429
+ color: item.status === "drifted" ? "secondary" : "default",
430
+ label: humanize(item.status)
431
+ }
432
+ ),
433
+ /* @__PURE__ */ jsx(Typography, { color: "textSecondary", children: item.status === "unknown" ? `Unknown is not drift: ${humanize(item.reason)}.` : `${item.comparedFields} supported fields compared.` }),
434
+ item.differences.length > 0 && /* @__PURE__ */ jsxs("details", { children: [
435
+ /* @__PURE__ */ jsx("summary", { children: "View sanitized differences" }),
436
+ /* @__PURE__ */ jsx("ul", { children: item.differences.map((difference) => /* @__PURE__ */ jsxs("li", { children: [
437
+ /* @__PURE__ */ jsx("code", { children: difference.field }),
438
+ ":",
439
+ " ",
440
+ difference.lastApplied ?? "\u2205",
441
+ " \u2192",
442
+ " ",
443
+ difference.observed ?? "\u2205"
444
+ ] }, difference.field)) })
445
+ ] })
446
+ ] }, item.terraform.hashedID)) : /* @__PURE__ */ jsx(Typography, { color: "textSecondary", children: "No comparable Terraform evidence is available." }) }) });
447
+ };
321
448
  const SectionState = ({
322
449
  section,
323
450
  children
@@ -354,5 +481,5 @@ function formatCloudTimestamp(value) {
354
481
  return Number.isNaN(normalizedTimestamp) ? value : new Date(normalizedTimestamp).toLocaleString();
355
482
  }
356
483
 
357
- export { CloudResourceBriefView, CloudResourceContent, cloudResourceIdentity, formatCloudTimestamp };
484
+ export { CloudAnyshiftEntityContent, CloudResourceBriefView, CloudResourceContent, formatCloudTimestamp, mergeCloudEventsEvidence };
358
485
  //# sourceMappingURL=CloudResourceContent.esm.js.map