Projekte: neue Gitea-Repos beim Oeffnen der Projekte-Seite abholen; Review-Kleinigkeiten
- Projekte-Seite holt neue/archivierte Repos selbst ab (vorher bis zu 10 Minuten unsichtbar) - Import-Drossel: nach einem Fehler sofort neuer Versuch - Zuordnen-Assistent nennt den Status einer vorhandenen Preview - Ticket "Neues Projekt": ungueltige Subdomain wird abgewiesen Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f4c8b235b9
commit
c262268015
5 files changed
+27
-8
No files matched your search
@@ -135,6 +135,10 @@ export default function TicketProjects({ ticket }) {
|
|||||||
|
|
||||||
const create = (e) => {
|
const create = (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
if (!slugify(createForm.subdomain)) {
|
||||||
|
setError('Bitte eine gültige Subdomain eingeben (Buchstaben, Ziffern, Bindestrich).')
|
||||||
|
return
|
||||||
|
}
|
||||||
run(async () => {
|
run(async () => {
|
||||||
const subdomain = slugify(createForm.subdomain)
|
const subdomain = slugify(createForm.subdomain)
|
||||||
await createProjectFromTemplate({
|
await createProjectFromTemplate({
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
// Ein Aufruf hoechstens alle `maxAgeMs`: ein laufender Aufruf wird geteilt, danach liefert die
|
// Ein Aufruf hoechstens alle `maxAgeMs`: ein laufender Aufruf wird geteilt, danach liefert die
|
||||||
// Funktion bis zum Ablauf null (uebersprungen). `now` nur fuer Tests.
|
// Funktion bis zum Ablauf null (uebersprungen). Nach einem Fehler darf sofort neu versucht
|
||||||
|
// werden (sonst verschwand der Hinweis und der Abgleich fiel eine Minute lang still aus).
|
||||||
|
// `now` nur fuer Tests.
|
||||||
export function throttleRuns(run, maxAgeMs) {
|
export function throttleRuns(run, maxAgeMs) {
|
||||||
let pending = null
|
let pending = null
|
||||||
let doneAt = 0
|
let doneAt = 0
|
||||||
@@ -8,10 +10,10 @@ export function throttleRuns(run, maxAgeMs) {
|
|||||||
if (doneAt && now - doneAt < maxAgeMs) return Promise.resolve(null)
|
if (doneAt && now - doneAt < maxAgeMs) return Promise.resolve(null)
|
||||||
pending = Promise.resolve()
|
pending = Promise.resolve()
|
||||||
.then(run)
|
.then(run)
|
||||||
.finally(() => {
|
.then(
|
||||||
doneAt = Date.now()
|
(result) => { doneAt = Date.now(); pending = null; return result },
|
||||||
pending = null
|
(err) => { doneAt = 0; pending = null; throw err }
|
||||||
})
|
)
|
||||||
return pending
|
return pending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -357,7 +357,9 @@ export default function AssignProjectPage() {
|
|||||||
</ul>
|
</ul>
|
||||||
{isWebsiteWithCustomer && hasPreviewAlready && (
|
{isWebsiteWithCustomer && hasPreviewAlready && (
|
||||||
<p className="muted" style={{ marginTop: 0 }}>
|
<p className="muted" style={{ marginTop: 0 }}>
|
||||||
Preview läuft bereits unter https://{project.subdomain}.project.webklar.com und bleibt unverändert.
|
{['deployed', 'ready'].includes(String(project.status || '').toLowerCase())
|
||||||
|
? <>Preview läuft bereits unter https://{project.subdomain}.project.webklar.com und bleibt unverändert.</>
|
||||||
|
: <>Preview ist unter https://{project.subdomain}.project.webklar.com eingerichtet (Status: {project.status || 'unbekannt'}). Neu bauen geht im Projekt über „Preview".</>}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{isWebsiteWithCustomer && !hasPreviewAlready && (
|
{isWebsiteWithCustomer && !hasPreviewAlready && (
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
updateWebsiteProject,
|
updateWebsiteProject,
|
||||||
archiveWebsiteProject,
|
archiveWebsiteProject,
|
||||||
unarchiveWebsiteProject,
|
unarchiveWebsiteProject,
|
||||||
|
importNewReposThrottled,
|
||||||
} from '../lib/projectAdminApi'
|
} from '../lib/projectAdminApi'
|
||||||
import { assignSummary, ticketIdForCustomer } from '../lib/projectAssign'
|
import { assignSummary, ticketIdForCustomer } from '../lib/projectAssign'
|
||||||
import { projectCounts, projectsInView } from '../lib/projectList'
|
import { projectCounts, projectsInView } from '../lib/projectList'
|
||||||
@@ -116,8 +117,15 @@ export default function ProjectsPage() {
|
|||||||
const [editError, setEditError] = useState('')
|
const [editError, setEditError] = useState('')
|
||||||
const [archiveBusy, setArchiveBusy] = useState(false)
|
const [archiveBusy, setArchiveBusy] = useState(false)
|
||||||
|
|
||||||
|
// Erst die bekannte Liste zeigen, dann neue/archivierte Gitea-Repos abholen und neu laden -
|
||||||
|
// sonst fehlte ein frisch angelegtes Repo hier bis zum naechsten 10-Minuten-Lauf.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let cancelled = false
|
||||||
fetchAllProjects()
|
fetchAllProjects()
|
||||||
|
importNewReposThrottled()
|
||||||
|
.then((r) => { if (r && !cancelled) fetchAllProjects() })
|
||||||
|
.catch(() => { /* Import-Fehler zeigt die Ticketkarte; die Liste bleibt wie sie ist */ })
|
||||||
|
return () => { cancelled = true }
|
||||||
}, [fetchAllProjects])
|
}, [fetchAllProjects])
|
||||||
|
|
||||||
const customerMap = useMemo(() => {
|
const customerMap = useMemo(() => {
|
||||||
|
|||||||
@@ -24,8 +24,11 @@ test('Repo-Import hoechstens einmal pro Minute, laufender Abgleich wird geteilt'
|
|||||||
assert.equal(runs, 2)
|
assert.equal(runs, 2)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Fehler wird weitergereicht, der naechste Versuch wartet trotzdem die Minute ab', async () => {
|
test('Fehler wird weitergereicht, danach darf sofort neu versucht werden', async () => {
|
||||||
const throttled = throttleRuns(async () => { throw new Error('weg') }, 60000)
|
let runs = 0
|
||||||
|
const throttled = throttleRuns(async () => { runs += 1; if (runs === 1) throw new Error('weg'); return { ok: true } }, 60000)
|
||||||
await assert.rejects(throttled(), /weg/)
|
await assert.rejects(throttled(), /weg/)
|
||||||
|
assert.deepEqual(await throttled(), { ok: true })
|
||||||
|
assert.equal(runs, 2)
|
||||||
assert.equal(await throttled(), null)
|
assert.equal(await throttled(), null)
|
||||||
})
|
})
|
||||||
Reference in new issue
Block a user