@nospt/backstage-plugin-apigee 1.1.1
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/README.md +98 -0
- package/config.d.ts +30 -0
- package/dist/components/ApiCard.esm.js +105 -0
- package/dist/components/ApiCard.esm.js.map +1 -0
- package/dist/components/ApiCard.module.css.esm.js +8 -0
- package/dist/components/ApiCard.module.css.esm.js.map +1 -0
- package/dist/components/ApiCardGrid.esm.js +14 -0
- package/dist/components/ApiCardGrid.esm.js.map +1 -0
- package/dist/components/ApiDetailsDrawer.esm.js +709 -0
- package/dist/components/ApiDetailsDrawer.esm.js.map +1 -0
- package/dist/components/ApiDetailsDrawer.module.css.esm.js +8 -0
- package/dist/components/ApiDetailsDrawer.module.css.esm.js.map +1 -0
- package/dist/components/ApiTable.esm.js +184 -0
- package/dist/components/ApiTable.esm.js.map +1 -0
- package/dist/components/ApiTable.module.css.esm.js +8 -0
- package/dist/components/ApiTable.module.css.esm.js.map +1 -0
- package/dist/components/ApigeeApiCatalogPage.esm.js +245 -0
- package/dist/components/ApigeeApiCatalogPage.esm.js.map +1 -0
- package/dist/components/ApigeeHomePage.esm.js +190 -0
- package/dist/components/ApigeeHomePage.esm.js.map +1 -0
- package/dist/components/ApigeeHomePage.module.css.esm.js +8 -0
- package/dist/components/ApigeeHomePage.module.css.esm.js.map +1 -0
- package/dist/components/ApigeeNavContent.esm.js +28 -0
- package/dist/components/ApigeeNavContent.esm.js.map +1 -0
- package/dist/components/CountCards.esm.js +120 -0
- package/dist/components/CountCards.esm.js.map +1 -0
- package/dist/components/CountCards.module.css.esm.js +8 -0
- package/dist/components/CountCards.module.css.esm.js.map +1 -0
- package/dist/components/FilterToolbar.esm.js +131 -0
- package/dist/components/FilterToolbar.esm.js.map +1 -0
- package/dist/components/FilterToolbar.module.css.esm.js +8 -0
- package/dist/components/FilterToolbar.module.css.esm.js.map +1 -0
- package/dist/components/SearchBar.esm.js +20 -0
- package/dist/components/SearchBar.esm.js.map +1 -0
- package/dist/components/apiCardViewModel.esm.js +57 -0
- package/dist/components/apiCardViewModel.esm.js.map +1 -0
- package/dist/components/apiDetailViewModel.esm.js +98 -0
- package/dist/components/apiDetailViewModel.esm.js.map +1 -0
- package/dist/components/apiOperations.esm.js +196 -0
- package/dist/components/apiOperations.esm.js.map +1 -0
- package/dist/components/apigeeFacetFilters.esm.js +62 -0
- package/dist/components/apigeeFacetFilters.esm.js.map +1 -0
- package/dist/components/apigeeOrgFilter.esm.js +11 -0
- package/dist/components/apigeeOrgFilter.esm.js.map +1 -0
- package/dist/components/apigeeSearchParams.esm.js +55 -0
- package/dist/components/apigeeSearchParams.esm.js.map +1 -0
- package/dist/components/brandIcons.esm.js +36 -0
- package/dist/components/brandIcons.esm.js.map +1 -0
- package/dist/components/codeHighlight.esm.js +71 -0
- package/dist/components/codeHighlight.esm.js.map +1 -0
- package/dist/components/codeSnippet.esm.js +128 -0
- package/dist/components/codeSnippet.esm.js.map +1 -0
- package/dist/components/facetModel.esm.js +86 -0
- package/dist/components/facetModel.esm.js.map +1 -0
- package/dist/components/grouping.esm.js +6 -0
- package/dist/components/grouping.esm.js.map +1 -0
- package/dist/components/states.esm.js +65 -0
- package/dist/components/states.esm.js.map +1 -0
- package/dist/components/useApigeeFilters.esm.js +191 -0
- package/dist/components/useApigeeFilters.esm.js.map +1 -0
- package/dist/components/useDebouncedValue.esm.js +13 -0
- package/dist/components/useDebouncedValue.esm.js.map +1 -0
- package/dist/constants.esm.js +9 -0
- package/dist/constants.esm.js.map +1 -0
- package/dist/index.d.ts +203 -0
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js +29 -0
- package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js.map +1 -0
- package/dist/plugin.esm.js +59 -0
- package/dist/plugin.esm.js.map +1 -0
- package/dist/routes.esm.js +8 -0
- package/dist/routes.esm.js.map +1 -0
- package/package.json +95 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const SNIPPET_LANGUAGES = [
|
|
2
|
+
{ id: "curl", label: "cURL" },
|
|
3
|
+
{ id: "javascript", label: "JavaScript" },
|
|
4
|
+
{ id: "java", label: "Java" },
|
|
5
|
+
{ id: "nodejs", label: "Node.js" },
|
|
6
|
+
{ id: "python", label: "Python" }
|
|
7
|
+
];
|
|
8
|
+
const PLACEHOLDER_BASE = "https://api.example.com";
|
|
9
|
+
function requestUrl(op) {
|
|
10
|
+
const base = (op.server ?? PLACEHOLDER_BASE).replace(/\/+$/, "");
|
|
11
|
+
return `${base}${op.path.startsWith("/") ? "" : "/"}${op.path}`;
|
|
12
|
+
}
|
|
13
|
+
function contentType(op) {
|
|
14
|
+
return op.requestBody?.contentTypes[0];
|
|
15
|
+
}
|
|
16
|
+
function bodyExample(op) {
|
|
17
|
+
return op.requestBody?.body?.example;
|
|
18
|
+
}
|
|
19
|
+
function sh(value) {
|
|
20
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
21
|
+
}
|
|
22
|
+
function dq(value) {
|
|
23
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\t/g, "\\t");
|
|
24
|
+
}
|
|
25
|
+
function buildCurl(op) {
|
|
26
|
+
const parts = [`curl --request ${op.method}`, `--url ${sh(requestUrl(op))}`];
|
|
27
|
+
const ct = contentType(op);
|
|
28
|
+
if (ct) parts.push(`--header ${sh(`Content-Type: ${ct}`)}`);
|
|
29
|
+
const body = bodyExample(op);
|
|
30
|
+
if (body !== void 0) parts.push(`--data ${sh(body)}`);
|
|
31
|
+
return parts.join(" \\\n ");
|
|
32
|
+
}
|
|
33
|
+
function buildJavaScript(op) {
|
|
34
|
+
const ct = contentType(op);
|
|
35
|
+
const body = bodyExample(op);
|
|
36
|
+
const options = [`method: '${op.method}'`];
|
|
37
|
+
if (ct) {
|
|
38
|
+
options.push(`headers: { 'Content-Type': '${ct}' }`);
|
|
39
|
+
}
|
|
40
|
+
if (body !== void 0) {
|
|
41
|
+
options.push(`body: JSON.stringify(${body})`);
|
|
42
|
+
}
|
|
43
|
+
const opts = options.length ? `, {
|
|
44
|
+
${options.join(",\n ")},
|
|
45
|
+
}` : "";
|
|
46
|
+
return [
|
|
47
|
+
`const response = await fetch('${requestUrl(op)}'${opts});`,
|
|
48
|
+
``,
|
|
49
|
+
`const data = await response.json();`,
|
|
50
|
+
`console.log(data);`
|
|
51
|
+
].join("\n");
|
|
52
|
+
}
|
|
53
|
+
function buildNode(op) {
|
|
54
|
+
const ct = contentType(op);
|
|
55
|
+
const body = bodyExample(op);
|
|
56
|
+
const options = [`method: '${op.method}'`];
|
|
57
|
+
if (ct) options.push(`headers: { 'Content-Type': '${ct}' }`);
|
|
58
|
+
if (body !== void 0) options.push(`body: JSON.stringify(${body})`);
|
|
59
|
+
const opts = options.length ? `, {
|
|
60
|
+
${options.join(",\n ")},
|
|
61
|
+
}` : "";
|
|
62
|
+
return [
|
|
63
|
+
`// Node 18+ (global fetch)`,
|
|
64
|
+
`const response = await fetch('${requestUrl(op)}'${opts});`,
|
|
65
|
+
`const data = await response.json();`,
|
|
66
|
+
`console.log(data);`
|
|
67
|
+
].join("\n");
|
|
68
|
+
}
|
|
69
|
+
function buildPython(op) {
|
|
70
|
+
const ct = contentType(op);
|
|
71
|
+
const body = bodyExample(op);
|
|
72
|
+
const args = [`"${requestUrl(op)}"`];
|
|
73
|
+
if (ct) args.push(`headers={"Content-Type": "${ct}"}`);
|
|
74
|
+
if (body !== void 0) {
|
|
75
|
+
args.push(`json=${body}`);
|
|
76
|
+
}
|
|
77
|
+
return [
|
|
78
|
+
`import requests`,
|
|
79
|
+
``,
|
|
80
|
+
`response = requests.${op.method.toLowerCase()}(`,
|
|
81
|
+
` ${args.join(",\n ")},`,
|
|
82
|
+
`)`,
|
|
83
|
+
`print(response.json())`
|
|
84
|
+
].join("\n");
|
|
85
|
+
}
|
|
86
|
+
function buildJava(op) {
|
|
87
|
+
const ct = contentType(op);
|
|
88
|
+
const body = bodyExample(op);
|
|
89
|
+
const lines = [`OkHttpClient client = new OkHttpClient();`, ``];
|
|
90
|
+
const bodyLines = [];
|
|
91
|
+
if (body !== void 0) {
|
|
92
|
+
bodyLines.push(
|
|
93
|
+
`MediaType mediaType = MediaType.parse("${ct ?? "application/json"}");`,
|
|
94
|
+
`RequestBody body = RequestBody.create(mediaType, "${dq(body)}");`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
lines.push(...bodyLines);
|
|
98
|
+
lines.push(
|
|
99
|
+
`Request request = new Request.Builder()`,
|
|
100
|
+
` .url("${requestUrl(op)}")`,
|
|
101
|
+
` .method("${op.method}"${body !== void 0 ? ", body" : ", null"})`
|
|
102
|
+
);
|
|
103
|
+
if (ct) lines.push(` .addHeader("Content-Type", "${ct}")`);
|
|
104
|
+
lines.push(
|
|
105
|
+
` .build();`,
|
|
106
|
+
`Response response = client.newCall(request).execute();`
|
|
107
|
+
);
|
|
108
|
+
return lines.join("\n");
|
|
109
|
+
}
|
|
110
|
+
function generateSnippet(op, language) {
|
|
111
|
+
switch (language) {
|
|
112
|
+
case "curl":
|
|
113
|
+
return buildCurl(op);
|
|
114
|
+
case "javascript":
|
|
115
|
+
return buildJavaScript(op);
|
|
116
|
+
case "java":
|
|
117
|
+
return buildJava(op);
|
|
118
|
+
case "nodejs":
|
|
119
|
+
return buildNode(op);
|
|
120
|
+
case "python":
|
|
121
|
+
return buildPython(op);
|
|
122
|
+
default:
|
|
123
|
+
return buildCurl(op);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { SNIPPET_LANGUAGES, generateSnippet };
|
|
128
|
+
//# sourceMappingURL=codeSnippet.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codeSnippet.esm.js","sources":["../../src/components/codeSnippet.ts"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport type { ApiOperation } from './apiOperations';\n\n/** Languages the code-snippet viewer can generate. */\nexport type SnippetLanguage =\n | 'curl'\n | 'javascript'\n | 'java'\n | 'nodejs'\n | 'python';\n\nexport const SNIPPET_LANGUAGES: ReadonlyArray<{\n id: SnippetLanguage;\n label: string;\n}> = [\n { id: 'curl', label: 'cURL' },\n { id: 'javascript', label: 'JavaScript' },\n { id: 'java', label: 'Java' },\n { id: 'nodejs', label: 'Node.js' },\n { id: 'python', label: 'Python' },\n];\n\nconst PLACEHOLDER_BASE = 'https://api.example.com';\n\n/** Absolute request URL: the operation's server (or a placeholder) + its path. */\nfunction requestUrl(op: ApiOperation): string {\n const base = (op.server ?? PLACEHOLDER_BASE).replace(/\\/+$/, '');\n return `${base}${op.path.startsWith('/') ? '' : '/'}${op.path}`;\n}\n\n/** First request content type, when the operation declares a body. */\nfunction contentType(op: ApiOperation): string | undefined {\n return op.requestBody?.contentTypes[0];\n}\n\n/** The request example payload (pretty JSON text) when one is available. */\nfunction bodyExample(op: ApiOperation): string | undefined {\n return op.requestBody?.body?.example;\n}\n\n/** Escape a single-quoted shell string (for cURL). */\nfunction sh(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\n/** Escape a JS/Python/Java double-quoted string. */\nfunction dq(value: string): string {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\t/g, '\\\\t');\n}\n\nfunction buildCurl(op: ApiOperation): string {\n const parts = [`curl --request ${op.method}`, `--url ${sh(requestUrl(op))}`];\n const ct = contentType(op);\n if (ct) parts.push(`--header ${sh(`Content-Type: ${ct}`)}`);\n const body = bodyExample(op);\n if (body !== undefined) parts.push(`--data ${sh(body)}`);\n return parts.join(' \\\\\\n ');\n}\n\nfunction buildJavaScript(op: ApiOperation): string {\n const ct = contentType(op);\n const body = bodyExample(op);\n const options: string[] = [`method: '${op.method}'`];\n if (ct) {\n options.push(`headers: { 'Content-Type': '${ct}' }`);\n }\n if (body !== undefined) {\n options.push(`body: JSON.stringify(${body})`);\n }\n const opts = options.length ? `, {\\n ${options.join(',\\n ')},\\n}` : '';\n return [\n `const response = await fetch('${requestUrl(op)}'${opts});`,\n ``,\n `const data = await response.json();`,\n `console.log(data);`,\n ].join('\\n');\n}\n\nfunction buildNode(op: ApiOperation): string {\n const ct = contentType(op);\n const body = bodyExample(op);\n const options: string[] = [`method: '${op.method}'`];\n if (ct) options.push(`headers: { 'Content-Type': '${ct}' }`);\n if (body !== undefined) options.push(`body: JSON.stringify(${body})`);\n const opts = options.length ? `, {\\n ${options.join(',\\n ')},\\n}` : '';\n return [\n `// Node 18+ (global fetch)`,\n `const response = await fetch('${requestUrl(op)}'${opts});`,\n `const data = await response.json();`,\n `console.log(data);`,\n ].join('\\n');\n}\n\nfunction buildPython(op: ApiOperation): string {\n const ct = contentType(op);\n const body = bodyExample(op);\n const args: string[] = [`\"${requestUrl(op)}\"`];\n if (ct) args.push(`headers={\"Content-Type\": \"${ct}\"}`);\n if (body !== undefined) {\n args.push(`json=${body}`);\n }\n return [\n `import requests`,\n ``,\n `response = requests.${op.method.toLowerCase()}(`,\n ` ${args.join(',\\n ')},`,\n `)`,\n `print(response.json())`,\n ].join('\\n');\n}\n\nfunction buildJava(op: ApiOperation): string {\n const ct = contentType(op);\n const body = bodyExample(op);\n const lines = [`OkHttpClient client = new OkHttpClient();`, ``];\n const bodyLines: string[] = [];\n if (body !== undefined) {\n bodyLines.push(\n `MediaType mediaType = MediaType.parse(\"${ct ?? 'application/json'}\");`,\n `RequestBody body = RequestBody.create(mediaType, \"${dq(body)}\");`,\n );\n }\n lines.push(...bodyLines);\n lines.push(\n `Request request = new Request.Builder()`,\n ` .url(\"${requestUrl(op)}\")`,\n ` .method(\"${op.method}\"${body !== undefined ? ', body' : ', null'})`,\n );\n if (ct) lines.push(` .addHeader(\"Content-Type\", \"${ct}\")`);\n lines.push(\n ` .build();`,\n `Response response = client.newCall(request).execute();`,\n );\n return lines.join('\\n');\n}\n\n/**\n * Generate a runnable request snippet for an operation in the given language. The URL uses\n * the spec's `servers[0].url` (placeholder when absent); the body reuses the request body's\n * example payload when one was extracted. Pure and unit-tested.\n */\nexport function generateSnippet(\n op: ApiOperation,\n language: SnippetLanguage,\n): string {\n switch (language) {\n case 'curl':\n return buildCurl(op);\n case 'javascript':\n return buildJavaScript(op);\n case 'java':\n return buildJava(op);\n case 'nodejs':\n return buildNode(op);\n case 'python':\n return buildPython(op);\n default:\n return buildCurl(op);\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { humanise } from './grouping.esm.js';
|
|
2
|
+
import { entityDepartment, entityOwnerLabel, entityOrg, entityLifecycle } from './apigeeFacetFilters.esm.js';
|
|
3
|
+
|
|
4
|
+
const FACET_ORDER = [
|
|
5
|
+
"lifecycle",
|
|
6
|
+
"org",
|
|
7
|
+
"owner",
|
|
8
|
+
"department"
|
|
9
|
+
];
|
|
10
|
+
const LIFECYCLE_SEVERITY = [
|
|
11
|
+
"production",
|
|
12
|
+
"experimental",
|
|
13
|
+
"deprecated",
|
|
14
|
+
"unknown"
|
|
15
|
+
];
|
|
16
|
+
const FACET_TRIGGER_LABEL = {
|
|
17
|
+
lifecycle: "Lifecycle",
|
|
18
|
+
org: "Org",
|
|
19
|
+
owner: "Owner",
|
|
20
|
+
department: "Department"
|
|
21
|
+
};
|
|
22
|
+
const EXTRACT = {
|
|
23
|
+
lifecycle: entityLifecycle,
|
|
24
|
+
org: entityOrg,
|
|
25
|
+
owner: entityOwnerLabel,
|
|
26
|
+
department: entityDepartment
|
|
27
|
+
};
|
|
28
|
+
function labelFor(key, value, orgLabels) {
|
|
29
|
+
switch (key) {
|
|
30
|
+
case "lifecycle":
|
|
31
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
32
|
+
case "owner":
|
|
33
|
+
return value;
|
|
34
|
+
case "org":
|
|
35
|
+
return orgLabels?.[value] ?? humanise(value);
|
|
36
|
+
default:
|
|
37
|
+
return humanise(value);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function sortValues(key, values) {
|
|
41
|
+
if (key === "lifecycle") {
|
|
42
|
+
const rank = (v) => {
|
|
43
|
+
const i = LIFECYCLE_SEVERITY.indexOf(v);
|
|
44
|
+
return i === -1 ? LIFECYCLE_SEVERITY.length : i;
|
|
45
|
+
};
|
|
46
|
+
return [...values].sort((a, b) => rank(a) - rank(b));
|
|
47
|
+
}
|
|
48
|
+
return [...values].sort((a, b) => a.localeCompare(b));
|
|
49
|
+
}
|
|
50
|
+
function deriveFacets(entities, orgLabels) {
|
|
51
|
+
return FACET_ORDER.map((key) => {
|
|
52
|
+
const distinct = Array.from(
|
|
53
|
+
new Set(entities.map(EXTRACT[key]).filter((v) => !!v))
|
|
54
|
+
);
|
|
55
|
+
const options = sortValues(key, distinct).map((value) => ({
|
|
56
|
+
value,
|
|
57
|
+
label: labelFor(key, value, orgLabels)
|
|
58
|
+
}));
|
|
59
|
+
return {
|
|
60
|
+
key,
|
|
61
|
+
label: FACET_TRIGGER_LABEL[key],
|
|
62
|
+
options,
|
|
63
|
+
enabled: options.length > 0
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function deriveCounts(entities, keys, orgLabels) {
|
|
68
|
+
return keys.map((key) => {
|
|
69
|
+
const counts = /* @__PURE__ */ new Map();
|
|
70
|
+
for (const entity of entities) {
|
|
71
|
+
const value = EXTRACT[key](entity);
|
|
72
|
+
if (value) {
|
|
73
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const buckets = sortValues(key, Array.from(counts.keys())).map((value) => ({
|
|
77
|
+
value,
|
|
78
|
+
label: labelFor(key, value, orgLabels),
|
|
79
|
+
count: counts.get(value) ?? 0
|
|
80
|
+
}));
|
|
81
|
+
return { key, label: FACET_TRIGGER_LABEL[key], buckets };
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { FACET_ORDER, deriveCounts, deriveFacets };
|
|
86
|
+
//# sourceMappingURL=facetModel.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"facetModel.esm.js","sources":["../../src/components/facetModel.ts"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport type { Entity } from '@backstage/catalog-model';\nimport { humanise } from './grouping';\nimport {\n entityDepartment,\n entityLifecycle,\n entityOrg,\n entityOwnerLabel,\n} from './apigeeFacetFilters';\n\nexport type FacetKey = 'lifecycle' | 'org' | 'owner' | 'department';\n\nexport interface FacetOption {\n value: string;\n label: string;\n}\n\nexport interface DerivedFacet {\n key: FacetKey;\n label: string;\n options: FacetOption[];\n /**\n * True when the facet has at least one selectable value. The toolbar ALWAYS renders every\n * facet (revised D-05); an `enabled: false` facet (no values in the loaded data) renders as a\n * disabled dropdown so all four filters stay visible.\n */\n enabled: boolean;\n}\n\n/** Facet order (D-02): Lifecycle → Org → Owner → Department. */\nexport const FACET_ORDER: FacetKey[] = [\n 'lifecycle',\n 'org',\n 'owner',\n 'department',\n];\n\n/**\n * Lifecycle options are ordered by severity (most common / least risky first), NOT\n * alphabetically (UI-SPEC Assumption 5). Unknown values fall to the end via `indexOf` = -1\n * being handled below.\n */\nconst LIFECYCLE_SEVERITY = [\n 'production',\n 'experimental',\n 'deprecated',\n 'unknown',\n];\n\n/** Fixed facet trigger labels (UI-SPEC §Component Inventory — order-locked). */\nconst FACET_TRIGGER_LABEL: Record<FacetKey, string> = {\n lifecycle: 'Lifecycle',\n org: 'Org',\n owner: 'Owner',\n department: 'Department',\n};\n\n/** The single value extractor per facet slot (06-01 extractors, verbatim). */\nconst EXTRACT: Record<FacetKey, (e: Entity) => string | undefined> = {\n lifecycle: entityLifecycle,\n org: entityOrg,\n owner: entityOwnerLabel,\n department: entityDepartment,\n};\n\n/**\n * Per-facet option label (LOCKED — UI-SPEC §Facet Data + §Assumption 5):\n * - lifecycle → capitalise first char ('production' → 'Production')\n * - owner → value AS-IS (already humanised by `entityOwnerLabel`; do NOT humanise again)\n * - org → explicit override for known org IDs from config, else humanise(value)\n */\nfunction labelFor(\n key: FacetKey,\n value: string,\n orgLabels?: Record<string, string>,\n): string {\n switch (key) {\n case 'lifecycle':\n return value.charAt(0).toUpperCase() + value.slice(1);\n case 'owner':\n return value;\n case 'org':\n return orgLabels?.[value] ?? humanise(value);\n default:\n return humanise(value);\n }\n}\n\n/**\n * Sort distinct facet values:\n * - lifecycle by fixed severity order\n * - org / owner / department alphabetically (localeCompare)\n */\nfunction sortValues(key: FacetKey, values: string[]): string[] {\n if (key === 'lifecycle') {\n // Unrecognized lifecycles sort to the END, not the front (indexOf === -1 would\n // otherwise rank them before 'production'). Matches the documented intent.\n const rank = (v: string) => {\n const i = LIFECYCLE_SEVERITY.indexOf(v);\n return i === -1 ? LIFECYCLE_SEVERITY.length : i;\n };\n return [...values].sort((a, b) => rank(a) - rank(b));\n }\n return [...values].sort((a, b) => a.localeCompare(b));\n}\n\n/**\n * Derive stable, labelled, sorted facets from the FULL loaded entity set. Every facet in\n * FACET_ORDER is ALWAYS returned (revised D-05): `enabled` is true when the facet has >= 1\n * distinct value and false when the data has none — the toolbar renders a disabled dropdown for\n * the latter so all filters stay visible even with uniform data. The caller passes the\n * pre-facet universe (`backendEntities.filter(isApigeeEntity)`) so options do not vanish as the\n * grid narrows.\n */\nexport function deriveFacets(\n entities: Entity[],\n orgLabels?: Record<string, string>,\n): DerivedFacet[] {\n return FACET_ORDER.map(key => {\n const distinct = Array.from(\n new Set(entities.map(EXTRACT[key]).filter((v): v is string => !!v)),\n );\n const options = sortValues(key, distinct).map(value => ({\n value,\n label: labelFor(key, value, orgLabels),\n }));\n return {\n key,\n label: FACET_TRIGGER_LABEL[key],\n options,\n enabled: options.length > 0,\n };\n });\n}\n\n/** A single value bucket in a count card: the value, its display label, and the API count. */\nexport interface CountBucket {\n value: string;\n label: string;\n count: number;\n}\n\n/** A facet's per-value API counts, ready to render as a summary count card. */\nexport interface DerivedCount {\n key: FacetKey;\n label: string;\n buckets: CountBucket[];\n}\n\n/**\n * Aggregate per-value API counts for the requested facet keys from an entity set — the data\n * behind the summary count cards. Reuses the facet extractors, labels, and sort order so the\n * cards agree with the filter dropdowns. Entities whose value is absent for a key are omitted,\n * exactly like the facet options.\n */\nexport function deriveCounts(\n entities: Entity[],\n keys: FacetKey[],\n orgLabels?: Record<string, string>,\n): DerivedCount[] {\n return keys.map(key => {\n const counts = new Map<string, number>();\n for (const entity of entities) {\n const value = EXTRACT[key](entity);\n if (value) {\n counts.set(value, (counts.get(value) ?? 0) + 1);\n }\n }\n const buckets = sortValues(key, Array.from(counts.keys())).map(value => ({\n value,\n label: labelFor(key, value, orgLabels),\n count: counts.get(value) ?? 0,\n }));\n return { key, label: FACET_TRIGGER_LABEL[key], buckets };\n });\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grouping.esm.js","sources":["../../src/components/grouping.ts"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/** Humanise a slug: split on `-`/`_`, drop empties, capitalise each word. */\nexport function humanise(slug: string): string {\n return slug\n .split(/[-_]/)\n .filter(Boolean)\n .map(word => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n"],"names":[],"mappings":"AAGO,SAAS,SAAS,IAAA,EAAsB;AAC7C,EAAA,OAAO,IAAA,CACJ,MAAM,MAAM,CAAA,CACZ,OAAO,OAAO,CAAA,CACd,IAAI,CAAA,IAAA,KAAQ,IAAA,CAAK,OAAO,CAAC,CAAA,CAAE,aAAY,GAAI,IAAA,CAAK,MAAM,CAAC,CAAC,CAAA,CACxD,IAAA,CAAK,GAAG,CAAA;AACb;;;;"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { ResponseErrorPanel, ItemCardGrid, EmptyState, LinkButton } from '@backstage/core-components';
|
|
3
|
+
import { Skeleton, Button } from '@backstage/ui';
|
|
4
|
+
|
|
5
|
+
const SR_ONLY = {
|
|
6
|
+
position: "absolute",
|
|
7
|
+
width: 1,
|
|
8
|
+
height: 1,
|
|
9
|
+
padding: 0,
|
|
10
|
+
margin: -1,
|
|
11
|
+
overflow: "hidden",
|
|
12
|
+
clip: "rect(0 0 0 0)",
|
|
13
|
+
whiteSpace: "nowrap",
|
|
14
|
+
border: 0
|
|
15
|
+
};
|
|
16
|
+
function SkeletonGrid({ count = 8 } = {}) {
|
|
17
|
+
return /* @__PURE__ */ jsxs("div", { "aria-busy": "true", children: [
|
|
18
|
+
/* @__PURE__ */ jsx("span", { style: SR_ONLY, "aria-live": "polite", children: "Loading Apigee APIs\u2026" }),
|
|
19
|
+
/* @__PURE__ */ jsx(ItemCardGrid, { children: Array.from({ length: count }).map((_, index) => /* @__PURE__ */ jsx(
|
|
20
|
+
Skeleton,
|
|
21
|
+
{
|
|
22
|
+
width: "100%",
|
|
23
|
+
height: 172,
|
|
24
|
+
"aria-hidden": "true"
|
|
25
|
+
},
|
|
26
|
+
`apigee-skeleton-${index}`
|
|
27
|
+
)) })
|
|
28
|
+
] });
|
|
29
|
+
}
|
|
30
|
+
function ApiCatalogEmptyState() {
|
|
31
|
+
return /* @__PURE__ */ jsx(
|
|
32
|
+
EmptyState,
|
|
33
|
+
{
|
|
34
|
+
missing: "content",
|
|
35
|
+
title: "No Apigee APIs found",
|
|
36
|
+
description: "No API entities with an Apigee org annotation are in the catalog yet. If you expect to see APIs here, check that the apigee-backend sync has run.",
|
|
37
|
+
action: /* @__PURE__ */ jsx(
|
|
38
|
+
LinkButton,
|
|
39
|
+
{
|
|
40
|
+
to: "/catalog?filters[kind]=API",
|
|
41
|
+
variant: "contained",
|
|
42
|
+
color: "primary",
|
|
43
|
+
children: "Browse the full catalog"
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
function NoMatchState({ onClearAll }) {
|
|
50
|
+
return /* @__PURE__ */ jsx(
|
|
51
|
+
EmptyState,
|
|
52
|
+
{
|
|
53
|
+
missing: "content",
|
|
54
|
+
title: "No APIs match your filters",
|
|
55
|
+
description: "Try removing a filter or clearing your search to see more APIs.",
|
|
56
|
+
action: /* @__PURE__ */ jsx(Button, { variant: "primary", onPress: onClearAll, children: "Clear all filters" })
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
function ApiCatalogError({ error }) {
|
|
61
|
+
return /* @__PURE__ */ jsx(ResponseErrorPanel, { title: "Couldn't load Apigee APIs", error });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { ApiCatalogEmptyState, ApiCatalogError, NoMatchState, SkeletonGrid };
|
|
65
|
+
//# sourceMappingURL=states.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"states.esm.js","sources":["../../src/components/states.tsx"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport {\n EmptyState,\n ItemCardGrid,\n LinkButton,\n ResponseErrorPanel,\n} from '@backstage/core-components';\nimport { Button, Skeleton } from '@backstage/ui';\nimport type { CSSProperties } from 'react';\n\nconst SR_ONLY: CSSProperties = {\n position: 'absolute',\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: 'hidden',\n clip: 'rect(0 0 0 0)',\n whiteSpace: 'nowrap',\n border: 0,\n};\n\n/**\n * Loading placeholder: `count` skeleton tiles inside the same responsive grid, using\n * @backstage/ui `Skeleton` blocks (D-07). The region is marked `aria-busy` with a\n * visually-hidden polite live region so assistive tech announces the load. (UI-05)\n */\nexport function SkeletonGrid({ count = 8 }: { count?: number } = {}) {\n return (\n <div aria-busy=\"true\">\n <span style={SR_ONLY} aria-live=\"polite\">\n Loading Apigee APIs…\n </span>\n <ItemCardGrid>\n {Array.from({ length: count }).map((_, index) => (\n <Skeleton\n key={`apigee-skeleton-${index}`}\n width=\"100%\"\n height={172}\n aria-hidden=\"true\"\n />\n ))}\n </ItemCardGrid>\n </div>\n );\n}\n\n/** Friendly empty state shown when no Apigee APIs are in the catalog (UI-05). */\nexport function ApiCatalogEmptyState() {\n return (\n <EmptyState\n missing=\"content\"\n title=\"No Apigee APIs found\"\n description=\"No API entities with an Apigee org annotation are in the catalog yet. If you expect to see APIs here, check that the apigee-backend sync has run.\"\n action={\n <LinkButton\n to=\"/catalog?filters[kind]=API\"\n variant=\"contained\"\n color=\"primary\"\n >\n Browse the full catalog\n </LinkButton>\n }\n />\n );\n}\n\n/**\n * Distinct \"filtered to zero\" state (D-10) — shown when Apigee APIs exist but the active\n * filters/search exclude them all. Unlike `ApiCatalogEmptyState` (which links out to the full\n * catalog), this offers a non-destructive reset CTA (the button below) which is fully reversible,\n * so it uses primary/neutral styling (no error color, no confirmation dialog). (UI-08)\n */\nexport function NoMatchState({ onClearAll }: { onClearAll: () => void }) {\n return (\n <EmptyState\n missing=\"content\"\n title=\"No APIs match your filters\"\n description=\"Try removing a filter or clearing your search to see more APIs.\"\n action={\n <Button variant=\"primary\" onPress={onClearAll}>\n Clear all filters\n </Button>\n }\n />\n );\n}\n\n/** Error state shown when the catalog fetch fails (UI-05). */\nexport function ApiCatalogError({ error }: { error: Error }) {\n return <ResponseErrorPanel title=\"Couldn't load Apigee APIs\" error={error} />;\n}\n"],"names":[],"mappings":";;;;AAUA,MAAM,OAAA,GAAyB;AAAA,EAC7B,QAAA,EAAU,UAAA;AAAA,EACV,KAAA,EAAO,CAAA;AAAA,EACP,MAAA,EAAQ,CAAA;AAAA,EACR,OAAA,EAAS,CAAA;AAAA,EACT,MAAA,EAAQ,EAAA;AAAA,EACR,QAAA,EAAU,QAAA;AAAA,EACV,IAAA,EAAM,eAAA;AAAA,EACN,UAAA,EAAY,QAAA;AAAA,EACZ,MAAA,EAAQ;AACV,CAAA;AAOO,SAAS,aAAa,EAAE,KAAA,GAAQ,CAAA,EAAE,GAAwB,EAAC,EAAG;AACnE,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,WAAA,EAAU,MAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,KAAA,EAAO,OAAA,EAAS,WAAA,EAAU,UAAS,QAAA,EAAA,2BAAA,EAEzC,CAAA;AAAA,oBACA,GAAA,CAAC,YAAA,EAAA,EACE,QAAA,EAAA,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,KAAA,EAAO,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,KAAA,qBACrC,GAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QAEC,KAAA,EAAM,MAAA;AAAA,QACN,MAAA,EAAQ,GAAA;AAAA,QACR,aAAA,EAAY;AAAA,OAAA;AAAA,MAHP,mBAAmB,KAAK,CAAA;AAAA,KAKhC,CAAA,EACH;AAAA,GAAA,EACF,CAAA;AAEJ;AAGO,SAAS,oBAAA,GAAuB;AACrC,EAAA,uBACE,GAAA;AAAA,IAAC,UAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAQ,SAAA;AAAA,MACR,KAAA,EAAM,sBAAA;AAAA,MACN,WAAA,EAAY,mJAAA;AAAA,MACZ,MAAA,kBACE,GAAA;AAAA,QAAC,UAAA;AAAA,QAAA;AAAA,UACC,EAAA,EAAG,4BAAA;AAAA,UACH,OAAA,EAAQ,WAAA;AAAA,UACR,KAAA,EAAM,SAAA;AAAA,UACP,QAAA,EAAA;AAAA;AAAA;AAED;AAAA,GAEJ;AAEJ;AAQO,SAAS,YAAA,CAAa,EAAE,UAAA,EAAW,EAA+B;AACvE,EAAA,uBACE,GAAA;AAAA,IAAC,UAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAQ,SAAA;AAAA,MACR,KAAA,EAAM,4BAAA;AAAA,MACN,WAAA,EAAY,iEAAA;AAAA,MACZ,wBACE,GAAA,CAAC,MAAA,EAAA,EAAO,SAAQ,SAAA,EAAU,OAAA,EAAS,YAAY,QAAA,EAAA,mBAAA,EAE/C;AAAA;AAAA,GAEJ;AAEJ;AAGO,SAAS,eAAA,CAAgB,EAAE,KAAA,EAAM,EAAqB;AAC3D,EAAA,uBAAO,GAAA,CAAC,kBAAA,EAAA,EAAmB,KAAA,EAAM,2BAAA,EAA4B,KAAA,EAAc,CAAA;AAC7E;;;;"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { useMemo, useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { useSearchParams } from 'react-router-dom';
|
|
3
|
+
import { useEntityList } from '@backstage/plugin-catalog-react';
|
|
4
|
+
import { FieldFacetFilter, entityDepartment, entityOwnerLabel, entityOrg, entityLifecycle, ApiSearchFilter } from './apigeeFacetFilters.esm.js';
|
|
5
|
+
import { parseSearchParams, parsePageParam, parsePageSizeParam, parseApiParam, selectOnlyFacetParams } from './apigeeSearchParams.esm.js';
|
|
6
|
+
import { useDebouncedValue } from './useDebouncedValue.esm.js';
|
|
7
|
+
|
|
8
|
+
const FACET_EXTRACT = {
|
|
9
|
+
lifecycle: entityLifecycle,
|
|
10
|
+
org: entityOrg,
|
|
11
|
+
owner: entityOwnerLabel,
|
|
12
|
+
department: entityDepartment
|
|
13
|
+
};
|
|
14
|
+
const FILTER_KEY = {
|
|
15
|
+
lifecycle: "apigeeLifecycle",
|
|
16
|
+
org: "apigeeOrgFacet",
|
|
17
|
+
owner: "apigeeOwner",
|
|
18
|
+
department: "apigeeDepartment"
|
|
19
|
+
};
|
|
20
|
+
function useApigeeFilters() {
|
|
21
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
22
|
+
const { updateFilters } = useEntityList();
|
|
23
|
+
const spString = searchParams.toString();
|
|
24
|
+
const state = useMemo(() => parseSearchParams(searchParams), [spString]);
|
|
25
|
+
const page = useMemo(() => parsePageParam(searchParams), [spString]);
|
|
26
|
+
const pageSizeParam = useMemo(
|
|
27
|
+
() => parsePageSizeParam(searchParams),
|
|
28
|
+
[spString]
|
|
29
|
+
// eslint-disable-line
|
|
30
|
+
);
|
|
31
|
+
const openApiRef = useMemo(() => parseApiParam(searchParams), [spString]);
|
|
32
|
+
const [inputValue, setInputValue] = useState(() => state.q);
|
|
33
|
+
const debouncedQ = useDebouncedValue(inputValue, 200);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
setSearchParams(
|
|
36
|
+
(prev) => {
|
|
37
|
+
const next = new URLSearchParams(prev);
|
|
38
|
+
const q = debouncedQ.trim();
|
|
39
|
+
const qChanged = q !== (prev.get("q") ?? "");
|
|
40
|
+
if (q) {
|
|
41
|
+
next.set("q", q);
|
|
42
|
+
} else {
|
|
43
|
+
next.delete("q");
|
|
44
|
+
}
|
|
45
|
+
if (qChanged) {
|
|
46
|
+
next.delete("page");
|
|
47
|
+
}
|
|
48
|
+
return next;
|
|
49
|
+
},
|
|
50
|
+
{ replace: true }
|
|
51
|
+
);
|
|
52
|
+
}, [debouncedQ, setSearchParams]);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const patch = {};
|
|
55
|
+
Object.keys(FILTER_KEY).forEach((k) => {
|
|
56
|
+
patch[FILTER_KEY[k]] = state.selected[k].length ? new FieldFacetFilter(FACET_EXTRACT[k], state.selected[k]) : void 0;
|
|
57
|
+
});
|
|
58
|
+
patch.apigeeSearch = state.q.trim() ? new ApiSearchFilter(state.q) : void 0;
|
|
59
|
+
updateFilters(patch);
|
|
60
|
+
}, [spString, updateFilters]);
|
|
61
|
+
const toggleFacet = useCallback(
|
|
62
|
+
(key, value) => {
|
|
63
|
+
setSearchParams(
|
|
64
|
+
(prev) => {
|
|
65
|
+
const next = new URLSearchParams(prev);
|
|
66
|
+
const cur = next.getAll(key);
|
|
67
|
+
next.delete(key);
|
|
68
|
+
const after = cur.includes(value) ? cur.filter((v) => v !== value) : [...cur, value];
|
|
69
|
+
after.forEach((v) => next.append(key, v));
|
|
70
|
+
next.delete("page");
|
|
71
|
+
return next;
|
|
72
|
+
},
|
|
73
|
+
{ replace: true }
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
[setSearchParams]
|
|
77
|
+
);
|
|
78
|
+
const selectOnlyFacet = useCallback(
|
|
79
|
+
(key, value) => {
|
|
80
|
+
setSearchParams((prev) => selectOnlyFacetParams(prev, key, value), {
|
|
81
|
+
replace: true
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
[setSearchParams]
|
|
85
|
+
);
|
|
86
|
+
const clearFacet = useCallback(
|
|
87
|
+
(key) => {
|
|
88
|
+
setSearchParams(
|
|
89
|
+
(prev) => {
|
|
90
|
+
const next = new URLSearchParams(prev);
|
|
91
|
+
next.delete(key);
|
|
92
|
+
next.delete("page");
|
|
93
|
+
return next;
|
|
94
|
+
},
|
|
95
|
+
{ replace: true }
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
[setSearchParams]
|
|
99
|
+
);
|
|
100
|
+
const clearAll = useCallback(() => {
|
|
101
|
+
setInputValue("");
|
|
102
|
+
setSearchParams(
|
|
103
|
+
(prev) => {
|
|
104
|
+
const next = new URLSearchParams();
|
|
105
|
+
const size = prev.get("size");
|
|
106
|
+
if (size) {
|
|
107
|
+
next.set("size", size);
|
|
108
|
+
}
|
|
109
|
+
const apiRef = prev.get("api");
|
|
110
|
+
if (apiRef) {
|
|
111
|
+
next.set("api", apiRef);
|
|
112
|
+
}
|
|
113
|
+
return next;
|
|
114
|
+
},
|
|
115
|
+
{ replace: true }
|
|
116
|
+
);
|
|
117
|
+
}, [setSearchParams]);
|
|
118
|
+
const setPage = useCallback(
|
|
119
|
+
(nextPage) => {
|
|
120
|
+
setSearchParams(
|
|
121
|
+
(prev) => {
|
|
122
|
+
const next = new URLSearchParams(prev);
|
|
123
|
+
if (nextPage > 1) {
|
|
124
|
+
next.set("page", String(nextPage));
|
|
125
|
+
} else {
|
|
126
|
+
next.delete("page");
|
|
127
|
+
}
|
|
128
|
+
return next;
|
|
129
|
+
},
|
|
130
|
+
{ replace: true }
|
|
131
|
+
);
|
|
132
|
+
},
|
|
133
|
+
[setSearchParams]
|
|
134
|
+
);
|
|
135
|
+
const setPageSize = useCallback(
|
|
136
|
+
(size) => {
|
|
137
|
+
setSearchParams(
|
|
138
|
+
(prev) => {
|
|
139
|
+
const next = new URLSearchParams(prev);
|
|
140
|
+
next.set("size", String(size));
|
|
141
|
+
next.delete("page");
|
|
142
|
+
return next;
|
|
143
|
+
},
|
|
144
|
+
{ replace: true }
|
|
145
|
+
);
|
|
146
|
+
},
|
|
147
|
+
[setSearchParams]
|
|
148
|
+
);
|
|
149
|
+
const openApi = useCallback(
|
|
150
|
+
(ref) => {
|
|
151
|
+
setSearchParams(
|
|
152
|
+
(prev) => {
|
|
153
|
+
const next = new URLSearchParams(prev);
|
|
154
|
+
next.set("api", ref);
|
|
155
|
+
return next;
|
|
156
|
+
},
|
|
157
|
+
{ replace: true }
|
|
158
|
+
);
|
|
159
|
+
},
|
|
160
|
+
[setSearchParams]
|
|
161
|
+
);
|
|
162
|
+
const closeApi = useCallback(() => {
|
|
163
|
+
setSearchParams(
|
|
164
|
+
(prev) => {
|
|
165
|
+
const next = new URLSearchParams(prev);
|
|
166
|
+
next.delete("api");
|
|
167
|
+
return next;
|
|
168
|
+
},
|
|
169
|
+
{ replace: true }
|
|
170
|
+
);
|
|
171
|
+
}, [setSearchParams]);
|
|
172
|
+
return {
|
|
173
|
+
state,
|
|
174
|
+
inputValue,
|
|
175
|
+
setInputValue,
|
|
176
|
+
toggleFacet,
|
|
177
|
+
selectOnlyFacet,
|
|
178
|
+
clearFacet,
|
|
179
|
+
clearAll,
|
|
180
|
+
page,
|
|
181
|
+
setPage,
|
|
182
|
+
pageSizeParam,
|
|
183
|
+
setPageSize,
|
|
184
|
+
openApiRef,
|
|
185
|
+
openApi,
|
|
186
|
+
closeApi
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export { useApigeeFilters };
|
|
191
|
+
//# sourceMappingURL=useApigeeFilters.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useApigeeFilters.esm.js","sources":["../../src/components/useApigeeFilters.ts"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { useEntityList } from '@backstage/plugin-catalog-react';\nimport type { Entity } from '@backstage/catalog-model';\nimport {\n ApiSearchFilter,\n FieldFacetFilter,\n entityDepartment,\n entityLifecycle,\n entityOrg,\n entityOwnerLabel,\n} from './apigeeFacetFilters';\nimport {\n parseApiParam,\n parsePageParam,\n parsePageSizeParam,\n parseSearchParams,\n selectOnlyFacetParams,\n} from './apigeeSearchParams';\nimport { useDebouncedValue } from './useDebouncedValue';\nimport type { ApigeeFilters } from './ApigeeApiCatalogPage';\nimport type { FacetKey } from './facetModel';\n\n/** Value extractor per facet slot used to build the FILTER (06-01 extractors). */\nconst FACET_EXTRACT: Record<FacetKey, (e: Entity) => string | undefined> = {\n lifecycle: entityLifecycle,\n org: entityOrg,\n owner: entityOwnerLabel,\n department: entityDepartment,\n};\n\n/** Facet slot → the `ApigeeFilters` key its `FieldFacetFilter` registers under. */\ntype FacetFilterKey =\n | 'apigeeLifecycle'\n | 'apigeeOrgFacet'\n | 'apigeeOwner'\n | 'apigeeDepartment';\nconst FILTER_KEY: Record<FacetKey, FacetFilterKey> = {\n lifecycle: 'apigeeLifecycle',\n org: 'apigeeOrgFacet',\n owner: 'apigeeOwner',\n department: 'apigeeDepartment',\n};\n\n/**\n * The `useSearchParams` ⇄ `updateFilters` bridge: the URL is the single source of truth. It parses\n * the clean facet + `q` keys into state, drives the provider's in-memory filters one-directionally\n * (URL → `updateFilters`, no re-fetch), and writes every change back with `{ replace: true }` (D-08)\n * so the address bar stays clean, shareable and reload-safe (UI-09). The search box keeps one local\n * `inputValue` (instant echo) with a single 200 ms trailing debounce (D-09) → URL; the URL never\n * writes back into the input, so there is no feedback loop (UI-06, UI-07).\n */\nexport function useApigeeFilters() {\n const [searchParams, setSearchParams] = useSearchParams();\n const { updateFilters } = useEntityList<ApigeeFilters>();\n\n // Depend on the stable STRING, not the per-render URLSearchParams object (Pitfall 4).\n const spString = searchParams.toString();\n const state = useMemo(() => parseSearchParams(searchParams), [spString]); // eslint-disable-line\n const page = useMemo(() => parsePageParam(searchParams), [spString]); // eslint-disable-line\n const pageSizeParam = useMemo(\n () => parsePageSizeParam(searchParams),\n [spString], // eslint-disable-line\n );\n const openApiRef = useMemo(() => parseApiParam(searchParams), [spString]); // eslint-disable-line\n\n // Search text is the only local state; seeded once from the URL.\n const [inputValue, setInputValue] = useState(() => state.q);\n const debouncedQ = useDebouncedValue(inputValue, 200); // D-09\n\n // E2: debounced q → URL (replace), merged with the current facet params.\n useEffect(() => {\n setSearchParams(\n prev => {\n const next = new URLSearchParams(prev);\n const q = debouncedQ.trim();\n // Only a REAL query change resets pagination: the mount pass re-sees the URL's\n // own q, so a shared `?q=…&page=2` link must keep its page on first render.\n const qChanged = q !== (prev.get('q') ?? '');\n if (q) {\n next.set('q', q);\n } else {\n next.delete('q');\n }\n if (qChanged) {\n next.delete('page');\n }\n return next;\n },\n { replace: true },\n );\n }, [debouncedQ, setSearchParams]);\n\n // E1: URL → provider filters (one-directional). Runs on mount (seeds shared links) + on change.\n useEffect(() => {\n const patch: Partial<ApigeeFilters> = {};\n (Object.keys(FILTER_KEY) as FacetKey[]).forEach(k => {\n patch[FILTER_KEY[k]] = state.selected[k].length\n ? new FieldFacetFilter(FACET_EXTRACT[k], state.selected[k])\n : undefined;\n });\n patch.apigeeSearch = state.q.trim()\n ? new ApiSearchFilter(state.q)\n : undefined;\n updateFilters(patch);\n }, [spString, updateFilters]); // eslint-disable-line\n\n const toggleFacet = useCallback(\n (key: FacetKey, value: string) => {\n setSearchParams(\n prev => {\n const next = new URLSearchParams(prev);\n const cur = next.getAll(key);\n next.delete(key);\n const after = cur.includes(value)\n ? cur.filter(v => v !== value)\n : [...cur, value];\n after.forEach(v => next.append(key, v));\n next.delete('page');\n return next;\n },\n { replace: true },\n );\n },\n [setSearchParams],\n );\n\n // Count-card single-select: the clicked value replaces any other card selection (see helper).\n const selectOnlyFacet = useCallback(\n (key: FacetKey, value: string) => {\n setSearchParams(prev => selectOnlyFacetParams(prev, key, value), {\n replace: true,\n });\n },\n [setSearchParams],\n );\n\n const clearFacet = useCallback(\n (key: FacetKey) => {\n setSearchParams(\n prev => {\n const next = new URLSearchParams(prev);\n next.delete(key);\n next.delete('page');\n return next;\n },\n { replace: true },\n );\n },\n [setSearchParams],\n );\n\n const clearAll = useCallback(() => {\n setInputValue('');\n // Clears filters + search + page, but keeps orthogonal view state: the page-size\n // preference and the open details drawer (`api`).\n setSearchParams(\n prev => {\n const next = new URLSearchParams();\n const size = prev.get('size');\n if (size) {\n next.set('size', size);\n }\n const apiRef = prev.get('api');\n if (apiRef) {\n next.set('api', apiRef);\n }\n return next;\n },\n { replace: true },\n );\n }, [setSearchParams]);\n\n // Pagination lives in the URL alongside the filters (shareable + reload-safe). Page 1\n // is the clean default, so it drops the `page` key rather than writing `page=1`.\n const setPage = useCallback(\n (nextPage: number) => {\n setSearchParams(\n prev => {\n const next = new URLSearchParams(prev);\n if (nextPage > 1) {\n next.set('page', String(nextPage));\n } else {\n next.delete('page');\n }\n return next;\n },\n { replace: true },\n );\n },\n [setSearchParams],\n );\n\n // Changing the page size returns to page 1 in the same write, so the offset stays valid.\n const setPageSize = useCallback(\n (size: number) => {\n setSearchParams(\n prev => {\n const next = new URLSearchParams(prev);\n next.set('size', String(size));\n next.delete('page');\n return next;\n },\n { replace: true },\n );\n },\n [setSearchParams],\n );\n\n // The open API details drawer is tracked in the URL (`?api=<namespace>/<name>`) so the drawer\n // is shareable and survives reloads. Writes replace history (D-08), like the filter writers,\n // and preserve every other param.\n const openApi = useCallback(\n (ref: string) => {\n setSearchParams(\n prev => {\n const next = new URLSearchParams(prev);\n next.set('api', ref);\n return next;\n },\n { replace: true },\n );\n },\n [setSearchParams],\n );\n\n const closeApi = useCallback(() => {\n setSearchParams(\n prev => {\n const next = new URLSearchParams(prev);\n next.delete('api');\n return next;\n },\n { replace: true },\n );\n }, [setSearchParams]);\n\n return {\n state,\n inputValue,\n setInputValue,\n toggleFacet,\n selectOnlyFacet,\n clearFacet,\n clearAll,\n page,\n setPage,\n pageSizeParam,\n setPageSize,\n openApiRef,\n openApi,\n closeApi,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
function useDebouncedValue(value, delayMs) {
|
|
4
|
+
const [debounced, setDebounced] = useState(value);
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const id = setTimeout(() => setDebounced(value), delayMs);
|
|
7
|
+
return () => clearTimeout(id);
|
|
8
|
+
}, [value, delayMs]);
|
|
9
|
+
return debounced;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { useDebouncedValue };
|
|
13
|
+
//# sourceMappingURL=useDebouncedValue.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDebouncedValue.esm.js","sources":["../../src/components/useDebouncedValue.ts"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport { useEffect, useState } from 'react';\n\n/**\n * Trailing-edge debounce: returns `value` immediately, then re-emits the latest `value`\n * after `delayMs` of no change. Rapid successive changes cancel the prior timer via\n * `clearTimeout`, so only the trailing value is emitted. No external dependency (D-09).\n */\nexport function useDebouncedValue<T>(value: T, delayMs: number): T {\n const [debounced, setDebounced] = useState(value);\n useEffect(() => {\n const id = setTimeout(() => setDebounced(value), delayMs);\n return () => clearTimeout(id);\n }, [value, delayMs]);\n return debounced;\n}\n"],"names":[],"mappings":";;AAQO,SAAS,iBAAA,CAAqB,OAAU,OAAA,EAAoB;AACjE,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAS,KAAK,CAAA;AAChD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,KAAK,UAAA,CAAW,MAAM,YAAA,CAAa,KAAK,GAAG,OAAO,CAAA;AACxD,IAAA,OAAO,MAAM,aAAa,EAAE,CAAA;AAAA,EAC9B,CAAA,EAAG,CAAC,KAAA,EAAO,OAAO,CAAC,CAAA;AACnB,EAAA,OAAO,SAAA;AACT;;;;"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const ANNOTATION_APIGEE_ORGS = "cloud.google.com/orgs";
|
|
2
|
+
const ANNOTATION_APIGEE_ORG = "cloud.google.com/org";
|
|
3
|
+
const ANNOTATION_HAS_SPEC = "cloud.google.com/has-spec";
|
|
4
|
+
const ANNOTATION_VERSION = "cloud.google.com/version";
|
|
5
|
+
const ANNOTATION_PROXY_NAME = "cloud.google.com/proxy-name";
|
|
6
|
+
const LABEL_DEPARTMENT = "department";
|
|
7
|
+
|
|
8
|
+
export { ANNOTATION_APIGEE_ORG, ANNOTATION_APIGEE_ORGS, ANNOTATION_HAS_SPEC, ANNOTATION_PROXY_NAME, ANNOTATION_VERSION, LABEL_DEPARTMENT };
|
|
9
|
+
//# sourceMappingURL=constants.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.esm.js","sources":["../src/constants.ts"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n// Apigee catalog annotation keys. Authoritative source:\n// plugins/apigee-backend/src/lib/entity-builder.ts (verified 2026-07-09).\n\n/** Org annotation written by proxyToEntities (PLURAL, proxy-derived API entities). */\nexport const ANNOTATION_APIGEE_ORGS = 'cloud.google.com/orgs';\n/** Org annotation written by hubOnlyToEntity (SINGULAR, hub-only API entities). */\nexport const ANNOTATION_APIGEE_ORG = 'cloud.google.com/org';\n/** 'true' | 'false' — set by applyHubMetadata when a spec is embedded or linked. */\nexport const ANNOTATION_HAS_SPEC = 'cloud.google.com/has-spec';\n/** Full API Hub resource name — present only on hub-only entities. */\nexport const ANNOTATION_API_HUB_ID = 'cloud.google.com/api-hub-id';\n/**\n * Spec `info.version` — emitted by the backend on both Component and API entities\n * (entity-provider.ts / applyHubMetadata). Primary source for the card version line.\n */\nexport const ANNOTATION_VERSION = 'cloud.google.com/version';\n/**\n * Proxy name — when `cloud.google.com/version` is absent, the card derives the version\n * from this name's trailing `-v<major>` segment (e.g. `...-v4` -> `4.0.0`).\n */\nexport const ANNOTATION_PROXY_NAME = 'cloud.google.com/proxy-name';\n\n/**\n * Department label key (`metadata.labels.department`). Written by the backend's\n * applyHubMetadata from the API Hub \"Business unit\" attribute.\n */\nexport const LABEL_DEPARTMENT = 'department';\n"],"names":[],"mappings":"AAKO,MAAM,sBAAA,GAAyB;AAE/B,MAAM,qBAAA,GAAwB;AAE9B,MAAM,mBAAA,GAAsB;AAO5B,MAAM,kBAAA,GAAqB;AAK3B,MAAM,qBAAA,GAAwB;AAM9B,MAAM,gBAAA,GAAmB;;;;"}
|