From acc0b8dcc64bcada16e6849402c57d95950793c0 Mon Sep 17 00:00:00 2001 From: Matarp13 Date: Sun, 26 Oct 2025 18:53:23 +0200 Subject: [PATCH 1/2] Add Generic HTTP integration documentation with interactive builder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created new Generic HTTP integration docs section (position 1 in custom integrations) - Added Overview page explaining when to use, how it works, and advanced configurations - Built interactive 'Build Your Integration' guide with 3 steps: - Step 1: Configure API (base URL, auth, pagination, advanced settings) - Step 2: Choose data to sync (endpoint, response, field mapping, blueprint config) - Step 3: Install and create in Port (dynamic Helm/Docker commands, blueprint JSON, mapping YAML) - Implemented React components with shared context for seamless state management - Labeled as 'Generic HTTP (New โญ)' in sidebar to highlight new feature - Updated API integration position to 2 --- .../custom-integration/api/_category_.json | 2 +- .../ocean-http/_category_.json | 19 + .../ocean-http/build-your-integration.md | 50 + .../custom-integration/ocean-http/overview.md | 338 +++++++ .../GenericHttp/ApiConfigBuilder.jsx | 209 ++++ .../GenericHttp/BlueprintGenerator.jsx | 157 +++ .../GenericHttp/ConfigGenerator.jsx | 188 ++++ .../GenericHttp/EndpointConfigurator.jsx | 337 +++++++ src/components/GenericHttp/EndpointTester.jsx | 126 +++ .../GenericHttp/IntegrationBuilder.jsx | 617 ++++++++++++ .../GenericHttp/IntegrationBuilderContext.jsx | 103 ++ .../GenericHttp/IntegrationBuilderSteps.jsx | 16 + .../GenericHttp/IntegrationInstaller.jsx | 126 +++ .../GenericHttp/MultiEndpointConfigurator.jsx | 446 +++++++++ src/components/GenericHttp/Step1ApiConfig.jsx | 232 +++++ .../GenericHttp/Step2DataMapping.jsx | 277 ++++++ .../GenericHttp/Step3Installation.jsx | 317 ++++++ src/components/GenericHttp/index.js | 13 + src/components/GenericHttp/styles.module.css | 940 ++++++++++++++++++ 19 files changed, 4512 insertions(+), 1 deletion(-) create mode 100644 docs/build-your-software-catalog/custom-integration/ocean-http/_category_.json create mode 100644 docs/build-your-software-catalog/custom-integration/ocean-http/build-your-integration.md create mode 100644 docs/build-your-software-catalog/custom-integration/ocean-http/overview.md create mode 100644 src/components/GenericHttp/ApiConfigBuilder.jsx create mode 100644 src/components/GenericHttp/BlueprintGenerator.jsx create mode 100644 src/components/GenericHttp/ConfigGenerator.jsx create mode 100644 src/components/GenericHttp/EndpointConfigurator.jsx create mode 100644 src/components/GenericHttp/EndpointTester.jsx create mode 100644 src/components/GenericHttp/IntegrationBuilder.jsx create mode 100644 src/components/GenericHttp/IntegrationBuilderContext.jsx create mode 100644 src/components/GenericHttp/IntegrationBuilderSteps.jsx create mode 100644 src/components/GenericHttp/IntegrationInstaller.jsx create mode 100644 src/components/GenericHttp/MultiEndpointConfigurator.jsx create mode 100644 src/components/GenericHttp/Step1ApiConfig.jsx create mode 100644 src/components/GenericHttp/Step2DataMapping.jsx create mode 100644 src/components/GenericHttp/Step3Installation.jsx create mode 100644 src/components/GenericHttp/index.js create mode 100644 src/components/GenericHttp/styles.module.css diff --git a/docs/build-your-software-catalog/custom-integration/api/_category_.json b/docs/build-your-software-catalog/custom-integration/api/_category_.json index a1e3abeaca..f3c5fb72d4 100644 --- a/docs/build-your-software-catalog/custom-integration/api/_category_.json +++ b/docs/build-your-software-catalog/custom-integration/api/_category_.json @@ -1,4 +1,4 @@ { "label": "API", - "position": 1 + "position": 2 } diff --git a/docs/build-your-software-catalog/custom-integration/ocean-http/_category_.json b/docs/build-your-software-catalog/custom-integration/ocean-http/_category_.json new file mode 100644 index 0000000000..95a43fddf2 --- /dev/null +++ b/docs/build-your-software-catalog/custom-integration/ocean-http/_category_.json @@ -0,0 +1,19 @@ +{ + "position": 1, + "label": "Generic HTTP (New โญ)", + "collapsible": true, + "collapsed": true, + "link": { + "type": "generated-index" + } +} + + + + + + + + + + diff --git a/docs/build-your-software-catalog/custom-integration/ocean-http/build-your-integration.md b/docs/build-your-software-catalog/custom-integration/ocean-http/build-your-integration.md new file mode 100644 index 0000000000..bc34ffd02f --- /dev/null +++ b/docs/build-your-software-catalog/custom-integration/ocean-http/build-your-integration.md @@ -0,0 +1,50 @@ +--- +sidebar_position: 2 +title: Build Your Integration +description: Interactive guide to configure and install your integration +--- + +import PortApiRegionTip from "/docs/generalTemplates/_port_api_available_regions.md" +import { IntegrationBuilderProvider, Step1ApiConfig, Step2DataMapping, Step3Installation } from '@site/src/components/GenericHttp'; + +# Build Your Integration + +This interactive guide will help you generate everything you need to connect your API to Port. + +**How it works:** +1. Configure your API connection settings +2. Choose an endpoint and select which fields to sync +3. Get your installation commands, blueprint, and mapping configuration + + + +--- + +## Step 1: Configure Your API + +Set up the connection details for your API: + + + +--- + +## Step 2: Choose What Data to Sync + +Select an endpoint and configure which fields to ingest into Port: + + + +--- + +## Step 3: Install and Create in Port + +Use the commands and configurations generated from your selections above. + + + + +:::info Port credentials needed +Get your `PORT_CLIENT_ID` and `PORT_CLIENT_SECRET` from [Port Settings โ†’ Credentials](https://app.getport.io/settings). +::: + + diff --git a/docs/build-your-software-catalog/custom-integration/ocean-http/overview.md b/docs/build-your-software-catalog/custom-integration/ocean-http/overview.md new file mode 100644 index 0000000000..ad10ca150e --- /dev/null +++ b/docs/build-your-software-catalog/custom-integration/ocean-http/overview.md @@ -0,0 +1,338 @@ +--- +sidebar_position: 1 +title: Overview +description: Understanding the Generic HTTP Integration +--- + +# Overview + +This integration allows Port customers to connect to any custom API, internal system, or HTTP service without requiring custom development. Each integration instance connects to one API backend, and users can map multiple endpoints through standard Ocean resource configuration. + +--- + +## When to Use This Integration? + +This integration is ideal when: + +- **No native Port integration exists** for your tool or service +- You're working with **internal or custom-built APIs** +- Your API follows **REST conventions** (JSON responses, HTTP methods) +- You want a **configuration-only solution** without custom code + +--- + +## Prerequisites + +Before installing, gather this information about your API: + +### 1. Authentication + +How does your API verify requests? + +- **Bearer Token:** OAuth2 tokens, personal access tokens (most modern APIs) +- **API Key:** Custom header like `X-API-Key` or `Authorization` +- **Basic Auth:** Username and password (legacy systems) +- **None:** Public APIs + +**Where to find it:** Check your API's documentation or settings page. Look for sections titled "API Keys," "Access Tokens," or "Authentication." + +### 2. Endpoints + +Which API endpoint returns the data you want to ingest? + +**Example:** `/api/v1/users`, `/v2/projects`, `/tickets` + +**How to find it:** Check your API documentation for available endpoints. Look for GET endpoints that return lists of resources. + +### 3. Data Structure + +Where is the actual data in your API's response? + +**Direct array:** +```json +[ + {"id": 1, "name": "Alice"}, + {"id": 2, "name": "Bob"} +] +``` + +**Nested data:** +```json +{ + "data": [ + {"id": 1, "name": "Alice"}, + {"id": 2, "name": "Bob"} + ] +} +``` + +**Deeply nested:** +```json +{ + "response": { + "users": { + "items": [ + {"id": 1, "name": "Alice"} + ] + } + } +} +``` + +You'll use a JQ `data_path` in your mapping to tell the integration where to find the array of items. + +--- + +## How It Works + +The Generic HTTP integration uses a **two-step setup** similar to other Ocean integrations you've used: + +### Step 1: Installation (Global Configuration) + +During installation, you configure the **connection settings** that apply to all API calls: + +- **Base URL:** The root URL of your API (e.g., `https://api.yourcompany.com`) +- **Authentication:** How to authenticate (bearer token, API key, basic auth, or none) +- **Pagination:** How your API handles large datasets (offset, page, cursor, or none) +- **Rate limiting:** Timeout, concurrent requests, SSL verification + +Think of this as setting up the "connection" to your API - these settings are used for every endpoint you'll sync. + +**Installation methods:** Docker or Helm (just like other Ocean integrations) + +#### Example: Installing with Helm + +```bash +helm repo add --force-update port-labs https://port-labs.github.io/helm-charts +helm install generic-http port-labs/port-ocean \ + --set port.clientId="" \ + --set port.clientSecret="" \ + --set port.baseUrl="https://api.getport.io" \ + --set initializePortResources=true \ + --set scheduledResyncInterval=60 \ + --set integration.identifier="generic-http" \ + --set integration.type="generic-http" \ + --set integration.eventListener.type="POLLING" \ + --set integration.config.baseUrl="https://api.yourcompany.com" \ + --set integration.config.authType="bearer_token" \ + --set integration.secrets.authValue="" \ + --set integration.config.paginationType="page" \ + --set integration.config.pageSize=100 +``` + +### Step 2: Resource Mapping + +After installation, you define **which endpoints to sync** in your `port-app-config.yml` file (or using the integration's configuration in Port). + +This is where you map each API endpoint to Port entities - similar to how you've mapped GitHub repositories or Jira issues in other integrations. + +#### ๐Ÿ†• Endpoint-as-Kind Feature + +The `kind` field is now the **endpoint path itself**! This provides better visibility in Port's UI, allowing you to: + +- โœ… Track each endpoint's sync status individually +- โœ… Debug mapping issues per endpoint +- โœ… Monitor data ingestion per API call + +#### Example: Mapping Two Endpoints + +```yaml +resources: + # First endpoint: users + - kind: /api/v1/users + selector: + query: 'true' # JQ filter - 'true' means sync all entities + data_path: '.users' # Where to find the data array in the response + query_params: # Optional: add query parameters to the API call + active: "true" + department: "engineering" + port: + entity: + mappings: + identifier: .id + title: .name + blueprint: '"user"' + properties: + email: .email + department: .department + active: .is_active + created: .created_at + + # Second endpoint: projects + - kind: /api/v1/projects + selector: + query: 'true' + data_path: '.data.projects' # Nested data extraction + query_params: + status: "active" + port: + entity: + mappings: + identifier: .project_id + title: .project_name + blueprint: '"project"' + properties: + description: .description + owner: .owner.email + budget: .budget_amount + created: .created_date +``` + +#### What Each Field Does + +- **`kind`**: The API endpoint path (combined with your base URL) +- **`selector.query`**: JQ filter to include/exclude entities (use `'true'` to sync all) +- **`selector.data_path`**: JQ expression pointing to the array of items in the response +- **`selector.query_params`**: (Optional) Query parameters added to the URL +- **`selector.method`**: (Optional) HTTP method, defaults to `GET` +- **`port.entity.mappings`**: How to map API fields to Port entity properties + +--- + +## Advanced Configurations + +Once you have the basics working, these features handle more complex scenarios. + +### Nested Endpoints + +Fetch data from dynamic endpoints that depend on other resources. + +**Use case:** Get all tickets, then fetch comments for each ticket. + +#### How It Works + +**Step 1 - Define parent endpoint:** +```yaml +resources: + - kind: /api/tickets + port: + entity: + mappings: + identifier: .id | tostring + blueprint: '"ticket"' +``` + +**Step 2 - Define nested endpoint:** +```yaml +resources: + - kind: /api/tickets/{ticket_id}/comments + selector: + path_parameters: + ticket_id: + endpoint: /api/tickets + field: .id + filter: 'true' + port: + entity: + mappings: + identifier: .id | tostring + blueprint: '"comment"' + relations: + ticket: .ticket_id | tostring +``` + +The integration will: +1. Call `/api/tickets` โ†’ Get ticket IDs [101, 102, 103] +2. Call `/api/tickets/101/comments`, `/api/tickets/102/comments`, `/api/tickets/103/comments` +3. Sync all comments with relations to their parent tickets + +**Real-world examples:** +- `/projects/{project_id}/tasks` - Tasks within projects +- `/repositories/{repo_id}/pull-requests` - PRs in repositories +- `/customers/{customer_id}/orders` - Orders for customers + +### Pagination + +For APIs that split data across multiple pages, configure how the integration fetches all pages. + +#### Pagination Types + +**Offset-based** (like SQL): +``` +GET /api/users?offset=0&limit=100 +GET /api/users?offset=100&limit=100 +``` + +**Page-based** (traditional): +``` +GET /api/users?page=1&size=100 +GET /api/users?page=2&size=100 +``` + +**Cursor-based** (for large datasets): +``` +GET /api/users?cursor=abc123&limit=100 +GET /api/users?cursor=xyz789&limit=100 +``` + +#### Custom Parameter Names + +APIs often use different parameter names. You can configure: + +- **Pagination parameter:** Use `skip` instead of `offset`, or `after` instead of `cursor` +- **Size parameter:** Use `per_page` instead of `limit`, or `page_size` instead of `size` +- **Start page:** Specify if pages start at 0 or 1 + +**Example:** +```yaml +# GitHub uses page/per_page +paginationType: page +paginationParam: page +sizeParam: per_page +startPage: 1 + +# Stripe uses limit/starting_after +paginationType: cursor +paginationParam: starting_after +sizeParam: limit +``` + +#### Cursor Path Configuration + +For cursor-based pagination, tell the integration where to find the next cursor in responses: + +**Example API response:** +```json +{ + "data": [...], + "meta": { + "after_cursor": "xyz123", + "has_more": true + } +} +``` + +**Configuration:** +```yaml +cursorPath: meta.after_cursor +hasMorePath: meta.has_more +``` + +### Rate Limiting + +Control how the integration interacts with your API to prevent overwhelming it or hitting rate limits. + +#### Request Timeout + +How long to wait for each API call to complete. + +```yaml +timeout: 30 # seconds (default: 30) +``` + +**When to adjust:** +- Increase for slow APIs or large responses (e.g., 60 seconds) +- Decrease for fast, local APIs (e.g., 10 seconds) + +--- + +## Ready to Build? + +Head to [Build Your Integration](./build-your-integration) for a step-by-step guide with an interactive configuration builder. + +--- + +## More Resources + +For all configuration options, code examples, and advanced use cases, check out the [Generic HTTP integration repository on GitHub](https://github.com/port-labs/ocean/tree/main/integrations/generic-http). + diff --git a/src/components/GenericHttp/ApiConfigBuilder.jsx b/src/components/GenericHttp/ApiConfigBuilder.jsx new file mode 100644 index 0000000000..38ea8405cb --- /dev/null +++ b/src/components/GenericHttp/ApiConfigBuilder.jsx @@ -0,0 +1,209 @@ +import React, { useState } from 'react'; +import styles from './styles.module.css'; + +export function ApiConfigBuilder() { + const [baseUrl, setBaseUrl] = useState(''); + const [authType, setAuthType] = useState('bearer_token'); + const [apiToken, setApiToken] = useState(''); + const [apiKey, setApiKey] = useState(''); + const [apiKeyHeader, setApiKeyHeader] = useState('X-API-Key'); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [paginationType, setPaginationType] = useState('none'); + const [pageSize, setPageSize] = useState('100'); + + const generateEnvVars = () => { + const vars = [`OCEAN__INTEGRATION__CONFIG__BASE_URL=${baseUrl}`]; + + // Auth config + vars.push(`OCEAN__INTEGRATION__CONFIG__AUTH_TYPE=${authType}`); + + if (authType === 'bearer_token' && apiToken) { + vars.push(`OCEAN__INTEGRATION__CONFIG__API_TOKEN=${apiToken}`); + } else if (authType === 'api_key' && apiKey) { + vars.push(`OCEAN__INTEGRATION__CONFIG__API_KEY=${apiKey}`); + vars.push(`OCEAN__INTEGRATION__CONFIG__API_KEY_HEADER=${apiKeyHeader}`); + } else if (authType === 'basic' && username && password) { + vars.push(`OCEAN__INTEGRATION__CONFIG__USERNAME=${username}`); + vars.push(`OCEAN__INTEGRATION__CONFIG__PASSWORD=${password}`); + } + + // Pagination config + if (paginationType !== 'none') { + vars.push(`OCEAN__INTEGRATION__CONFIG__PAGINATION_TYPE=${paginationType}`); + vars.push(`OCEAN__INTEGRATION__CONFIG__PAGE_SIZE=${pageSize}`); + } + + return vars.join('\n'); + }; + + const copyToClipboard = () => { + navigator.clipboard.writeText(generateEnvVars()); + }; + + return ( +
+
+

๐ŸŒ API Base URL

+ setBaseUrl(e.target.value)} + className={styles.input} + /> + +

๐Ÿ” Authentication

+
+ + + + +
+ + {authType === 'bearer_token' && ( + setApiToken(e.target.value)} + className={styles.input} + /> + )} + + {authType === 'api_key' && ( + <> + setApiKey(e.target.value)} + className={styles.input} + /> + setApiKeyHeader(e.target.value)} + className={styles.input} + /> + + )} + + {authType === 'basic' && ( + <> + setUsername(e.target.value)} + className={styles.input} + /> + setPassword(e.target.value)} + className={styles.input} + /> + + )} + +

๐Ÿ“„ Pagination

+
+ + + + +
+ + {paginationType !== 'none' && ( + setPageSize(e.target.value)} + className={styles.input} + /> + )} +
+ +
+
+

โœจ Generated Configuration

+ +
+
+          {generateEnvVars() || '# Fill in the form to generate configuration'}
+        
+
+
+ ); +} + diff --git a/src/components/GenericHttp/BlueprintGenerator.jsx b/src/components/GenericHttp/BlueprintGenerator.jsx new file mode 100644 index 0000000000..d70eda7db2 --- /dev/null +++ b/src/components/GenericHttp/BlueprintGenerator.jsx @@ -0,0 +1,157 @@ +import React, { useState } from 'react'; +import styles from './styles.module.css'; + +export function BlueprintGenerator() { + const [sampleData, setSampleData] = useState(''); + const [blueprintId, setBlueprintId] = useState(''); + const [blueprintTitle, setBlueprintTitle] = useState(''); + const [selectedFields, setSelectedFields] = useState({}); + + const parseFields = (jsonString) => { + try { + const json = JSON.parse(jsonString); + // Get first object if array + const sample = Array.isArray(json) ? json[0] : json; + + if (!sample || typeof sample !== 'object') { + return []; + } + + return Object.entries(sample).map(([key, value]) => { + let type = 'string'; + let format = null; + + if (typeof value === 'number') { + type = 'number'; + } else if (typeof value === 'boolean') { + type = 'boolean'; + } else if (typeof value === 'string') { + // Try to detect special formats + if (value.includes('@')) { + format = 'email'; + } else if (value.match(/^\d{4}-\d{2}-\d{2}/)) { + format = 'date-time'; + } else if (value.match(/^https?:\/\//)) { + format = 'url'; + } + } + + return { key, type, format, value }; + }); + } catch { + return []; + } + }; + + const fields = parseFields(sampleData); + + const toggleField = (key) => { + setSelectedFields(prev => ({ + ...prev, + [key]: !prev[key] + })); + }; + + const generateBlueprint = () => { + const properties = {}; + + fields.forEach(field => { + if (selectedFields[field.key]) { + properties[field.key] = { + type: field.type, + title: field.key.charAt(0).toUpperCase() + field.key.slice(1).replace(/_/g, ' ') + }; + + if (field.format) { + properties[field.key].format = field.format; + } + } + }); + + return JSON.stringify({ + identifier: blueprintId || 'my_blueprint', + title: blueprintTitle || 'My Blueprint', + icon: 'BlankPage', + schema: { + properties, + required: [] + } + }, null, 2); + }; + + const copyBlueprint = () => { + navigator.clipboard.writeText(generateBlueprint()); + }; + + return ( +
+
+

๐Ÿ“‹ Sample Data

+

Paste a sample object from your API response:

+