Персональные инструменты
 

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

Материал из CustisWiki

Перейти к: навигация, поиск
м (stw-config.py)
 
(не показано 15 промежуточных версий 3 участников)
Строка 1: Строка 1:
== Проблема ==
+
= Проблема =
 
При грамотном процессе разработки, с применением средств групповой работы, таких, как:
 
При грамотном процессе разработки, с применением средств групповой работы, таких, как:
 
* трекеры задач ([[Bugzilla]], [[Jira]], [[Mantis]]),  
 
* трекеры задач ([[Bugzilla]], [[Jira]], [[Mantis]]),  
Строка 25: Строка 25:
 
Однако на практике возникает проблемы:
 
Однако на практике возникает проблемы:
  
* {{question}} как {{good-words|эффективно исследовать}} этот пласт информации?
+
{{question}} как {{good-words|эффективно исследовать}} этот пласт информации?
  
* {{question}} как {{good-words|эффектно показать}} свою работу лицом?
+
{{question}} как {{good-words|эффектно показать}} свою работу лицом?
  
 
----  
 
----  
Строка 36: Строка 36:
  
 
В зависимости от глубины детализации можно получить:
 
В зависимости от глубины детализации можно получить:
** либо пару унылых метрик («KSLOCs в месяц на сферического разработчика в вакууме», то есть в нашей метафоре максимум — «площадь лесного массива»)<ref>На самом деле сейчас уже есть интересные варианты с  
+
* либо пару унылых метрик («KSLOCs в месяц на сферического разработчика в вакууме», то есть в нашей метафоре максимум — «площадь лесного массива»)<ref>На самом деле сейчас уже есть интересные варианты с  
 
нетривиальной статической визуализацией SVN-репозиториев: [http://code.google.com/p/svnplot/ SVNPlot], примеры: [http://thinkingcraftsman.in/projects/sna_subversion/sna_subversion.htm], [http://svnplot.googlecode.com/svn/trunk/examples/centralitytreemap/centralitytreemap.htm]. Но в динамике все равно гораздо веселей.</ref>.
 
нетривиальной статической визуализацией SVN-репозиториев: [http://code.google.com/p/svnplot/ SVNPlot], примеры: [http://thinkingcraftsman.in/projects/sna_subversion/sna_subversion.htm], [http://svnplot.googlecode.com/svn/trunk/examples/centralitytreemap/centralitytreemap.htm]. Но в динамике все равно гораздо веселей.</ref>.
** либо многостраничные Excel-dashboardы, заполненные мириадами цифр, в которых почти также бессмысленно лезть человеку, ''если он не профессор Чарли Эппс из сериала Numb3rs с его верными суперкомпьютерами и волшебными алгоритмами DataMining-a''.
+
* либо многостраничные Excel-dashboardы, заполненные мириадами цифр, в которых почти также бессмысленно лезть человеку, ''если он не профессор Чарли Эппс из сериала Numb3rs с его верными суперкомпьютерами и волшебными алгоритмами DataMining-a''.
  
 
----  
 
----  
  
* Читать логи переписки и коммиты в VCS?  
+
{{question}} Читать логи переписки и коммиты в VCS?  
 
{{no}} То есть разрабатывать «шахту знаний» киркой и мотыгой? Бродить по лесу и считать деревья? {{bad-words|Муторно}}!
 
{{no}} То есть разрабатывать «шахту знаний» киркой и мотыгой? Бродить по лесу и считать деревья? {{bad-words|Муторно}}!
  
Строка 50: Строка 50:
  
 
Есть альтернативный способ «увидеть лес за деревьями» и при этом выжать краткую информацию по процессу — {{good-words|Визуализация}}. В {{good-words|динамике}}!
 
Есть альтернативный способ «увидеть лес за деревьями» и при этом выжать краткую информацию по процессу — {{good-words|Визуализация}}. В {{good-words|динамике}}!
 +
 +
= Визуализация =
  
 
Есть уже даже несколько моделей!
 
Есть уже даже несколько моделей!
Строка 63: Строка 65:
 
=== Основная идея ===
 
=== Основная идея ===
  
** «Люди» не любят друг друга — {{bad-words|отталкиваются!}}
+
* «Люди» не любят друг друга — {{bad-words|отталкиваются!}}
** «Файлы» тоже {{bad-words|отталкиваются!}} «обратно пропорционально расстоянию».
+
* «Файлы» тоже {{bad-words|отталкиваются!}} «обратно пропорционально расстоянию».
** «Людей» и «Файлы» {{good-words|притягивает при коммитах}}!
+
* «Людей» и «Файлы» {{good-words|притягивает при коммитах}}!
** «Люди» {{good-words|сближаются из-за совместной работы}} над одними файлами
+
* «Люди» {{good-words|сближаются из-за совместной работы}} над одними файлами
  
  
Строка 74: Строка 76:
 
* {{good-words|Работа красит}} — файлы красят людей.
 
* {{good-words|Работа красит}} — файлы красят людей.
 
* Цветной пульс проекта в динамике.
 
* Цветной пульс проекта в динамике.
 +
 +
{{vimeoembed|9085962|640|640}}
  
 
== Gource ==
 
== Gource ==
Строка 80: Строка 84:
 
* Визуализация и {{good-words|структур каталогов}} («облака имен»)!
 
* Визуализация и {{good-words|структур каталогов}} («облака имен»)!
 
* Модель {{good-words|«Пчелы» и «цветы»}}!
 
* Модель {{good-words|«Пчелы» и «цветы»}}!
* Видны:
 
** «Горячие зоны».
 
** {{good-words|Кроссфункциональные}} парни.
 
** {{bad-words|Заброшенные}} люди и код.
 
 
  
 +
Видны:
 +
* «Горячие зоны».
 +
* {{good-words|Кроссфункциональные}} парни.
 +
* {{bad-words|Заброшенные}} люди и код.
  
 +
{{vimeoembed|8758085|640|640}}
  
 
== Что не хватает? ==
 
== Что не хватает? ==
{{important}} Именно поэтому «самодельных»  {{bad-words|визуализаций — почти нет}}!
 
  
 
* Сохраненного {{good-words|видео}}. Только {{bad-words|рисует}}.
 
* Сохраненного {{good-words|видео}}. Только {{bad-words|рисует}}.
Строка 97: Строка 100:
 
** {{good-words|«старт с нуля»}} — только включить и дать на вход лог!
 
** {{good-words|«старт с нуля»}} — только включить и дать на вход лог!
 
** {{good-words|«эволюционные доработки»}} — легко и быстро «править кино».
 
** {{good-words|«эволюционные доработки»}} — легко и быстро «править кино».
 +
 +
{{important}}  Именно поэтому «самодельных»  {{bad-words|визуализаций  — почти нет}}!
  
  
== Наш фреймворк ShowTeamWork==
+
= Наш фреймворк ShowTeamWork=
  
 
{{important}} Наше решение {{good-words|все это делает}}!
 
{{important}} Наше решение {{good-words|все это делает}}!
  
 
А именно:
 
А именно:
* Вытаскивается лог-информация, если запустить в workspace проекта под какой-нибудь из известных VCS — поддерживаются самые распространенные системы контроля версий: <tt>CVS</tt>, <tt>SVN</tt>, <tt>GIT</tt>, <tt>Bazaar</tt>,  <tt>Mercurial</tt> (в принципе — можно визуализировать любую активность, хоть торговлю трейдеров).
+
* Вытаскивается лог-информация, если запустить в ''workspace'' проекта под какой-нибудь из известных VCS — поддерживаются самые распространенные системы контроля версий: <tt>CVS</tt>, <tt>SVN</tt>, <tt>GIT</tt>, <tt>Bazaar</tt>,  <tt>Mercurial</tt> (в принципе — можно визуализировать любую активность, хоть торговлю трейдеров).
 
* Автоматически выбирается набор подкаталогов проекта с наибольшей активностью.
 
* Автоматически выбирается набор подкаталогов проекта с наибольшей активностью.
 
* Для них генерируется случайная цветовая палитра, с взаимоконтрастными и яркими цветами.
 
* Для них генерируется случайная цветовая палитра, с взаимоконтрастными и яркими цветами.
Строка 111: Строка 116:
 
* Порождает видео, с длительностью равной аудиозаписи, и накладывает субтитры, вычисляя их из формата «сценария субтитров», где события привязаны к датам.
 
* Порождает видео, с длительностью равной аудиозаписи, и накладывает субтитры, вычисляя их из формата «сценария субтитров», где события привязаны к датам.
  
=== Примеры ===
+
== Примеры ==
  
 
Альбом готовых визуализаций для известных софтверных проектов (
 
Альбом готовых визуализаций для известных софтверных проектов (
Строка 129: Строка 134:
 
<tt>viewvc</tt>) можно посмотреть [http://vimeo.com/album/160744 здесь].
 
<tt>viewvc</tt>) можно посмотреть [http://vimeo.com/album/160744 здесь].
  
=== Мы используем ===
+
== Мы используем ==
  
 
Свободные, ''open-source'' программы:
 
Свободные, ''open-source'' программы:
* [http://code.google.com/p/codeswarm/ CodeSwarm] (GPLv3) (требует установленной <tt>Java</tt>).
+
* [http://code.google.com/p/codeswarm/ <tt>CodeSwarm</tt>] (<tt>GPLv3</tt>) (требует установленной <tt>Java</tt>, версии не меньше <tt>1.6</tt>).
* [http://code.google.com/p/gource/ Gource] (GPLv3).
+
* [http://code.google.com/p/gource/ <tt>Gource</tt>] (<tt>GPLv3</tt>).
* [http://www.mplayerhq.hu/ MEncoder] (GPL version 2).
+
* [http://www.mplayerhq.hu/ <tt>MEncoder</tt>] (<tt>GPLv2</tt>).
* [http://ffmpeg.org/ FFmpeg] (LGPL or GPL).
+
* [http://ffmpeg.org/ <tt>FFmpeg</tt>] (<tt>LGPL</tt> или <tt>GPL</tt>).
  
 
А также несколько треков свободно доступной электронной музыки от [http://tunguskagrooves.com/ Tunguska Music Society] (<tt>License: Creative Commons</tt>).
 
А также несколько треков свободно доступной электронной музыки от [http://tunguskagrooves.com/ Tunguska Music Society] (<tt>License: Creative Commons</tt>).
Строка 142: Строка 147:
 
Если хотите публиковать свои ролики, напоминаем о пока еще существующих цифровых правах на музыку и рекомендуем использовать только свободно доступную музыку — [http://www.jamendo.com/en/tag/ambient тут огромный выбор музыки в стиле Ambient].
 
Если хотите публиковать свои ролики, напоминаем о пока еще существующих цифровых правах на музыку и рекомендуем использовать только свободно доступную музыку — [http://www.jamendo.com/en/tag/ambient тут огромный выбор музыки в стиле Ambient].
  
=== Инсталляция ===
+
== Инсталляция ==
  
 
Проект (исходники и бинарники) хостится на Google Code — http://code.google.com/p/showteamwork/.
 
Проект (исходники и бинарники) хостится на Google Code — http://code.google.com/p/showteamwork/.
Строка 163: Строка 168:
  
  
=== Быстрый старт ===
+
== Быстрый старт ==
  
 
{{caution}} Для Windows-пользователей. Linux-пользователи, думаю, поймут сами, что и как.
 
{{caution}} Для Windows-пользователей. Linux-пользователи, думаю, поймут сами, что и как.
  
 
Скачайте [http://code.google.com/p/showteamwork/downloads/ дистрибутив], распакуйте куда-нибудь его.
 
Скачайте [http://code.google.com/p/showteamwork/downloads/ дистрибутив], распакуйте куда-нибудь его.
 
 
 
Зайдите в каталог проекта, то есть воркспейс под одной из систем контроля версий (<tt>CVS</tt>, <tt>SVN</tt>, <tt>GIT</tt>, <tt>Bazaar</tt>, <tt>Mercurial</tt>), причем не обязательно верхний уровень — можно на два-три уровня ниже, и вызовите
 
Зайдите в каталог проекта, то есть воркспейс под одной из систем контроля версий (<tt>CVS</tt>, <tt>SVN</tt>, <tt>GIT</tt>, <tt>Bazaar</tt>, <tt>Mercurial</tt>), причем не обязательно верхний уровень — можно на два-три уровня ниже, и вызовите
 
<tt>showteamwork.exe</tt>, и отправляйтесь пить кофе.
 
<tt>showteamwork.exe</tt>, и отправляйтесь пить кофе.
Строка 177: Строка 180:
 
Далее, вы сможете редактировать эти файлы, подбирая оптимальные настройки, подходящую музыку, и редактируя историю проекта в субтитрах, и по окончании редактирования, перезапускайте <tt>showteamwork.exe</tt>.
 
Далее, вы сможете редактировать эти файлы, подбирая оптимальные настройки, подходящую музыку, и редактируя историю проекта в субтитрах, и по окончании редактирования, перезапускайте <tt>showteamwork.exe</tt>.
  
Пересчет будет относительно экономичным — например, если вы меняли только параметры относящиеся к <tt>codeswarm</tt>-визуализации, то gource-визуализация пересчитыватся не будет, а если правили только субтитры — то будет выполнятся только наложение субтитров, без вызова <tt>gource</tt> или <tt>codeswarm</tt>.
+
Пересчет будет относительно экономичным — например, если вы меняли только параметры относящиеся к <tt>codeswarm</tt>-визуализации, то <tt>gource</tt>-визуализация пересчитыватся не будет, а если правили только субтитры — то будет выполнятся только наложение субтитров, без вызова <tt>gource</tt> или <tt>codeswarm</tt>.
  
 
Более детально, схема генерации описана ниже.
 
Более детально, схема генерации описана ниже.
  
=== Схема генерации ===
+
== Схема генерации ==
  
 
Кликабельная (!) схема генерации:
 
Кликабельная (!) схема генерации:
Строка 194: Строка 197:
 
   node [fillcolor="goldenrod1" shape=box3d]
 
   node [fillcolor="goldenrod1" shape=box3d]
 
   workspace [label="Workspaсe-каталог \n под VCS" shape=folder]
 
   workspace [label="Workspaсe-каталог \n под VCS" shape=folder]
   svcslog [label="Входной лог-файл: \n cvs.log, svn.log, git.log, \n bzr.log, hg.log, activity.xml"]
+
   svcslog [label="Входной лог-файл: \n cvs.log, svn-log.xml, git.log, \n bzr.log, hg.log, activity.xml"]
 
   getlog [label="Генератор лога: \n «getlog.py»" URL="#getlog.py"]
 
   getlog [label="Генератор лога: \n «getlog.py»" URL="#getlog.py"]
  
Строка 212: Строка 215:
 
   vcslog1 [label="лог-файл"]
 
   vcslog1 [label="лог-файл"]
 
   vcslog2 [label="лог-файл"]
 
   vcslog2 [label="лог-файл"]
   vcslog [label="Лог-файл: \n cvs.log, svn.log, git.log, \n bzr.log, hg.log, activity.xml"]
+
   vcslog [label="Лог-файл: \n cvs.log, svn-log.xml, git.log, \n bzr.log, hg.log, activity.xml"]
  
   node    [fillcolor="#0084FF" shape=egg]
+
   node    [fillcolor="#0084FF" shape=egg fontcolor=yellow]
 
   scenario [label="История \n stw-scenario.txt" URL="#stw-scenario.txt"]
 
   scenario [label="История \n stw-scenario.txt" URL="#stw-scenario.txt"]
 
   audio    [label="Музыка \n stw-audio.mp3" URL="#stw-audio.mp3"]
 
   audio    [label="Музыка \n stw-audio.mp3" URL="#stw-audio.mp3"]
Строка 227: Строка 230:
 
   cache    [label="Кеш-данные" URL="#TemporaryCache"]
 
   cache    [label="Кеш-данные" URL="#TemporaryCache"]
  
 
+
  node [fontcolor=black]
 
   "Визуализация"  [fillcolor=yellow shape=diamond]
 
   "Визуализация"  [fillcolor=yellow shape=diamond]
 
   audio  -> "Визуализация"  [label="Задает \n длительность"]
 
   audio  -> "Визуализация"  [label="Задает \n длительность"]
Строка 248: Строка 251:
 
Упрощенная схема генерации представлена выше.
 
Упрощенная схема генерации представлена выше.
  
{{note}} Единственное необходимое — история проекта, в виде лог-файла, файла-генератора лог-файла, или просто workspace-проекта под одно из известных VCS, у которое лог-файл можно запросить.
+
{{note}} Единственное необходимое — история проекта, в виде лог-файла, файла-генератора лог-файла, или просто ''workspace''-проекта под одно из известных VCS, у которое лог-файл можно запросить.
  
 
Будучи запущенной <tt>ShowTeamWork</tt> без дополнительных файлов автоматически проанализирует лог, и  
 
Будучи запущенной <tt>ShowTeamWork</tt> без дополнительных файлов автоматически проанализирует лог, и  
Строка 258: Строка 261:
 
А теперь поясним, зачем остальные сложности.
 
А теперь поясним, зачем остальные сложности.
  
=== getlog.py ===  
+
== getlog.py ==  
 
При отсутствии известного системе логфайла, <tt>ShowTeamWork</tt> пытается вызвать скрипт <tt>getlog.py</tt>, где вы пропишете, что и откуда надо тянуть. Например, вы собираетесь визуализировать историю большого проекта, у которого свой репозиторий, независимый от вашего проекта по его визуализации, то надо как-то прописать, как вытащить лог этого проекта.
 
При отсутствии известного системе логфайла, <tt>ShowTeamWork</tt> пытается вызвать скрипт <tt>getlog.py</tt>, где вы пропишете, что и откуда надо тянуть. Например, вы собираетесь визуализировать историю большого проекта, у которого свой репозиторий, независимый от вашего проекта по его визуализации, то надо как-то прописать, как вытащить лог этого проекта.
  
Строка 272: Строка 275:
 
os.system(
 
os.system(
 
"""
 
"""
svn log --verbose --revision "{%(begindate)s}:HEAD" http://svn.wikimedia.org/svnroot/mediawiki/ >svn.log
+
svn log --xml --verbose --revision "{%(begindate)s}:HEAD" http://svn.wikimedia.org/svnroot/mediawiki/ >svn-log.xml
 
""" % vars())
 
""" % vars())
 
</code-python>
 
</code-python>
Строка 282: Строка 285:
 
;cvs.log: получаемый командой
 
;cvs.log: получаемый командой
 
   cvs log
 
   cvs log
;svn.log: получаемый командой
+
;svn-log.xml: получаемый командой
   svn log -v
+
   svn log --xml --verbose
 
;bzr.log:  получаемый командой
 
;bzr.log:  получаемый командой
 
   bzr log -v
 
   bzr log -v
Строка 298: Строка 301:
 
<tt>CVS</tt>, <tt>.svn</tt>, <tt>.bzr</tt>, <tt>.git</tt>, <tt>.hg</tt> — и обнаружив таковой, пытается получить лог-файл от соответствующей системы контроля версий (должна быть установлена и «в путях»).
 
<tt>CVS</tt>, <tt>.svn</tt>, <tt>.bzr</tt>, <tt>.git</tt>, <tt>.hg</tt> — и обнаружив таковой, пытается получить лог-файл от соответствующей системы контроля версий (должна быть установлена и «в путях»).
  
=== activity.xml ===
+
== activity.xml ==
  
 
Стандартный XML-формат представления активности (когда, кто, с чем, что сделал):
 
Стандартный XML-формат представления активности (когда, кто, с чем, что сделал):
Строка 332: Строка 335:
 
Кодировка — UTF-8, но если не подходит, пытается попробовать однобайтовые русскоязычные <tt>1251</tt>/<tt>koi-8</tt>.
 
Кодировка — UTF-8, но если не подходит, пытается попробовать однобайтовые русскоязычные <tt>1251</tt>/<tt>koi-8</tt>.
  
=== stw-filter-events.py ===
+
== stw-filter-events.py ==
  
 
В этом файле (по умолчанию будет сгенерирована «заготовка-рыба») определяется функция <tt>filter_events</tt>, которой передается объект <tt>event</tt>, с атрибутами, описанными [[#activity.xml|выше]], и которая возвращает  
 
В этом файле (по умолчанию будет сгенерирована «заготовка-рыба») определяется функция <tt>filter_events</tt>, которой передается объект <tt>event</tt>, с атрибутами, описанными [[#activity.xml|выше]], и которая возвращает  
Строка 390: Строка 393:
  
  
=== stw-scenario.txt ===
+
== stw-scenario.txt ==
 
Файл текстового «сценария» — история проекта в субтитрах.
 
Файл текстового «сценария» — история проекта в субтитрах.
 
Строка начинающаяся с <tt>#</tt>, считается комментарием, остальные непустые строки  
 
Строка начинающаяся с <tt>#</tt>, считается комментарием, остальные непустые строки  
Строка 399: Строка 402:
 
По умолчанию, генерируется «заготовка-рыба» из событий, где для каждое первое появление нового участника пишется «Hi <участник>!», плюс закомментированная строка с лог-комментарием для каждого коммита — вы можете пройтись по тексту, выделить и раскомментировать ключевые события.
 
По умолчанию, генерируется «заготовка-рыба» из событий, где для каждое первое появление нового участника пишется «Hi <участник>!», плюс закомментированная строка с лог-комментарием для каждого коммита — вы можете пройтись по тексту, выделить и раскомментировать ключевые события.
  
=== stw-config.py ===
+
== stw-config.py ==
 
Основной файл настроек.
 
Основной файл настроек.
  
Строка 461: Строка 464:
 
В общем, см. примеры файлов <tt>stw-config.py</tt> в подкаталоге дистрибутива <tt>samples</tt>.
 
В общем, см. примеры файлов <tt>stw-config.py</tt> в подкаталоге дистрибутива <tt>samples</tt>.
  
=== stw-audio.mp3 ===
+
== stw-audio.mp3 ==
 
Музыкальный файл сопровождения.
 
Музыкальный файл сопровождения.
 
Если вы не предложите свой, будет взят случайный файл из подкаталога <tt>audio</tt>.
 
Если вы не предложите свой, будет взят случайный файл из подкаталога <tt>audio</tt>.
Строка 467: Строка 470:
 
{{note}} Так что если у вас есть любимая коллекция музыки, и вы собираетесь визуализировать кучу проектов, не подбирая индивидуально звук для каждого проекта, просто залейте вашу коллекцию в подкаталог <tt>audio</tt>, оттуда <tt>mp3</tt>-файл будет выбираться случайно.
 
{{note}} Так что если у вас есть любимая коллекция музыки, и вы собираетесь визуализировать кучу проектов, не подбирая индивидуально звук для каждого проекта, просто залейте вашу коллекцию в подкаталог <tt>audio</tt>, оттуда <tt>mp3</tt>-файл будет выбираться случайно.
  
=== TemporaryCache ===
+
== TemporaryCache ==
 
Структуру временных файлов описывать специально не будем, отметим только, что все они лежат в
 
Структуру временных файлов описывать специально не будем, отметим только, что все они лежат в
 
вашем каталоге для временных файлов (под Windows он указан в переменной <tt>TEMP</tt>), в подкаталогах с префиксом <tt>STW-</tt> (кстати, они с атрибутами скрытых файлов под Windows).
 
вашем каталоге для временных файлов (под Windows он указан в переменной <tt>TEMP</tt>), в подкаталогах с префиксом <tt>STW-</tt> (кстати, они с атрибутами скрытых файлов под Windows).
Строка 476: Строка 479:
  
 
== Контакты ==
 
== Контакты ==
* [http://belonesox.moikrug.ru Стас Фомин].
 
  
 +
Все баги, пожелания и предложения, пожалуйста [http://code.google.com/p/showteamwork/issues/list сюда].
 +
Ну, на худой конец, обращайтесь на прямую — все мои контакты: [http://belonesox.moikrug.ru Стас Фомин].
  
 
+
= Резюме =
== Резюме ==
+
  
 
* Это забавная игрушка.
 
* Это забавная игрушка.
 
* Пользоваться ей можно сходу, не приходя в сознание.  
 
* Пользоваться ей можно сходу, не приходя в сознание.  
 
* Но в ней куча настроек и прочих «степеней свободы» — подбор музыки, движка визулизации, параметров, палитры и т.п. — так что изготовление роликов визуализации более-менее искусство.
 
* Но в ней куча настроек и прочих «степеней свободы» — подбор музыки, движка визулизации, параметров, палитры и т.п. — так что изготовление роликов визуализации более-менее искусство.
* Результаты полезны для:
+
 
** Рекламы проекта:
+
Результаты полезны для:
*** Представления проекта на конференции (очень сейчас модно, см. например [http://vimeo.com/6553097 выступление об проекте Debian Installer]). У вас это займет всего минуты четыре, а аудитория проснется и заинтересуется.
+
* Рекламы проекта:
*** Показа проекта без раскрытия кодов, при хедхантинге. Длинные, унылые, проекты с ''legacy''-кодом будут даже смотреться интересней новых и перспективных проектов. И возможно тут предпочтительней использовать Codeswarm-визуализацию.  
+
** Представления проекта на конференции (очень сейчас модно, см. например [http://vimeo.com/6553097 выступление об проекте Debian Installer]). У вас это займет всего минуты четыре, а аудитория проснется и заинтересуется.
** Показ работы в рамках SCRUM-демонстрации (или чего-то подобного), для донесения идеи — «мы тут вкалываем!» для заказчиков и прочих стейкхолдеров. Когда показывается короткая работа (неделя-месяц) — лучше использовать Gource-визуализацию.
+
** Показа проекта без раскрытия кодов, при хедхантинге. Длинные, унылые, проекты с ''legacy''-кодом будут даже смотреться интересней новых и перспективных проектов. И возможно тут предпочтительней использовать <tt>Codeswarm</tt>-визуализацию.  
 +
* Показ работы в рамках <tt>SCRUM</tt>-демонстрации (или чего-то подобного), для донесения идеи — «мы тут вкалываем!» для заказчиков и прочих стейкхолдеров. Когда показывается короткая работа (неделя-месяц) — лучше использовать <tt>Gource</tt>-визуализацию.
 
* Ну и полезно посмотреть на свой или чужой проект, для быстрого и целостного гештальт-восприятия (не затухает ли проект, сколько народу вкладывается в «ядре», и т.п.).
 
* Ну и полезно посмотреть на свой или чужой проект, для быстрого и целостного гештальт-восприятия (не затухает ли проект, сколько народу вкладывается в «ядре», и т.п.).
  
В принципе, наверное все сказано, на всякий случай, вдруг кому пригодится — короткий доклад о ShowTeamWorks на [[AgileDays-2009_(Technical_excelence)|конференции AgileDays-2009]] (особого ничего нового, по сравнению с написанным выше).
+
В принципе, наверное все сказано, на всякий случай, вдруг кому пригодится — короткий доклад о [[ShowTeamWork]] на [[AgileDays-2009_(Technical_excelence)|конференции AgileDays-2009]] (особого ничего нового, по сравнению с написанным выше).
<html><center>
+
{{vimeoembed|8246851|640|512}}
<object width="640" height="512"><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=8246851&amp;server=vimeo.com&amp;show_title=1&amp;show_byline=0&amp;show_portrait=0&amp;color=00ADEF&amp;fullscreen=1" /><embed src="http://vimeo.com/moogaloop.swf?clip_id=8246851&amp;server=vimeo.com&amp;show_title=1&amp;show_byline=0&amp;show_portrait=0&amp;color=00ADEF&amp;fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="640" height="512"></embed></object>
+
 
</center></html>
+
 
 +
== Проблемы и ToDo ==
 +
* Есть две версии <tt>ffmpeg</tt>, с одной STW пока не совместим (проблема только под <tt>Linux</tt>).
 +
* <tt>Gource</tt> делает нелинейную визуализацию (время в визуализации течет нелинейно), игнорирует параметры исторического времени (<tt>--seconds-per-day</tt>) и визуализации. За пару часов под Windows/MinGW собрать его не удалось, буду разбираться когда руки дойдут.
 +
* <tt>Gource</tt> требует много места на винчестере под временные файлы (ибо под Windows он не умеет передавать видеопоток через пайп или другой аналог, и приходится записывать непакованное видео).
 +
* Мне не ясно, как из <tt>Mercurial</tt>-лога вытащить информацию о типе изменения («добавление», «правка», «удаление»), чтобы корректно показать это в <tt>Gource</tt>.
 +
* <tt>Codeswarm</tt> часто зависает при использовании <tt>PhysicsEngineChaotic</tt>. Не используйте пока эту модель. Поставлен [http://code.google.com/p/codeswarm/issues/detail?id=67 баг] разработчику.
 +
* Описание на  английском.
  
 
== Примечания ==
 
== Примечания ==
Строка 501: Строка 512:
  
 
{{replicate-from-custiswiki-to-lib}}
 
{{replicate-from-custiswiki-to-lib}}
 +
{{replicate-from-custiswiki-to-4intranet}}

Текущая версия на 17:53, 5 апреля 2012

Проблема

При грамотном процессе разработки, с применением средств групповой работы, таких, как:

накапливается огромная история коллективного взаимодействия команды.


Хочется:

  • эффективно извлечь из этих данных полезные знания:
Работа
кто, как и где «вкалывает»,
Взаимодействие
кто с кем, и в каких темах
например:
  • достаточно ли было Agile-кроссфункциональности?
  • где были проблемы (заброшенные области, забытые люди)
  • и т.п.
  • быстро и нескучно рассказать о работе своей команды:
    • демонстрации заказчикам;
    • гордость перед коллегами;
    • хедхантинг новых сотрудников.

Однако на практике возникает проблемы:

как эффективно исследовать этот пласт информации?

как эффектно показать свою работу лицом?


Может посчитать метрики? Ненавистные SLOC и иже с ними? Да, такие инструменты есть:

В зависимости от глубины детализации можно получить:

  • либо пару унылых метрик («KSLOCs в месяц на сферического разработчика в вакууме», то есть в нашей метафоре максимум — «площадь лесного массива»)[1].
  • либо многостраничные Excel-dashboardы, заполненные мириадами цифр, в которых почти также бессмысленно лезть человеку, если он не профессор Чарли Эппс из сериала Numb3rs с его верными суперкомпьютерами и волшебными алгоритмами DataMining-a.

Читать логи переписки и коммиты в VCS? No.svg То есть разрабатывать «шахту знаний» киркой и мотыгой? Бродить по лесу и считать деревья? Муторно!


Что же делать?

Есть альтернативный способ «увидеть лес за деревьями» и при этом выжать краткую информацию по процессу — Визуализация. В динамике!

Визуализация

Есть уже даже несколько моделей!

Целых две ☻ .

Codeswarm

  • CodeSwarm.
  • Самая древняя! (год с копейками).
  • Визуализация физической модели «Люди и файлы» (на самом деле 4-ре модели: «Simple», «Chaotic», «Maxwell», «Legacy», но не все они «одинаково полезны», наиболее разумная — «Simple»).

Основная идея

  • «Люди» не любят друг друга — отталкиваются!
  • «Файлы» тоже отталкиваются! «обратно пропорционально расстоянию».
  • «Людей» и «Файлы» притягивает при коммитах!
  • «Люди» сближаются из-за совместной работы над одними файлами



А также:

  • Файлы можно красить (по директориям).
  • Работа красит — файлы красят людей.
  • Цветной пульс проекта в динамике.

Gource

Основная идея

  • Визуализация и структур каталогов («облака имен»)!
  • Модель «Пчелы» и «цветы»!

Видны:

  • «Горячие зоны».
  • Кроссфункциональные парни.
  • Заброшенные люди и код.

Что не хватает?

  • Сохраненного видео. Только рисует.
  • Какое видео без музыки?
  • Инфоканал — текст бегущей строкой — субтитры!
  • Максимальная автоматизация:
    • «старт с нуля» — только включить и дать на вход лог!
    • «эволюционные доработки» — легко и быстро «править кино».

Important.svg Именно поэтому «самодельных» визуализаций — почти нет!


Наш фреймворк ShowTeamWork

Important.svg Наше решение все это делает!

А именно:

  • Вытаскивается лог-информация, если запустить в workspace проекта под какой-нибудь из известных VCS — поддерживаются самые распространенные системы контроля версий: CVS, SVN, GIT, Bazaar, Mercurial (в принципе — можно визуализировать любую активность, хоть торговлю трейдеров).
  • Автоматически выбирается набор подкаталогов проекта с наибольшей активностью.
  • Для них генерируется случайная цветовая палитра, с взаимоконтрастными и яркими цветами.
  • При отсутствии «сценария субтитров» — генерируется шаблон-заготовка.
  • При отсутствии аудио сопровождения — предлагает заготовленную музыку в стиле «Ambient»
  • Порождает видео, с длительностью равной аудиозаписи, и накладывает субтитры, вычисляя их из формата «сценария субтитров», где события привязаны к датам.

Примеры

Альбом готовых визуализаций для известных софтверных проектов ( bugzilla, bzr-svn, codeswarm, ffmpeg, freemind, git, gource, inkscape, mediawiki, mercurial, mplayer, postgres, subversion, viewvc) можно посмотреть здесь.

Мы используем

Свободные, open-source программы:

  • CodeSwarm (GPLv3) (требует установленной Java, версии не меньше 1.6).
  • Gource (GPLv3).
  • MEncoder (GPLv2).
  • FFmpeg (LGPL или GPL).

А также несколько треков свободно доступной электронной музыки от Tunguska Music Society (License: Creative Commons). Если вы поленитесь искать музыку — мы сделаем клип на базе случайного трека (несколько треков идут в комплекте). Бесплатно и патриотично! Если хотите публиковать свои ролики, напоминаем о пока еще существующих цифровых правах на музыку и рекомендуем использовать только свободно доступную музыку — тут огромный выбор музыки в стиле Ambient.

Инсталляция

Проект (исходники и бинарники) хостится на Google Code — http://code.google.com/p/showteamwork/.

То есть скомпилированные под Windows бинарники, вместе можно скачать отсюда, а если хотите поучаствовать в разработке — см. http://code.google.com/p/showteamwork/source/checkout

Единственное софтверное требование под Windows — наличие Java (все остальное идет в комплекте).

Под Linux нужно установить (из исходников или пакетами — неважно):

и сделать, чтобы эти утилиты были прописаны в путях.

Cразу посмотрите каталог samples, должно быть все понятно.

Чуть позже, мы подробно распишем здесь назначение каждой настройки.


Быстрый старт

Caution.svg Для Windows-пользователей. Linux-пользователи, думаю, поймут сами, что и как.

Скачайте дистрибутив, распакуйте куда-нибудь его. Зайдите в каталог проекта, то есть воркспейс под одной из систем контроля версий (CVS, SVN, GIT, Bazaar, Mercurial), причем не обязательно верхний уровень — можно на два-три уровня ниже, и вызовите showteamwork.exe, и отправляйтесь пить кофе.

В конце работы вы получите в каталоге, в котором его запустили пару видеофайлов (с codeswarm и gource визуализациями, по именам все будет понятно), а также вспомогательные файлы настроек и аудио, с префиксом stw-.

Далее, вы сможете редактировать эти файлы, подбирая оптимальные настройки, подходящую музыку, и редактируя историю проекта в субтитрах, и по окончании редактирования, перезапускайте showteamwork.exe.

Пересчет будет относительно экономичным — например, если вы меняли только параметры относящиеся к codeswarm-визуализации, то gource-визуализация пересчитыватся не будет, а если правили только субтитры — то будет выполнятся только наложение субтитров, без вызова gource или codeswarm.

Более детально, схема генерации описана ниже.

Схема генерации

Кликабельная (!) схема генерации:

[svg]

Упрощенная схема генерации представлена выше.

Note.svg Единственное необходимое — история проекта, в виде лог-файла, файла-генератора лог-файла, или просто workspace-проекта под одно из известных VCS, у которое лог-файл можно запросить.

Будучи запущенной ShowTeamWork без дополнительных файлов автоматически проанализирует лог, и

  • выделит наиболее «активные» каталоги
  • подберет для них случайную контрастную палитру цветов
  • сгенерирует файлы настроек
  • предложит одну из заготовленных[2] музыкальных дорожек в стиле Ambient.

А теперь поясним, зачем остальные сложности.

getlog.py

При отсутствии известного системе логфайла, ShowTeamWork пытается вызвать скрипт getlog.py, где вы пропишете, что и откуда надо тянуть. Например, вы собираетесь визуализировать историю большого проекта, у которого свой репозиторий, независимый от вашего проекта по его визуализации, то надо как-то прописать, как вытащить лог этого проекта.

getlog.pyPython-скрипт, он мультиплаформенный, в отличие от bat-файлов или shell-скриптов, и собственно не требует знания Python — для всех проектов в подкаталоге samples есть скрипт getlog.py, так что при необходимости, просто скопируйте скрипт, вытаскивающий лог из нужной вам системы контроля версий (в каталоге samples представлены все поддерживаемые типы VCS).

В скрипте, вы можете указать, например, параметры ограничивающие дату выборки лог-файла (например, только «последний спринт»).

import os
import datetime
 
begindate=(datetime.date.today()-datetime.timedelta(days=14)).strftime("%Y-%m-%d")
os.system(
"""
svn log  --xml --verbose --revision "{%(begindate)s}:HEAD" http://svn.wikimedia.org/svnroot/mediawiki/ >svn-log.xml
""" % vars())

Или вы можете написать свой скрипт, который, например, будет доставать данные из вики-системы, трекера-задач, системы документооборота или любой системы, подразумевающей командную работу.

После работы этого скрипта, ожидается, что в каталоге будет один из следующих файлов:

cvs.log
получаемый командой
 cvs log
svn-log.xml
получаемый командой
 svn log --xml --verbose
bzr.log
получаемый командой
 bzr log -v
git.log
получаемый командой (без шуток и одной строкой):
git log --name-status  
    --pretty=format:"%n------------------------------------------------------------------------%nr%h | %ae | %ai (%aD) | x lines%nChanged paths:
hg.log
получаемый командой
 hg -v log
activity.xml
Стандартный XML-формат представления активности (см. #activity.xml).

Если getlog.py не обнаружено, или он не выдал ожидаемые лог-файлы, ShowTeamWork пытается получить их самостоятельно, ожидая, что ее запустили в каталоге проекта под какой-нибудь системой контроля версий. ShowTeamWork смотрит, нет ли одного из следующих подкаталогов (в каталоге запуска или на один-два уровня выше) CVS, .svn, .bzr, .git, .hg — и обнаружив таковой, пытается получить лог-файл от соответствующей системы контроля версий (должна быть установлена и «в путях»).

activity.xml

Стандартный XML-формат представления активности (когда, кто, с чем, что сделал):

<?xml version="1.0"?>
<file_events>
<event 
  date="1263155767000" 
  author="rotem"
  action="A"
  filename="/trunk/phase3/languages/messages/MessagesEn.php" 
  comment="Localization update for he, and whitespace fix in en.. " />
<event 
  date="1263155767000" 
  author="rotem"
  action="M"
  filename="/trunk/phase3/languages/messages/MessagesHe.php" 
  comment="Localization update for he, and whitespace fix in en.. " />
</file_events>

Т.е. все просто — последовательность event вложенных в file_events, а атрибуты event следующие:

date
Время события в миллисекундах от стандартной компьютерной эпохи (1970…).
author
Автор действия
filename
путь
action
(необязательно)
A
добавление
M
модификация
D
удаление
comment
(необязательно) — комментарий

Кодировка — UTF-8, но если не подходит, пытается попробовать однобайтовые русскоязычные 1251/koi-8.

stw-filter-events.py

В этом файле (по умолчанию будет сгенерирована «заготовка-рыба») определяется функция filter_events, которой передается объект event, с атрибутами, описанными выше, и которая возвращает

  • True, если событие нужно обрабатывать, и
  • False, если событие нужно игнорировать.

Для чего это нужно? Например, для того, чтобы обфускировать идентификаторы авторов или наоборот, восстанавливать, сводить несколько идентификаторов вида вася@дома, <Василий Иванович Васильев> вася@office и вася@gmail к одному IDу вася. Для того, чтобы игнорировать некорректные входы (например даты из будущего — такое бывает).

Вот, к примеру фильтры для проекта bzr-svn — фильтруем события из будущего, сводим всех jelmer к одному, и для всех авторов пытаемся вытащить их email, выкинув ФИО и прочее:

def filter_events(event):
    # You can modify event attribute, or disable (filter) event, returning False
    # Sample processing below
    emailre_ = re.compile(r"(?P<email>[-a-z0-9_.]+@(?:[-a-z0-9\.]+))",
                    re.IGNORECASE)
 
    if event.date > time.time()*1000:
       return False # Something wrong: event from future
 
    if event.author.startswith("jelmer"):
       event.author="jelmer@samba.org"      
    event.author = event.author.lower().replace('"',"'")
    m = emailre_.search(event.author)
    if m:
        event.author = m.group('email') 
    event.author = event.author.replace('"',"'")
    if event.author in ["(no author)"]:
        event.author = "anonymous"
 
    event.comment = re.sub('[Bb][Uu][Gg]\s*\d+\.?', '', event.comment)
    if event.comment.startswith("*** empty log message ***"):
        event.comment = ""
 
    if len(event.comment) < 10:
        event.comment = ""
 
    return True


А также для «сведения» файловых путей (например, если CVS-репозиторий перемещался по файловой системе. Например, для вычисления истории PostgresQL, мы применяем следующий фильтр:

def filter_events(event):
    prefixes=[
        "/Users/neilc/postgres/cvs_root/",
        "/home/projects/pgsql/cvsroot/",
        "/projects/cvsroot/"
    ]
 
    for p in prefixes:
        if event.filename.startswith(p):
           event.filename=event.filename.replace(p,"/") 
    return True


stw-scenario.txt

Файл текстового «сценария» — история проекта в субтитрах. Строка начинающаяся с #, считается комментарием, остальные непустые строки состоят из даты и текста, разделенных пробелом[ами].

Дата
в формате %d.%m.%Y или %Y-%m-%d, как вам угодно.
Комментарий
простая текстовая строка в кодировке UTF-8.

По умолчанию, генерируется «заготовка-рыба» из событий, где для каждое первое появление нового участника пишется «Hi <участник>!», плюс закомментированная строка с лог-комментарием для каждого коммита — вы можете пройтись по тексту, выделить и раскомментировать ключевые события.

stw-config.py

Основной файл настроек.

Он также представляет собой скрипт на Python, в котором вы можете делать что угодно, главное — заполнить две строковых переменных

config
Собственно строка общей конфигурации, где перечислены настройки в формате «параметр=значение». Хотя большая часть настроек относиться только к Codeswarm-визуализации.

Наиболее важными будут параметры раскраски (вы наверняка захотите поменять их), ведь только автор знает, какое деление по каталогам правильно (по умолчанию, цветовая карта создается для каталогов с наибольшей активностью, что конечно, не всегда правильно). Вот некоторый пример для визуализации Bugzilla — смысл, думаю понятен (метка, регэксп, ргб-цвет):

 ColorAssign1="Attic",    "/cvsroot/mozilla/webtools/bugzilla/Attic.*",    255,242,146,  255,242,146
 ColorAssign2="Bugzilla", "/cvsroot/mozilla/webtools/bugzilla/Bugzilla.*", 207,240,62,   207,240,62
 ColorAssign3="docs",     "/cvsroot/mozilla/webtools/bugzilla/docs.*",     198,152,247,  198,152,247
 ColorAssign4="template", "/cvsroot/mozilla/webtools/bugzilla/template.*", 6,195,37,     6,195,37
 ColorAssign5="contrib",  "/cvsroot/mozilla/webtools/bugzilla/contrib.*",   132,120,49,   132,120,49
 ColorAssign6="skins",    "/cvsroot/mozilla/webtools/bugzilla/skins.*",       9,115,81,     9,115,81 

Caution.svg важно — нумеровать нужно последовательно, не пропуская цифр.

Из остальных, наиболее важных параметров, отметим следующие

Width
Ширина видео.
Height
Высота видео.
DrawNamesHalos
Рисовать гало вокруг меток (только для codeswarm).
ShowEdges
Показывать ли линии притяжения (только для codeswarm).

Для чего такие сложности? Просто генерация видео штука очень долгая, и сильно зависит от размера ролика. Поэтому, вы можете сначала сделать черновое видео маленького размера, и без спецэффектов, на котором обкатаете музыку, цвета, субтитры — и только потом, смените один символ, и сгенерируете видео высокого разрешения. Типичная часть из stw-config.py выглядит как-то так:

draft=0
if draft:
    config+="""
Width=512
Height=384
DrawNamesHalos=false
ShowEdges=false
    """
else:
    config+="""
Width=1280
Height=800
    """


engine
Выбор типа визуализации и параметров, только для Codeswarm-визуализации.

Note.svg Рекомендуем использовать только «PhysicsEngineSimple» визуализацию, как наиболее разумную (и не зависающую). Типичный набор параметров:

# name of the engine class
name=PhysicsEngineSimple
# parameters specific to this engine
edgeMultiplier=1.0
speedMultiplier=1.0
nodesMultiplier=100.0
drag=0.05

Тут наиболее интересен параметр nodesMultiplier — сила отталкивания элементов — если у вас все слишком далеко друг от друга, а лентяев вообще выкидывает к границе экрана — можете уменьшить, если все свалились в кучу — увеличивайте. За более детальными параметрами визуализации Codeswarm, обращайтесь к на страницу этого проекта.

Также в этом скрипте можно задать параметры:

GOURCE
Ненулевое значение — делать ролик с Gource-визуализацией. По умолчанию — делать.
CODESWARM
Ненулевое значение — делать ролик с CODESWARM-визуализацией. По умолчанию — делать.

В общем, см. примеры файлов stw-config.py в подкаталоге дистрибутива samples.

stw-audio.mp3

Музыкальный файл сопровождения. Если вы не предложите свой, будет взят случайный файл из подкаталога audio.

Note.svg Так что если у вас есть любимая коллекция музыки, и вы собираетесь визуализировать кучу проектов, не подбирая индивидуально звук для каждого проекта, просто залейте вашу коллекцию в подкаталог audio, оттуда mp3-файл будет выбираться случайно.

TemporaryCache

Структуру временных файлов описывать специально не будем, отметим только, что все они лежат в вашем каталоге для временных файлов (под Windows он указан в переменной TEMP), в подкаталогах с префиксом STW- (кстати, они с атрибутами скрытых файлов под Windows).

Кстати, на диске с каталогом временных файлов может потребоваться немало места — размер зависит от проекта, но в целом, порядка нескольких гигабайт на проект.

Если у вас что-то как-то пошло не так (прервали генерацию, а потом, что-то как-то не так выглядит), или просто место понадобилось — можно очистить кеш, стерев эти каталоги.

Контакты

Все баги, пожелания и предложения, пожалуйста сюда. Ну, на худой конец, обращайтесь на прямую — все мои контакты: Стас Фомин.

Резюме

  • Это забавная игрушка.
  • Пользоваться ей можно сходу, не приходя в сознание.
  • Но в ней куча настроек и прочих «степеней свободы» — подбор музыки, движка визулизации, параметров, палитры и т.п. — так что изготовление роликов визуализации более-менее искусство.

Результаты полезны для:

  • Рекламы проекта:
    • Представления проекта на конференции (очень сейчас модно, см. например выступление об проекте Debian Installer). У вас это займет всего минуты четыре, а аудитория проснется и заинтересуется.
    • Показа проекта без раскрытия кодов, при хедхантинге. Длинные, унылые, проекты с legacy-кодом будут даже смотреться интересней новых и перспективных проектов. И возможно тут предпочтительней использовать Codeswarm-визуализацию.
  • Показ работы в рамках SCRUM-демонстрации (или чего-то подобного), для донесения идеи — «мы тут вкалываем!» для заказчиков и прочих стейкхолдеров. Когда показывается короткая работа (неделя-месяц) — лучше использовать Gource-визуализацию.
  • Ну и полезно посмотреть на свой или чужой проект, для быстрого и целостного гештальт-восприятия (не затухает ли проект, сколько народу вкладывается в «ядре», и т.п.).

В принципе, наверное все сказано, на всякий случай, вдруг кому пригодится — короткий доклад о ShowTeamWork на конференции AgileDays-2009 (особого ничего нового, по сравнению с написанным выше).


Проблемы и ToDo

  • Есть две версии ffmpeg, с одной STW пока не совместим (проблема только под Linux).
  • Gource делает нелинейную визуализацию (время в визуализации течет нелинейно), игнорирует параметры исторического времени (--seconds-per-day) и визуализации. За пару часов под Windows/MinGW собрать его не удалось, буду разбираться когда руки дойдут.
  • Gource требует много места на винчестере под временные файлы (ибо под Windows он не умеет передавать видеопоток через пайп или другой аналог, и приходится записывать непакованное видео).
  • Мне не ясно, как из Mercurial-лога вытащить информацию о типе изменения («добавление», «правка», «удаление»), чтобы корректно показать это в Gource.
  • Codeswarm часто зависает при использовании PhysicsEngineChaotic. Не используйте пока эту модель. Поставлен баг разработчику.
  • Описание на английском.

Примечания

  1. На самом деле сейчас уже есть интересные варианты с нетривиальной статической визуализацией SVN-репозиториев: SVNPlot, примеры: [1], [2]. Но в динамике все равно гораздо веселей.
  2. Свободный, выпущенных под лицензией Creative Commons

Репликация: База Знаний «Заказных Информ Систем» → «ShowTeamWork»

Любые правки этой статьи будут перезаписаны при следующем сеансе репликации. Если у вас есть серьезное замечание по тексту статьи, запишите его в раздел «discussion».


Статья реплицируется в Wiki4IntraNet.