# Баг-репорт і GitHub issues: шаблони, мітки й майлстоуни

> Як перетворити запит в один рядок на issues, з якими команда може працювати: як написати баг-репорт із кроками відтворення, запити на фічі з критеріями приймання, шаблон issue на GitHub, мітки, виконавці, sub-issues і майлстоун із дедлайном. А ще як ключові слова закриття й згадки пов'язують pull requests з issues і що кожен стан issue каже продакту.

- Курс: [Git і GitHub для продактів](https://productovi.com/uk/courses/git-for-product.md)
- Модуль: GitHub у продуктовій роботі
- Урок 13/15, ~42 хв
- Джерело: https://productovi.com/uk/courses/git-for-product/issues-labels-milestones

## Один рядок від CEO

**Alex Tutor:** Понеділок третього тижня в Котокормі, і робота твоєї продуктової команди над балами лояльності розкидана по чатах, трьох pull requests і одному issue від CEO. Де б її шукала нова людина в команді?

**Sofia:** Скрізь, і половину все одно не знайти. CEO минулого тижня відкрив #147. Назва: «Loyalty». Опис: «Need this before Black Friday!!» І це все.

**Alex Tutor:** Тоді дамо цій роботі нормальний дім. **Issue** (задача) це місце на GitHub для всього, про що команда має знати або що має зробити: баг-репорт, запит на фічу, задача. Зазвичай воно з'являється раніше за будь-який код, а сьогодні отримає ще й шаблон.

**Daniel:** Issue це питання, а pull request одна з відповідей на нього. Pull request можна відкрити й без issue, але за місяць ніхто не згадає, яку проблему він розв'язував.

```text
Issues у catchow/website, понеділок зранку
- [Open] Loyalty #147: Відкрив CEO минулого тижня · без міток і виконавця
- [Open] Test an orange checkout button #135: Мітка idea · відкрив Daniel
- [Closed as completed] Checkout button stays grey after editing cart #131: Звіт про баг від підтримки · закрито через #134
- [Closed as not planned] Show prices in euros #138: Запит на фічу · закрито як незаплановане
```

**Sofia:** Тобто #131 був звітом про баг, #138 запитом на фічу, від якого ми відмовились, а #135 ідеєю, що чекає на тест.

**Alex Tutor:** А #147 це запит на фічу, який поки не каже, що це за фіча. Сьогодні це виправимо. Але спершу навчімося розрізняти, що заслуговує на issue, а що приходить як pull request.

> **Вправа:** Зістав кожну ситуацію з тим, чим вона має стати на GitHub.

## Як написати баг-репорт, який повторить будь-хто

**Alex Tutor:** Issues читають люди, яких не було в кімнаті. Добре issue каже, що не так або чого хочуть, чому це важливо і як ми зрозуміємо, що все готово.

**Alex Tutor:** Для фічі це назва, що описує результат, кілька рядків контексту, **критерії приймання** (acceptance criteria: перевірки, які мають виконуватися, коли роботу завершено) і що не входить у межі задачі.

**Sofia:** А для бага? Сьогоднішній ранковий тест для працівників якраз підкинув ідеальний приклад.

**Daniel:** Кроки, щоб відтворити, очікуваний результат, фактичний результат і де саме: телефон чи комп'ютер, який акаунт, яке замовлення. Якщо я не можу повторити баг у себе, я не можу його виправити.

**Alex Tutor:** Спершу контекст. Після того як сьогодні зранку змерджили #156, Daniel увімкнув `loyalty-points` лише для працівників. Клієнти досі нічого не бачать.

**Sofia:** Замовлення на 600 грн із промокодом на 100 грн, тобто оплачено 500. У кабінеті 60 балів. А має бути 50: один бал за кожні 10 оплачених гривень.

> **Issue #159 у тому вигляді, як його подано**
>
> **Title:** Points counted before promo discount
>
> ```markdown
> **Steps to reproduce**
> 1. Log in with a staff account (flag loyalty-points is on for staff).
> 2. Add products for 600 UAH to the cart, apply a 100 UAH promo code.
> 3. Pay 500 UAH by card.
> 4. Open Account → Loyalty points.
>
> **Expected:** 50 points (1 point per 10 UAH paid).
> **Actual:** 60 points, counted from 600 UAH before the discount.
>
> **Where:** desktop, Chrome, staff account, order 58213.
> **Screenshot:** the account page showing 60 points.
> ```
> Issues команда пише англійською, як і коміти. Steps to reproduce: кроки, щоб відтворити. Expected: що мало статися. Actual: що сталося насправді. Where: де саме. Мітки `bug` і `loyalty`, виконавець Daniel.

**Alex Tutor:** Будь-хто повторить це за дві хвилини, а два числа одразу показують розбіжність. Тепер порівняй із тим, що приходить від підтримки в гарячий день.

![Людина йде намальованими на підлозі слідами до прилавка, де поруч лежать дві купки монет помітно різної висоти.](https://productovi.com/courses/git-for-product/inline/issues-labels-milestones-illustration-2.webp)

*Кроки, очікуваний і фактичний результат: проблему видно будь-кому за дві хвилини.*

> **Вправа:** Підтримка створює issue з назвою «Points broken» і описом «customer says points are wrong, pls fix ASAP». Чого бракує Daniel, щоб почати роботу? Познач усе, що підходить.

**Alex Tutor:** У баг-репорту є близнюк: запит на фічу. Ось дві версії того самого запиту від відділу продажів. З якою Daniel міг би почати роботу?

> **Вправа:** Відділ продажів хоче, щоб клієнти могли повторно використати адресу доставки. Дві версії запиту на фічу. З якою команда **може працювати**?

## Шаблони issues на GitHub

**Sofia:** А GitHub може змусити всіх писати issues саме так?

**Daniel:** Може підштовхнути. **Шаблон issue** (issue template) це файл у папці `.github/ISSUE_TEMPLATE/` репозиторію, який заздалегідь заповнює кожне нове issue певного типу заголовками й мітками. Сьогодні зранку я відкрив #160 із двома такими шаблонами. Годину тому Oscar його схвалив, тож вони вже працюють.

**Alex Tutor:** Тепер `New issue` («нове issue») спершу питає, що саме створюємо: «Bug report», «Feature request» чи порожнє issue (станом на 2026 рік). Шаблон баг-репорту починається із заголовків, які ти щойно бачиш, і сам додає мітку `bug`.

**Daniel:** Є ще варіант у вигляді форми: окремі поля замість заголовків, і деякі можна зробити обов'язковими. Наші я зробив простими: заголовки й підказка в один рядок під кожним.

**Sofia:** Тобто шаблон дає заголовки. А заповнити їх добре все одно має той, хто пише.

**Alex Tutor:** Саме так: шаблон може вимагати поле, але не добру відповідь. Спробуй на наступному повідомленні з командного чату.

> **Вправа:** Emma, контент-маркетологиня Котокорму, пише в командному чаті: «Учора замовила на 450 грн з телефона, акаунт працівника, оплата карткою при отриманні. Сьогодні балів досі 0. Скриншот додаю.» Заповни шаблон «Bug report»: назва, кроки, щоб відтворити, очікуваний і фактичний результат, де саме. Познач, що ще треба уточнити.

## Мітки й виконавці на GitHub

**Alex Tutor:** Тепер бокова панель праворуч від issue. У ній ті самі поля, що й у pull requests, і вони відповідають на щоденні питання продакта.

**Alex Tutor:** `Labels` («мітки») кажуть, що це за робота і якої частини продукту стосується: `bug`, `content` і `loyalty`, мітка, яку Daniel створив для програми. Відфільтруй список за `label:loyalty`, і вся програма на одному екрані.

**Sofia:** А `Assignees` («виконавці»)? У #134 це був Daniel, автор.

**Alex Tutor:** В issue це той, хто відповідає за те, щоб справа рухалася, а не той, хто його написав. #159 написано тобою, а виконавець Daniel. Можна призначити кількох людей, але одне ім'я одразу показує, кого питати.

**Daniel:** І міток має бути небагато. Якщо на кожному issue по шість міток, фільтр за будь-якою з них нічого не скаже.

**Alex Tutor:** Ще два поля, `Milestone` і `Development`, сьогодні отримають окремі розділи. А спершу вся панель одним поглядом.

> **Вправа:** Зістав кожне поле в боковій панелі issue з питанням, на яке воно відповідає.

## Sub-issues: батьківське issue і його частини

**Sofia:** Повернімося до #147. Програма завелика для одного issue. Відкривати десять окремих?

**Alex Tutor:** Окремих, так, але пов'язаних. Станом на 2026 рік issue може мати **sub-issues** (підзадачі): звичайні issues, прикріплені до батьківського. Батьківське показує їхній список і скільки з них закрито, наприклад «1 of 4».

![Великий повітряний змій летить високо, до його хвоста прив'язано кілька менших, і мотузку кожного тримає інша людина.](https://productovi.com/courses/git-for-product/inline/issues-labels-milestones-illustration-3.webp)

**Daniel:** Кожне sub-issue це повноцінне issue з власним виконавцем, мітками й станом. Я можу закрити своє, не чіпаючи твого.

**Alex Tutor:** У старіших репозиторіях щось схоже роблять чеклистом в описі: рядки, що починаються з `- [ ]`, показуються як квадратики для галочок. Це працює, але галочки хтось ставить вручну.

**Sofia:** Отже, #147 стає батьківським: мета і що означає «запущено». А sub-issues це шматки, які люди справді беруть у роботу.

> **Вправа:** Sub-issue це лише рядок тексту в описі батьківського issue, тож власного стану, міток і виконавця в нього немає.

## Переписуємо #147

**Alex Tutor:** Час переписати #147. Один рядок від CEO має стати батьківським issue, з яким можна працювати. Залиш його думку про Black Friday і додай усе, що він пропустив.

**Daniel:** З мого боку: #155 і #156 змерджено, а #159 це твій баг. Ще треба зробити баланс у кабінеті й списання балів під час оформлення.

**Sofia:** І #157 від Emma, сторінка програми, досі чернетка. «Запущено» означає, що флаг увімкнено для всіх клієнтів, і це перемикання має бути окремою задачею.

**Alex Tutor:** Тоді напиши батьківське issue, перелічи sub-issues з відповідальними й мітками та виріши, як на них посилатимуться майлстоун і pull requests.

> **Вправа:** Перепиши issue #147 як батьківське й сплануй його sub-issues.

## Майлстоуни на GitHub: мета з датою

**Alex Tutor:** Ось до чого дійшла команда. #147 перейменовано, під ним чотири sub-issues, і є **майлстоун** (milestone): «Loyalty launch», дедлайн 13 листопада.

**Alex Tutor:** Майлстоун об'єднує issues і pull requests під однією метою, з необов'язковим дедлайном і смугою прогресу. Мітки відповідають на «що це за робота», а майлстоун на «заради чого і до коли».

**Daniel:** Я додав туди й #155, #156 і #157. Pull requests теж можуть належати до майлстоуна, а змерджені рахуються як закриті.

```text
Майлстоун «Loyalty launch» · Due by November 13
- [Open] Launch loyalty points before Black Friday #147: Батьківське · закрито 0 з 4 sub-issues · Sofia
- [Merged] Add loyalty points behind a flag #155
- [Merged] Earn points on paid orders #156
- [Draft] Write loyalty programme page #157: Emma
- [Open] Points counted before promo discount #159: bug, loyalty · Daniel
- [Open] Show points balance in the account #162: loyalty · Daniel
- [Open] Spend points at checkout #163: loyalty · Daniel
- [Open] Switch on loyalty points for all customers #164: loyalty · Sofia
```

*GitHub показує «25% complete»: 2 закриті, 6 відкритих.*

**Sofia:** Тобто чверть зроблено?

**Alex Tutor:** Закрито чверть *елементів*. Смуга рахує елементи, а не обсяг роботи, і рахує кожен закритий елемент, хоч би з якої причини його закрили. Відкинута ідея, закрита як незапланована, рухає смугу так само, як завершена робота.

![Смуга прогресу з чотирьох однакових блоків, один заповнено; за порожніми блоками лежать валуни зовсім різного розміру.](https://productovi.com/courses/git-for-product/inline/issues-labels-milestones-illustration-4.webp)

*Читай список під смугою, а не лише частку виконаного.*

**Sofia:** Отже, читаю список під смугою, а не лише відсоток.

> **Вправа:** Що показує смуга прогресу цього майлстоуна?

## Як пов'язати pull request з issue

**Alex Tutor:** Вівторок зранку. Daniel відкриває #165, виправлення твого бага. Ключові слова закриття вже траплялися тобі на #134; сьогодні подивимося на них з боку issue, коли є ще й батьківське.

**Daniel:** Я почав просто з issue. У розділі `Development` («розробка») в #159 є `Create a branch` («створити гілку»). GitHub пропонує назву з номера й назви issue; я змінив її за нашою звичкою на `fix/points-after-discount`.

```text
Граф комітів, від найстарішого: Від issue #159 до main
1. коміт e5f81a3 у main "Hide loyalty menu link when flag is off (#158)", тег v1.4.1
2. коміт b2e6f10 у main "Earn points on paid orders (#156)"
3. коміт 6e0b4d8 у main "Add issue templates (#160)"
4. гілка fix/points-after-discount починається від коміту 6e0b4d8
5. коміт 4f8c2e1 у fix/points-after-discount "Count points from the paid amount"
6. коміт 7a1d9c3: fix/points-after-discount змерджено в main через squash "Count points after discounts (#165)" (виділено)
```

*Гілку створено просто з issue, тож #159 показує її в розділі Development. Коли #165 змерджать через squash, його коміт у main закриє #159.*

**Alex Tutor:** В описі #165 є два посилання, які схожі на вигляд, але поводяться по-різному.

> **Розділ «Why» в #165**
>
> ```markdown
> ## Why
> Fixes #159. Part of #147.
> Points were counted from the order total before promo codes.
> ```
> `Fixes #159`: ключове слово закриття. `Part of #147`: просто згадка.

**Sofia:** `Fixes #159` закриє баг, коли #165 змерджать у `main`. А `Part of #147` лише згадка?

**Alex Tutor:** Так. Просте `#147` створює перехресне посилання: у хронології #147 з'явиться запис, що #165 його згадав, і нічого не закриється. Закриває лише close, fix чи resolve перед номером, і тільки після мерджу в основну гілку.

**Daniel:** Саме тому я ніколи не пишу `Fixes #147` в одному зі шматків. Перший же змерджений шматок закрив би всю програму.

**Alex Tutor:** І ще одна пастка: кожен номер потребує власного ключового слова. «Fixes #159 and #162» закриє лише #159. Треба писати «Fixes #159, fixes #162».

![Fixes #159 у pull request #165 закриває баг після мерджу в main; Part of #147 лише згадує батьківське issue, і воно лишається відкритим.](https://productovi.com/courses/git-for-product/diagrams/m5-l1-closing-keywords-uk.png)

> **Вправа:** Наступний pull request від Daniel, #166, має в описі: «Fixes #162. Related to #163 and #147.» Його схвалюють і зливають у `main` через squash. Які issues закриє GitHub?

**Alex Tutor:** Тепер пастка з кількома номерами. Наступний pull request від Daniel завершує одразу два sub-issues.

> **Вправа:** Pull request #167 від Daniel завершує два sub-issues, #162 (баланс) і #163 (списання під час оформлення), і є ще одною частиною програми лояльності #147. Який опис закриє **обидва sub-issues і нічого більше**, коли його змерджать у `main`?

## Закрито як виконане, а далі?

**Sofia:** Після мерджу в #159 фіолетова іконка: закрито як виконане, автоматично.

**Alex Tutor:** А `Development` зберігає посилання на #165, тож кожен бачить, що саме його закрило і коли. Якщо виправлення не спрацює, issue можна знову відкрити.

**Daniel:** Тест для працівників після обіду пройшов: оплачено 500 грн, 50 балів. Тож у коді на сайті баг виправлено, хоча клієнти балів досі взагалі не бачать.

**Alex Tutor:** Не змішуй ці дві речі у звітах. Стан issue каже, що роботу зроблено; флаг каже, хто її бачить.

> **Вправа:** Підтримка питає: «Клієнт чув про бали лояльності. Баг із балами для клієнтів уже виправлено?» Флаг `loyalty-points` досі увімкнено лише для працівників. Яка відповідь точна?

**Alex Tutor:** Ось такий GitHub з боку issues: чітко описана проблема, відповідальний і кілька міток, батьківське issue з sub-issues, майлстоун із датою і pull requests, які закривають рівно те, що виправляють.

**Sofia:** Майлстоун каже, скільки елементів закрито. Але не каже, хто над чим працює просто зараз.

**Daniel:** Для цього потрібна дошка. Завтра я волів би перетягувати свої картки, а не писати повідомлення про статус.

**Alex Tutor:** Тоді наступний урок: дошка проєкту, яка веде бали лояльності від першого issue до дня, коли їх побачать клієнти.

- Попередній урок: [Релізи на GitHub і версії: теги, SemVer, changelog](https://productovi.com/uk/courses/git-for-product/releases-and-versions.md)
- Наступний урок: [GitHub Projects: дошка проєкту від issue до релізу](https://productovi.com/uk/courses/git-for-product/project-boards.md)

---
Productovi Learning. Безкоштовно, читати можна без реєстрації.
