@anyshift/backstage-plugin-anyshift 0.1.0-beta.13 → 0.1.0-beta.15

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/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.1.0-beta.15
6
+
7
+ - Resolve Affected footprint entries against one Backstage catalog snapshot and link only entities
8
+ that are proven to exist.
9
+ - Repair stale runtime-namespace links, de-duplicate logical names, and keep unresolved graph
10
+ evidence visible as plain text instead of dead links.
11
+
12
+ ## 0.1.0-beta.14
13
+
14
+ - Expose available APM HTTP methods and templated paths from service `CALLS_TO` edges through a compact architecture-diagram affordance and source-grouped evidence inspector.
15
+ - Keep missing operation or observation metadata explicitly unknown without changing legacy edge rendering or adding per-edge Graph API requests.
16
+
5
17
  ## 0.1.0-beta.13
6
18
 
7
19
  - Keep zero-event cloud-resource pages quiet: identity-completeness codes do not become alerts
@@ -1,15 +1,16 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { useState, useRef, useCallback, useEffect } from 'react';
2
+ import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
3
3
  import { Typography, Box, Chip, Button, CircularProgress, Grid } from '@material-ui/core';
4
4
  import { Progress } from '@backstage/core-components';
5
- import { useApi } from '@backstage/frontend-plugin-api';
5
+ import { useApi, useApiHolder } from '@backstage/frontend-plugin-api';
6
6
  import { ANYSHIFT_KIND_ANNOTATION, ANYSHIFT_CONFIGURATION_ENDPOINTS_ANNOTATION, ANYSHIFT_CONFIGURATION_DEPENDENCIES_ANNOTATION, ANYSHIFT_RUNTIME_RESOURCE_ID_ANNOTATION } from '@anyshift/backstage-plugin-anyshift-common';
7
- import { useEntity } from '@backstage/plugin-catalog-react';
7
+ import { useEntity, catalogApiRef } from '@backstage/plugin-catalog-react';
8
8
  import { anyshiftApiRef } from '../../api/AnyshiftApi.esm.js';
9
9
  import { AskAnyshiftCard } from '../ask/AskAnyshiftCard.esm.js';
10
10
  import { askScopeForEntity } from '../ask/askScope.esm.js';
11
11
  import { CloudAnyshiftEntityContent } from '../cloud/CloudResourceContent.esm.js';
12
12
  import { isCloudResourceEntity, entityTarget } from './entityIdentity.esm.js';
13
+ import { resolveRelatedCatalogEntities } from './relatedCatalogEntities.esm.js';
13
14
  import { OverviewSection, WhyNowSection, DatadogSignalSection, ArchitectureSection, DependencySection, BriefRail, TimelineSection } from './BriefSections.esm.js';
14
15
 
15
16
  const AnyshiftEntityContent = () => {
@@ -19,6 +20,7 @@ const AnyshiftEntityContent = () => {
19
20
  const ServiceAnyshiftEntityContent = () => {
20
21
  const { entity } = useEntity();
21
22
  const api = useApi(anyshiftApiRef);
23
+ const catalogApi = useApiHolder().get(catalogApiRef);
22
24
  const annotations = entity.metadata.annotations ?? {};
23
25
  const target = entityTarget(entity);
24
26
  const kind = annotations[ANYSHIFT_KIND_ANNOTATION] || "workload";
@@ -27,6 +29,7 @@ const ServiceAnyshiftEntityContent = () => {
27
29
  const configurationDependencies = annotations[ANYSHIFT_CONFIGURATION_DEPENDENCIES_ANNOTATION];
28
30
  const configurationTarget = annotations[ANYSHIFT_RUNTIME_RESOURCE_ID_ANNOTATION];
29
31
  const [brief, setBrief] = useState();
32
+ const [catalogEntities, setCatalogEntities] = useState([]);
30
33
  const [capabilities, setCapabilities] = useState({
31
34
  askEnabled: false,
32
35
  queryEnabled: false
@@ -115,6 +118,26 @@ const ServiceAnyshiftEntityContent = () => {
115
118
  active.current = false;
116
119
  };
117
120
  }, [load]);
121
+ useEffect(() => {
122
+ let cancelled = false;
123
+ if (!catalogApi) {
124
+ setCatalogEntities([]);
125
+ return () => {
126
+ cancelled = true;
127
+ };
128
+ }
129
+ catalogApi.getEntities({
130
+ filter: [{ kind: "Component" }, { kind: "Resource" }],
131
+ fields: ["kind", "metadata.name", "metadata.namespace"]
132
+ }).then((response) => {
133
+ if (!cancelled) setCatalogEntities(response.items);
134
+ }).catch(() => {
135
+ if (!cancelled) setCatalogEntities([]);
136
+ });
137
+ return () => {
138
+ cancelled = true;
139
+ };
140
+ }, [catalogApi]);
118
141
  useEffect(() => {
119
142
  api.capabilities().then((next) => {
120
143
  if (active.current) setCapabilities(next);
@@ -124,12 +147,31 @@ const ServiceAnyshiftEntityContent = () => {
124
147
  }
125
148
  });
126
149
  }, [api]);
127
- if (error && !brief) return /* @__PURE__ */ jsx(Typography, { color: "error", children: error });
128
- if (!brief) return /* @__PURE__ */ jsx(Progress, {});
150
+ const resolvedBrief = useMemo(() => {
151
+ if (!brief || brief.impact.state !== "data" || !brief.impact.data)
152
+ return brief;
153
+ const impactData = brief.impact.data;
154
+ return {
155
+ ...brief,
156
+ impact: {
157
+ ...brief.impact,
158
+ data: {
159
+ ...impactData,
160
+ relatedEntities: resolveRelatedCatalogEntities(
161
+ impactData.relatedEntities,
162
+ catalogEntities
163
+ )
164
+ }
165
+ }
166
+ };
167
+ }, [brief, catalogEntities]);
168
+ if (error && !resolvedBrief)
169
+ return /* @__PURE__ */ jsx(Typography, { color: "error", children: error });
170
+ if (!resolvedBrief) return /* @__PURE__ */ jsx(Progress, {});
129
171
  return /* @__PURE__ */ jsx(
130
172
  ServiceBriefView,
131
173
  {
132
- brief,
174
+ brief: resolvedBrief,
133
175
  onRetry: retrySection,
134
176
  refreshing,
135
177
  onRefresh: () => void load(true),
@@ -1 +1 @@
1
- {"version":3,"file":"AnyshiftEntityContent.esm.js","sources":["../../../src/components/entity/AnyshiftEntityContent.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport {\n Box,\n Button,\n Chip,\n CircularProgress,\n Grid,\n Typography,\n} from '@material-ui/core';\nimport { Progress } from '@backstage/core-components';\nimport { useApi } from '@backstage/frontend-plugin-api';\nimport {\n ANYSHIFT_CONFIGURATION_DEPENDENCIES_ANNOTATION,\n ANYSHIFT_CONFIGURATION_ENDPOINTS_ANNOTATION,\n ANYSHIFT_KIND_ANNOTATION,\n ANYSHIFT_RUNTIME_RESOURCE_ID_ANNOTATION,\n} from '@anyshift/backstage-plugin-anyshift-common';\nimport { useEntity } from '@backstage/plugin-catalog-react';\nimport { anyshiftApiRef } from '../../api/AnyshiftApi';\nimport type {\n AnyshiftCapabilities,\n AskScope,\n BriefSectionName,\n ServiceBrief,\n} from '../../api/types';\nimport { AskAnyshiftCard } from '../ask/AskAnyshiftCard';\nimport { askScopeForEntity } from '../ask/askScope';\nimport { CloudAnyshiftEntityContent } from '../cloud/CloudResourceContent';\nimport { entityTarget, isCloudResourceEntity } from './entityIdentity';\nimport {\n ArchitectureSection,\n BriefRail,\n DatadogSignalSection,\n DependencySection,\n OverviewSection,\n TimelineSection,\n WhyNowSection,\n} from './BriefSections';\n\nexport const AnyshiftEntityContent = () => {\n const { entity } = useEntity();\n return isCloudResourceEntity(entity) ? (\n <CloudAnyshiftEntityContent />\n ) : (\n <ServiceAnyshiftEntityContent />\n );\n};\n\nexport const ServiceAnyshiftEntityContent = () => {\n const { entity } = useEntity();\n const api = useApi(anyshiftApiRef);\n const annotations = entity.metadata.annotations ?? {};\n const target = entityTarget(entity);\n const kind = annotations[ANYSHIFT_KIND_ANNOTATION] || 'workload';\n const namespace = entity.metadata.namespace;\n const configurationEndpoints =\n annotations[ANYSHIFT_CONFIGURATION_ENDPOINTS_ANNOTATION];\n const configurationDependencies =\n annotations[ANYSHIFT_CONFIGURATION_DEPENDENCIES_ANNOTATION];\n const configurationTarget =\n annotations[ANYSHIFT_RUNTIME_RESOURCE_ID_ANNOTATION];\n const [brief, setBrief] = useState<ServiceBrief>();\n const [capabilities, setCapabilities] = useState<AnyshiftCapabilities>({\n askEnabled: false,\n queryEnabled: false,\n });\n const [error, setError] = useState<string>();\n const [refreshingAll, setRefreshingAll] = useState(false);\n const [refreshing, setRefreshing] = useState<Set<BriefSectionName>>(\n new Set(),\n );\n const active = useRef(true);\n\n const load = useCallback(\n async (refresh = false) => {\n setError(undefined);\n if (refresh) setRefreshingAll(true);\n try {\n const next = await api.brief(kind, target, {\n namespace,\n refresh,\n configurationEndpoints,\n configurationDependencies,\n configurationTarget,\n });\n if (active.current) setBrief(next);\n } catch (cause) {\n if (active.current)\n setError(cause instanceof Error ? cause.message : String(cause));\n } finally {\n if (active.current) setRefreshingAll(false);\n }\n },\n [\n api,\n configurationDependencies,\n configurationEndpoints,\n configurationTarget,\n kind,\n namespace,\n target,\n ],\n );\n\n const retrySection = useCallback(\n async (section: BriefSectionName) => {\n setRefreshing(current => new Set(current).add(section));\n try {\n const response = await api.briefSection(kind, target, section, {\n namespace,\n refresh: true,\n configurationEndpoints,\n configurationDependencies,\n configurationTarget,\n });\n if (active.current) {\n setBrief(current =>\n current\n ? {\n ...current,\n [section]: response.value,\n refreshedAt: response.refreshedAt,\n ...(response.observedService\n ? { observedService: response.observedService }\n : {}),\n }\n : current,\n );\n }\n } finally {\n if (active.current) {\n setRefreshing(current => {\n const next = new Set(current);\n next.delete(section);\n return next;\n });\n }\n }\n },\n [\n api,\n configurationDependencies,\n configurationEndpoints,\n configurationTarget,\n kind,\n namespace,\n target,\n ],\n );\n\n useEffect(() => {\n active.current = true;\n void load();\n return () => {\n active.current = false;\n };\n }, [load]);\n\n useEffect(() => {\n api\n .capabilities()\n .then(next => {\n if (active.current) setCapabilities(next);\n })\n .catch(() => {\n if (active.current) {\n setCapabilities({ askEnabled: false, queryEnabled: false });\n }\n });\n }, [api]);\n\n if (error && !brief) return <Typography color=\"error\">{error}</Typography>;\n if (!brief) return <Progress />;\n return (\n <ServiceBriefView\n brief={brief}\n onRetry={retrySection}\n refreshing={refreshing}\n onRefresh={() => void load(true)}\n refreshingAll={refreshingAll}\n refreshError={error}\n askEnabled={capabilities.askEnabled}\n askScope={askScopeForEntity(entity)}\n />\n );\n};\n\nexport const ServiceBriefView = ({\n brief,\n onRetry,\n refreshing = new Set(),\n onRefresh = () => {},\n refreshingAll = false,\n refreshError,\n askEnabled = false,\n askScope,\n}: {\n brief: ServiceBrief;\n onRetry: (section: BriefSectionName) => void;\n refreshing?: ReadonlySet<BriefSectionName>;\n onRefresh?: () => void;\n refreshingAll?: boolean;\n refreshError?: string;\n askEnabled?: boolean;\n askScope?: AskScope;\n}) => (\n <>\n <Box\n mb={3}\n px={2.5}\n py={2}\n borderLeft={4}\n borderColor=\"primary.main\"\n bgcolor=\"background.paper\"\n >\n <Box display=\"flex\" justifyContent=\"space-between\" gridGap={16}>\n <Box>\n <Typography variant=\"overline\" color=\"textSecondary\">\n Anyshift evidence trace\n </Typography>\n <Box display=\"flex\" flexWrap=\"wrap\" alignItems=\"center\" gridGap={8}>\n <Chip size=\"small\" label={`Workload · ${brief.target}`} />\n <Typography aria-hidden=\"true\" color=\"primary\">\n →\n </Typography>\n <Chip\n size=\"small\"\n color=\"primary\"\n label={`Observed service · ${\n brief.observedService || 'Not resolved'\n }`}\n />\n </Box>\n <Typography variant=\"caption\" color=\"textSecondary\">\n Runtime namespace: {brief.namespace || 'Not set'} · Refreshed{' '}\n {formatTimestamp(brief.refreshedAt)}\n </Typography>\n </Box>\n <Button\n type=\"button\"\n variant=\"outlined\"\n size=\"small\"\n onClick={onRefresh}\n disabled={refreshingAll}\n startIcon={refreshingAll ? <CircularProgress size={14} /> : undefined}\n >\n {refreshingAll ? 'Refreshing…' : 'Refresh brief'}\n </Button>\n </Box>\n {refreshError && (\n <Typography color=\"error\" variant=\"caption\">\n Refresh failed: {refreshError}. Existing evidence is still shown.\n </Typography>\n )}\n </Box>\n\n <Grid container spacing={3} alignItems=\"stretch\">\n <Grid item xs={12}>\n <OverviewSection\n section={brief.overview}\n onRetry={onRetry}\n refreshing={refreshing.has('overview')}\n />\n </Grid>\n <Grid item xs={12}>\n <WhyNowSection\n section={brief.whyNow}\n onRetry={onRetry}\n refreshing={refreshing.has('whyNow')}\n />\n </Grid>\n {askEnabled && (\n <Grid item xs={12}>\n <AskAnyshiftCard context=\"entity\" scope={askScope} />\n </Grid>\n )}\n <Grid item xs={12}>\n <DatadogSignalSection\n section={brief.observability}\n onRetry={onRetry}\n refreshing={refreshing.has('observability')}\n />\n </Grid>\n <Grid item xs={12}>\n <ArchitectureSection\n section={brief.architecture}\n onRetry={onRetry}\n refreshing={refreshing.has('architecture')}\n />\n </Grid>\n <Grid item xs={12} md={8}>\n <DependencySection\n section={brief.topology}\n onRetry={onRetry}\n refreshing={refreshing.has('topology')}\n />\n </Grid>\n <Grid item xs={12} md={4}>\n <BriefRail brief={brief} onRetry={onRetry} refreshing={refreshing} />\n </Grid>\n <Grid item xs={12}>\n <TimelineSection\n section={brief.timeline}\n onRetry={onRetry}\n refreshing={refreshing.has('timeline')}\n />\n </Grid>\n </Grid>\n </>\n);\n\nfunction formatTimestamp(value: string): string {\n const parsed = Date.parse(value);\n return Number.isNaN(parsed) ? value : new Date(parsed).toLocaleString();\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AnyshiftEntityContent.esm.js","sources":["../../../src/components/entity/AnyshiftEntityContent.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n Box,\n Button,\n Chip,\n CircularProgress,\n Grid,\n Typography,\n} from '@material-ui/core';\nimport { Progress } from '@backstage/core-components';\nimport { useApi, useApiHolder } from '@backstage/frontend-plugin-api';\nimport type { Entity } from '@backstage/catalog-model';\nimport {\n ANYSHIFT_CONFIGURATION_DEPENDENCIES_ANNOTATION,\n ANYSHIFT_CONFIGURATION_ENDPOINTS_ANNOTATION,\n ANYSHIFT_KIND_ANNOTATION,\n ANYSHIFT_RUNTIME_RESOURCE_ID_ANNOTATION,\n} from '@anyshift/backstage-plugin-anyshift-common';\nimport { catalogApiRef, useEntity } from '@backstage/plugin-catalog-react';\nimport { anyshiftApiRef } from '../../api/AnyshiftApi';\nimport type {\n AnyshiftCapabilities,\n AskScope,\n BriefSectionName,\n ServiceBrief,\n} from '../../api/types';\nimport { AskAnyshiftCard } from '../ask/AskAnyshiftCard';\nimport { askScopeForEntity } from '../ask/askScope';\nimport { CloudAnyshiftEntityContent } from '../cloud/CloudResourceContent';\nimport { entityTarget, isCloudResourceEntity } from './entityIdentity';\nimport { resolveRelatedCatalogEntities } from './relatedCatalogEntities';\nimport {\n ArchitectureSection,\n BriefRail,\n DatadogSignalSection,\n DependencySection,\n OverviewSection,\n TimelineSection,\n WhyNowSection,\n} from './BriefSections';\n\nexport const AnyshiftEntityContent = () => {\n const { entity } = useEntity();\n return isCloudResourceEntity(entity) ? (\n <CloudAnyshiftEntityContent />\n ) : (\n <ServiceAnyshiftEntityContent />\n );\n};\n\nexport const ServiceAnyshiftEntityContent = () => {\n const { entity } = useEntity();\n const api = useApi(anyshiftApiRef);\n const catalogApi = useApiHolder().get(catalogApiRef);\n const annotations = entity.metadata.annotations ?? {};\n const target = entityTarget(entity);\n const kind = annotations[ANYSHIFT_KIND_ANNOTATION] || 'workload';\n const namespace = entity.metadata.namespace;\n const configurationEndpoints =\n annotations[ANYSHIFT_CONFIGURATION_ENDPOINTS_ANNOTATION];\n const configurationDependencies =\n annotations[ANYSHIFT_CONFIGURATION_DEPENDENCIES_ANNOTATION];\n const configurationTarget =\n annotations[ANYSHIFT_RUNTIME_RESOURCE_ID_ANNOTATION];\n const [brief, setBrief] = useState<ServiceBrief>();\n const [catalogEntities, setCatalogEntities] = useState<Entity[]>([]);\n const [capabilities, setCapabilities] = useState<AnyshiftCapabilities>({\n askEnabled: false,\n queryEnabled: false,\n });\n const [error, setError] = useState<string>();\n const [refreshingAll, setRefreshingAll] = useState(false);\n const [refreshing, setRefreshing] = useState<Set<BriefSectionName>>(\n new Set(),\n );\n const active = useRef(true);\n\n const load = useCallback(\n async (refresh = false) => {\n setError(undefined);\n if (refresh) setRefreshingAll(true);\n try {\n const next = await api.brief(kind, target, {\n namespace,\n refresh,\n configurationEndpoints,\n configurationDependencies,\n configurationTarget,\n });\n if (active.current) setBrief(next);\n } catch (cause) {\n if (active.current)\n setError(cause instanceof Error ? cause.message : String(cause));\n } finally {\n if (active.current) setRefreshingAll(false);\n }\n },\n [\n api,\n configurationDependencies,\n configurationEndpoints,\n configurationTarget,\n kind,\n namespace,\n target,\n ],\n );\n\n const retrySection = useCallback(\n async (section: BriefSectionName) => {\n setRefreshing(current => new Set(current).add(section));\n try {\n const response = await api.briefSection(kind, target, section, {\n namespace,\n refresh: true,\n configurationEndpoints,\n configurationDependencies,\n configurationTarget,\n });\n if (active.current) {\n setBrief(current =>\n current\n ? {\n ...current,\n [section]: response.value,\n refreshedAt: response.refreshedAt,\n ...(response.observedService\n ? { observedService: response.observedService }\n : {}),\n }\n : current,\n );\n }\n } finally {\n if (active.current) {\n setRefreshing(current => {\n const next = new Set(current);\n next.delete(section);\n return next;\n });\n }\n }\n },\n [\n api,\n configurationDependencies,\n configurationEndpoints,\n configurationTarget,\n kind,\n namespace,\n target,\n ],\n );\n\n useEffect(() => {\n active.current = true;\n void load();\n return () => {\n active.current = false;\n };\n }, [load]);\n\n useEffect(() => {\n let cancelled = false;\n if (!catalogApi) {\n setCatalogEntities([]);\n return () => {\n cancelled = true;\n };\n }\n catalogApi\n .getEntities({\n filter: [{ kind: 'Component' }, { kind: 'Resource' }],\n fields: ['kind', 'metadata.name', 'metadata.namespace'],\n })\n .then(response => {\n if (!cancelled) setCatalogEntities(response.items);\n })\n .catch(() => {\n if (!cancelled) setCatalogEntities([]);\n });\n return () => {\n cancelled = true;\n };\n }, [catalogApi]);\n\n useEffect(() => {\n api\n .capabilities()\n .then(next => {\n if (active.current) setCapabilities(next);\n })\n .catch(() => {\n if (active.current) {\n setCapabilities({ askEnabled: false, queryEnabled: false });\n }\n });\n }, [api]);\n\n const resolvedBrief = useMemo<ServiceBrief | undefined>(() => {\n if (!brief || brief.impact.state !== 'data' || !brief.impact.data)\n return brief;\n const impactData = brief.impact.data;\n return {\n ...brief,\n impact: {\n ...brief.impact,\n data: {\n ...impactData,\n relatedEntities: resolveRelatedCatalogEntities(\n impactData.relatedEntities,\n catalogEntities,\n ),\n },\n },\n };\n }, [brief, catalogEntities]);\n\n if (error && !resolvedBrief)\n return <Typography color=\"error\">{error}</Typography>;\n if (!resolvedBrief) return <Progress />;\n return (\n <ServiceBriefView\n brief={resolvedBrief}\n onRetry={retrySection}\n refreshing={refreshing}\n onRefresh={() => void load(true)}\n refreshingAll={refreshingAll}\n refreshError={error}\n askEnabled={capabilities.askEnabled}\n askScope={askScopeForEntity(entity)}\n />\n );\n};\n\nexport const ServiceBriefView = ({\n brief,\n onRetry,\n refreshing = new Set(),\n onRefresh = () => {},\n refreshingAll = false,\n refreshError,\n askEnabled = false,\n askScope,\n}: {\n brief: ServiceBrief;\n onRetry: (section: BriefSectionName) => void;\n refreshing?: ReadonlySet<BriefSectionName>;\n onRefresh?: () => void;\n refreshingAll?: boolean;\n refreshError?: string;\n askEnabled?: boolean;\n askScope?: AskScope;\n}) => (\n <>\n <Box\n mb={3}\n px={2.5}\n py={2}\n borderLeft={4}\n borderColor=\"primary.main\"\n bgcolor=\"background.paper\"\n >\n <Box display=\"flex\" justifyContent=\"space-between\" gridGap={16}>\n <Box>\n <Typography variant=\"overline\" color=\"textSecondary\">\n Anyshift evidence trace\n </Typography>\n <Box display=\"flex\" flexWrap=\"wrap\" alignItems=\"center\" gridGap={8}>\n <Chip size=\"small\" label={`Workload · ${brief.target}`} />\n <Typography aria-hidden=\"true\" color=\"primary\">\n →\n </Typography>\n <Chip\n size=\"small\"\n color=\"primary\"\n label={`Observed service · ${\n brief.observedService || 'Not resolved'\n }`}\n />\n </Box>\n <Typography variant=\"caption\" color=\"textSecondary\">\n Runtime namespace: {brief.namespace || 'Not set'} · Refreshed{' '}\n {formatTimestamp(brief.refreshedAt)}\n </Typography>\n </Box>\n <Button\n type=\"button\"\n variant=\"outlined\"\n size=\"small\"\n onClick={onRefresh}\n disabled={refreshingAll}\n startIcon={refreshingAll ? <CircularProgress size={14} /> : undefined}\n >\n {refreshingAll ? 'Refreshing…' : 'Refresh brief'}\n </Button>\n </Box>\n {refreshError && (\n <Typography color=\"error\" variant=\"caption\">\n Refresh failed: {refreshError}. Existing evidence is still shown.\n </Typography>\n )}\n </Box>\n\n <Grid container spacing={3} alignItems=\"stretch\">\n <Grid item xs={12}>\n <OverviewSection\n section={brief.overview}\n onRetry={onRetry}\n refreshing={refreshing.has('overview')}\n />\n </Grid>\n <Grid item xs={12}>\n <WhyNowSection\n section={brief.whyNow}\n onRetry={onRetry}\n refreshing={refreshing.has('whyNow')}\n />\n </Grid>\n {askEnabled && (\n <Grid item xs={12}>\n <AskAnyshiftCard context=\"entity\" scope={askScope} />\n </Grid>\n )}\n <Grid item xs={12}>\n <DatadogSignalSection\n section={brief.observability}\n onRetry={onRetry}\n refreshing={refreshing.has('observability')}\n />\n </Grid>\n <Grid item xs={12}>\n <ArchitectureSection\n section={brief.architecture}\n onRetry={onRetry}\n refreshing={refreshing.has('architecture')}\n />\n </Grid>\n <Grid item xs={12} md={8}>\n <DependencySection\n section={brief.topology}\n onRetry={onRetry}\n refreshing={refreshing.has('topology')}\n />\n </Grid>\n <Grid item xs={12} md={4}>\n <BriefRail brief={brief} onRetry={onRetry} refreshing={refreshing} />\n </Grid>\n <Grid item xs={12}>\n <TimelineSection\n section={brief.timeline}\n onRetry={onRetry}\n refreshing={refreshing.has('timeline')}\n />\n </Grid>\n </Grid>\n </>\n);\n\nfunction formatTimestamp(value: string): string {\n const parsed = Date.parse(value);\n return Number.isNaN(parsed) ? value : new Date(parsed).toLocaleString();\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
+ import { Box, Typography, Chip } from '@material-ui/core';
3
+
4
+ const formatObservationTime = (value, now = Date.now()) => {
5
+ if (!value) return { relative: "Observation time unavailable" };
6
+ const timestamp = Date.parse(value);
7
+ if (Number.isNaN(timestamp)) {
8
+ return { relative: "Observation time unavailable" };
9
+ }
10
+ const minutes = Math.round((timestamp - now) / 6e4);
11
+ const [amount, unit] = Math.abs(minutes) < 60 ? [minutes, "minute"] : Math.abs(minutes) < 2880 ? [Math.round(minutes / 60), "hour"] : [Math.round(minutes / 1440), "day"];
12
+ const relative = new Intl.RelativeTimeFormat("en", {
13
+ numeric: "auto"
14
+ }).format(amount, unit);
15
+ return {
16
+ relative: `Observed ${relative}`,
17
+ exact: new Date(timestamp).toISOString()
18
+ };
19
+ };
20
+ const isUsableOperation = (operation) => operation.method !== null || operation.pathTemplate !== null;
21
+ const usableOperations = (relationship) => relationship.operationSources.flatMap((source) => source.operations).filter(isUsableOperation);
22
+ const usableOperationCount = (relationship) => usableOperations(relationship).length;
23
+ const operationPreview = (relationship) => {
24
+ const operations = usableOperations(relationship);
25
+ const operation = operations[0];
26
+ if (relationship.operationCount === 0 || !operation) return "";
27
+ const summary = [
28
+ operation.method ?? "Method unavailable",
29
+ operation.pathTemplate ?? "Path unavailable"
30
+ ].join(" ");
31
+ const remaining = relationship.operationCount - 1;
32
+ return remaining > 0 ? `${summary} \xB7 +${remaining} ${remaining === 1 ? "operation" : "operations"}` : summary;
33
+ };
34
+ const ArchitectureOperationDetails = ({
35
+ relationship,
36
+ onClose,
37
+ now
38
+ }) => {
39
+ if (relationship.operationCount === 0 || usableOperationCount(relationship) === 0) {
40
+ return null;
41
+ }
42
+ return /* @__PURE__ */ jsxs(Box, { border: 1, borderColor: "divider", borderRadius: 4, p: 2, children: [
43
+ /* @__PURE__ */ jsxs(
44
+ Box,
45
+ {
46
+ display: "flex",
47
+ justifyContent: "space-between",
48
+ alignItems: "flex-start",
49
+ children: [
50
+ /* @__PURE__ */ jsxs(Typography, { component: "h3", variant: "h6", children: [
51
+ relationship.fromLabel,
52
+ " to ",
53
+ relationship.toLabel
54
+ ] }),
55
+ /* @__PURE__ */ jsx(
56
+ "button",
57
+ {
58
+ "aria-label": "Close HTTP operation details",
59
+ onClick: onClose,
60
+ type: "button",
61
+ children: "Close"
62
+ }
63
+ )
64
+ ]
65
+ }
66
+ ),
67
+ relationship.operationSources.map((source) => /* @__PURE__ */ jsxs(Box, { mt: 2, children: [
68
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", children: source.source }),
69
+ source.operations.filter(isUsableOperation).map((operation, index) => {
70
+ const observed = formatObservationTime(operation.observedAt, now);
71
+ return /* @__PURE__ */ jsxs(
72
+ Box,
73
+ {
74
+ display: "flex",
75
+ flexWrap: "wrap",
76
+ alignItems: "center",
77
+ gridGap: 8,
78
+ mt: 0.5,
79
+ children: [
80
+ operation.method ? /* @__PURE__ */ jsx(
81
+ Chip,
82
+ {
83
+ label: operation.method,
84
+ size: "small",
85
+ variant: "outlined"
86
+ }
87
+ ) : /* @__PURE__ */ jsx(Typography, { variant: "body2", children: "Method unavailable" }),
88
+ /* @__PURE__ */ jsx(Typography, { variant: "body2", children: operation.pathTemplate ?? "Path unavailable" }),
89
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "textSecondary", children: /* @__PURE__ */ jsxs("time", { dateTime: operation.observedAt ?? void 0, children: [
90
+ /* @__PURE__ */ jsx("span", { children: observed.relative }),
91
+ observed.exact && /* @__PURE__ */ jsxs(Fragment, { children: [
92
+ " \xB7 ",
93
+ /* @__PURE__ */ jsx("span", { children: observed.exact })
94
+ ] })
95
+ ] }) })
96
+ ]
97
+ },
98
+ `${operation.method ?? ""}\0${operation.pathTemplate ?? ""}\0${index}`
99
+ );
100
+ }),
101
+ source.hasIncompleteOperations && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "textSecondary", component: "p", children: "Operation details incomplete" })
102
+ ] }, source.source))
103
+ ] });
104
+ };
105
+
106
+ export { ArchitectureOperationDetails, formatObservationTime, operationPreview };
107
+ //# sourceMappingURL=ArchitectureOperationDetails.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ArchitectureOperationDetails.esm.js","sources":["../../../src/components/entity/ArchitectureOperationDetails.tsx"],"sourcesContent":["import { Box, Chip, Typography } from '@material-ui/core';\nimport type { ArchitectureDiagramRelationship } from './architectureDiagram';\n\nexport const formatObservationTime = (\n value: string | null,\n now = Date.now(),\n): { relative: string; exact?: string } => {\n if (!value) return { relative: 'Observation time unavailable' };\n\n const timestamp = Date.parse(value);\n if (Number.isNaN(timestamp)) {\n return { relative: 'Observation time unavailable' };\n }\n\n const minutes = Math.round((timestamp - now) / 60_000);\n const [amount, unit] =\n Math.abs(minutes) < 60\n ? ([minutes, 'minute'] as const)\n : Math.abs(minutes) < 2_880\n ? ([Math.round(minutes / 60), 'hour'] as const)\n : ([Math.round(minutes / 1_440), 'day'] as const);\n const relative = new Intl.RelativeTimeFormat('en', {\n numeric: 'auto',\n }).format(amount, unit);\n\n return {\n relative: `Observed ${relative}`,\n exact: new Date(timestamp).toISOString(),\n };\n};\n\nconst isUsableOperation = (operation: {\n method: string | null;\n pathTemplate: string | null;\n}) => operation.method !== null || operation.pathTemplate !== null;\n\nconst usableOperations = (relationship: ArchitectureDiagramRelationship) =>\n relationship.operationSources\n .flatMap(source => source.operations)\n .filter(isUsableOperation);\n\nconst usableOperationCount = (relationship: ArchitectureDiagramRelationship) =>\n usableOperations(relationship).length;\n\nexport const operationPreview = (\n relationship: ArchitectureDiagramRelationship,\n): string => {\n const operations = usableOperations(relationship);\n const operation = operations[0];\n if (relationship.operationCount === 0 || !operation) return '';\n\n const summary = [\n operation.method ?? 'Method unavailable',\n operation.pathTemplate ?? 'Path unavailable',\n ].join(' ');\n const remaining = relationship.operationCount - 1;\n return remaining > 0\n ? `${summary} · +${remaining} ${\n remaining === 1 ? 'operation' : 'operations'\n }`\n : summary;\n};\n\nexport const ArchitectureOperationDetails = ({\n relationship,\n onClose,\n now,\n}: {\n relationship: ArchitectureDiagramRelationship;\n onClose: () => void;\n now?: number;\n}) => {\n if (\n relationship.operationCount === 0 ||\n usableOperationCount(relationship) === 0\n ) {\n return null;\n }\n\n return (\n <Box border={1} borderColor=\"divider\" borderRadius={4} p={2}>\n <Box\n display=\"flex\"\n justifyContent=\"space-between\"\n alignItems=\"flex-start\"\n >\n <Typography component=\"h3\" variant=\"h6\">\n {relationship.fromLabel} to {relationship.toLabel}\n </Typography>\n <button\n aria-label=\"Close HTTP operation details\"\n onClick={onClose}\n type=\"button\"\n >\n Close\n </button>\n </Box>\n\n {relationship.operationSources.map(source => (\n <Box key={source.source} mt={2}>\n <Typography variant=\"subtitle2\">{source.source}</Typography>\n {source.operations\n .filter(isUsableOperation)\n .map((operation, index) => {\n const observed = formatObservationTime(operation.observedAt, now);\n return (\n <Box\n key={`${operation.method ?? ''}\\u0000${\n operation.pathTemplate ?? ''\n }\\u0000${index}`}\n display=\"flex\"\n flexWrap=\"wrap\"\n alignItems=\"center\"\n gridGap={8}\n mt={0.5}\n >\n {operation.method ? (\n <Chip\n label={operation.method}\n size=\"small\"\n variant=\"outlined\"\n />\n ) : (\n <Typography variant=\"body2\">Method unavailable</Typography>\n )}\n <Typography variant=\"body2\">\n {operation.pathTemplate ?? 'Path unavailable'}\n </Typography>\n <Typography variant=\"caption\" color=\"textSecondary\">\n <time dateTime={operation.observedAt ?? undefined}>\n <span>{observed.relative}</span>\n {observed.exact && (\n <>\n {' · '}\n <span>{observed.exact}</span>\n </>\n )}\n </time>\n </Typography>\n </Box>\n );\n })}\n {source.hasIncompleteOperations && (\n <Typography variant=\"caption\" color=\"textSecondary\" component=\"p\">\n Operation details incomplete\n </Typography>\n )}\n </Box>\n ))}\n </Box>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -1,47 +1,114 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
2
- import { Box, CircularProgress, Typography } from '@material-ui/core';
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Box, CircularProgress, Typography, makeStyles } from '@material-ui/core';
3
3
  import mermaid from 'mermaid';
4
4
  import { useMemo, useRef, useState, useEffect } from 'react';
5
5
  import { buildArchitectureDiagram } from './architectureDiagram.esm.js';
6
+ import { operationPreview, ArchitectureOperationDetails } from './ArchitectureOperationDetails.esm.js';
6
7
 
7
8
  let diagramSequence = 0;
8
9
  let mermaidInitialized = false;
9
- const RelationshipFallback = ({
10
- relationships
11
- }) => /* @__PURE__ */ jsxs(Box, { mt: 2, children: [
12
- /* @__PURE__ */ jsx(Typography, { color: "error", variant: "body2", role: "alert", children: "The architecture diagram could not be drawn. Showing relationships instead." }),
13
- /* @__PURE__ */ jsx(
14
- Box,
15
- {
16
- component: "ul",
17
- m: 0,
18
- mt: 1,
19
- p: 0,
20
- "aria-label": "Architecture relationships",
21
- children: relationships.map((relationship) => /* @__PURE__ */ jsx(
22
- Box,
23
- {
24
- component: "li",
25
- py: 0.5,
26
- style: { listStyle: "none" },
27
- children: /* @__PURE__ */ jsx(Typography, { variant: "body2", children: relationship })
28
- },
29
- relationship
30
- ))
10
+ const useStyles = makeStyles((theme) => ({
11
+ diagram: {
12
+ "& .anyshift-http-edge-affordance": {
13
+ cursor: "pointer"
14
+ },
15
+ "& .anyshift-http-edge-affordance:focus": {
16
+ outline: `2px solid ${theme.palette.text.primary}`,
17
+ outlineOffset: 2
18
+ },
19
+ "& .edgeLabel.anyshift-http-edge-affordance rect": {
20
+ rx: 4,
21
+ ry: 4,
22
+ stroke: theme.palette.divider,
23
+ strokeWidth: 1
24
+ }
25
+ },
26
+ fallbackButton: {
27
+ appearance: "none",
28
+ background: "transparent",
29
+ border: 0,
30
+ color: "inherit",
31
+ cursor: "pointer",
32
+ font: "inherit",
33
+ padding: 0,
34
+ textAlign: "left",
35
+ "&:focus": {
36
+ outline: `2px solid ${theme.palette.text.primary}`,
37
+ outlineOffset: 2
31
38
  }
32
- )
33
- ] });
39
+ }
40
+ }));
41
+ const hasUsableOperations = (relationship) => relationship.operationCount > 0 && operationPreview(relationship) !== "";
42
+ const RelationshipFallback = ({
43
+ relationships,
44
+ onSelect,
45
+ selectedId
46
+ }) => {
47
+ const classes = useStyles();
48
+ return /* @__PURE__ */ jsxs(Box, { mt: 2, children: [
49
+ /* @__PURE__ */ jsx(Typography, { color: "error", variant: "body2", role: "alert", children: "The architecture diagram could not be drawn. Showing relationships instead." }),
50
+ /* @__PURE__ */ jsx(
51
+ Box,
52
+ {
53
+ component: "ul",
54
+ m: 0,
55
+ mt: 1,
56
+ p: 0,
57
+ "aria-label": "Architecture relationships",
58
+ children: relationships.map((relationship) => /* @__PURE__ */ jsx(
59
+ Box,
60
+ {
61
+ component: "li",
62
+ py: 0.5,
63
+ style: { listStyle: "none" },
64
+ children: hasUsableOperations(relationship) ? /* @__PURE__ */ jsx(
65
+ "button",
66
+ {
67
+ "aria-label": `Show HTTP operations for ${relationship.fromLabel} to ${relationship.toLabel}`,
68
+ "aria-expanded": selectedId === relationship.id,
69
+ className: classes.fallbackButton,
70
+ onClick: () => onSelect(relationship),
71
+ type: "button",
72
+ children: /* @__PURE__ */ jsx(Typography, { component: "span", variant: "body2", children: relationship.label })
73
+ }
74
+ ) : /* @__PURE__ */ jsx(Typography, { variant: "body2", children: relationship.label })
75
+ },
76
+ relationship.id
77
+ ))
78
+ }
79
+ )
80
+ ] });
81
+ };
34
82
  const ArchitectureDiagram = ({
35
83
  graph,
36
84
  view = "runtime"
37
85
  }) => {
86
+ const classes = useStyles();
38
87
  const definition = useMemo(
39
88
  () => buildArchitectureDiagram(graph, { view }),
40
89
  [graph, view]
41
90
  );
42
91
  const diagramId = useRef(`anyshift-architecture-${diagramSequence++}`);
92
+ const diagramRef = useRef(null);
43
93
  const [svg, setSvg] = useState();
44
94
  const [renderFailed, setRenderFailed] = useState(false);
95
+ const [selectedId, setSelectedId] = useState();
96
+ const [previewId, setPreviewId] = useState();
97
+ const operationRelationships = useMemo(
98
+ () => definition.relationships.filter(hasUsableOperations),
99
+ [definition.relationships]
100
+ );
101
+ const relationshipsById = useMemo(
102
+ () => new Map(
103
+ operationRelationships.map((relationship) => [
104
+ relationship.id,
105
+ relationship
106
+ ])
107
+ ),
108
+ [operationRelationships]
109
+ );
110
+ const selectedRelationship = selectedId ? relationshipsById.get(selectedId) : void 0;
111
+ const previewRelationship = previewId ? relationshipsById.get(previewId) : void 0;
45
112
  useEffect(() => {
46
113
  let active = true;
47
114
  setSvg(void 0);
@@ -64,25 +131,117 @@ const ArchitectureDiagram = ({
64
131
  active = false;
65
132
  };
66
133
  }, [definition.source]);
67
- if (renderFailed) {
68
- return /* @__PURE__ */ jsx(RelationshipFallback, { relationships: definition.fallbackRelationships });
69
- }
70
- if (!svg) {
71
- return /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", gridGap: 8, py: 3, children: [
134
+ useEffect(() => {
135
+ if (selectedId && !relationshipsById.has(selectedId)) {
136
+ setSelectedId(void 0);
137
+ }
138
+ if (previewId && !relationshipsById.has(previewId)) {
139
+ setPreviewId(void 0);
140
+ }
141
+ }, [previewId, relationshipsById, selectedId]);
142
+ useEffect(() => {
143
+ if (!svg || !diagramRef.current) return;
144
+ const edgePaths = diagramRef.current.querySelectorAll(".edgePath");
145
+ const edgeLabels = diagramRef.current.querySelectorAll(".edgeLabel");
146
+ operationRelationships.forEach((relationship) => {
147
+ const ariaLabel = `HTTP operations for ${relationship.fromLabel} to ${relationship.toLabel}`;
148
+ [
149
+ edgePaths.item(relationship.diagramIndex),
150
+ edgeLabels.item(relationship.diagramIndex)
151
+ ].forEach((element) => {
152
+ if (!(element instanceof HTMLElement || element instanceof SVGElement)) {
153
+ return;
154
+ }
155
+ element.dataset.anyshiftEdgeId = relationship.id;
156
+ element.setAttribute("role", "button");
157
+ element.setAttribute("tabindex", "0");
158
+ element.setAttribute("aria-label", ariaLabel);
159
+ element.setAttribute(
160
+ "aria-expanded",
161
+ String(selectedId === relationship.id)
162
+ );
163
+ element.classList.add("anyshift-http-edge-affordance");
164
+ });
165
+ });
166
+ }, [operationRelationships, selectedId, svg]);
167
+ const relationshipFromTarget = (target) => {
168
+ if (!(target instanceof Element)) return void 0;
169
+ const edge = target.closest("[data-anyshift-edge-id]");
170
+ const id = edge?.dataset.anyshiftEdgeId;
171
+ return id ? relationshipsById.get(id) : void 0;
172
+ };
173
+ const selectRelationship = (target) => {
174
+ const relationship = relationshipFromTarget(target);
175
+ if (relationship) setSelectedId(relationship.id);
176
+ };
177
+ const handleKeyDown = (event) => {
178
+ if (event.key === "Enter" || event.key === " ") {
179
+ const relationship = relationshipFromTarget(event.target);
180
+ if (relationship) {
181
+ event.preventDefault();
182
+ setSelectedId(relationship.id);
183
+ }
184
+ }
185
+ if (event.key === "Escape") setSelectedId(void 0);
186
+ };
187
+ const handleMouseOver = (event) => {
188
+ const relationship = relationshipFromTarget(event.target);
189
+ if (relationship) setPreviewId(relationship.id);
190
+ };
191
+ const handleMouseOut = (event) => {
192
+ const relationship = relationshipFromTarget(event.target);
193
+ const nextRelationship = relationshipFromTarget(event.relatedTarget);
194
+ if (relationship && relationship.id !== nextRelationship?.id) {
195
+ setPreviewId(void 0);
196
+ }
197
+ };
198
+ const handleFocus = (event) => {
199
+ const relationship = relationshipFromTarget(event.target);
200
+ if (relationship) setPreviewId(relationship.id);
201
+ };
202
+ const handleBlur = (event) => {
203
+ const relationship = relationshipFromTarget(event.target);
204
+ const nextRelationship = relationshipFromTarget(event.relatedTarget);
205
+ if (relationship && relationship.id !== nextRelationship?.id) {
206
+ setPreviewId(void 0);
207
+ }
208
+ };
209
+ return /* @__PURE__ */ jsxs(Box, { onKeyDown: handleKeyDown, children: [
210
+ renderFailed ? /* @__PURE__ */ jsx(
211
+ RelationshipFallback,
212
+ {
213
+ relationships: definition.relationships,
214
+ onSelect: (relationship) => setSelectedId(relationship.id),
215
+ selectedId
216
+ }
217
+ ) : !svg ? /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", gridGap: 8, py: 3, children: [
72
218
  /* @__PURE__ */ jsx(CircularProgress, { size: 18 }),
73
219
  /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "textSecondary", children: "Drawing architecture\u2026" })
74
- ] });
75
- }
76
- return /* @__PURE__ */ jsx(
77
- Box,
78
- {
79
- "aria-label": "C4 architecture diagram",
80
- role: "img",
81
- mt: 1,
82
- style: { overflowX: "auto" },
83
- dangerouslySetInnerHTML: { __html: svg }
84
- }
85
- );
220
+ ] }) : /* @__PURE__ */ jsx(
221
+ Box,
222
+ {
223
+ "aria-label": "C4 architecture diagram",
224
+ className: classes.diagram,
225
+ onBlur: handleBlur,
226
+ onClick: (event) => selectRelationship(event.target),
227
+ onFocus: handleFocus,
228
+ onMouseOut: handleMouseOut,
229
+ onMouseOver: handleMouseOver,
230
+ role: "group",
231
+ mt: 1,
232
+ style: { overflowX: "auto" },
233
+ children: /* @__PURE__ */ jsx("div", { ref: diagramRef, dangerouslySetInnerHTML: { __html: svg } })
234
+ }
235
+ ),
236
+ previewRelationship && /* @__PURE__ */ jsx(Typography, { role: "tooltip", variant: "caption", color: "textSecondary", children: operationPreview(previewRelationship) }),
237
+ selectedRelationship && /* @__PURE__ */ jsx(Box, { mt: 2, children: /* @__PURE__ */ jsx(
238
+ ArchitectureOperationDetails,
239
+ {
240
+ relationship: selectedRelationship,
241
+ onClose: () => setSelectedId(void 0)
242
+ }
243
+ ) })
244
+ ] });
86
245
  };
87
246
 
88
247
  export { ArchitectureDiagram };