DataFlow Analytics Platform – Technical Documentation
Table of Contents
- Getting Started Guide
- API Reference
- Troubleshooting Guide
- System Architecture Overview
- Security Implementation Guide
- Performance Optimization
Getting Started Guide
Overview
DataFlow Analytics Platform is a cloud-based data processing and visualization solution that enables organizations to transform raw data into actionable insights through automated pipelines and customizable dashboards.
Prerequisites
Before beginning installation, ensure your environment meets these requirements:
- Node.js 18.x or higher
- PostgreSQL 14.x or higher
- Redis 6.x or higher
- Minimum 8GB RAM
- Docker and Docker Compose (for containerized deployment)
Quick Start Installation
Method 1: Docker Deployment (Recommended)
# Clone the repository
git clone https://github.com/company/dataflow-platform.git
cd dataflow-platform
# Start services
docker-compose up -d
# Initialize database
docker-compose exec api npm run db:migrate
docker-compose exec api npm run db:seed
Method 2: Manual Installation
# Install dependencies
npm install
# Configure environment
cp .env.example .env
# Edit .env with your database credentials
# Run database migrations
npm run db:migrate
# Start the application
npm run start:dev
Initial Configuration
After installation, access the platform at http://localhost:3000 and complete the setup wizard:
- Admin Account Setup: Create your administrator credentials
- Data Source Configuration: Connect your first data source
- Pipeline Creation: Set up your initial data processing pipeline
- Dashboard Setup: Create your first visualization dashboard
Verification Steps
Confirm successful installation by:
- Accessing the web interface without errors
- Creating a test data pipeline
- Verifying data ingestion from connected sources
- Generating a sample report
API Reference
Authentication
All API requests require authentication via JWT tokens obtained through the login endpoint.
POST /api/auth/login
Authenticate user and receive access token.
Request Body:
{
“email”: “user@example.com”,
“password”: “secure_password”
}
Response:
{
“token”: “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…”,
“refreshToken”: “dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4…”,
“expiresIn”: 3600,
“user”: {
“id”: “uuid-here”,
“email”: “user@example.com”,
“role”: “admin”
}
}
Data Pipelines
GET /api/pipelines
Retrieve all data pipelines for the authenticated user.
Parameters:
- page (optional): Page number for pagination (default: 1)
- limit (optional): Number of results per page (default: 20)
- status (optional): Filter by pipeline status (active, paused, error)
Headers:
Authorization: Bearer {your_jwt_token}
Content-Type: application/json
Response:
{
“pipelines”: [
{
“id”: “pipeline-uuid”,
“name”: “Sales Data Pipeline”,
“status”: “active”,
“lastRun”: “2024-01-15T10:30:00Z”,
“source”: {
“type”: “database”,
“connection”: “postgres-prod”
},
“transformations”: 3,
“destination”: “data-warehouse”
}
],
“pagination”: {
“page”: 1,
“limit”: 20,
“total”: 45,
“pages”: 3
}
}
POST /api/pipelines
Create a new data pipeline.
Request Body:
{
“name”: “Customer Analytics Pipeline”,
“description”: “Processes customer interaction data”,
“source”: {
“type”: “api”,
“endpoint”: “https://api.crm.example.com/customers”,
“authentication”: {
“type”: “api_key”,
“key”: “your-api-key”
}
},
“transformations”: [
{
“type”: “filter”,
“conditions”: {“status”: “active”}
},
{
“type”: “aggregate”,
“groupBy”: “region”,
“metrics”: [“total_revenue”, “customer_count”]
}
],
“destination”: {
“type”: “warehouse”,
“table”: “customer_analytics”
},
“schedule”: “0 2 * * *”
}
Data Sources
GET /api/sources
List all configured data sources.
Response:
{
“sources”: [
{
“id”: “source-uuid”,
“name”: “Production Database”,
“type”: “postgresql”,
“status”: “connected”,
“lastSync”: “2024-01-15T09:15:00Z”,
“recordCount”: 1250000
}
]
}
Error Responses
The API uses standard HTTP status codes and returns detailed error information:
{
“error”: {
“code”: “VALIDATION_ERROR”,
“message”: “Invalid pipeline configuration”,
“details”: {
“field”: “source.endpoint”,
“reason”: “URL format is invalid”
},
“timestamp”: “2024-01-15T10:30:00Z”
}
}
Troubleshooting Guide
Common Installation Issues
Issue: Database Connection Failed
Symptoms: Application fails to start with “Cannot connect to database” error
Causes and Solutions:
- Incorrect credentials: Verify database credentials in .env file
- Database not running: Start PostgreSQL service
- # Ubuntu/Debiansudo systemctl start postgresql# macOS with Homebrewbrew services start postgresql
- Firewall blocking connection: Check if port 5432 is accessible
- Database doesn’t exist: Create the database manually
- CREATE DATABASE dataflow_platform;
Issue: Redis Connection Timeout
Symptoms: Slow page loads, session management errors
Solutions:
- Check Redis status:
- redis-cli ping# Should return “PONG”
- Restart Redis service:
- sudo systemctl restart redis
- Verify Redis configuration in .env:
- REDIS_URL=redis://localhost:6379REDIS_PASSWORD=your_redis_password
Pipeline Processing Issues
Issue: Pipeline Stuck in “Processing” State
Symptoms: Pipeline shows processing status for extended periods
Diagnostic Steps:
- Check pipeline logs:
- docker-compose logs api | grep pipeline-uuid
- Monitor resource usage:
- docker stats
- Verify data source connectivity:
- Test API endpoints manually
- Check database query performance
- Validate authentication credentials
Solutions:
- Increase timeout values in pipeline configuration
- Optimize data transformations for large datasets
- Scale worker processes if processing multiple pipelines
Issue: Data Quality Validation Failures
Symptoms: Pipelines fail with validation error messages
Common Validation Rules:
- Missing required fields: Ensure all mandatory columns are present
- Data type mismatches: Verify numeric fields contain valid numbers
- Date format errors: Use ISO 8601 format (YYYY-MM-DD)
- Duplicate records: Check for unique identifier conflicts
Resolution Process:
- Review validation error logs in the pipeline detail view
- Examine source data for anomalies
- Update data transformation rules to handle edge cases
- Configure data cleaning steps before validation
Performance Issues
Issue: Slow Dashboard Loading
Symptoms: Dashboards take more than 10 seconds to load
Performance Optimization Checklist:
- Database query optimization:
- Add indexes on frequently queried columns
- Optimize JOIN operations
- Use query result caching
- Data aggregation:
- Pre-calculate common metrics
- Use materialized views for complex calculations
- Frontend optimization:
- Enable data pagination
- Implement progressive loading
- Cache visualization components
Issue: Memory Usage Spikes
Symptoms: System becomes unresponsive during large data processing
Memory Management:
- Monitor memory usage:
- docker stats –format “table {{.Container}}\t{{.MemUsage}}\t{{.MemPerc}}”
- Optimize processing batch sizes:
- { “processing”: { “batchSize”: 1000, “maxMemoryUsage”: “2GB” }}
- Configure garbage collection for Node.js applications:
- NODE_OPTIONS=”–max_old_space_size=4096″ npm start
Getting Additional Help
Log File Locations
- Application logs: ./logs/app.log
- Pipeline logs: ./logs/pipelines/
- Database logs: /var/log/postgresql/
- System logs: journalctl -u dataflow-platform
Diagnostic Commands
# Health check endpoint
curl http://localhost:3000/api/health
# Database connection test
npm run db:test
# Generate system report
npm run diagnostics:report
Support Contacts
- Technical Issues: Submit ticket at support.dataflow.com
- Documentation Updates: docs@dataflow.com
- Emergency Support: Call +1-800-DATAFLOW (24/7)
System Architecture Overview
High-Level Architecture
The DataFlow Analytics Platform follows a microservices architecture pattern, designed for scalability, maintainability, and fault tolerance. The system is composed of several interconnected services that handle different aspects of data processing and visualization.
Core Components
API Gateway Service
- Routes incoming requests to appropriate microservices
- Handles authentication and authorization
- Implements rate limiting and request validation
- Manages API versioning and backward compatibility
Data Ingestion Service
- Connects to various data sources (databases, APIs, file systems)
- Handles data extraction with configurable scheduling
- Implements retry logic and error handling
- Supports real-time and batch processing modes
Processing Engine
- Executes data transformations and business logic
- Manages pipeline orchestration and dependencies
- Provides data validation and quality checks
- Supports custom transformation plugins
Storage Layer
- Operational Database: PostgreSQL for application data
- Data Warehouse: Optimized for analytical queries
- Cache Layer: Redis for session management and temporary data
- Object Storage: S3-compatible storage for files and backups
Visualization Service
- Generates charts, graphs, and reports
- Handles dashboard configuration and rendering
- Provides export functionality (PDF, Excel, CSV)
- Manages user preferences and saved views
Data Flow Architecture
[Data Sources] → [Ingestion Service] → [Message Queue] → [Processing Engine]
↓
[Visualization] ← [API Gateway] ← [Storage Layer] ← [Data Warehouse]
Deployment Architecture
Production Environment
The platform is deployed across multiple availability zones for high availability:
Load Balancer Tier
- Application Load Balancer (ALB) with SSL termination
- Health checks and automatic failover
- Geographic traffic distribution
Application Tier
- Kubernetes cluster with auto-scaling capabilities
- Container orchestration with Docker
- Service mesh for inter-service communication
Data Tier
- Primary-replica database configuration
- Automated backups and point-in-time recovery
- Read replicas for analytical workloads
Development Environment
Simplified single-node deployment using Docker Compose:
- All services running on single host
- Shared development database
- Local file storage
- Simplified networking configuration
Security Architecture
Authentication & Authorization
- OAuth 2.0/OpenID Connect integration with enterprise identity providers
- Role-based access control (RBAC) with granular permissions
- Multi-factor authentication support
- API key management for service-to-service communication
Data Security
- Encryption at rest using AES-256 for all stored data
- Encryption in transit using TLS 1.3 for all communications
- Field-level encryption for sensitive data elements
- Data masking for non-production environments
Network Security
- Virtual Private Cloud (VPC) with private subnets
- Security groups restricting access to necessary ports
- Web Application Firewall (WAF) for attack prevention
- VPN or private connectivity for data source access
Monitoring and Observability
Application Monitoring
- Health checks for all services with automated alerting
- Performance metrics collection and visualization
- Error tracking with detailed stack traces
- User activity monitoring for audit compliance
Infrastructure Monitoring
- Resource utilization tracking (CPU, memory, disk, network)
- Database performance monitoring with query analysis
- Container orchestration metrics and events
- Log aggregation with centralized searching and analysis
Security Implementation Guide
Authentication Implementation
JWT Token Management
The platform uses JSON Web Tokens for stateless authentication with the following configuration:
// JWT Configuration
const jwtConfig = {
secret: process.env.JWT_SECRET, // 256-bit secret key
expiresIn: ‘1h’, // Token expiration time
algorithm: ‘HS256’, // HMAC SHA-256
issuer: ‘dataflow-platform’, // Token issuer
audience: ‘dataflow-api’ // Intended audience
};
// Token validation middleware
const validateToken = (req, res, next) => {
const token = extractToken(req);
if (!token) {
return res.status(401).json({ error: ‘No token provided’ });
}
jwt.verify(token, jwtConfig.secret, jwtConfig, (err, decoded) => {
if (err) {
return res.status(403).json({ error: ‘Invalid token’ });
}
req.user = decoded;
next();
});
};
Password Security
- Minimum requirements: 12 characters, mixed case, numbers, special characters
- Hashing: bcrypt with salt rounds of 12
- Password history: Prevents reuse of last 12 passwords
- Account lockout: After 5 failed attempts for 15 minutes
const bcrypt = require(‘bcrypt’);
const SALT_ROUNDS = 12;
const hashPassword = async (password) => {
return await bcrypt.hash(password, SALT_ROUNDS);
};
const validatePassword = (password) => {
const minLength = 12;
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?”:{}|<>]/.test(password);
return password.length >= minLength &&
hasUpperCase && hasLowerCase &&
hasNumbers && hasSpecialChar;
};
Data Protection
Database Security Configuration
— Create application user with limited privileges
CREATE USER dataflow_app WITH PASSWORD ‘secure_generated_password’;
— Grant only necessary permissions
GRANT SELECT, INSERT, UPDATE, DELETE ON user_data TO dataflow_app;
GRANT SELECT ON system_config TO dataflow_app;
— Enable row-level security
ALTER TABLE user_data ENABLE ROW LEVEL SECURITY;
— Create policy for data isolation
CREATE POLICY user_data_policy ON user_data
FOR ALL TO dataflow_app
USING (user_id = current_setting(‘app.current_user_id’)::uuid);
Encryption Implementation
const crypto = require(‘crypto’);
class DataEncryption {
constructor(encryptionKey) {
this.algorithm = ‘aes-256-gcm’;
this.key = Buffer.from(encryptionKey, ‘hex’);
}
encrypt(plaintext) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipher(this.algorithm, this.key, iv);
let encrypted = cipher.update(plaintext, ‘utf8’, ‘hex’);
encrypted += cipher.final(‘hex’);
const authTag = cipher.getAuthTag();
return {
iv: iv.toString(‘hex’),
encrypted: encrypted,
authTag: authTag.toString(‘hex’)
};
}
decrypt(encryptedData) {
const decipher = crypto.createDecipher(
this.algorithm,
this.key,
Buffer.from(encryptedData.iv, ‘hex’)
);
decipher.setAuthTag(Buffer.from(encryptedData.authTag, ‘hex’));
let decrypted = decipher.update(encryptedData.encrypted, ‘hex’, ‘utf8’);
decrypted += decipher.final(‘utf8’);
return decrypted;
}
}
API Security
Rate Limiting Configuration
const rateLimit = require(‘express-rate-limit’);
// General API rate limiting
const generalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: ‘Too many requests from this IP’,
standardHeaders: true,
legacyHeaders: false,
});
// Strict limiting for authentication endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Limit each IP to 5 login attempts per windowMs
skipSuccessfulRequests: true,
message: ‘Too many login attempts, please try again later’
});
app.use(‘/api/’, generalLimiter);
app.use(‘/api/auth/’, authLimiter);
Input Validation and Sanitization
const { body, validationResult } = require(‘express-validator’);
const DOMPurify = require(‘isomorphic-dompurify’);
// Validation rules for pipeline creation
const validatePipeline = [
body(‘name’)
.isLength({ min: 3, max: 100 })
.matches(/^[a-zA-Z0-9\s\-_]+$/)
.withMessage(‘Name must be 3-100 characters, alphanumeric only’),
body(‘source.endpoint’)
.isURL({ protocols: [‘http’, ‘https’] })
.withMessage(‘Source endpoint must be a valid URL’),
body(‘transformations’)
.isArray({ min: 1, max: 50 })
.withMessage(‘Must have 1-50 transformations’),
// Custom sanitization
body(‘description’).customSanitizer((value) => {
return DOMPurify.sanitize(value);
})
];
// Validation error handling
const handleValidationErrors = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
error: ‘Validation failed’,
details: errors.array()
});
}
next();
};
Security Monitoring
Audit Logging Implementation
class AuditLogger {
constructor(database) {
this.db = database;
}
async logSecurityEvent(eventType, userId, details) {
const event = {
event_type: eventType,
user_id: userId,
ip_address: details.ipAddress,
user_agent: details.userAgent,
resource_accessed: details.resource,
action_performed: details.action,
timestamp: new Date().toISOString(),
additional_data: JSON.stringify(details.metadata || {})
};
await this.db.query(
‘INSERT INTO security_audit_log (event_type, user_id, ip_address, user_agent, resource_accessed, action_performed, timestamp, additional_data) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)’,
Object.values(event)
);
}
async logFailedLogin(email, ipAddress, userAgent) {
await this.logSecurityEvent(‘FAILED_LOGIN’, null, {
ipAddress,
userAgent,
resource: ‘/api/auth/login’,
action: ‘LOGIN_ATTEMPT’,
metadata: { email }
});
}
async logSuspiciousActivity(userId, activityType, details) {
await this.logSecurityEvent(‘SUSPICIOUS_ACTIVITY’, userId, {
ipAddress: details.ipAddress,
userAgent: details.userAgent,
resource: details.resource,
action: activityType,
metadata: details
});
}
}
Security Alert Configuration
# security-alerts.yml
security_rules:
– name: “Multiple Failed Logins”
condition: “failed_login_count > 3 in 5 minutes”
action: “block_ip”
notification: “security-team@company.com”
– name: “Unusual API Usage”
condition: “api_calls > 1000 in 1 minute”
action: “rate_limit”
notification: “ops-team@company.com”
– name: “Privilege Escalation Attempt”
condition: “role_change_requested”
action: “require_admin_approval”
notification: “admin-team@company.com”
– name: “Data Export Large Volume”
condition: “export_size > 100MB”
action: “require_approval”
notification: “data-governance@company.com”
Performance Optimization
Database Performance Tuning
Query Optimization Strategies
Index Management The platform implements strategic indexing to optimize query performance across all data access patterns:
— Composite index for pipeline queries
CREATE INDEX CONCURRENTLY idx_pipelines_user_status_created
ON pipelines (user_id, status, created_at DESC);
— Partial index for active pipelines only
CREATE INDEX CONCURRENTLY idx_active_pipelines
ON pipelines (last_run_at DESC)
WHERE status = ‘active’;
— Full-text search index for pipeline names and descriptions
CREATE INDEX CONCURRENTLY idx_pipelines_search
ON pipelines USING gin(to_tsvector(‘english’, name || ‘ ‘ || description));
— Index for time-series data queries
CREATE INDEX CONCURRENTLY idx_pipeline_runs_timeline
ON pipeline_runs (pipeline_id, started_at)
WHERE status IN (‘completed’, ‘failed’);
Query Performance Monitoring
— Enable query statistics collection
ALTER SYSTEM SET track_activities = on;
ALTER SYSTEM SET track_counts = on;
ALTER SYSTEM SET track_io_timing = on;
ALTER SYSTEM SET log_min_duration_statement = 1000; — Log slow queries
— Query to identify slow queries
SELECT
query,
calls,
total_time,
mean_time,
stddev_time,
rows
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10;
Connection Pool Configuration
const pool = new Pool({
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
// Pool configuration for optimal performance
max: 20, // Maximum number of connections
min: 5, // Minimum number of connections
idleTimeoutMillis: 30000, // Close idle connections after 30s
connectionTimeoutMillis: 5000, // Wait 5s for connection
maxUses: 7500, // Refresh connection after 7500 uses
// Query timeout
query_timeout: 30000, // 30 second query timeout
// SSL configuration for production
ssl: process.env.NODE_ENV === ‘production’ ? {
require: true,
rejectUnauthorized: true
} : false
});
Application Performance
Caching Strategy Implementation
Redis Cache Configuration
const Redis = require(‘ioredis’);
class CacheManager {
constructor() {
this.redis = new Redis({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
password: process.env.REDIS_PASSWORD,
retryDelayOnFailover: 100,
maxRetriesPerRequest: 3,
lazyConnect: true,
// Connection pool settings
maxclients: 100,
// Memory optimization
maxmemory: ‘512mb’,
‘maxmemory-policy’: ‘allkeys-lru’
});
}
async get(key, fallbackFunction, ttl = 3600) {
try {
const cached = await this.redis.get(key);
if (cached) {
return JSON.parse(cached);
}
const data = await fallbackFunction();
await this.set(key, data, ttl);
return data;
} catch (error) {
console.error(‘Cache error:’, error);
return await fallbackFunction(); // Fallback to direct data fetch
}
}
async set(key, data, ttl = 3600) {
try {
await this.redis.setex(key, ttl, JSON.stringify(data));
} catch (error) {
console.error(‘Cache set error:’, error);
}
}
async invalidatePattern(pattern) {
const keys = await this.redis.keys(pattern);
if (keys.length > 0) {
await this.redis.del(…keys);
}
}
}
// Usage example
const cache = new CacheManager();
app.get(‘/api/pipelines’, async (req, res) => {
const cacheKey = `pipelines:user:${req.user.id}:${JSON.stringify(req.query)}`;
const pipelines = await cache.get(cacheKey, async () => {
return await pipelineService.getUserPipelines(req.user.id, req.query);
}, 1800); // 30 minutes TTL
res.json(pipelines);
});
Memory Management
Memory Usage Monitoring
const { performance } = require(‘perf_hooks’);
class MemoryMonitor {
constructor() {
this.startMonitoring();
}
getMemoryUsage() {
const usage = process.memoryUsage();
return {
rss: Math.round(usage.rss / 1024 / 1024), // MB
heapTotal: Math.round(usage.heapTotal / 1024 / 1024),
heapUsed: Math.round(usage.heapUsed / 1024 / 1024),
external: Math.round(usage.external / 1024 / 1024),
arrayBuffers: Math.round(usage.arrayBuffers / 1024 / 1024)
};
}
startMonitoring() {
setInterval(() => {
const usage = this.getMemoryUsage();
// Alert if memory usage exceeds threshold
if (usage.heapUsed > 500) { // 500MB threshold
console.warn(‘High memory usage detected:’, usage);
// Trigger garbage collection if needed
if (global.gc) {
global.gc();
}
}
}, 30000); // Check every 30 seconds
}
}
// Memory-efficient data processing
class DataProcessor {
async processLargeDataset(data, batchSize = 1000) {
const results = [];
for (let i = 0; i < data.length; i += batchSize) {
const batch = data.slice(i, i + batchSize);
const processed = await this.processBatch(batch);
results.push(…processed);
// Clear batch from memory
batch.length = 0;
// Allow garbage collection between batches
if (i % (batchSize * 10) === 0) {
await this.sleep(10); // Small delay for GC
}
}
return results;
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
Frontend Performance
Bundle Optimization Configuration
// webpack.config.js
const path = require(‘path’);
const CompressionPlugin = require(‘compression-webpack-plugin’);
module.exports = {
mode: ‘production’,
optimization: {
splitChunks: {
chunks: ‘all’,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: ‘vendors’,
priority: 10,
enforce: true
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
},
usedExports: true,
sideEffects: false
},
plugins: [
new CompressionPlugin({
algorithm: ‘gzip’,
test: /\.(js|css|html|svg)$/,
threshold: 8192,
minRatio: 0.8
})
],
resolve: {
alias: {
‘@components’: path.resolve(__dirname, ‘src/components’),
‘@utils’: path.resolve(__dirname, ‘src/utils’)
}
}
};
React Component Optimization
import React, { memo, useMemo, useCallback } from ‘react’;
import { debounce } from ‘lodash’;
// Memoized component to prevent unnecessary re-renders
const PipelineListItem = memo(({ pipeline, onStatusChange, onEdit }) => {
const handleStatusChange = useCallback((newStatus) => {
onStatusChange(pipeline.id, newStatus);
}, [pipeline.id, onStatusChange]);
const statusColor = useMemo(() => {
return pipeline.status === ‘active’ ? ‘green’ :
pipeline.status === ‘error’ ? ‘red’ : ‘gray’;
}, [pipeline.status]);
return (
<div className={`pipeline-item status-${pipeline.status}`}>
<h3>{pipeline.name}</h3>
<span style={{ color: statusColor }}>{pipeline.status}</span>
<button onClick={handleStatusChange}>Toggle Status</button>
</div>
);
});
// Debounced search to reduce API calls
const SearchInput = ({ onSearch }) => {
const debouncedSearch = useMemo(
() => debounce((query) => onSearch(query), 300),
[onSearch]
);
return (
<input
type=”text”
placeholder=”Search pipelines…”
onChange={(e) => debouncedSearch(e.target.value)}
/>
);
};