Nastaveni prezije nasazeni, ukazkova data uz se nevraci

Bez databaze lezi data uvnitr containeru, takze redeploy je smaze a seed je
nasype znovu. Dokud nebude Postgres, resi se to trema vecmi:

Ukazkova data jen se SEED_DEMO=1. Automatizace, tickety a incidenty se uz po
kazdem nasazeni nevraci. Konfigurace se nasypava dal, bez ni je portal
nepouzitelny.

Token webhooku z WEBHOOK_TOKEN_TEST. Driv se pri kazdem nasazeni vygeneroval
novy, takze odesilatel musel prepisovat adresu ve svem kodu. Ted je token
v promenne aplikace: neni v gitu a adresa se nemeni.

Automatizace, ktera na instanci opravdu bezi, je v seedu. Je to provizorium,
ne cil - az data prezijou nasazeni, patri zpatky do dat.

Dal:

- `ticket/upsert` umi vsechna pole ticketu: zakaznik, kanal, odkaz na zdroj,
  priorita, stitky, resitel, skupina a vlastni pole typu jako JSON. Zakaznik
  a kanal se vyplnuji jen pri zalozeni, aby pozdejsi udalost s prazdnym
  jmenem neprepsala, co uz tam je.
- Kroky ve strome jdou sbalit, vychozi je sbaleno. Sbaleny krok ukazuje, co
  ma vyplneno, ne popis operace.
- Seskupovani widgetu podle faze a dva nove widgety: tickety podle stavu
  a podle faze za tento mesic, obojí s proklikem na vyfiltrovany seznam.
- Faze se ukazuje jako stav. Driv byl videt jen nas ctyrprvkovy ciselnik,
  coz u ticketu z cizi aplikace nedava smysl. Zivotni cyklus zustava vedle
  jako drobny text, protoze se z nej pocitaji statistiky.

Overeno 18 kontrolami proti bezicimu serveru.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
JiriUhlir
2026-08-13 17:21:32 +02:00
co-authored by Claude Opus 5
parent 6abac82d5e
commit ef3080d3f6
17 changed files with 1170 additions and 757 deletions
+2
View File
@@ -475,6 +475,8 @@ function ticketFilterFrom(query: Record<string, unknown>, scope: ResolvedScope):
if (typeof typeId === 'string' && typeId !== '') filter.typeId = typeId;
const tag = query.tag;
if (typeof tag === 'string' && tag !== '') filter.tag = tag;
const stage = query.stage;
if (typeof stage === 'string' && stage !== '') filter.stage = stage;
const groupId = query.groupId;
if (typeof groupId === 'string' && groupId !== '') filter.groupId = groupId;
+3 -1
View File
@@ -663,7 +663,9 @@ const sourceSchema = z.discriminatedUnion('kind', [
z.object({
kind: z.literal('ticketCount'),
filter: z.record(z.unknown()).default({}),
groupBy: z.enum(['assignee', 'group', 'status', 'type', 'tag', 'channel']).optional(),
groupBy: z
.enum(['assignee', 'group', 'status', 'stage', 'type', 'tag', 'channel'])
.optional(),
}),
z.object({
kind: z.literal('ticketList'),
+5
View File
@@ -179,6 +179,8 @@ function groupLabel(key: string, groupBy: WidgetGroupBy): string {
return key === '' ? 'Bez typu' : (findTicketType(key)?.name ?? key);
case 'status':
return statusLabels[key] ?? key;
case 'stage':
return key === '' ? 'Bez fáze' : key;
case 'channel':
return channelLabels[key as keyof typeof channelLabels] ?? key;
case 'tag':
@@ -198,6 +200,8 @@ function groupKeys(ticket: Ticket, groupBy: string): string[] {
return [ticket.typeId ?? ''];
case 'status':
return [ticket.status];
case 'stage':
return [ticket.stage ?? ''];
case 'channel':
return [ticket.channel];
case 'tag':
@@ -377,6 +381,7 @@ function ticketsHref(
assignee: 'assignee',
group: 'groupId',
status: 'status',
stage: 'stage',
type: 'typeId',
tag: 'tag',
channel: 'channel',