feat: live enrichment feed with per-company progress rows
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
- Live panel with spinner, progress bar, company counter - Each company appears as a row with platform badges showing status (changes/no_changes/skipped/error/no_data) - Incremental polling (since= param) for efficient updates - After completion: link to review page if changes found - Blue highlighted rows for companies with new data Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d39acc303e
commit
ce5a259749
@ -979,8 +979,47 @@ def admin_social_audit_run_enrichment():
|
||||
@login_required
|
||||
@role_required(SystemRole.OFFICE_MANAGER)
|
||||
def admin_social_audit_enrichment_status():
|
||||
"""Get current enrichment job status with pending changes summary."""
|
||||
"""Get current enrichment job status with live results feed."""
|
||||
pending = _enrichment_status.get('pending_changes', [])
|
||||
results = _enrichment_status.get('results', [])
|
||||
|
||||
# Return last N results for live feed (since_index param for incremental updates)
|
||||
since = request.args.get('since', 0, type=int)
|
||||
new_results = results[since:]
|
||||
|
||||
# Build compact live feed entries
|
||||
feed = []
|
||||
for r in new_results:
|
||||
profiles_summary = []
|
||||
for p in r.get('profiles', []):
|
||||
status = p.get('status', 'unknown')
|
||||
icon = {'changes': '+', 'no_changes': '=', 'skipped': '~', 'error': '!', 'no_data': '-'}.get(status, '?')
|
||||
platform = p.get('platform', '?')
|
||||
change_count = len(p.get('changes', []))
|
||||
desc = ''
|
||||
if status == 'changes':
|
||||
desc = f'{change_count} zmian'
|
||||
elif status == 'skipped':
|
||||
desc = 'API'
|
||||
elif status == 'error':
|
||||
desc = p.get('reason', 'błąd')[:40]
|
||||
elif status == 'no_data':
|
||||
desc = 'brak danych'
|
||||
elif status == 'no_changes':
|
||||
desc = 'aktualne'
|
||||
profiles_summary.append({
|
||||
'platform': platform,
|
||||
'icon': icon,
|
||||
'status': status,
|
||||
'desc': desc,
|
||||
})
|
||||
feed.append({
|
||||
'company_name': r.get('company_name', '?'),
|
||||
'company_id': r.get('company_id'),
|
||||
'has_changes': r.get('has_changes', False),
|
||||
'profiles': profiles_summary,
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'running': _enrichment_status['running'],
|
||||
'progress': _enrichment_status['progress'],
|
||||
@ -990,6 +1029,8 @@ def admin_social_audit_enrichment_status():
|
||||
'last_run': _enrichment_status['last_run'].strftime('%d.%m.%Y %H:%M') if _enrichment_status['last_run'] else None,
|
||||
'pending_count': len(pending),
|
||||
'approved': _enrichment_status.get('approved', False),
|
||||
'feed': feed,
|
||||
'results_count': len(results),
|
||||
})
|
||||
|
||||
|
||||
|
||||
@ -523,10 +523,7 @@
|
||||
</svg>
|
||||
Uruchom audyt
|
||||
</button>
|
||||
<div id="enrichProgress" style="display: none; font-size: 12px; padding: 4px 12px; background: #eff6ff; border-radius: var(--radius); color: #2563eb;">
|
||||
<span id="enrichText">Audyt...</span>
|
||||
<span id="enrichPct">0%</span>
|
||||
</div>
|
||||
<span id="enrichMiniStatus" style="display: none; font-size: 12px; color: #2563eb; font-weight: 500;"></span>
|
||||
<a href="{{ url_for('admin.admin_social_media') }}" class="btn btn-outline btn-sm">
|
||||
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
|
||||
@ -536,6 +533,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Live enrichment panel -->
|
||||
<div id="enrichPanel" style="display: none; margin-bottom: var(--spacing-xl); background: var(--surface); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); overflow: hidden;">
|
||||
<div style="padding: var(--spacing-md) var(--spacing-lg); background: #eff6ff; border-bottom: 1px solid #bfdbfe; display: flex; align-items: center; gap: var(--spacing-md);">
|
||||
<div id="enrichSpinner" style="width: 20px; height: 20px; border: 3px solid #bfdbfe; border-top-color: #2563eb; border-radius: 50%; animation: spin 0.8s linear infinite;"></div>
|
||||
<div style="flex: 1;">
|
||||
<div style="font-weight: 600; color: #1e40af;" id="enrichTitle">Skanowanie profili social media...</div>
|
||||
<div style="font-size: var(--font-size-xs); color: #3b82f6;" id="enrichSubtitle">Dane nie zostaną zapisane bez Twojej zgody</div>
|
||||
</div>
|
||||
<span id="enrichCounter" style="font-size: var(--font-size-sm); font-weight: 600; color: #1e40af;">0 / 0</span>
|
||||
</div>
|
||||
<!-- Progress bar -->
|
||||
<div style="height: 4px; background: #dbeafe;">
|
||||
<div id="enrichBar" style="height: 100%; background: #2563eb; transition: width 0.5s; width: 0%;"></div>
|
||||
</div>
|
||||
<!-- Live feed -->
|
||||
<div id="enrichFeed" style="max-height: 400px; overflow-y: auto; padding: var(--spacing-sm) var(--spacing-lg); font-size: var(--font-size-sm);"></div>
|
||||
</div>
|
||||
<style>@keyframes spin { to { transform: rotate(360deg); } }</style>
|
||||
|
||||
<!-- Summary Stats -->
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
@ -989,14 +1005,17 @@ function resetFilters() {
|
||||
}
|
||||
|
||||
// Enrichment
|
||||
var _enrichSince = 0;
|
||||
var _enrichPendingCount = 0;
|
||||
|
||||
function startEnrichment() {
|
||||
if (!confirm('Uruchomić skanowanie social media dla wszystkich firm?\n\nProces działa w tle i może potrwać kilka minut.\nDane NIE zostaną zapisane bez Twojej zgody — po zakończeniu zobaczysz raport ze zmianami.\nProfile z danymi z API (OAuth) nie będą nadpisywane.')) return;
|
||||
if (!confirm('Uruchomić skanowanie social media dla wszystkich firm?\n\nProces działa w tle. Dane NIE zostaną zapisane bez Twojej zgody.\nPo zakończeniu zobaczysz raport ze zmianami do zatwierdzenia.')) return;
|
||||
|
||||
var btn = document.getElementById('enrichBtn');
|
||||
var progress = document.getElementById('enrichProgress');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Uruchamianie...';
|
||||
progress.style.display = 'inline-flex';
|
||||
_enrichSince = 0;
|
||||
_enrichPendingCount = 0;
|
||||
|
||||
fetch('{{ url_for("admin.admin_social_audit_run_enrichment") }}', {
|
||||
method: 'POST',
|
||||
@ -1005,48 +1024,109 @@ function startEnrichment() {
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.status === 'started') {
|
||||
document.getElementById('enrichText').textContent = 'Skanowanie: 0/' + data.total;
|
||||
// Show live panel
|
||||
document.getElementById('enrichPanel').style.display = 'block';
|
||||
document.getElementById('enrichCounter').textContent = '0 / ' + data.total;
|
||||
document.getElementById('enrichFeed').innerHTML = '';
|
||||
document.getElementById('enrichMiniStatus').style.display = 'inline';
|
||||
document.getElementById('enrichMiniStatus').textContent = 'Skanowanie...';
|
||||
pollEnrichment();
|
||||
} else {
|
||||
alert(data.error || 'Błąd uruchamiania');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Uruchom audyt';
|
||||
progress.style.display = 'none';
|
||||
}
|
||||
})
|
||||
.catch(function(e) {
|
||||
alert('Błąd: ' + e.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Uruchom audyt';
|
||||
progress.style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function _statusIcon(status) {
|
||||
if (status === 'changes') return '<span style="color:#2563eb;">●</span>';
|
||||
if (status === 'skipped') return '<span style="color:#9ca3af;">○</span>';
|
||||
if (status === 'error') return '<span style="color:#ef4444;">⚠</span>';
|
||||
if (status === 'no_changes') return '<span style="color:#22c55e;">✓</span>';
|
||||
return '<span style="color:#9ca3af;">—</span>';
|
||||
}
|
||||
|
||||
function _platformBadge(p) {
|
||||
var colors = {
|
||||
'changes': 'background:#dbeafe;color:#1d4ed8;',
|
||||
'skipped': 'background:#f3f4f6;color:#6b7280;',
|
||||
'error': 'background:#fee2e2;color:#991b1b;',
|
||||
'no_changes': 'background:#f0fdf4;color:#15803d;',
|
||||
'no_data': 'background:#f3f4f6;color:#9ca3af;'
|
||||
};
|
||||
var style = colors[p.status] || 'background:#f3f4f6;color:#6b7280;';
|
||||
var name = p.platform.charAt(0).toUpperCase() + p.platform.slice(1);
|
||||
return '<span style="display:inline-flex;align-items:center;gap:3px;padding:1px 6px;border-radius:4px;font-size:11px;' + style + '" title="' + p.desc + '">' + name + ': ' + p.desc + '</span>';
|
||||
}
|
||||
|
||||
function pollEnrichment() {
|
||||
fetch('{{ url_for("admin.admin_social_audit_enrichment_status") }}')
|
||||
fetch('{{ url_for("admin.admin_social_audit_enrichment_status") }}?since=' + _enrichSince)
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
document.getElementById('enrichPct').textContent = data.progress + '%';
|
||||
document.getElementById('enrichText').textContent = 'Skanowanie: ' + data.completed + '/' + data.total;
|
||||
// Update progress
|
||||
document.getElementById('enrichCounter').textContent = data.completed + ' / ' + data.total;
|
||||
document.getElementById('enrichBar').style.width = data.progress + '%';
|
||||
document.getElementById('enrichMiniStatus').textContent = data.completed + '/' + data.total;
|
||||
|
||||
// Append new feed entries
|
||||
var feed = document.getElementById('enrichFeed');
|
||||
if (data.feed && data.feed.length > 0) {
|
||||
for (var i = 0; i < data.feed.length; i++) {
|
||||
var r = data.feed[i];
|
||||
var row = document.createElement('div');
|
||||
row.style.cssText = 'display:flex;align-items:center;gap:8px;padding:6px 0;border-bottom:1px solid #f3f4f6;';
|
||||
|
||||
var badges = r.profiles.map(_platformBadge).join(' ');
|
||||
var nameStyle = r.has_changes ? 'font-weight:600;color:#1d4ed8;' : 'color:var(--text-secondary);';
|
||||
|
||||
row.innerHTML = '<span style="min-width:24px;text-align:center;">' + (_enrichSince + i + 1) + '.</span>' +
|
||||
'<span style="min-width:200px;' + nameStyle + '">' + r.company_name + '</span>' +
|
||||
'<span style="display:flex;gap:4px;flex-wrap:wrap;">' + badges + '</span>';
|
||||
|
||||
feed.appendChild(row);
|
||||
feed.scrollTop = feed.scrollHeight;
|
||||
}
|
||||
_enrichSince += data.feed.length;
|
||||
}
|
||||
|
||||
if (data.pending_count > _enrichPendingCount) {
|
||||
_enrichPendingCount = data.pending_count;
|
||||
document.getElementById('enrichSubtitle').textContent = _enrichPendingCount + ' profili z nowymi danymi (do zatwierdzenia)';
|
||||
}
|
||||
|
||||
if (data.running) {
|
||||
setTimeout(pollEnrichment, 3000);
|
||||
setTimeout(pollEnrichment, 2000);
|
||||
} else {
|
||||
// Scan complete — redirect to review page
|
||||
// Scan complete
|
||||
document.getElementById('enrichSpinner').style.animation = 'none';
|
||||
document.getElementById('enrichSpinner').style.borderTopColor = '#22c55e';
|
||||
document.getElementById('enrichSpinner').style.borderColor = '#22c55e';
|
||||
|
||||
var btn = document.getElementById('enrichBtn');
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg> Uruchom audyt';
|
||||
|
||||
if (data.pending_count > 0) {
|
||||
document.getElementById('enrichText').textContent = data.pending_count + ' zmian do zatwierdzenia...';
|
||||
window.location.href = '{{ url_for("admin.admin_social_audit_enrichment_review") }}';
|
||||
document.getElementById('enrichTitle').textContent = 'Skanowanie zakończone — ' + data.pending_count + ' zmian do zatwierdzenia';
|
||||
document.getElementById('enrichSubtitle').innerHTML = '<a href="{{ url_for("admin.admin_social_audit_enrichment_review") }}" style="color:#2563eb;font-weight:600;">Przejdź do raportu →</a>';
|
||||
document.getElementById('enrichMiniStatus').innerHTML = '<a href="{{ url_for("admin.admin_social_audit_enrichment_review") }}" style="color:#2563eb;">' + data.pending_count + ' zmian →</a>';
|
||||
|
||||
// Add review link row
|
||||
var linkRow = document.createElement('div');
|
||||
linkRow.style.cssText = 'padding:12px 0;text-align:center;font-weight:600;';
|
||||
linkRow.innerHTML = '<a href="{{ url_for("admin.admin_social_audit_enrichment_review") }}" style="color:#2563eb;font-size:14px;">Przejdź do raportu ze zmianami (' + data.pending_count + ' profili) →</a>';
|
||||
feed.appendChild(linkRow);
|
||||
feed.scrollTop = feed.scrollHeight;
|
||||
} else {
|
||||
var btn = document.getElementById('enrichBtn');
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg> Uruchom audyt';
|
||||
|
||||
var errInfo = data.errors > 0 ? ', ' + data.errors + ' błędów' : '';
|
||||
document.getElementById('enrichText').textContent = 'Brak nowych danych' + errInfo;
|
||||
|
||||
setTimeout(function() {
|
||||
document.getElementById('enrichProgress').style.display = 'none';
|
||||
}, 8000);
|
||||
document.getElementById('enrichTitle').textContent = 'Skanowanie zakończone — brak nowych danych';
|
||||
document.getElementById('enrichSubtitle').textContent = data.errors > 0 ? data.errors + ' błędów' : 'Wszystkie profile aktualne';
|
||||
document.getElementById('enrichMiniStatus').textContent = 'Zakończono';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user