BugzillaORM — различия между версиями

Материал из YourcmcWiki
Перейти к: навигация, поиск
м (Обновление модели)
 
(не показано 58 промежуточных версий этого же участника)
Строка 1: Строка 1:
Здесь, наверное, будет статья, в которой я постараюсь выразить свои мысли по поводу ORM, обычного взгляда на вещи и необычного взгляда на вещи.
+
[[Категория:Разработка]]
 +
В Bugzilla половина кода в шаблонах, половина на основе полу-ORM’а Bugzilla::Object. Большая часть — говнокод. С одной стороны — ORM бы туда, было бы классно, НО! ORM мало, и нужен не он! Почти все существующие ORM-движки (кроме разве что [https://docs.djangoproject.com/en/dev/topics/db/models/ Django Models]) — это просто объектный интерфейс к базе данных. А нужно некое объектное ядро, которое бы позволяло создавать свои объекты, с полями различных типов, в том числе и ссылающимися на другие объекты, с возможностью приписывания специальных особенностей полям, с общими механизмами хранения истории, рассылки уведомлений, системой прав и с автоматическим базовым CRUD-интерфейсом.
  
А пока что это сборище идей.
+
== Идеи по созданию объектного ядра ==
  
== Типичный взгляд ==
+
=== Объекты в Bugzilla ===
  
Пример: http://rutube.ru/tracks/793494.html - доклад с YAPC::Russia. Автор — Егор Шиповалов, название «Применение ORM в Perl».
+
* баг
 +
* классификация, продукт, компонент
 +
* тег (ключевое слово / keyword), milestone, версия, статус бага, agreement (пример кастомного поля)
 +
* вложение, комментарий, флаг, тип флага
 +
* пользователь, группа
 +
* если таки будет ядро — ещё появляется метакласс сущности, объекты которого описывают типы сущностей
  
Первый же слайд ясно даёт понять проблемы, обычно решаемые авторами объектно-реляционных мапперов в Perl’е:
+
<graph>
 +
digraph G {
 +
  Классификация -> Продукт -> Компонент -> Баг;
 +
  Продукт -> Версия -> Баг;
 +
  Продукт -> Milestone -> Баг;
 +
  Продукт -> Agreement -> Баг;
 +
  Продукт -> Тег;
 +
  Продукт -> "Тип флага" -> Флаг;
 +
  Статус -> Баг;
 +
  Баг -> Коммент;
 +
  Баг -> Вложение;
 +
  Баг -> Флаг;
 +
  Баг -> Тег [dir=both, color=blue];
 +
  Баг [shape=box, style=filled, fillcolor="#ffe0e0"];
 +
  Продукт [shape=box];
 +
}
 +
</graph>
  
* Дублирование кода работы с БД.
+
Причём, понятное дело, если баг принадлежит к некоторому продукту (через компонент), то и его версия, milestone, agreement должны принадлежать к тому же продукту. Сейчас в Bugzilla это реализуется так — для каждой сущности, связанной с багом, в баге есть «поле», а для ограничения значений полей поля делаются «зависимыми» от других полей. Из-за этого там куча костылей и, по сути, повесить зависимость на отличное от поля «продукт» поле невозможно.
* Трудоёмкость адаптации кода под изменения схемы и различные СУБД.
+
* Необходимость знания устройства и работы с БД всеми программистами команды.
+
  
Так вот, все эти причины — «[[lurkmore:4.2|4.2 (чёрта с два)]]». Бесплатная подсказка, тривиальный путь решения всех трёх проблем: код работы с БД для каждого «компонента» программы выносится в отдельный класс и разбивается на отдельные процедуры. При грамотном разбиении кода работы с БД на отдельные процедуры он получается достаточно компактным, а если не компактным, то по меньшей мере простым и очевидным. Весь код работы с БД таким образом концентрируется в одном месте, и переписывать под различные СУБД его становится гораздо проще. И уж конечно, имея код работы с БД в отдельном классе, неспециалисты по БД могут и не заниматься работой с ней. Между прочим, этот подход использует [http://www.skype.com/ Skype], за тем исключением, что функционал в процедуры они оборачивали не на стороне клиента БД, а внутри самой СУБД ([http://www.postgresql.org/ PostgreSQL]) — об этом Иван Золотухин рассказывал в статье «[http://postgresmen.ru/articles/view/25 Масштабирование PostgreSQL: Готовые решения от Skype]».
+
=== Минусы текущей реализации ===
  
Ещё одна почти бессмысленная для веб-приложений возможность: итераторы. Даже {{CPAN|DBR}} её, кстати, не лишён. То есть она осмысленная — в GUI-приложениях. Но не в веб-приложениях, где, во-первых, сам DBI никогда не держит открытых курсоров, во-вторых, это часто не даёт делать СУБД, а в-третьих, это совершенно не нужно, так как время исполнения запроса должно быть как можно меньше, а следующий запрос, вероятно, попадёт в другой поток — а может, и вообще в другой процесс.
+
Чем плоха текущая багзильная реализация этого «добра»?
  
Небольшой обзор существующих модулей ORM:
+
Очень просто: '''почти ни хрена не настраивается''' - нельзя ни добавить свои возможности, ни отрубить ненужные встроенные. Ибо всё жёстко и криво (хардкод). А ещё:
 +
* История хранится только для багов, но не для остальных объектов.
 +
* Шаблоны переусложнены.
 +
* Нет "объектов", есть «значения полей», соответсвенно, им не добавишь атрибутов и не навесишь логики.
  
* {{CPAN|SPOPS}} — ужас кривой, тормозной, неиллюзорный, заброшенный в 2004 году.
+
* Значения кастом полей в таблице багов хранятся не ссылками по ID, а по именам, что создаёт геморрой для зависимых полей, так как чтобы точно идентифицировать значение, приходится брать ещё и значение поля, от которого оно зависит.
* {{CPAN|DBIx::Class}} — лидер области. Тяжеловесный, имеет кучу возможностей и вторую кучу — зависимостей. Например, использует {{CPAN|mro}} 'c3'. Частично совместим с Class::DBI. Но вы как хотите, а «БД-независимый» язык запросов, основанный на хешах — это ад. Что-то типа Vsem::DB::Query из [http://www.vsem.ru vsem.ru], кстати.
+
* Для выпадающих списков есть как «контроль значений», так и «контроль видимости поля», что в корне '''''неверно'''''! Типа поле скрыто, но при этом есть варианты, которые можно выбрать? Бред! Или наоборот — поле доступно, но из вариантов — только пустое значение.
* {{CPAN|Class::DBI}} — говорят, легковесный. Наиболее старый — родился в 2001 году.
+
* По факту, ни от чего, кроме продукта, зависимым значение не сделаешь.
* {{CPAN|ORM}} — сразу настораживает фраза «No SQL queries needed». '''Не бывает''' такого.
+
* Код полей не генерится автоматически, а ручками вписываются в шаблоны.
* {{CPAN|DBR}} — весьма похож на Funq. Но явно попроще. Наиболее, кажется, «альтернативный».
+
* На всё это навёрнута туча неструктурированного кода, определяющего поведение «встроенных» полей.
* {{CPAN|Rose::DB::Object}} — ещё одна вариация на тему Class::DBI.
+
* {{CPAN|Class::AutoDB}}.
+
  
== Реальная проблема ==
+
=== Проект супер-объектного-ядра ===
  
Реальная проблема, ради решения которой задуман ORM — сложность, возникающая при попытке организовать сохранение в БД ''большого числа'' объектов '''разных типов''' со '''сложными отношениями''' друг между другом.
+
Что предлагается сделать:
 +
* Есть объекты. У объектов есть поля (разных типов). В том числе, есть типы «multi-select» и «single-select», ссылающиеся на другие объекты.
 +
* Зависимости полей могут прописываться дополнительно, в виде ограничений вида: «баг.компонент.продукт == баг.продукт». Могут и не прописываться. Теоретически, могут прописываться несколько ограничений на одно поле, например, чтобы какой-нибудь атрибут зависел и от продукта, и от, скажем, операционной системы. Такое устройство наиболее гибкое и в частности позволяет багу иметь несколько полей, ссылающихся на один тип объектов.
 +
* Поля могут скрываться в зависимости от значений других полей. Селект-поля скрываются, если в них по зависимостям нечего выбирать. На поля остальных типов можно вешать «настройку видимости», подобную текущей багзильной реализации — просто тупо «поле = одно из значений…».
 +
* HTML-код полей должен генерироваться автоматически. Также желательна возможность задавать группировку полей в интерфейсе — например, опционально раскрываемые «Advanced Fields» (только общим fieldset’ом, а не как сейчас — просто скрытие полей в разных местах формы).
 +
* На поля можно назначить дополнительные обработчики, задающие более хитрое поведение. Сами обработчики пишутся в коде, по возможности — максимально абстрагированно от конкретного поля. Назначаться они при этом должны не из кода, а из интерфейса конфигурации модели. На этих дополнительных обработчиках должно работать всё! Примеры:
 +
** Платформа, ОС: функции угадывания значения по умолчанию на основе заголовков запроса.
 +
** Статус: есть ограничения переходов из состояния в состояние (workflow). Возможно, его нужно попытаться вынести в базовые настройки модели.
 +
** CC: специальное поведение при клонировании, смене Assignee и QA (старые добавляются в CC), специальное «значение по умолчанию», зависящее от компонента. Возможно, зависимые значения по умолчанию стоит как-то протащить в общее ядро, но это чуть более тонкий момент, чем всё остальное.
 +
** Keywords/теги: автосоздание новых значений.
 +
* История хранится централизованно для всех объектов.
 +
* Общий механизм рассылки изменений по почте для всех объектов каким-либо образом «связанным» пользователям. Настройки уведомлений на уровнях: событие (изменено значение поля, добавлена сущность) и отношение (собственно связь).
 +
* Поиск: адаптировать сильно прокачанный движок поиска из [http://wiki.4intra.net/Bugzilla4Intranet Bugzilla4Intranet]. Адаптировать долго не придётся, потому что с точки зрения read-only все типы полей останутся, по сути, те же самые. С другой стороны, его нужно обобщить, так что кое-что сделать всё-таки будет нужно. Но зато в итоге получится супер-инструмент — система выборок, которая отлично хавает большие базы, сложные ''и произвольные'' структуры запросов и не давится :) (доказано Bugzilla4Intranet)
 +
* Права доступа: Нужна опять-таки общая схема для всех объектов, с одной стороны, простая для вычисления системой, а с другой стороны, дающая достаточную гибкость разных настроек. По всей видимости, нужны с одной стороны опять-таки «связанные» пользователи, а с другой стороны — роли. Причём, пользователи могут быть «связанные» через какое-то поле — например, человек может быть связан с багом, потому что прописан как Global Watcher для продукта этого бага. Роли могут набираться из действий над полями различных объектов и права на просмотр объектов. Возможно, нужно давать возможность разграничивать доступ на просмотр к разным полям — например, в Bugzilla только группа time-tracker’ов может посмотреть информацию о рабочем времени.
  
Даже [[lib:Bugzilla|Bugzilla]] имеет своё подобие ORM’а. И причина именно в этом — нужно управлять многими типами объектов. У бага много атрибутов, для каждого атрибута заведён отдельный тип «поле», а некоторые из этих типов одновременно являются самостоятельными объектами (продукты, компоненты). Отличительная черта багзильного ORM’а, по-моему, заключается в том, что он ориентирован на поля объектов и на универсальность доступа к значениям этих полей, но при этом не использует термина «связь» (один-к-одному, один-ко-многим и т. п.).
+
Таким образом наш трекер превратится, по сути, в модульное приложение, построенное вокруг очень прокачанного автоинтерфейса полей и объектов. Естественно, это уже не Bugzilla ни разу, но зато чем-то похоже на [http://roundup.sourceforge.net/ Roundup] — «конструктор трекеров». Однако и с ним различий много — roundup не умеет зависимых селект-полей, модель задаётся в коде, права доступа слабые, поиск слабый.
  
В целом этот ORM, конечно, кривоват и простоват. Но зато минимален и имеет одну очень правильную черту — он никогда не тратит силы на выполнение тех операций, о которых его ''пока ещё'' никто не просил. Создание экземпляра класса выглядит очень просто, в одну строчку — это просто bless хеша-строчки, полученной из БД. Никаких дополнительных манёвров при этом не происходит. А все методы, возвращающие те или иные данные, создают, извлекают или вычисляют эти данные только в момент первого вызова.
+
=== Обновление модели ===
  
Для ORM’а «ленивая» идеология, с моей точки зрения, особенно важна, так как если на каждый чих будут извлекаться и создаваться кучи объектов — связанных и т. п. — даже тогда, когда это вообще не нужно, производительность, конечно, пострадает.
+
Обновление базы багзильское (тупая последовательность операций):
 +
* (+) Очевидный порядок обновлений, нет проблем с их зависимостями друг от друга (новые просто дописываются в конец, порядок всегда правильный)
 +
* (-) Не проверяется, корректна ли схема БД после обновлений
 +
* (-) Обновления задаются именно для SQL БД, а не для метамодели
 +
* (-) Не очень красивая портянка в функции обновления БД
  
«Правильный» ORM должен делиться на две части:
+
Обновление базы наше:
 +
* AddType -> RenameFields -> AddFields -> ChangeFields -> DropFields
 +
* ChangeFields с помощью функций обновления
 +
* (???) Самый интересный вопрос - как сделать функции обновления не на SQL, в условиях когда модель в неконсистентном и вообще не знает, в каком состоянии?
 +
* После каждого шага запуск хука
 +
* Переименования задаются декларативно
 +
* Возможность Dry Run, просмотра и проверки последовательности обновлений
 +
* Зависимости обновлений в рамках каждого шага можно задать декларативно (хотя ситуация редкая)
 +
*: А может, просто стоит задавать их в ассоциативном массиве по порядку
 +
* Ещё может быть, что обновление одного типа зависит от обновления другого (причём это уже более частая ситуация)
  
# Обеспечивающий язык запросов и
+
=== Модель ===
# Схема объектного взаимодействия.
+
  
При этом язык запросов ''должен'' быть полным относительно возможностей нижележащего SQL. Аргументация простая: если полноты не будет, некоторый процент ситуаций потребует перехода на «ручное» выполнение собственных SQL-запросов, что обычно означает потерю всех преимуществ языка. Остаётся, конечно, вопрос процента таких ситуаций — если их очень мало, то и проблем, наверное, также будет очень мало.
+
Получается, что ядро модели состоит из следующих таблиц:
 +
* Типы объектов
 +
** Название типа
 +
** Дополнительное поведение типа
 +
* Поля
 +
** Ссылка на объект
 +
** ID поля
 +
** Название поля
 +
** Тип поля
 +
** Nullable? Т.е. разрешено ли пустое значение
 +
** Поле этого же объекта, контролирующее видимость данного (для не-select полей)
 +
** Связи поля (для select-полей)
 +
** Дополнительное поведение поля
 +
* Контроль видимости полей
 +
** Ссылка на поле
 +
** ID объекта, для которого оно видно
 +
* Транзакции (группы изменений)
 +
** Момент времени
 +
** Изменивший пользователь
 +
* История изменений
 +
** Ссылка на транзакцию
 +
** Тип объекта
 +
** Ссылка на объект
 +
** Ссылка на поле (кроме добавления объекта)
 +
** Старое значение
 +
** Новое значение
 +
* Один неудаляемый тип — пользователи (их поля, тем не менее, можно менять)
 +
* Связи объектов с пользователями
 +
** ID связи
 +
** Название связи
 +
** Ссылка на объект
 +
** Цепочка свойств, заканчивающаяся объектом «пользователь»
 +
* Пользовательские настройки оповещений
 +
** Ссылка на пользователя (либо пустая для настроек по умолчанию)
 +
** Ссылка на связь
 +
** Тип события: добавление/удаление/изменение
 +
** Ссылка на поле объекта (может быть пустая)
 +
* Роли (или группы, что примерно то же самое)
 +
** Включаемые роли
 +
** Включаемые разрешения
 +
* Разрешения (из которых состоят роли)
 +
** Ссылка на связь
 +
** Тип разрешения: просмотр/создание/удаление/изменение
 +
** Ссылка на поле объекта (пустая = весь объект)
 +
** Ссылка на значение поля (пустая = любое)
  
Реально же многие (если не большинство) ORM-движков пытается скрестить ужа и ежа, то есть обе части, в одну.
+
=== Типы полей ===
  
== Багзилла ==
+
<tab sep="bar" class="wikitable" head="top">
 +
Тип | Параметры | Представления
 +
Строка | Длина | Input, textarea, rich edit
 +
Логический | | Флажок
 +
Числовой | Точность (M.N) | Input
 +
Дата и/или время | Дата? Время? | Поле со всплывающим календарём, просто календарь, просто поле
 +
Файл | | Поле загрузки файла
 +
Single-select | Тип объекта | Радиобатон, HTML-селект
 +
Multi-select | Тип объекта | HTML-мультиселект, флажки, комбо-бокс
 +
</tab>
  
Надо так:
+
== Текущее состояние ==
  
== FunMap ==
+
Базовые поля (то, чего не быть логически не может):
 +
* Объект, баг, пользователь, группа, комментарий к багу.
 +
* Продукт имеет специальный смысл — разграничение прав. Поэтому он тоже обязателен.
  
# Обеспечивающий язык запросов — [[Funq]].
+
У каждого поля есть:
# Схема объектного взаимодействия — ниже.
+
* Тип
 +
* Значение по умолчанию
 +
* Вид показа в интерфейсе (по умолчанию?)
 +
* Копируется ли значение атрибута при клонировании
 +
* Показывается ли атрибут в форме создания
 +
* Ссылка на контролирующий атрибут того же объекта, что и этот, и на его значения
 +
*: Если выбрано, означает, что атрибут показывается, только если другой атрибут той же сущности, которой принадлежит этот, имеет одно из заданных значений
  
 +
Типы атрибутов:
 +
* {{ok}} Строка
 +
* Boolean
 +
* {{ok}} Десятичное число
 +
* Дата
 +
* Время
 +
* {{ok}} Дата+время
 +
* Файл (вложение)
 +
* Single-Select &rarr; ссылка на сущность (то есть «многие к 1»)
 +
* Multi-Select &rarr; ссылка на несколько сущностей одного типа (то есть «многие ко многим»)
 +
** Два способа показа — поле со списком (комбобокс), или мультиселект
 +
* Список тегов, с автодобавлением значений
  
 +
== Существующие поля ==
  
[[Категория:Разработка]]
+
=== Поля багов ===
 +
 
 +
Поля багов перечислены ниже. Жирное «да» в колонке «можно отключить» означает, что отключать можно уже сейчас (скорее всего, через параметры типа usevotes и т. п.). Нежирное «да» в колонках «можно отключить» и «можно менять тип» означают, что чисто теоретически логика работы Bugzilla это позволяет.
 +
<tab sep=bar class=simpletable head=topleft>
 +
поле | тип | можно отключить? | можно менять тип? | примечания
 +
short_desc | строка | нет | нет |
 +
classification | single-select | '''да''' | нет |
 +
product | single-select | да | нет | контролируется classification (или никем), у поля есть привязка к правам пользователя
 +
component | single-select | да | нет | контролируется product
 +
version | single-select | да | да | контролируется product, если тип = single-select. значение по умолчанию контролируется component
 +
rep_platform | single-select | '''да''' | да | специальная функция «угадывания» дефолтного значения
 +
bug_file_loc | строка | да | да |
 +
op_sys | single-select | '''да''' | да | специальная функция «угадывания» дефолтного значения
 +
bug_status | single-select | нет | нет | есть функция валидации (Bug Status Workflow)
 +
resolution | single-select | да | нет | у атрибута есть контролирующая видимость сущность (показывается только при bug_status.closed=1)
 +
status_whiteboard | строка | да | да |
 +
keywords | multi-select | да | да | показ в виде списка
 +
bug_severity | single-select | да | нет |
 +
priority | single-select | да | да | есть желание сделать decimal
 +
assigned_to | single-select | нет | нет | ссылка на пользователя. значение по умолчанию контролируется component
 +
reporter | single-select | нет | нет | ссылка на пользователя
 +
qa_contact | single-select | '''да''' | нет | ссылка на пользователя. значение по умолчанию контролируется component
 +
votes | decimal(,0) | '''да''' | нет |
 +
cc | multi-select | да | нет | ссылка на пользователей. значение по умолчанию контролируется component
 +
dependson | multi-select | да | нет | ссылка на баги. показ в виде списка
 +
blocked | multi-select | да | нет | ссылка на баги. показ в виде списка
 +
target_milestone | single-select | да | да | контролируется product, если тип = single-select
 +
see_also | multi-select | да | да | ссылка на баги. показ в виде списка (багов), если тип = single-select
 +
alias | строка | '''да''' | да |
 +
reporter_accessible | boolean | нет | нет |
 +
cclist_accessible | boolean | нет | нет |
 +
estimated_time | время | '''да''' | нет |
 +
remaining_time | время | '''да''' | нет |
 +
deadline | дата | '''да''' | нет |
 +
creation_ts | дата+время | нет | нет |
 +
delta_ts | дата+время | нет | нет |
 +
cf_agreement | single-select | '''да''' | да | контролируется product
 +
*** нет в fielddescs *** |
 +
lastdiffed | дата+время | нет | нет | скрыто в интерфейсе
 +
attachments | multi-select | нет | нет | 1 ко многим
 +
longdescs | multi-select | нет | нет | 1 ко многим
 +
flags | multi-select | да | нет | 1 ко многим
 +
</tab>
 +
 
 +
Примечания:
 +
* Все поля-ссылки на пользователей показываются в виде строки либо в виде combo-box’а, и у них есть функция валидации — она угадывает юзеров по некорректным именам.
 +
 
 +
==== Логически минимальный набор полей ====
 +
 
 +
Совершенно точно, никогда и ни при каких условиях у бага не могут отсутствовать поля:
 +
* ID
 +
* Заголовок
 +
* История => reporter, время создания (creation_ts), время изменения (delta_ts), время оповещения по почте (lastdiffed)
 +
* Комментарии
 +
Без этого никакой «баг» смысла не имеет ни в одном баг-трекере.
 +
 
 +
==== Устаревшие поля ====
 +
 
 +
Устаревшие поля / поля, которые ХЗ зачем нужны в таблице полей багов:
 +
* assignee_accessible
 +
* qacontact_accessible
 +
* longdesc
 +
* commenter
 +
* longdescs.isprivate
 +
* content
 +
* bug_group
 +
* flagtypes.name
 +
* requestees.login_name
 +
* setters.login_name
 +
 
 +
==== Вычисляемые поля ====
 +
 
 +
Вычисляемые поля багов:
 +
<tab sep=bar class=simpletable head=left>
 +
work_time | Сумма work_time от связанных longdescs
 +
percentage_complete | (Сумма work_time от dependson)/(Сумма estimated_time от dependson)
 +
owner_idle_time | Текущая дата минус MAX(дата последнего коммента от Assignee, дата последней активности от Assignee)
 +
days_elapsed | Текущая дата минус delta_ts
 +
everconfirmed | Менялся ли статус хоть раз на != UNCONFIRMED
 +
</tab>
 +
 
 +
=== Поля вложений ===
 +
 
 +
<tab sep=bar class=simpletable head=left>
 +
submitter | single-select, показ в виде select’а или строки, есть функция валидации (угадывания юзера по некорректному имени)
 +
description | строка
 +
filename | строка
 +
mimetype | строка
 +
ispatch | boolean
 +
isobsolete | boolean
 +
isprivate | boolean
 +
isurl | boolean
 +
thedata | потенциально строка, а вообще-то обычно NULL, так как данные хранятся в локальных файлах
 +
</tab>
 +
 
 +
=== Поля компонентов ===
 +
 
 +
<tab sep=bar class=simpletable head=top>
 +
поле | тип | можно отключить? | можно менять тип? | примечания
 +
name | строка | нет | нет |
 +
initialowner | single-select | да | нет | ссылка на пользователя
 +
initialqacontact| single-select | да | нет | ссылка на пользователя
 +
initialcc | multi-select | да | нет | ссылка на пользователей
 +
default_version | single-select | да | нет | ссылка на версию
 +
description | строка | да | да |
 +
product_id | single-select | нет | нет |
 +
wiki_url | строка | да | нет |
 +
is_active | boolean | да | нет |
 +
</tab>
 +
 
 +
== Из чего состоит багзилла? ==
 +
 
 +
=== С внешней точки зрения ===
 +
 
 +
Крупные блоки функционала в Bugzilla с точки зрения пользователя:
 +
 
 +
* Создание, просмотр, изменение багов, история по багу
 +
** Форматирование комментариев
 +
* Создание, просмотр, изменение вложений
 +
* Поиск багов, форма поиска
 +
* Графики одни, графики другие, Summarize Time
 +
* Исходящая почта
 +
* Входящая почта
 +
* Запросы флагов
 +
* Напоминания (whining)
 +
* Регистрация, изменение, раздача групп пользователям
 +
* Пользовательские настройки
 +
* ''XML-импорт (в полудохлом виде)''
 +
* Web-сервисы (XML-RPC, JSON-RPC)
 +
* Sanity Check
 +
* Графы и деревья зависимостей
 +
* Голосование за баги
 +
 
 +
==== Наши крупные фичи ====
 +
 
 +
* SCRUM-карточки
 +
* RSS-лента активности
 +
* XML-Simple Web-сервисы
 +
* Проверки корректности
 +
* Excel-импорт
 +
* Информер
 +
* Глобальная авторизация
 +
* Today Worktime, Super Worktime
 +
 
 +
Плюс вагон и маленькая тележка мелочи.
 +
 
 +
=== Страница создания бага (enter_bug.cgi) ===
 +
 
 +
Текущая логика страницы создания бага:
 +
 
 +
* выбор classification, если она включена
 +
* classification выбрана &rarr; выбор product, если он не вообще один
 +
* тот же выбор продукта/классификации при клонировании багов
 +
* продукт выбран &rarr; форма создания бага
 +
* показ корректных списков возможных значений полей:
 +
** типы флагов в зависимости от компонента
 +
** cf_agreement в зависимости от продукта
 +
** списки пользователей, относящихся к багу в combo-box’ы
 +
** опциональный запрет на ввод приоритета на основе конфигурации (letsubmitterchoosepriority)
 +
** список флажков — ограничителей доступа группами
 +
* значения полей по умолчанию:
 +
** которые совсем по умолчанию
 +
** угадывание op_sys и rep_platform на основе заголовков запроса
 +
** версия, qa_contact, assigned_to, cc по умолчанию для компонента
 +
** хитрая логика для изменения списков cc при выборе компонентов
 +
** assigned_to=ты при выборе статуса ASSIGNED
 +
** показ поля resolution при выборе закрытого статуса
 +
** версия из cookies
 +
** загруженные из шаблона ввода бага
 +
** загруженные из клонированного бага
 +
*** ссылка на старый аттач в описании нового клонированного бага
 +
*** хитрая логика для CC при клонировании багов
 +
* напоминания о вводе времени
 +
* предпросмотр комментариев
 +
* добавление ссылки на старый аттач для клонированного из коммента бага
 +
* переключатель Show Expert Fields
 +
* Submit по Ctrl-Enter
 +
 
 +
=== Обработчик создания бага (post_bug.cgi) ===
 +
 
 +
* Редирект на enter_bug.cgi если форма не заполнена
 +
* Проверка, не был ли уже использован этот token для постановки другого бага?
 +
* Вывод страницы с URL-шаблоном постановки бага, если попросили
 +
* Заполнение описания по шаблону, если использовался спецвид формы (например create_guided)
 +
* Отправка куки VERSION
 +
* Начало/конец транзакции в БД
 +
* Постановка собственно бага (Bug::create)
 +
* Постановка вложения сразу при создании бага
 +
* Постановка флагов на вложение
 +
* Постановка флагов на баг
 +
* Рассылка почты по багу, по его зависимостям
 +
* Вывод сообщения о том, что баг поставлен, о том, что CC-список обрезан по группе
 +
 
 +
=== Обработчик изменения бага (process_bug.cgi) ===
 +
 
 +
* Начало/конец транзакции в БД
 +
* SELECT FOR UPDATE багов
 +
* Удаление значений полей, равных dontchange (используется в групповом редактировании)
 +
* Угадывание юзеров по части логина/имени
 +
* Проверка коллизии по delta_ts, вывод только действительно изменённых полей в форму подтверждения
 +
* Проверка token формы
 +
* Загрузка следующего бага из списка ДО обновления текущего (O_O)
 +
* Проверка прав редактирования по всем багам
 +
* Установка значения продукта до всех остальных полей
 +
* Установка новых групп до остальных изменений, когда включён strict_isolation
 +
* Установка/изменение флагов
 +
* Установка зависимостей бага
 +
* Установка ключевых слов
 +
* Установка остальных полей
 +
* Установка некоторых значений только для обновлений отдельных багов (alias, cclist_accessible, reporter_accessible, isprivate на комментах)
 +
* БОльшая часть логики обновления поля CC
 +
* Вызов функции проверки strict_isolation
 +
* Перемещение багов (MOVE) — полуживое, хз как работает
 +
* И после всего этого — ещё изменения, status, resolution, dup_id
 +
* Теперь всё это ещё незакоммичено, вызов реальных обновлений из Bug.pm
 +
* Отправка почты по зависимостям, если статус поменялся с открытого на закрытый
 +
* Обрезание CC-списка по группе (наше)
 +
* Сообщение, если очищено remaining_time
 +
* Удаление голосов за баг, если переместили в другой продукт, и проверка голосо-подтвеждённости бага
 +
* Отправка почты CC, Assignee, Reporter’у, QA, старым Assignee, QA и CC
 +
* Отправка почты по багу, дубликатом которого был помечен данный
 +
* Добавление нескольких вложений (наше)
 +
* Отправка почты по флагам, через отдельный механизм сбоку
 +
* Редирект (наше) или показ следующего/того же/никакого бага
 +
* Не показывает ничего, если USAGE_MODE_EMAIL
 +
 
 +
=== Список/поиск багов (buglist.cgi) ===
 +
 
 +
* SuperWorkTime (наше доработко)
 +
* Редирект на форму поиска для пустого запроса
 +
* Редирект на форму поиска с добавленными пустыми полями, если на форме поиска жмут Add/Remove поле без JS
 +
* Редирект с POST’енного запроса на GET
 +
* Быстрый поиск
 +
* Посыл на хрен, если content пуст при включённом параметре specific_search_allow_empty_words
 +
* Обратная совместимость: format=rdf -> ctype=rdf, ctype=rss -> ctype=atom
 +
* ctype=js слать в хрен
 +
* Поддержка server-push
 +
* regetlastlist — открытие последнего просмотренного списка багов (берётся из куки)
 +
* Удаление колонки relevance из списка колонок, если юзер не просит полнотекстовый поиск
 +
* В buglist.cgi почему-то тусуются функции InsertNamedQuery, LookupSeries, GetQuip, GetGroups
 +
* Генерация имени файла, если попросили список не в html-формате
 +
* Выполнение запросов поиска
 +
* Сохранение и удаление сохранённых запросов из БД
 +
* Запуск series
 +
* Если сохранённый запрос не говорил нам свой формат, решаем что advanced
 +
* Вкуривание списка колонок (длинное, тварь)
 +
* Определение порядка сортировки
 +
* Подсчёт сумм значений полей таймтрекинга
 +
* Проверка доступа к багам и установка значения в implied или manual (х.з зачем нужно)
 +
* Массовое редактирование багов, в частности сборка пересечений доступных значений полей
 +
* Кодировка CSV (наше доработко)
 +
 
 +
=== Система прав ===
 +
 
 +
Какие в Bugzilla есть права / ограничения доступа?
 +
 
 +
* Права доступа к продуктам
 +
** MANDATORY / SHOWN / DEFAULT / NA
 +
** Entry
 +
** Canedit
 +
** Editcomponents
 +
** Canconfirm
 +
** Editbugs
 +
* Private комментарии — видны только инсайдер-группе
 +
* Информация о таймтрекинге — видна только группе, списывающей время
 +
* reporter_accessible, cclist_accessible на отдельных багах
 +
* Опциональные группы на отдельных багах
 +
* Права на правку отдельных групп
 +
* Права editclassifications, editcomponents, editfields, editkeywords, editusers
 +
 
 +
=== Настройки оповещений о багах ===
 +
 
 +
Отношения:
 +
* Assignee, QA, Reporter, CC
 +
* Requestee или Setter флага (реально в Bugzilla прикручено сильно сбоку)
 +
* По идее — также создатель аттачмента, коммента (если разрешать править)
 +
* Watcher указанного выше
 +
* Global Watcher
 +
 
 +
События:
 +
* Добавлен/удалён из указанного выше
 +
* Изменено одно из полей бага (м.б отдельные настройки, м.б вместе)
 +
* Баг / блокирующий баг меняет статус с закрытого на открытый или обратно
 +
* «Но кроме» UNCONFIRMED
 +
* «Но кроме» своих изменений
 +
 
 +
== Что не так? ==
 +
 
 +
Что в Bugzilla не так? Что нужно поменять, чтобы она перестала быть говном?
 +
 
 +
Да, я знаю, что почти всё, но дело не в этом — дело в том, что создавать систему «от противного» — есть хороший тон, ведущий к прогрессу.
 +
 
 +
=== Ядро ===
 +
 
 +
Самое жирное «не так» в Bugzilla — это слабость ядра. Если бы ядро было прокачанным, как в начале статьи, оно бы заменило:
 +
 
 +
* Custom Fields, то есть, управление полями багов.
 +
* Отключение стандартных полей типа OS, Hardware.
 +
* Добавление спецполей в продукты, компоненты и т. д. — сейчас это делается в коде.
 +
* Версионирование всех объектов. История бы хранилась унифицированно. Не было бы отдельно таблиц bugs_activity и longdescs, соответственно не было бы и тормозов при проверке «Only bugs changed between…» либо комментарии бы дублировались в bugs_activity (логичнее)
 +
* Отправка почты, которая сейчас отправляется через 2 разных места.
 +
* Простые интерфейсы типа CRUD (Create/Read/Update/Delete), сейчас созданные непонятно каким копипастом. В основном имеется ввиду админка.
 +
* Система прав стала бы и проще (по сравнению с хрен-пойми-системой Mandatory/Default/Shown/NA), и имела бы больше возможностей.
 +
* Валидаторы и подсказки значений.
 +
* Корректная валидация зависимостей полей друг от друга.
 +
* Часть SQL-запросов, написанных ручками в коде.
 +
* Вся логика постановки/обновления багов из process_bug и post_bug, дублированная сейчас в обработке входящих e-mailов и Excel-импорте
 +
* Даже Excel- и XML-импорт, причём импортировать можно было бы вообще всё что угодно.
 +
 
 +
Появились бы дополнительные возможности:
 +
 
 +
* Создание новых объектов типа SCRUM карточек.
 +
* Изменение типов стандартных полей типа целочисленных приоритетов.
 +
* Можно было бы прикрутить статистику по любым объектам, причём с Time Machine, то есть просмотром статистики за любой прошедший момент времени. :)
 +
 
 +
=== Внешности ===
 +
 
 +
* Большая часть интерфейсов нуждается в полной переделке. Например:
 +
** На форме бага «Show Advanced Fields» должно быть сделано отдельной группкой полей (fieldset), а не скрытием полей прямо посреди формы в разных местах (пугает).
 +
** Страница настройки прав продукта — мегастрёмная. Таблица с группами там на хрен не нужна никому вообще.
 +
** Частично относится к ядру, но — интерфейс редактирования «контроля значений» кастом-полей это сейчас полная жесть — например, нельзя посмотреть список значений кастом поля для конкретного продукта (от которого зависят наборы значений).
 +
* Историю изменений бага показывать вперемешку с комментариями к багу (а-ля Trac). Туда же можно заинтегрировать и замешать коммиты из системы контроля версий.
 +
* Добавить ленту обновлений по багам, поиск «последних багов», что-то типа форум-функциональности.
 +
* Возможно, добавить Grid View с возможностью массовых обновлений в поиске.
 +
* Отрефакторить кучу действий, связанных с поиском, расположенных под списком багов.
 +
* Keywords переделать в «теги», то есть, сделать, чтобы они сами заводились новые.
 +
* <s>Заменить YUI на jQuery</s> в Bugzilla4Intranet уже, форму поиска сгруппировать (но не так, как в 4.0), сделать почти все фильтры добавляемыми, добавляемыми через JS, чтобы не перезагружалась страница каждый раз
 +
* Документацию «насытить» в вики, POD-документацию по коду туда же.
 +
 
 +
=== Кишки, кроме ядра ===
 +
 
 +
* Добавить глобальный объект статуса, в который сохранять почту, которую нужно отправить, и сообщения, которые сейчас руками сохраняются в сессию.
 +
* Использовать не CGI, а хеши параметров запроса, это улучшает и производительность, и переносимость, и спасает от кучи глюков, так как например, $cgi->param может вернуть как список, так и скаляр
 +
* Использовать не кучу CGI-скриптов, а несколько (мало) точек входа и классы, всю мохнатую логику перенести в них. Не использовать модуль CGI.pm вообще (!), потому что он прости господи даже без use strict написан и порождает некоторые глюки сам по себе. В крайней форме точки доступа — это один index.cgi, позволяющий работать через CGI, один server.fcgi (FastCGI), один модуль для апача, и м.б. один для HTTP::Server::Simple. Ибо по сути, интерфейс «запроса» тривиален и не требует никакого бешеного CGI.
 +
* Убрать ВСЕ строки (terms и т. п.) и описания ошибок из шаблонов и «длинного if’а», добавить отдельный уровень «локализации» / «таблицы строк», и переместить всё это в него. Потенциально также переместить туда же вообще все строки/тексты из шаблонов, для лёгкой локализации.
 +
* Обязательно оставить совместимость с PostgreSQL, но генерилка запросов не должна на это рассчитывать и должна работать оптимальнее, чем сейчас, например, в смысле дурацких подзапросов assigned_to=(select id from profiles where login_name=?) и т. п.
 +
* Желательно заменить Template::Toolkit на что-нибудь, хотя бы даже на моё поделие VMX::Template, чтобы не общаться с этим тормозом. Но как МИНИМУМ, даже если не заменять — убрать ВСЮ обратную связь с БД из шаблонов, ибо именно она убивает производительность в первую очередь! Ну ладно, не в первую, а во вторую, после дурацкого движка поиска. Хотя это и будет уже не MVC, а MVP :)

Текущая версия на 12:09, 24 июля 2012

В Bugzilla половина кода в шаблонах, половина на основе полу-ORM’а Bugzilla::Object. Большая часть — говнокод. С одной стороны — ORM бы туда, было бы классно, НО! ORM мало, и нужен не он! Почти все существующие ORM-движки (кроме разве что Django Models) — это просто объектный интерфейс к базе данных. А нужно некое объектное ядро, которое бы позволяло создавать свои объекты, с полями различных типов, в том числе и ссылающимися на другие объекты, с возможностью приписывания специальных особенностей полям, с общими механизмами хранения истории, рассылки уведомлений, системой прав и с автоматическим базовым CRUD-интерфейсом.

Идеи по созданию объектного ядра

Объекты в Bugzilla

  • баг
  • классификация, продукт, компонент
  • тег (ключевое слово / keyword), milestone, версия, статус бага, agreement (пример кастомного поля)
  • вложение, комментарий, флаг, тип флага
  • пользователь, группа
  • если таки будет ядро — ещё появляется метакласс сущности, объекты которого описывают типы сущностей

[svg]

Причём, понятное дело, если баг принадлежит к некоторому продукту (через компонент), то и его версия, milestone, agreement должны принадлежать к тому же продукту. Сейчас в Bugzilla это реализуется так — для каждой сущности, связанной с багом, в баге есть «поле», а для ограничения значений полей поля делаются «зависимыми» от других полей. Из-за этого там куча костылей и, по сути, повесить зависимость на отличное от поля «продукт» поле невозможно.

Минусы текущей реализации

Чем плоха текущая багзильная реализация этого «добра»?

Очень просто: почти ни хрена не настраивается - нельзя ни добавить свои возможности, ни отрубить ненужные встроенные. Ибо всё жёстко и криво (хардкод). А ещё:

  • История хранится только для багов, но не для остальных объектов.
  • Шаблоны переусложнены.
  • Нет "объектов", есть «значения полей», соответсвенно, им не добавишь атрибутов и не навесишь логики.
  • Значения кастом полей в таблице багов хранятся не ссылками по ID, а по именам, что создаёт геморрой для зависимых полей, так как чтобы точно идентифицировать значение, приходится брать ещё и значение поля, от которого оно зависит.
  • Для выпадающих списков есть как «контроль значений», так и «контроль видимости поля», что в корне неверно! Типа поле скрыто, но при этом есть варианты, которые можно выбрать? Бред! Или наоборот — поле доступно, но из вариантов — только пустое значение.
  • По факту, ни от чего, кроме продукта, зависимым значение не сделаешь.
  • Код полей не генерится автоматически, а ручками вписываются в шаблоны.
  • На всё это навёрнута туча неструктурированного кода, определяющего поведение «встроенных» полей.

Проект супер-объектного-ядра

Что предлагается сделать:

  • Есть объекты. У объектов есть поля (разных типов). В том числе, есть типы «multi-select» и «single-select», ссылающиеся на другие объекты.
  • Зависимости полей могут прописываться дополнительно, в виде ограничений вида: «баг.компонент.продукт == баг.продукт». Могут и не прописываться. Теоретически, могут прописываться несколько ограничений на одно поле, например, чтобы какой-нибудь атрибут зависел и от продукта, и от, скажем, операционной системы. Такое устройство наиболее гибкое и в частности позволяет багу иметь несколько полей, ссылающихся на один тип объектов.
  • Поля могут скрываться в зависимости от значений других полей. Селект-поля скрываются, если в них по зависимостям нечего выбирать. На поля остальных типов можно вешать «настройку видимости», подобную текущей багзильной реализации — просто тупо «поле = одно из значений…».
  • HTML-код полей должен генерироваться автоматически. Также желательна возможность задавать группировку полей в интерфейсе — например, опционально раскрываемые «Advanced Fields» (только общим fieldset’ом, а не как сейчас — просто скрытие полей в разных местах формы).
  • На поля можно назначить дополнительные обработчики, задающие более хитрое поведение. Сами обработчики пишутся в коде, по возможности — максимально абстрагированно от конкретного поля. Назначаться они при этом должны не из кода, а из интерфейса конфигурации модели. На этих дополнительных обработчиках должно работать всё! Примеры:
    • Платформа, ОС: функции угадывания значения по умолчанию на основе заголовков запроса.
    • Статус: есть ограничения переходов из состояния в состояние (workflow). Возможно, его нужно попытаться вынести в базовые настройки модели.
    • CC: специальное поведение при клонировании, смене Assignee и QA (старые добавляются в CC), специальное «значение по умолчанию», зависящее от компонента. Возможно, зависимые значения по умолчанию стоит как-то протащить в общее ядро, но это чуть более тонкий момент, чем всё остальное.
    • Keywords/теги: автосоздание новых значений.
  • История хранится централизованно для всех объектов.
  • Общий механизм рассылки изменений по почте для всех объектов каким-либо образом «связанным» пользователям. Настройки уведомлений на уровнях: событие (изменено значение поля, добавлена сущность) и отношение (собственно связь).
  • Поиск: адаптировать сильно прокачанный движок поиска из Bugzilla4Intranet. Адаптировать долго не придётся, потому что с точки зрения read-only все типы полей останутся, по сути, те же самые. С другой стороны, его нужно обобщить, так что кое-что сделать всё-таки будет нужно. Но зато в итоге получится супер-инструмент — система выборок, которая отлично хавает большие базы, сложные и произвольные структуры запросов и не давится :) (доказано Bugzilla4Intranet)
  • Права доступа: Нужна опять-таки общая схема для всех объектов, с одной стороны, простая для вычисления системой, а с другой стороны, дающая достаточную гибкость разных настроек. По всей видимости, нужны с одной стороны опять-таки «связанные» пользователи, а с другой стороны — роли. Причём, пользователи могут быть «связанные» через какое-то поле — например, человек может быть связан с багом, потому что прописан как Global Watcher для продукта этого бага. Роли могут набираться из действий над полями различных объектов и права на просмотр объектов. Возможно, нужно давать возможность разграничивать доступ на просмотр к разным полям — например, в Bugzilla только группа time-tracker’ов может посмотреть информацию о рабочем времени.

Таким образом наш трекер превратится, по сути, в модульное приложение, построенное вокруг очень прокачанного автоинтерфейса полей и объектов. Естественно, это уже не Bugzilla ни разу, но зато чем-то похоже на Roundup — «конструктор трекеров». Однако и с ним различий много — roundup не умеет зависимых селект-полей, модель задаётся в коде, права доступа слабые, поиск слабый.

Обновление модели

Обновление базы багзильское (тупая последовательность операций):

  • (+) Очевидный порядок обновлений, нет проблем с их зависимостями друг от друга (новые просто дописываются в конец, порядок всегда правильный)
  • (-) Не проверяется, корректна ли схема БД после обновлений
  • (-) Обновления задаются именно для SQL БД, а не для метамодели
  • (-) Не очень красивая портянка в функции обновления БД

Обновление базы наше:

  • AddType -> RenameFields -> AddFields -> ChangeFields -> DropFields
  • ChangeFields с помощью функций обновления
  • (???) Самый интересный вопрос - как сделать функции обновления не на SQL, в условиях когда модель в неконсистентном и вообще не знает, в каком состоянии?
  • После каждого шага запуск хука
  • Переименования задаются декларативно
  • Возможность Dry Run, просмотра и проверки последовательности обновлений
  • Зависимости обновлений в рамках каждого шага можно задать декларативно (хотя ситуация редкая)
    А может, просто стоит задавать их в ассоциативном массиве по порядку
  • Ещё может быть, что обновление одного типа зависит от обновления другого (причём это уже более частая ситуация)

Модель

Получается, что ядро модели состоит из следующих таблиц:

  • Типы объектов
    • Название типа
    • Дополнительное поведение типа
  • Поля
    • Ссылка на объект
    • ID поля
    • Название поля
    • Тип поля
    • Nullable? Т.е. разрешено ли пустое значение
    • Поле этого же объекта, контролирующее видимость данного (для не-select полей)
    • Связи поля (для select-полей)
    • Дополнительное поведение поля
  • Контроль видимости полей
    • Ссылка на поле
    • ID объекта, для которого оно видно
  • Транзакции (группы изменений)
    • Момент времени
    • Изменивший пользователь
  • История изменений
    • Ссылка на транзакцию
    • Тип объекта
    • Ссылка на объект
    • Ссылка на поле (кроме добавления объекта)
    • Старое значение
    • Новое значение
  • Один неудаляемый тип — пользователи (их поля, тем не менее, можно менять)
  • Связи объектов с пользователями
    • ID связи
    • Название связи
    • Ссылка на объект
    • Цепочка свойств, заканчивающаяся объектом «пользователь»
  • Пользовательские настройки оповещений
    • Ссылка на пользователя (либо пустая для настроек по умолчанию)
    • Ссылка на связь
    • Тип события: добавление/удаление/изменение
    • Ссылка на поле объекта (может быть пустая)
  • Роли (или группы, что примерно то же самое)
    • Включаемые роли
    • Включаемые разрешения
  • Разрешения (из которых состоят роли)
    • Ссылка на связь
    • Тип разрешения: просмотр/создание/удаление/изменение
    • Ссылка на поле объекта (пустая = весь объект)
    • Ссылка на значение поля (пустая = любое)

Типы полей

Тип Параметры Представления
Строка Длина Input, textarea, rich edit
Логический Флажок
Числовой Точность (M.N) Input
Дата и/или время Дата? Время? Поле со всплывающим календарём, просто календарь, просто поле
Файл Поле загрузки файла
Single-select Тип объекта Радиобатон, HTML-селект
Multi-select Тип объекта HTML-мультиселект, флажки, комбо-бокс

Текущее состояние

Базовые поля (то, чего не быть логически не может):

  • Объект, баг, пользователь, группа, комментарий к багу.
  • Продукт имеет специальный смысл — разграничение прав. Поэтому он тоже обязателен.

У каждого поля есть:

  • Тип
  • Значение по умолчанию
  • Вид показа в интерфейсе (по умолчанию?)
  • Копируется ли значение атрибута при клонировании
  • Показывается ли атрибут в форме создания
  • Ссылка на контролирующий атрибут того же объекта, что и этот, и на его значения
    Если выбрано, означает, что атрибут показывается, только если другой атрибут той же сущности, которой принадлежит этот, имеет одно из заданных значений

Типы атрибутов:

  • Ok16.png Строка
  • Boolean
  • Ok16.png Десятичное число
  • Дата
  • Время
  • Ok16.png Дата+время
  • Файл (вложение)
  • Single-Select → ссылка на сущность (то есть «многие к 1»)
  • Multi-Select → ссылка на несколько сущностей одного типа (то есть «многие ко многим»)
    • Два способа показа — поле со списком (комбобокс), или мультиселект
  • Список тегов, с автодобавлением значений

Существующие поля

Поля багов

Поля багов перечислены ниже. Жирное «да» в колонке «можно отключить» означает, что отключать можно уже сейчас (скорее всего, через параметры типа usevotes и т. п.). Нежирное «да» в колонках «можно отключить» и «можно менять тип» означают, что чисто теоретически логика работы Bugzilla это позволяет.

поле тип можно отключить? можно менять тип? примечания
short_desc строка нет нет
classification single-select да нет
product single-select да нет контролируется classification (или никем), у поля есть привязка к правам пользователя
component single-select да нет контролируется product
version single-select да да контролируется product, если тип = single-select. значение по умолчанию контролируется component
rep_platform single-select да да специальная функция «угадывания» дефолтного значения
bug_file_loc строка да да
op_sys single-select да да специальная функция «угадывания» дефолтного значения
bug_status single-select нет нет есть функция валидации (Bug Status Workflow)
resolution single-select да нет у атрибута есть контролирующая видимость сущность (показывается только при bug_status.closed=1)
status_whiteboard строка да да
keywords multi-select да да показ в виде списка
bug_severity single-select да нет
priority single-select да да есть желание сделать decimal
assigned_to single-select нет нет ссылка на пользователя. значение по умолчанию контролируется component
reporter single-select нет нет ссылка на пользователя
qa_contact single-select да нет ссылка на пользователя. значение по умолчанию контролируется component
votes decimal(,0) да нет
cc multi-select да нет ссылка на пользователей. значение по умолчанию контролируется component
dependson multi-select да нет ссылка на баги. показ в виде списка
blocked multi-select да нет ссылка на баги. показ в виде списка
target_milestone single-select да да контролируется product, если тип = single-select
see_also multi-select да да ссылка на баги. показ в виде списка (багов), если тип = single-select
alias строка да да
reporter_accessible boolean нет нет
cclist_accessible boolean нет нет
estimated_time время да нет
remaining_time время да нет
deadline дата да нет
creation_ts дата+время нет нет
delta_ts дата+время нет нет
cf_agreement single-select да да контролируется product
*** нет в fielddescs ***
lastdiffed дата+время нет нет скрыто в интерфейсе
attachments multi-select нет нет 1 ко многим
longdescs multi-select нет нет 1 ко многим
flags multi-select да нет 1 ко многим

Примечания:

  • Все поля-ссылки на пользователей показываются в виде строки либо в виде combo-box’а, и у них есть функция валидации — она угадывает юзеров по некорректным именам.

Логически минимальный набор полей

Совершенно точно, никогда и ни при каких условиях у бага не могут отсутствовать поля:

  • ID
  • Заголовок
  • История => reporter, время создания (creation_ts), время изменения (delta_ts), время оповещения по почте (lastdiffed)
  • Комментарии

Без этого никакой «баг» смысла не имеет ни в одном баг-трекере.

Устаревшие поля

Устаревшие поля / поля, которые ХЗ зачем нужны в таблице полей багов:

  • assignee_accessible
  • qacontact_accessible
  • longdesc
  • commenter
  • longdescs.isprivate
  • content
  • bug_group
  • flagtypes.name
  • requestees.login_name
  • setters.login_name

Вычисляемые поля

Вычисляемые поля багов:

work_time Сумма work_time от связанных longdescs
percentage_complete (Сумма work_time от dependson)/(Сумма estimated_time от dependson)
owner_idle_time Текущая дата минус MAX(дата последнего коммента от Assignee, дата последней активности от Assignee)
days_elapsed Текущая дата минус delta_ts
everconfirmed Менялся ли статус хоть раз на != UNCONFIRMED

Поля вложений

submitter single-select, показ в виде select’а или строки, есть функция валидации (угадывания юзера по некорректному имени)
description строка
filename строка
mimetype строка
ispatch boolean
isobsolete boolean
isprivate boolean
isurl boolean
thedata потенциально строка, а вообще-то обычно NULL, так как данные хранятся в локальных файлах

Поля компонентов

поле тип можно отключить? можно менять тип? примечания
name строка нет нет
initialowner single-select да нет ссылка на пользователя
initialqacontact single-select да нет ссылка на пользователя
initialcc multi-select да нет ссылка на пользователей
default_version single-select да нет ссылка на версию
description строка да да
product_id single-select нет нет
wiki_url строка да нет
is_active boolean да нет

Из чего состоит багзилла?

С внешней точки зрения

Крупные блоки функционала в Bugzilla с точки зрения пользователя:

  • Создание, просмотр, изменение багов, история по багу
    • Форматирование комментариев
  • Создание, просмотр, изменение вложений
  • Поиск багов, форма поиска
  • Графики одни, графики другие, Summarize Time
  • Исходящая почта
  • Входящая почта
  • Запросы флагов
  • Напоминания (whining)
  • Регистрация, изменение, раздача групп пользователям
  • Пользовательские настройки
  • XML-импорт (в полудохлом виде)
  • Web-сервисы (XML-RPC, JSON-RPC)
  • Sanity Check
  • Графы и деревья зависимостей
  • Голосование за баги

Наши крупные фичи

  • SCRUM-карточки
  • RSS-лента активности
  • XML-Simple Web-сервисы
  • Проверки корректности
  • Excel-импорт
  • Информер
  • Глобальная авторизация
  • Today Worktime, Super Worktime

Плюс вагон и маленькая тележка мелочи.

Страница создания бага (enter_bug.cgi)

Текущая логика страницы создания бага:

  • выбор classification, если она включена
  • classification выбрана → выбор product, если он не вообще один
  • тот же выбор продукта/классификации при клонировании багов
  • продукт выбран → форма создания бага
  • показ корректных списков возможных значений полей:
    • типы флагов в зависимости от компонента
    • cf_agreement в зависимости от продукта
    • списки пользователей, относящихся к багу в combo-box’ы
    • опциональный запрет на ввод приоритета на основе конфигурации (letsubmitterchoosepriority)
    • список флажков — ограничителей доступа группами
  • значения полей по умолчанию:
    • которые совсем по умолчанию
    • угадывание op_sys и rep_platform на основе заголовков запроса
    • версия, qa_contact, assigned_to, cc по умолчанию для компонента
    • хитрая логика для изменения списков cc при выборе компонентов
    • assigned_to=ты при выборе статуса ASSIGNED
    • показ поля resolution при выборе закрытого статуса
    • версия из cookies
    • загруженные из шаблона ввода бага
    • загруженные из клонированного бага
      • ссылка на старый аттач в описании нового клонированного бага
      • хитрая логика для CC при клонировании багов
  • напоминания о вводе времени
  • предпросмотр комментариев
  • добавление ссылки на старый аттач для клонированного из коммента бага
  • переключатель Show Expert Fields
  • Submit по Ctrl-Enter

Обработчик создания бага (post_bug.cgi)

  • Редирект на enter_bug.cgi если форма не заполнена
  • Проверка, не был ли уже использован этот token для постановки другого бага?
  • Вывод страницы с URL-шаблоном постановки бага, если попросили
  • Заполнение описания по шаблону, если использовался спецвид формы (например create_guided)
  • Отправка куки VERSION
  • Начало/конец транзакции в БД
  • Постановка собственно бага (Bug::create)
  • Постановка вложения сразу при создании бага
  • Постановка флагов на вложение
  • Постановка флагов на баг
  • Рассылка почты по багу, по его зависимостям
  • Вывод сообщения о том, что баг поставлен, о том, что CC-список обрезан по группе

Обработчик изменения бага (process_bug.cgi)

  • Начало/конец транзакции в БД
  • SELECT FOR UPDATE багов
  • Удаление значений полей, равных dontchange (используется в групповом редактировании)
  • Угадывание юзеров по части логина/имени
  • Проверка коллизии по delta_ts, вывод только действительно изменённых полей в форму подтверждения
  • Проверка token формы
  • Загрузка следующего бага из списка ДО обновления текущего (O_O)
  • Проверка прав редактирования по всем багам
  • Установка значения продукта до всех остальных полей
  • Установка новых групп до остальных изменений, когда включён strict_isolation
  • Установка/изменение флагов
  • Установка зависимостей бага
  • Установка ключевых слов
  • Установка остальных полей
  • Установка некоторых значений только для обновлений отдельных багов (alias, cclist_accessible, reporter_accessible, isprivate на комментах)
  • БОльшая часть логики обновления поля CC
  • Вызов функции проверки strict_isolation
  • Перемещение багов (MOVE) — полуживое, хз как работает
  • И после всего этого — ещё изменения, status, resolution, dup_id
  • Теперь всё это ещё незакоммичено, вызов реальных обновлений из Bug.pm
  • Отправка почты по зависимостям, если статус поменялся с открытого на закрытый
  • Обрезание CC-списка по группе (наше)
  • Сообщение, если очищено remaining_time
  • Удаление голосов за баг, если переместили в другой продукт, и проверка голосо-подтвеждённости бага
  • Отправка почты CC, Assignee, Reporter’у, QA, старым Assignee, QA и CC
  • Отправка почты по багу, дубликатом которого был помечен данный
  • Добавление нескольких вложений (наше)
  • Отправка почты по флагам, через отдельный механизм сбоку
  • Редирект (наше) или показ следующего/того же/никакого бага
  • Не показывает ничего, если USAGE_MODE_EMAIL

Список/поиск багов (buglist.cgi)

  • SuperWorkTime (наше доработко)
  • Редирект на форму поиска для пустого запроса
  • Редирект на форму поиска с добавленными пустыми полями, если на форме поиска жмут Add/Remove поле без JS
  • Редирект с POST’енного запроса на GET
  • Быстрый поиск
  • Посыл на хрен, если content пуст при включённом параметре specific_search_allow_empty_words
  • Обратная совместимость: format=rdf -> ctype=rdf, ctype=rss -> ctype=atom
  • ctype=js слать в хрен
  • Поддержка server-push
  • regetlastlist — открытие последнего просмотренного списка багов (берётся из куки)
  • Удаление колонки relevance из списка колонок, если юзер не просит полнотекстовый поиск
  • В buglist.cgi почему-то тусуются функции InsertNamedQuery, LookupSeries, GetQuip, GetGroups
  • Генерация имени файла, если попросили список не в html-формате
  • Выполнение запросов поиска
  • Сохранение и удаление сохранённых запросов из БД
  • Запуск series
  • Если сохранённый запрос не говорил нам свой формат, решаем что advanced
  • Вкуривание списка колонок (длинное, тварь)
  • Определение порядка сортировки
  • Подсчёт сумм значений полей таймтрекинга
  • Проверка доступа к багам и установка значения в implied или manual (х.з зачем нужно)
  • Массовое редактирование багов, в частности сборка пересечений доступных значений полей
  • Кодировка CSV (наше доработко)

Система прав

Какие в Bugzilla есть права / ограничения доступа?

  • Права доступа к продуктам
    • MANDATORY / SHOWN / DEFAULT / NA
    • Entry
    • Canedit
    • Editcomponents
    • Canconfirm
    • Editbugs
  • Private комментарии — видны только инсайдер-группе
  • Информация о таймтрекинге — видна только группе, списывающей время
  • reporter_accessible, cclist_accessible на отдельных багах
  • Опциональные группы на отдельных багах
  • Права на правку отдельных групп
  • Права editclassifications, editcomponents, editfields, editkeywords, editusers

Настройки оповещений о багах

Отношения:

  • Assignee, QA, Reporter, CC
  • Requestee или Setter флага (реально в Bugzilla прикручено сильно сбоку)
  • По идее — также создатель аттачмента, коммента (если разрешать править)
  • Watcher указанного выше
  • Global Watcher

События:

  • Добавлен/удалён из указанного выше
  • Изменено одно из полей бага (м.б отдельные настройки, м.б вместе)
  • Баг / блокирующий баг меняет статус с закрытого на открытый или обратно
  • «Но кроме» UNCONFIRMED
  • «Но кроме» своих изменений

Что не так?

Что в Bugzilla не так? Что нужно поменять, чтобы она перестала быть говном?

Да, я знаю, что почти всё, но дело не в этом — дело в том, что создавать систему «от противного» — есть хороший тон, ведущий к прогрессу.

Ядро

Самое жирное «не так» в Bugzilla — это слабость ядра. Если бы ядро было прокачанным, как в начале статьи, оно бы заменило:

  • Custom Fields, то есть, управление полями багов.
  • Отключение стандартных полей типа OS, Hardware.
  • Добавление спецполей в продукты, компоненты и т. д. — сейчас это делается в коде.
  • Версионирование всех объектов. История бы хранилась унифицированно. Не было бы отдельно таблиц bugs_activity и longdescs, соответственно не было бы и тормозов при проверке «Only bugs changed between…» либо комментарии бы дублировались в bugs_activity (логичнее)
  • Отправка почты, которая сейчас отправляется через 2 разных места.
  • Простые интерфейсы типа CRUD (Create/Read/Update/Delete), сейчас созданные непонятно каким копипастом. В основном имеется ввиду админка.
  • Система прав стала бы и проще (по сравнению с хрен-пойми-системой Mandatory/Default/Shown/NA), и имела бы больше возможностей.
  • Валидаторы и подсказки значений.
  • Корректная валидация зависимостей полей друг от друга.
  • Часть SQL-запросов, написанных ручками в коде.
  • Вся логика постановки/обновления багов из process_bug и post_bug, дублированная сейчас в обработке входящих e-mailов и Excel-импорте
  • Даже Excel- и XML-импорт, причём импортировать можно было бы вообще всё что угодно.

Появились бы дополнительные возможности:

  • Создание новых объектов типа SCRUM карточек.
  • Изменение типов стандартных полей типа целочисленных приоритетов.
  • Можно было бы прикрутить статистику по любым объектам, причём с Time Machine, то есть просмотром статистики за любой прошедший момент времени. :)

Внешности

  • Большая часть интерфейсов нуждается в полной переделке. Например:
    • На форме бага «Show Advanced Fields» должно быть сделано отдельной группкой полей (fieldset), а не скрытием полей прямо посреди формы в разных местах (пугает).
    • Страница настройки прав продукта — мегастрёмная. Таблица с группами там на хрен не нужна никому вообще.
    • Частично относится к ядру, но — интерфейс редактирования «контроля значений» кастом-полей это сейчас полная жесть — например, нельзя посмотреть список значений кастом поля для конкретного продукта (от которого зависят наборы значений).
  • Историю изменений бага показывать вперемешку с комментариями к багу (а-ля Trac). Туда же можно заинтегрировать и замешать коммиты из системы контроля версий.
  • Добавить ленту обновлений по багам, поиск «последних багов», что-то типа форум-функциональности.
  • Возможно, добавить Grid View с возможностью массовых обновлений в поиске.
  • Отрефакторить кучу действий, связанных с поиском, расположенных под списком багов.
  • Keywords переделать в «теги», то есть, сделать, чтобы они сами заводились новые.
  • Заменить YUI на jQuery в Bugzilla4Intranet уже, форму поиска сгруппировать (но не так, как в 4.0), сделать почти все фильтры добавляемыми, добавляемыми через JS, чтобы не перезагружалась страница каждый раз
  • Документацию «насытить» в вики, POD-документацию по коду туда же.

Кишки, кроме ядра

  • Добавить глобальный объект статуса, в который сохранять почту, которую нужно отправить, и сообщения, которые сейчас руками сохраняются в сессию.
  • Использовать не CGI, а хеши параметров запроса, это улучшает и производительность, и переносимость, и спасает от кучи глюков, так как например, $cgi->param может вернуть как список, так и скаляр
  • Использовать не кучу CGI-скриптов, а несколько (мало) точек входа и классы, всю мохнатую логику перенести в них. Не использовать модуль CGI.pm вообще (!), потому что он прости господи даже без use strict написан и порождает некоторые глюки сам по себе. В крайней форме точки доступа — это один index.cgi, позволяющий работать через CGI, один server.fcgi (FastCGI), один модуль для апача, и м.б. один для HTTP::Server::Simple. Ибо по сути, интерфейс «запроса» тривиален и не требует никакого бешеного CGI.
  • Убрать ВСЕ строки (terms и т. п.) и описания ошибок из шаблонов и «длинного if’а», добавить отдельный уровень «локализации» / «таблицы строк», и переместить всё это в него. Потенциально также переместить туда же вообще все строки/тексты из шаблонов, для лёгкой локализации.
  • Обязательно оставить совместимость с PostgreSQL, но генерилка запросов не должна на это рассчитывать и должна работать оптимальнее, чем сейчас, например, в смысле дурацких подзапросов assigned_to=(select id from profiles where login_name=?) и т. п.
  • Желательно заменить Template::Toolkit на что-нибудь, хотя бы даже на моё поделие VMX::Template, чтобы не общаться с этим тормозом. Но как МИНИМУМ, даже если не заменять — убрать ВСЮ обратную связь с БД из шаблонов, ибо именно она убивает производительность в первую очередь! Ну ладно, не в первую, а во вторую, после дурацкого движка поиска. Хотя это и будет уже не MVC, а MVP :)