import React, { useState, useEffect } from 'react';
import { BarChart3, Database, Users, Clock, AlertCircle, CheckCircle, Activity, ArrowLeft, Eye, TrendingUp, Server } from 'lucide-react';
const API_BASE_URL = 'http://localhost:5000';
const WarehouseAnalyticsDashboard = () => {
const [currentView, setCurrentView] = useState('warehouses');
const [warehouses, setWarehouses] = useState([]);
const [selectedWarehouse, setSelectedWarehouse] = useState(null);
const [selectedMetric, setSelectedMetric] = useState(null);
const [selectedUser, setSelectedUser] = useState(null);
const [queriesByMetric, setQueriesByMetric] = useState({});
const [userQueries, setUserQueries] = useState([]);
const [queryDetails, setQueryDetails] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// Fetch warehouses data
useEffect(() => {
fetchWarehouses();
}, []);
const fetchWarehouses = async () => {
try {
setLoading(true);
const response = await fetch(`${API_BASE_URL}/warehouses`);
const data = await response.json();
if (data.status === 'success') {
setWarehouses(data.data);
setError(null);
} else {
setError(data.error || 'Failed to fetch warehouses');
}
} catch (err) {
setError('Failed to connect to backend. Make sure Flask server is running.');
} finally {
setLoading(false);
}
};
const fetchQueriesByMetric = async (warehouseId, metric) => {
try {
setLoading(true);
const response = await fetch(`${API_BASE_URL}/queries/by-warehouse/${warehouseId}/${metric}`);
const data = await response.json();
if (data.status === 'success') {
setQueriesByMetric(data);
setSelectedWarehouse(warehouseId);
setSelectedMetric(metric);
setCurrentView('queries-by-metric');
}
} catch (err) {
setError('Failed to fetch queries');
} finally {
setLoading(false);
}
};
const fetchUserQueries = async (warehouseId, metric, userName) => {
try {
setLoading(true);
const response = await fetch(`${API_BASE_URL}/queries/by-user/${warehouseId}/${metric}/${encodeURIComponent(userName)}`);
const data = await response.json();
if (data.status === 'success') {
setUserQueries(data.data);
setSelectedUser(userName);
setCurrentView('user-queries');
}
} catch (err) {
setError('Failed to fetch user queries');
} finally {
setLoading(false);
}
};
const fetchQueryDetails = async (queryId) => {
try {
setLoading(true);
const response = await fetch(`${API_BASE_URL}/query/details/${queryId}`);
const data = await response.json();
if (data.status === 'success') {
setQueryDetails(data.data);
setCurrentView('query-details');
}
} catch (err) {
setError('Failed to fetch query details');
} finally {
setLoading(false);
}
};
const formatNumber = (num) => {
if (num === null || num === undefined) return '0';
return num.toLocaleString();
};
const formatDuration = (ms) => {
if (!ms) return 'N/A';
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) return `${hours}h ${minutes % 60}m`;
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
return `${seconds}s`;
};
const getMetricColor = (metric) => {
const colors = {
'queries-1-10-sec': 'text-green-600 bg-green-50',
'queries-10-20-sec': 'text-blue-600 bg-blue-50',
'queries-20-60-sec': 'text-yellow-600 bg-yellow-50',
'queries-1-3-min': 'text-orange-600 bg-orange-50',
'queries-3-5-min': 'text-red-600 bg-red-50',
'queries-5-plus-min': 'text-red-800 bg-red-100',
'failed-queries': 'text-red-600 bg-red-50',
'successful-queries': 'text-green-600 bg-green-50',
'queries-spilled-local': 'text-purple-600 bg-purple-50',
'queries-spilled-remote': 'text-purple-800 bg-purple-100'
};
return colors[metric] || 'text-gray-600 bg-gray-50';
};
const getStatusIcon = (status) => {
switch (status) {
case 'SUCCESS': return <CheckCircle className="w-4 h-4 text-green-500" />;
case 'FAIL': return <AlertCircle className="w-4 h-4 text-red-500" />;
case 'RUNNING': return <Activity className="w-4 h-4 text-blue-500" />;
default: return <Clock className="w-4 h-4 text-gray-500" />;
}
};
if (loading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="flex flex-col items-center space-y-4">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
<p className="text-gray-600">Loading analytics data...</p>
</div>
</div>
);
}
if (error) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="bg-white p-8 rounded-lg shadow-md max-w-md w-full">
<AlertCircle className="w-12 h-12 text-red-500 mx-auto mb-4" />
<h2 className="text-xl font-semibold text-gray-900 text-center mb-2">Connection Error</h2>
<p className="text-gray-600 text-center mb-4">{error}</p>
<button
onClick={fetchWarehouses}
className="w-full bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 transition-colors"
>
Retry
</button>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50">
{/* Header */}
<header className="bg-white shadow-sm border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16">
<div className="flex items-center space-x-4">
{currentView !== 'warehouses' && (
<button
onClick={() => {
if (currentView === 'query-details') setCurrentView('user-queries');
else if (currentView === 'user-queries') setCurrentView('queries-by-metric');
else if (currentView === 'queries-by-metric') setCurrentView('warehouses');
}}
className="p-2 text-gray-600 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors"
>
<ArrowLeft className="w-5 h-5" />
</button>
)}
<div className="flex items-center space-x-3">
<Database className="w-8 h-8 text-blue-600" />
<h1 className="text-2xl font-bold text-gray-900">Warehouse Analytics</h1>
</div>
</div>
<div className="flex items-center space-x-2 text-sm text-gray-500">
<Server className="w-4 h-4" />
<span>{warehouses.length} Warehouses</span>
</div>
</div>
</div>
</header>
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{currentView === 'warehouses' && (
<WarehousesView
warehouses={warehouses}
onMetricClick={fetchQueriesByMetric}
/>
)}
{currentView === 'queries-by-metric' && (
<QueriesByMetricView
data={queriesByMetric}
onUserClick={fetchUserQueries}
/>
)}
{currentView === 'user-queries' && (
<UserQueriesView
queries={userQueries}
userName={selectedUser}
onQueryClick={fetchQueryDetails}
/>
)}
{currentView === 'query-details' && (
<QueryDetailsView queryDetails={queryDetails} />
)}
</main>
</div>
);
};
// Warehouses View Component
const WarehousesView = ({ warehouses, onMetricClick }) => {
const formatNumber = (num) => num ? num.toLocaleString() : '0';
const getClickableMetrics = (warehouse) => [
{ key: 'QUERIES_1_10_SEC', label: '1-10 sec', value: warehouse.QUERIES_1_10_SEC, apiKey: '1-10-seconds' },
{ key: 'QUERIES_10_20_SEC', label: '10-20 sec', value: warehouse.QUERIES_10_20_SEC, apiKey: '10-20-seconds' },
{ key: 'QUERIES_20_60_SEC', label: '20-60 sec', value: warehouse.QUERIES_20_60_SEC, apiKey: '20-60-seconds' },
{ key: 'QUERIES_1_3_MIN', label: '1-3 min', value: warehouse.QUERIES_1_3_MIN, apiKey: '1-3-minutes' },
{ key: 'QUERIES_3_5_MIN', label: '3-5 min', value: warehouse.QUERIES_3_5_MIN, apiKey: '3-5-minutes' },
{ key: 'QUERIES_5_PLUS_MIN', label: '5+ min', value: warehouse.QUERIES_5_PLUS_MIN, apiKey: '5-plus-minutes' },
{ key: 'FAILED_QUERIES', label: 'Failed', value: warehouse.FAILED_QUERIES, apiKey: 'failed-queries' },
{ key: 'SUCCESSFUL_QUERIES', label: 'Success', value: warehouse.SUCCESSFUL_QUERIES, apiKey: 'successful-queries' },
{ key: 'QUERIES_SPILLED_LOCAL', label: 'Spilled Local', value: warehouse.QUERIES_SPILLED_LOCAL, apiKey: 'spilled-local' },
{ key: 'QUERIES_SPILLED_REMOTE', label: 'Spilled Remote', value: warehouse.QUERIES_SPILLED_REMOTE, apiKey: 'spilled-remote' }
];
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold text-gray-900">Data Warehouses</h2>
<div className="text-sm text-gray-500">Click on any metric to drill down</div>
</div>
<div className="grid gap-6">
{warehouses.map((warehouse) => (
<div key={warehouse.WAREHOUSE_ID} className="bg-white rounded-lg shadow-md border border-gray-200 overflow-hidden">
<div className="px-6 py-4 bg-gray-50 border-b border-gray-200">
<div className="flex items-center justify-between">
<div>
<h3 className="text-xl font-semibold text-gray-900">{warehouse.WAREHOUSE_NAME}</h3>
<p className="text-sm text-gray-600">ID: {warehouse.WAREHOUSE_ID}</p>
</div>
<div className="text-right">
<div className="text-2xl font-bold text-blue-600">{formatNumber(warehouse.TOTAL_QUERIES)}</div>
<div className="text-sm text-gray-500">Total Queries</div>
</div>
</div>
</div>
<div className="p-6">
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
{getClickableMetrics(warehouse).map((metric) => (
<div
key={metric.key}
onClick={() => metric.value > 0 && onMetricClick(warehouse.WAREHOUSE_ID, metric.apiKey)}
className={`p-4 rounded-lg border transition-all duration-200 ${
metric.value > 0
? 'cursor-pointer hover:shadow-md hover:scale-105 border-blue-200 bg-blue-50'
: 'border-gray-200 bg-gray-50'
}`}
>
<div className="text-2xl font-bold text-gray-900">{formatNumber(metric.value)}</div>
<div className="text-sm text-gray-600 mt-1">{metric.label}</div>
{metric.value > 0 && (
<div className="text-xs text-blue-600 mt-2 flex items-center">
<TrendingUp className="w-3 h-3 mr-1" />
Click to explore
</div>
)}
</div>
))}
</div>
<div className="mt-6 pt-4 border-t border-gray-200">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
<div>
<span className="text-gray-500">Size:</span>
<span className="ml-2 font-medium">{warehouse.WAREHOUSE_SIZE || 'N/A'}</span>
</div>
<div>
<span className="text-gray-500">Type:</span>
<span className="ml-2 font-medium">{warehouse.WAREHOUSE_TYPE || 'N/A'}</span>
</div>
<div>
<span className="text-gray-500">Credits Used:</span>
<span className="ml-2 font-medium">{formatNumber(warehouse.TOTAL_CREDITS_USED)}</span>
</div>
</div>
</div>
</div>
</div>
))}
</div>
</div>
);
};
// Queries by Metric View Component
const QueriesByMetricView = ({ data, onUserClick }) => {
const formatNumber = (num) => num ? num.toLocaleString() : '0';
return (
<div className="space-y-6">
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-2xl font-bold text-gray-900 mb-4">
Queries by {data.metric_type?.replace(/-/g, ' ')} - Warehouse {data.warehouse_id}
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm bg-gray-50 p-4 rounded-lg">
<div><span className="text-gray-500">Total Queries:</span> <span className="font-semibold ml-2">{formatNumber(data.count)}</span></div>
<div><span className="text-gray-500">Users:</span> <span className="font-semibold ml-2">{formatNumber(data.users)}</span></div>
<div><span className="text-gray-500">Metric:</span> <span className="font-semibold ml-2 capitalize">{data.metric_type?.replace(/-/g, ' ')}</span></div>
</div>
</div>
<div className="grid gap-4">
{Object.entries(data.data || {}).map(([userName, queries]) => (
<div
key={userName}
onClick={() => onUserClick(data.warehouse_id, data.metric_type, userName)}
className="bg-white rounded-lg shadow-md p-6 cursor-pointer hover:shadow-lg hover:bg-gray-50 transition-all duration-200 border border-gray-200 hover:border-blue-300"
>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<Users className="w-6 h-6 text-blue-600" />
<div>
<h3 className="text-lg font-semibold text-gray-900">{userName}</h3>
<p className="text-sm text-gray-600">{queries.length} queries in this category</p>
</div>
</div>
<div className="text-right">
<div className="text-2xl font-bold text-blue-600">{queries.length}</div>
<div className="text-xs text-blue-600 flex items-center justify-end mt-1">
<Eye className="w-3 h-3 mr-1" />
View Details
</div>
</div>
</div>
</div>
))}
</div>
{Object.keys(data.data || {}).length === 0 && (
<div className="text-center py-12 bg-white rounded-lg shadow-md">
<AlertCircle className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">No queries found</h3>
<p className="text-gray-600">No queries match this metric for the selected warehouse.</p>
</div>
)}
</div>
);
};
// User Queries View Component
const UserQueriesView = ({ queries, userName, onQueryClick }) => {
const formatDuration = (ms) => {
if (!ms) return 'N/A';
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
return minutes > 0 ? `${minutes}m ${seconds % 60}s` : `${seconds}s`;
};
const getStatusIcon = (status) => {
switch (status) {
case 'SUCCESS': return <CheckCircle className="w-4 h-4 text-green-500" />;
case 'FAIL': return <AlertCircle className="w-4 h-4 text-red-500" />;
case 'RUNNING': return <Activity className="w-4 h-4 text-blue-500" />;
default: return <Clock className="w-4 h-4 text-gray-500" />;
}
};
return (
<div className="space-y-6">
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-2xl font-bold text-gray-900 mb-4">Queries for {userName}</h2>
<p className="text-gray-600">{queries.length} queries found</p>
</div>
<div className="bg-white rounded-lg shadow-md overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Query</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Duration</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Start Time</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{queries.map((query, index) => (
<tr key={query.QUERY_ID} className="hover:bg-gray-50">
<td className="px-6 py-4">
<div className="text-sm font-medium text-gray-900 truncate max-w-xs">
{query.QUERY_TEXT_PREVIEW || query.QUERY_TYPE || 'N/A'}
</div>
<div className="text-xs text-gray-500">ID: {query.QUERY_ID}</div>
</td>
<td className="px-6 py-4">
<div className="flex items-center space-x-2">
{getStatusIcon(query.EXECUTION_STATUS)}
<span className="text-sm text-gray-900">{query.EXECUTION_STATUS}</span>
</div>
</td>
<td className="px-6 py-4 text-sm text-gray-900">
{formatDuration(query.TOTAL_ELAPSED_TIME)}
</td>
<td className="px-6 py-4 text-sm text-gray-900">
{query.START_TIME ? new Date(query.START_TIME).toLocaleString() : 'N/A'}
</td>
<td className="px-6 py-4">
<button
onClick={() => onQueryClick(query.QUERY_ID)}
className="inline-flex items-center px-3 py-1 text-xs font-medium text-blue-600 bg-blue-100 rounded-full hover:bg-blue-200 transition-colors"
>
<Eye className="w-3 h-3 mr-1" />
Details
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
};
// Query Details View Component
const QueryDetailsView = ({ queryDetails }) => {
const formatDuration = (ms) => {
if (!ms) return 'N/A';
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
return minutes > 0 ? `${minutes}m ${seconds % 60}s` : `${seconds}s`;
};
const formatBytes = (bytes) => {
if (!bytes) return 'N/A';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
};
const formatNumber = (num) => num ? num.toLocaleString() : 'N/A';
return (
<div className="space-y-6">
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-2xl font-bold text-gray-900 mb-4">Query Details</h2>
<div className="text-sm text-gray-600 bg-gray-50 p-3 rounded-lg">
<strong>Query ID:</strong> {queryDetails.QUERY_ID}
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Basic Information */}
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<BarChart3 className="w-5 h-5 mr-2 text-blue-600" />
Basic Information
</h3>
<div className="space-y-3">
<div><span className="text-gray-500">Query Type:</span> <span className="ml-2 font-medium">{queryDetails.QUERY_TYPE}</span></div>
<div><span className="text-gray-500">Status:</span> <span className="ml-2 font-medium">{queryDetails.EXECUTION_STATUS}</span></div>
<div><span className="text-gray-500">User:</span> <span className="ml-2 font-medium">{queryDetails.USER_NAME}</span></div>
<div><span className="text-gray-500">Role:</span> <span className="ml-2 font-medium">{queryDetails.ROLE_NAME}</span></div>
<div><span className="text-gray-500">Database:</span> <span className="ml-2 font-medium">{queryDetails.DATABASE_NAME}</span></div>
<div><span className="text-gray-500">Schema:</span> <span className="ml-2 font-medium">{queryDetails.SCHEMA_NAME}</span></div>
</div>
</div>
{/* Performance Metrics */}
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<TrendingUp className="w-5 h-5 mr-2 text-green-600" />
Performance Metrics
</h3>
<div className="space-y-3">
<div><span className="text-gray-500">Total Duration:</span> <span className="ml-2 font-medium">{formatDuration(queryDetails.TOTAL_ELAPSED_TIME)}</span></div>
<div><span className="text-gray-500">Execution Time:</span> <span className="ml-2 font-medium">{formatDuration(queryDetails.EXECUTION_TIME)}</span></div>
<div><span className="text-gray-500">Compilation Time:</span> <span className="ml-2 font-medium">{formatDuration(queryDetails.COMPILATION_TIME)}</span></div>
<div><span className="text-gray-500">Rows Produced:</span> <span className="ml-2 font-medium">{formatNumber(queryDetails.ROWS_PRODUCED)}</span></div>
<div><span className="text-gray-500">Bytes Scanned:</span> <span className="ml-2 font-medium">{formatBytes(queryDetails.BYTES_SCANNED)}</span></div>
<div><span className="text-gray-500">Cache Hit %:</span> <span className="ml-2 font-medium">{queryDetails.PERCENTAGE_SCANNED_FROM_CACHE}%</span></div>
</div>
</div>
{/* Warehouse Information */}
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<Server className="w-5 h-5 mr-2 text-purple-600" />
Warehouse Information
</h3>
<div className="space-y-3">
<div><span className="text-gray-500">Warehouse:</span> <span className="ml-2 font-medium">{queryDetails.WAREHOUSE_NAME}</span></div>
<div><span className="text-gray-500">Size:</span> <span className="ml-2 font-medium">{queryDetails.WAREHOUSE_SIZE}</span></div>
<div><span className="text-gray-500">Type:</span> <span className="ml-2 font-medium">{queryDetails.WAREHOUSE_TYPE}</span></div>
<div><span className="text-gray-500">Credits Used:</span> <span className="ml-2 font-medium">{queryDetails.CREDITS_USED_CLOUD_SERVICES}</span></div>
<div><span className="text-gray-500">Local Spill:</span> <span className="ml-2 font-medium">{formatBytes(queryDetails.BYTES_SPILLED_TO_LOCAL_STORAGE)}</span></div>
<div><span className="text-gray-500">Remote Spill:</span> <span className="ml-2 font-medium">{formatBytes(queryDetails.BYTES_SPILLED_TO_REMOTE_STORAGE)}</span></div>
</div>
</div>
{/* Timing Details */}
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<Clock className="w-5 h-5 mr-2 text-orange-600" />
Timing Details
</h3>
<div className="space-y-3">
<div><span className="text-gray-500">Start Time:</span> <span className="ml-2 font-medium">{queryDetails.START_TIME ? new Date(queryDetails.START_TIME).toLocaleString() : 'N/A'}</span></div>
<div><span className="text-gray-500">End Time:</span> <span className="ml-2 font-medium">{queryDetails.END_TIME ? new Date(queryDetails.END_TIME).toLocaleString() : 'N/A'}</span></div>
<div><span className="text-gray-500">Queue Time:</span> <span className="ml-2 font-medium">{formatDuration(queryDetails.QUEUED_PROVISIONING_TIME)}</span></div>
<div><span className="text-gray-500">Blocked Time:</span> <span className="ml-2 font-medium">{formatDuration(queryDetails.TRANSACTION_BLOCKED_TIME)}</span></div>
<div><span className="text-gray-500">Performance:</span> <span className="ml-2 font-medium">{queryDetails.PERFORMANCE_CATEGORY}</span></div>
<div><span className="text-gray-500">Cache Efficiency:</span> <span className="ml-2 font-medium">{queryDetails.CACHE_EFFICIENCY}</span></div>
</div>
</div>
</div>
{/* Query Text */}
{queryDetails.QUERY_TEXT && (
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Query Text</h3>
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto">
<pre className="text-sm whitespace-pre-wrap">{queryDetails.QUERY_TEXT}</pre>
</div>
</div>
)}
{/* Error Information */}
{queryDetails.ERROR_MESSAGE && (
<div className="bg-white rounded-lg shadow-md p-6">
<h3 className="text-lg font-semibold text-red-600 mb-4 flex items-center">
<AlertCircle className="w-5 h-5 mr-2" />
Error Information
</h3>
<div className="space-y-3">
<div><span className="text-gray-500">Error Code:</span> <span className="ml-2 font-medium text-red-600">{queryDetails.ERROR_CODE}</span></div>
<div className="bg-red-50 p-4 rounded-lg">
<span className="text-red-800">{queryDetails.ERROR_MESSAGE}</span>
</div>
</div>
</div>
)}
</div>
);
};
export default WarehouseAnalyticsDashboard;
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)