From f7c2027065b0d3cff5e4117a2aceb8cb9cc39c11 Mon Sep 17 00:00:00 2001
From: bwnyasse <5323628+bwnyasse@users.noreply.github.com>
Date: Sun, 16 Nov 2025 21:45:17 -0500
Subject: [PATCH 1/2] feat: add internal API test harness
This commit introduces a new internal API test harness built with React and Vite. This harness provides a user interface for testing various API endpoints, including authentication, core integrations, and entity-related APIs.
The harness includes the following features:
- Firebase authentication integration for secure API testing.
- A modular design with separate components for different API categories.
- Form-based input for API parameters, allowing users to easily configure requests.
- JSON-based response display for clear and readable API results.
- Error handling and display for debugging purposes.
- A navigation system for easy access to different API endpoints.
- Environment-specific configuration for testing in different environments.
This harness will enable developers to quickly and efficiently test API endpoints, ensuring the quality and reliability of the KROW backend services.
The following files were added:
- Makefile: Added targets for installing, developing, building, and deploying the API test harness.
- firebase.json: Added hosting configurations for the API test harness in development and staging environments.
- firebase/internal-launchpad/index.html: Updated with accordion styles and navigation for diagrams and documents.
- internal-api-harness/.env.example: Example environment variables for the API test harness.
- internal-api-harness/.gitignore: Git ignore file for the API test harness.
- internal-api-harness/README.md: README file for the API test harness.
- internal-api-harness/components.json: Configuration file for shadcn-ui components.
- internal-api-harness/eslint.config.js: ESLint configuration file.
- internal-api-harness/index.html: Main HTML file for the API test harness.
- internal-api-harness/jsconfig.json: JSConfig file for the API test harness.
- internal-api-harness/package.json: Package file for the API test harness.
- internal-api-harness/postcss.config.js: PostCSS configuration file.
- internal-api-harness/public/logo.svg: Krow logo.
- internal-api-harness/public/vite.svg: Vite logo.
- internal-api-harness/src/App.css: CSS file for the App component.
- internal-api-harness/src/App.jsx: Main App component.
- internal-api-harness/src/api/client.js: API client for making requests to the backend.
- internal-api-harness/src/api/krowSDK.js: SDK for interacting with Krow APIs.
- internal-api-harness/src/assets/react.svg: React logo.
- internal-api-harness/src/components/ApiResponse.jsx: Component for displaying API responses.
- internal-api-harness/src/components/Layout.jsx: Layout component for the API test harness.
- internal-api-harness/src/components/ServiceTester.jsx: Component for testing individual services.
- internal-api-harness/src/components/ui/button.jsx: Button component.
- internal-api-harness/src/components/ui/card.jsx: Card component.
- internal-api-harness/src/components/ui/collapsible.jsx: Collapsible component.
- internal-api-harness/src/components/ui/input.jsx: Input component.
- internal-api-harness/src/components/ui/label.jsx: Label component.
- internal-api-harness/src/components/ui/select.jsx: Select component.
- internal-api-harness/src/components/ui/textarea.jsx: Textarea component.
- internal-api-harness/src/firebase.js: Firebase configuration file.
- internal-api-harness/src/index.css: Main CSS file.
- internal-api-harness/src/lib/utils.js: Utility functions.
- internal-api-harness/src/main.jsx: Main entry point for the React application.
- internal-api-harness/src/pages/ApiPlaceholder.jsx: Placeholder component for unimplemented APIs.
- internal-api-harness/src/pages/EntityTester.jsx: Component for testing entity APIs.
- internal-api-harness/src/pages/GenerateImage.jsx: Component for testing the Generate Image API.
- internal-api-harness/src/pages/Home.jsx: Home page component.
- internal-api-harness/src/pages/Login.jsx: Login page component.
- internal-api-harness/src/pages/auth/GetMe.jsx: Component for testing the Get Me API.
- internal-api-harness/src/pages/core/CreateSignedUrl.jsx: Component for testing the Create Signed URL API.
- internal-api-harness/src/pages/core/InvokeLLM.jsx: Component for testing the Invoke LLM API.
- internal-api-harness/src/pages/core/SendEmail.jsx: Component for testing the Send Email API.
- internal-api-harness/src/pages/core/UploadFile.jsx: Component for testing the Upload File API.
- internal-api-harness/src/pages/core/UploadPrivateFile.jsx: Component for testing the Upload Private File API.
- internal-api-harness/tailwind.config.js: Tailwind CSS configuration file.
- internal-api-harness/vite.config.js: Vite configuration file.
---
Makefile | 17 +
firebase.json | 32 +
firebase/internal-launchpad/index.html | 273 +-
internal-api-harness/.env.example | 8 +
internal-api-harness/.gitignore | 24 +
internal-api-harness/README.md | 16 +
internal-api-harness/components.json | 17 +
internal-api-harness/eslint.config.js | 29 +
internal-api-harness/index.html | 13 +
internal-api-harness/jsconfig.json | 10 +
internal-api-harness/package-lock.json | 6611 +++++++++++++++++
internal-api-harness/package.json | 45 +
internal-api-harness/postcss.config.js | 6 +
internal-api-harness/public/logo.svg | 18 +
internal-api-harness/public/vite.svg | 1 +
internal-api-harness/src/App.css | 42 +
internal-api-harness/src/App.jsx | 45 +
internal-api-harness/src/api/client.js | 22 +
internal-api-harness/src/api/krowSDK.js | 147 +
internal-api-harness/src/assets/react.svg | 1 +
.../src/components/ApiResponse.jsx | 38 +
.../src/components/Layout.jsx | 125 +
.../src/components/ServiceTester.jsx | 120 +
.../src/components/ui/button.jsx | 48 +
.../src/components/ui/card.jsx | 50 +
.../src/components/ui/collapsible.jsx | 11 +
.../src/components/ui/input.jsx | 19 +
.../src/components/ui/label.jsx | 16 +
.../src/components/ui/select.jsx | 105 +
.../src/components/ui/textarea.jsx | 18 +
internal-api-harness/src/firebase.js | 17 +
internal-api-harness/src/index.css | 76 +
internal-api-harness/src/lib/utils.js | 6 +
internal-api-harness/src/main.jsx | 10 +
.../src/pages/ApiPlaceholder.jsx | 13 +
.../src/pages/EntityTester.jsx | 190 +
.../src/pages/GenerateImage.jsx | 28 +
internal-api-harness/src/pages/Home.jsx | 31 +
internal-api-harness/src/pages/Login.jsx | 37 +
internal-api-harness/src/pages/auth/GetMe.jsx | 49 +
.../src/pages/core/CreateSignedUrl.jsx | 74 +
.../src/pages/core/InvokeLLM.jsx | 81 +
.../src/pages/core/SendEmail.jsx | 75 +
.../src/pages/core/UploadFile.jsx | 67 +
.../src/pages/core/UploadPrivateFile.jsx | 67 +
internal-api-harness/tailwind.config.js | 96 +
internal-api-harness/vite.config.js | 17 +
47 files changed, 8707 insertions(+), 154 deletions(-)
create mode 100644 internal-api-harness/.env.example
create mode 100644 internal-api-harness/.gitignore
create mode 100644 internal-api-harness/README.md
create mode 100644 internal-api-harness/components.json
create mode 100644 internal-api-harness/eslint.config.js
create mode 100644 internal-api-harness/index.html
create mode 100644 internal-api-harness/jsconfig.json
create mode 100644 internal-api-harness/package-lock.json
create mode 100644 internal-api-harness/package.json
create mode 100644 internal-api-harness/postcss.config.js
create mode 100644 internal-api-harness/public/logo.svg
create mode 100644 internal-api-harness/public/vite.svg
create mode 100644 internal-api-harness/src/App.css
create mode 100644 internal-api-harness/src/App.jsx
create mode 100644 internal-api-harness/src/api/client.js
create mode 100644 internal-api-harness/src/api/krowSDK.js
create mode 100644 internal-api-harness/src/assets/react.svg
create mode 100644 internal-api-harness/src/components/ApiResponse.jsx
create mode 100644 internal-api-harness/src/components/Layout.jsx
create mode 100644 internal-api-harness/src/components/ServiceTester.jsx
create mode 100644 internal-api-harness/src/components/ui/button.jsx
create mode 100644 internal-api-harness/src/components/ui/card.jsx
create mode 100644 internal-api-harness/src/components/ui/collapsible.jsx
create mode 100644 internal-api-harness/src/components/ui/input.jsx
create mode 100644 internal-api-harness/src/components/ui/label.jsx
create mode 100644 internal-api-harness/src/components/ui/select.jsx
create mode 100644 internal-api-harness/src/components/ui/textarea.jsx
create mode 100644 internal-api-harness/src/firebase.js
create mode 100644 internal-api-harness/src/index.css
create mode 100644 internal-api-harness/src/lib/utils.js
create mode 100644 internal-api-harness/src/main.jsx
create mode 100644 internal-api-harness/src/pages/ApiPlaceholder.jsx
create mode 100644 internal-api-harness/src/pages/EntityTester.jsx
create mode 100644 internal-api-harness/src/pages/GenerateImage.jsx
create mode 100644 internal-api-harness/src/pages/Home.jsx
create mode 100644 internal-api-harness/src/pages/Login.jsx
create mode 100644 internal-api-harness/src/pages/auth/GetMe.jsx
create mode 100644 internal-api-harness/src/pages/core/CreateSignedUrl.jsx
create mode 100644 internal-api-harness/src/pages/core/InvokeLLM.jsx
create mode 100644 internal-api-harness/src/pages/core/SendEmail.jsx
create mode 100644 internal-api-harness/src/pages/core/UploadFile.jsx
create mode 100644 internal-api-harness/src/pages/core/UploadPrivateFile.jsx
create mode 100644 internal-api-harness/tailwind.config.js
create mode 100644 internal-api-harness/vite.config.js
diff --git a/Makefile b/Makefile
index e734abdb..eebde364 100644
--- a/Makefile
+++ b/Makefile
@@ -145,6 +145,23 @@ admin-build:
@node scripts/patch-admin-layout-for-env-label.js
@cd admin-web && VITE_APP_ENV=$(ENV) npm run build
+# --- API Test Harness ---
+harness-install:
+ @echo "--> Installing API Test Harness dependencies..."
+ @cd internal-api-harness && npm install
+
+harness-dev:
+ @echo "--> Starting API Test Harness development server on http://localhost:5175 ..."
+ @cd internal-api-harness && npm run dev -- --port 5175
+
+harness-build:
+ @echo "--> Building API Test Harness for production..."
+ @cd internal-api-harness && npm run build -- --mode $(ENV)
+
+harness-deploy: harness-build
+ @echo "--> Deploying API Test Harness to [$(ENV)] environment..."
+ @firebase deploy --only hosting:api-harness-$(ENV) --project=$(FIREBASE_ALIAS)
+
deploy-admin: admin-build
@echo "--> Building and deploying Admin Console to Cloud Run [$(ENV)]..."
@echo " - Step 1: Building container image..."
diff --git a/firebase.json b/firebase.json
index 360ffbe7..8900d6b8 100644
--- a/firebase.json
+++ b/firebase.json
@@ -44,6 +44,38 @@
"destination": "/index.html"
}
]
+ },
+ {
+ "target": "api-harness-dev",
+ "public": "internal-api-harness/dist",
+ "site": "krow-api-harness-dev",
+ "ignore": [
+ "firebase.json",
+ "**/.*",
+ "**/node_modules/**"
+ ],
+ "rewrites": [
+ {
+ "source": "**",
+ "destination": "/index.html"
+ }
+ ]
+ },
+ {
+ "target": "api-harness-staging",
+ "public": "internal-api-harness/dist",
+ "site": "krow-api-harness-staging",
+ "ignore": [
+ "firebase.json",
+ "**/.*",
+ "**/node_modules/**"
+ ],
+ "rewrites": [
+ {
+ "source": "**",
+ "destination": "/index.html"
+ }
+ ]
}
],
"emulators": {
diff --git a/firebase/internal-launchpad/index.html b/firebase/internal-launchpad/index.html
index 2e3b73f0..9b8e22a3 100644
--- a/firebase/internal-launchpad/index.html
+++ b/firebase/internal-launchpad/index.html
@@ -93,6 +93,35 @@
#diagram-container:active {
cursor: grabbing;
}
+
+ /* Accordion styles */
+ .accordion-button {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ padding: 0.75rem 1rem;
+ font-size: 0.875rem;
+ color: #4b5563; /* text-gray-600 */
+ text-align: left;
+ background-color: #f9fafb; /* bg-gray-50 */
+ border-radius: 0.5rem; /* rounded-lg */
+ transition: background-color 0.2s ease;
+ }
+ .accordion-button:hover {
+ background-color: #f3f4f6; /* bg-gray-100 */
+ }
+ .accordion-button .chevron {
+ transition: transform 0.2s ease;
+ }
+ .accordion-button[aria-expanded="true"] .chevron {
+ transform: rotate(90deg);
+ }
+ .accordion-panel {
+ overflow: hidden;
+ transition: max-height 0.3s ease-out;
+ max-height: 0;
+ }
/* Modal styles */
.modal-overlay {
@@ -566,150 +595,96 @@
});
// Build hierarchical structure from paths
- function buildDiagramHierarchy(diagrams) {
- const hierarchy = {};
+ function buildHierarchy(items, pathPrefix) {
+ const hierarchy = { _root: { _items: [], _children: {} } };
- diagrams.forEach(diagram => {
- const parts = diagram.path.split('/');
- const relevantParts = parts.slice(2, -1); // Remove 'assets/diagrams/' and filename
-
- let current = hierarchy;
- relevantParts.forEach(part => {
- if (!current[part]) {
- current[part] = { _items: [], _children: {} };
- }
- current = current[part]._children;
- });
-
- // Add the item to appropriate level
- if (relevantParts.length > 0) {
- let parent = hierarchy[relevantParts[0]];
- for (let i = 1; i < relevantParts.length; i++) {
- parent = parent._children[relevantParts[i]];
- }
- parent._items.push(diagram);
- } else {
- // Root level diagrams
- if (!hierarchy._root) {
- hierarchy._root = { _items: [], _children: {} };
- }
- hierarchy._root._items.push(diagram);
+ items.forEach(item => {
+ let relativePath = item.path;
+ if (relativePath.startsWith('./')) {
+ relativePath = relativePath.substring(2);
}
+ if (relativePath.startsWith(pathPrefix)) {
+ relativePath = relativePath.substring(pathPrefix.length);
+ }
+
+ const parts = relativePath.split('/');
+ const relevantParts = parts.slice(0, -1); // remove filename
+
+ let current = hierarchy._root;
+ relevantParts.forEach(part => {
+ if (!current._children[part]) {
+ current._children[part] = { _items: [], _children: {} };
+ }
+ current = current._children[part];
+ });
+ current._items.push(item);
});
-
return hierarchy;
}
- // Build hierarchical structure from paths (for documents)
- function buildDocumentHierarchy(documents) {
- const hierarchy = {};
-
- documents.forEach(doc => {
- const parts = doc.path.split('/');
- const relevantParts = parts.slice(2, -1); // Remove 'assets/documents/' and filename
-
- let current = hierarchy;
- relevantParts.forEach(part => {
- if (!current[part]) {
- current[part] = { _items: [], _children: {} };
- }
- current = current[part]._children;
- });
-
- // Add the item to appropriate level
- if (relevantParts.length > 0) {
- let parent = hierarchy[relevantParts[0]];
- for (let i = 1; i < relevantParts.length; i++) {
- parent = parent._children[relevantParts[i]];
- }
- parent._items.push(doc);
- } else {
- // Root level documents
- if (!hierarchy._root) {
- hierarchy._root = { _items: [], _children: {} };
- }
- hierarchy._root._items.push(doc);
- }
- });
-
- return hierarchy;
- }
+ // Generic function to create accordion navigation
+ function createAccordionNavigation(hierarchy, parentElement, createLinkFunction, sectionTitle) {
+ const createAccordion = (title, items, children) => {
+ const container = document.createElement('div');
+ container.className = 'mb-1';
- // Create navigation from hierarchy
- function createNavigation(hierarchy, parentElement, level = 0) {
- // First, show root level items if any
- if (hierarchy._root && hierarchy._root._items.length > 0) {
- const mainHeading = document.createElement('div');
- mainHeading.className = 'px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider mt-6 mb-3';
- mainHeading.textContent = 'Diagrams';
- parentElement.appendChild(mainHeading);
+ const button = document.createElement('button');
+ button.className = 'accordion-button';
+ button.setAttribute('aria-expanded', 'false');
+ button.innerHTML = `
+ ${title.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
+
+ `;
+
+ const panel = document.createElement('div');
+ panel.className = 'accordion-panel pl-4 pt-1';
- hierarchy._root._items.forEach(diagram => {
- createDiagramLink(diagram, parentElement, 0);
- });
- }
-
- // Then process nested categories
- Object.keys(hierarchy).forEach(key => {
- if (key === '_items' || key === '_children' || key === '_root') return;
-
- const section = hierarchy[key];
- const heading = document.createElement('div');
- heading.className = 'px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider ' +
- (level === 0 ? 'mt-6 mb-3' : 'mt-4 mb-2 pl-8');
- heading.textContent = key.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
- parentElement.appendChild(heading);
-
- // Add items in this section
- if (section._items && section._items.length > 0) {
- section._items.forEach(diagram => {
- createDiagramLink(diagram, parentElement, level);
+ if (items) {
+ items.forEach(item => createLinkFunction(item, panel, 1));
+ }
+
+ if (children) {
+ Object.keys(children).forEach(childKey => {
+ const childSection = children[childKey];
+ const childHeading = document.createElement('div');
+ childHeading.className = 'px-4 pt-2 pb-1 text-xs font-semibold text-gray-400 uppercase tracking-wider';
+ childHeading.textContent = childKey.replace(/-/g, ' ');
+ panel.appendChild(childHeading);
+ childSection._items.forEach(item => createLinkFunction(item, panel, 2));
});
}
-
- // Recursively add children
- if (section._children && Object.keys(section._children).length > 0) {
- createNavigation(section._children, parentElement, level + 1);
- }
- });
- }
- // Create document navigation from hierarchy
- function createDocumentNavigation(hierarchy, parentElement, level = 0) {
- // First, show root level items if any
- if (hierarchy._root && hierarchy._root._items.length > 0) {
- const mainHeading = document.createElement('div');
- mainHeading.className = 'px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider mt-6 mb-3';
- mainHeading.textContent = 'Documentation';
- parentElement.appendChild(mainHeading);
-
- hierarchy._root._items.forEach(doc => {
- createDocumentLink(doc, parentElement, 0);
+ button.addEventListener('click', () => {
+ const isExpanded = button.getAttribute('aria-expanded') === 'true';
+ button.setAttribute('aria-expanded', !isExpanded);
+ if (!isExpanded) {
+ panel.style.maxHeight = panel.scrollHeight + 'px';
+ } else {
+ panel.style.maxHeight = '0px';
+ }
});
+
+ container.appendChild(button);
+ container.appendChild(panel);
+ return container;
+ };
+
+ const heading = document.createElement('div');
+ heading.className = 'px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider mt-6 mb-3';
+ heading.textContent = sectionTitle;
+ parentElement.appendChild(heading);
+
+ // Process root items first
+ if (hierarchy._root && hierarchy._root._items.length > 0) {
+ hierarchy._root._items.forEach(item => createLinkFunction(item, parentElement, 0));
}
-
- // Then process nested categories
- Object.keys(hierarchy).forEach(key => {
- if (key === '_items' || key === '_children' || key === '_root') return;
-
- const section = hierarchy[key];
- const heading = document.createElement('div');
- heading.className = 'px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider ' +
- (level === 0 ? 'mt-6 mb-3' : 'mt-4 mb-2 pl-8');
- heading.textContent = key.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
- parentElement.appendChild(heading);
-
- // Add items in this section
- if (section._items && section._items.length > 0) {
- section._items.forEach(doc => {
- createDocumentLink(doc, parentElement, level);
- });
- }
-
- // Recursively add children
- if (section._children && Object.keys(section._children).length > 0) {
- createDocumentNavigation(section._children, parentElement, level + 1);
- }
+
+ // Process categories as accordions
+ Object.keys(hierarchy._root._children).forEach(key => {
+ if (key.startsWith('_')) return;
+ const section = hierarchy._root._children[key];
+ const accordion = createAccordion(key, section._items, section._children);
+ parentElement.appendChild(accordion);
});
}
@@ -717,8 +692,8 @@
function createDocumentLink(doc, parentElement, level) {
const link = document.createElement('a');
link.href = '#';
- link.className = 'nav-item flex items-center space-x-3 px-4 py-3 rounded-xl text-gray-700 hover:bg-gray-50 mb-1' +
- (level > 0 ? ' pl-8' : '');
+ link.className = 'nav-item flex items-center space-x-3 px-4 py-2.5 rounded-lg text-gray-600 hover:bg-gray-100 text-sm mb-1' +
+ (level > 0 ? ' ' : '');
link.onclick = (e) => {
e.preventDefault();
showView('document', link, doc.path, doc.title);
@@ -728,7 +703,7 @@